//# sourceURL=J_ALTUI_utils.js
// "use strict";
// http://192.168.1.16:3480/data_request?id=lr_ALTUI_Handler&command=home
// This program is free software: you can redistribute it and/or modify
// it under the condition that it is for private or home useage and
// this whole comment is reproduced in the source code file.
// Commercial utilisation is not authorized without the appropriate
// written agreement from amg0 / alexis . mermet @ gmail . com
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
function getQueryStringValue (key) {
return unescape(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + escape(key).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
}
function isIE11() {
var ie11andabove = navigator.userAgent.indexOf('Trident') != -1 && navigator.userAgent.indexOf('MSIE') == -1 // IE11 or above Boolean
return ie11andabove;
}
function Altui_SelectText(element) {
var doc = document;
var text = doc.getElementById(element);
if (doc.body.createTextRange) { // ms
var range = doc.body.createTextRange();
range.moveToElementText(text);
range.select();
} else if (window.getSelection) {
var selection = window.getSelection();
var range = doc.createRange();
range.selectNodeContents(text);
selection.removeAllRanges();
selection.addRange(range);
}
};
function Altui_LoadStyle(styleFunctionName) {
// var head = document.getElementsByTagName('head')[0];
// var style = document.createElement('style');
// style.type = 'text/css';
// var css = Altui_ExecuteFunctionByName(styleFunctionName, window);
// style.appendChild(document.createTextNode(css));
// head.appendChild(style);
var title = document.getElementsByTagName('title')[0];
var style = document.createElement('style');
style.type = 'text/css';
var css = Altui_ExecuteFunctionByName(styleFunctionName, window);
style.appendChild(document.createTextNode(css));
title.parentNode.insertBefore(style,title);
};
function Altui_ExecuteFunctionByName(functionName, context , device, extraparam) {
var namespaces = functionName.split(".");
var func = namespaces.pop();
for (var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
return context[func].call(context, device, extraparam);
};
var Localization = ( function (undefined) {
var _unknown_terms = {};
var _terms = {};
var __T = function(t) {
var v =_terms[t]
if (v)
return v;
_unknown_terms[t] = t;
return t;
};
var _initTerms = function(terms) {
_terms = $.extend({},terms);
_unknown_terms = {};
};
var _dumpTerms = function() {
if (AltuiDebug.IsDebug()) {
console.log( JSON.stringify(_unknown_terms) );
console.log( JSON.stringify(_terms) );
}
var text = "browser query:{3} userlanguage:{0} language:{1}\n Unknown terms:{2}".format(
window.navigator.userLanguage || "",
window.navigator.language || "",
JSON.stringify(_unknown_terms),
getQueryStringValue("lang")
);
UIManager.pageEditorForm(_T("Localization information"),text,null,_T("Close"),function() {
UIManager.pageHome();
});
};
return {
_T : __T,
init : _initTerms,
dump : _dumpTerms
}
})();
var _T = Localization._T;
if (typeof RegExp.escape == 'undefined') {
RegExp.escape = function(string) {
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};
};
if (typeof Array.prototype.in_array != 'function') {
Array.prototype.in_array = function ( obj ) {
var len = this.length;
for ( var x = 0 ; x < len ; x++ ) {
if ( this[x] == obj ) return true;
}
return false;
}
};
if (typeof Number.prototype.toPaddedString != 'function') {
function toPaddedString(number, length, radix) {
var string = number.toString(radix || 10),
slength = string.length;
for (var i=0; i<(length - slength); i++) string = '0' + string;
return string;
}
Number.prototype.toPaddedString = function(length, radix) {
var number = this;
return toPaddedString(number, length, radix);
}
};
if (typeof String.prototype.toHHMMSS != 'function') {
String.prototype.toHHMMSS = function () {
var sec_num = parseInt(this, 10); // don't forget the second param
if ( isNaN(sec_num) )
sec_num=0;
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
var time = hours+':'+minutes+':'+seconds;
return time;
}
String.prototype.fromHHMMSS = function () {
var hms = this;
var a = hms.split(':'); // split it at the colons
// minutes are worth 60 seconds. Hours are worth 60 minutes.
var seconds = (+a[0] || 0) * 60 * 60 + (+a[1] || 0) * 60 + (+a[2] || 0 );
return seconds;
}
};
if (typeof String.prototype.escapeQuotes != 'function') {
// see below for better implementation!
String.prototype.escapeQuotes = function (){
var content = this;
return content.replace(/'/g, "\\'");
};
String.prototype.escapeDoubleQuotes = function (){
var content = this;
return content.replace(/"/g, "\\\"");
};
};
if (typeof String.prototype.escapeXml != 'function') {
// see below for better implementation!
String.prototype.escapeXml = function (){
var XML_CHAR_MAP = {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
"'": '''
};
var content = this;
return content.replace(/[<>&"']/g, function (ch) {
return XML_CHAR_MAP[ch];
});
};
};
if (typeof String.prototype.format == 'undefined') {
String.prototype.format = function()
{
var args = new Array(arguments.length);
for (var i = 0; i < args.length; ++i) {
// `i` is always valid index in the arguments object
// so we merely retrieve the value
args[i] = arguments[i];
}
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
// var content = this;
// for (var i=0; i < arguments.length; i++)
// {
// var replacement = new RegExp('\\{' + i + '\\}', 'g'); // regex requires \ and assignment into string requires \\,
// content = content.replace(replacement, arguments[i]);
// }
// return content;
};
};
if (typeof String.prototype.startsWith != 'function') {
// see below for better implementation!
String.prototype.startsWith = function (str){
return this.indexOf(str) == 0;
};
};
if (typeof String.prototype.htmlEncode == 'undefined') {
String.prototype.htmlEncode = function()
{
var value = this;
return $('
').text(value).html();
}
String.prototype.htmlDecode= function()
{
var value = this;
return $('').html(value).text();
}
};
if (typeof String.prototype.evalJSON != 'function') {
// see below for better implementation!
String.prototype.evalJSON = function (){
var content = this;
return JSON.parse(content);
}
};
function _format2Digits(d) {
return ("0"+d).substr(-2);
};
function _toIso(date,sep) {
sep = sep || 'T';
var iso = "{0}-{1}-{2}{6}{3}:{4}:{5}".format(
date.getFullYear(),
_format2Digits(date.getMonth()+1),
_format2Digits(date.getDate()),
_format2Digits(date.getHours()),
_format2Digits(date.getMinutes()),
_format2Digits(date.getSeconds()),
sep );
return iso;
};
function _array2Table(arr,idcolumn,viscols) {
var html="";
var idcolumn = idcolumn || 'id';
var viscols = viscols || [idcolumn];
html+="
";
if ( (arr) && ($.isArray(arr) && (arr.length>0)) ) {
var bFirst=true;
html+="
";
$.each(arr, function(idx,obj) {
if (bFirst) {
html+=""
html+="
";
return html;
};
function isObject(obj)
{
return (Object.prototype.toString.call(obj)== "[object Object]");
};
function isInteger(data) {
return (data === parseInt(data, 10));
};
function isNullOrEmpty(value) {
return (value == null || value.length === 0); // undefined == null also
};
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
var getCSS = function (prop, fromClass) {
var $inspector = $("
").css('display', 'none').addClass(fromClass);
$("body").append($inspector); // add to DOM, in order to read the CSS property
try {
return $inspector.css(prop);
} finally {
$inspector.remove(); // and remove from DOM
}
};
var AltuiDebug = ( function (undefined) {
var g_debug = false;
function _debug(str) {
if (g_debug==true)
console.log(new Date().toISOString()+": ALTUI "+g_DeviceTypes.info["PluginVersion"]+":"+str);
}
return {
SetDebug: function(bDebug) { g_debug=bDebug; },
IsDebug : function() { return g_debug; },
debug: _debug,
}
})();
function formatAjaxErrorMessage(jqXHR, exception) {
if (jqXHR.status === 0) {
return ('Not connected. Please verify your network connection.');
} else if (jqXHR.status == 404) {
return ('The requested page not found. [404]');
} else if (jqXHR.status == 500) {
return ('Internal Server Error [500].');
} else if (exception === 'parsererror') {
return ('Requested JSON parse failed.');
} else if (exception === 'timeout') {
return ('Time out error.');
} else if (exception === 'abort') {
return ('Ajax request aborted.');
} else {
return ('Uncaught Error.\n' + jqXHR.responseText);
}
};
var MyLocalStorage = ( function (undefined) {
function _set(key, item) {
if (key==undefined)
return null;
localStorage.setItem( key, JSON.stringify(item) );
return item;
};
function _get(key) {
if (key==undefined)
return null;
var json = localStorage.getItem( key );
return json != undefined ? JSON.parse(json) : null;
};
function _clear(key) {
if (key==undefined)
return null;
return localStorage.removeItem(key);
};
function _setSettings(key, val) {
var settings = _get("ALTUI_Settings");
if (settings==null) {
settings = {};
}
settings[key] = val;
return _set("ALTUI_Settings",settings);
};
function _getSettings(key) {
var settings = _get("ALTUI_Settings");
return (settings) ? settings[key] : null;
};
return {
set: _set,
get: _get,
setSettings: _setSettings,
getSettings: _getSettings,
clear: _clear,
}
})( );
var Favorites = ( function (undefined) {
var _favorites = $.extend( {'device':{}, 'scene':{} }, MyLocalStorage.getSettings("Favorites") );
MyLocalStorage.setSettings("Favorites",_favorites);
function _save() {
MyLocalStorage.setSettings("Favorites",_favorites);
};
function _set(type, id, bFavorite) {
_favorites[type][id]=bFavorite;
if (MyLocalStorage.getSettings('UseVeraFavorites')==1) {
switch(type) {
case "device":
var device = MultiBox.getDeviceByAltuiID( id );
MultiBox.setAttr(device, "onDashboard", bFavorite ? 1 : 0 );
break;
case "scene":
var scene = MultiBox.getSceneByAltuiID( id );
scene.onDashboard = (bFavorite ? 1 : 0);
MultiBox.editScene(scene.altuiid,scene);
break;
}
}
_save();
};
function _get(type, id) {
if (MyLocalStorage.getSettings('UseVeraFavorites')==1) {
switch(type) {
case "device":
var device = MultiBox.getDeviceByAltuiID( id );
_favorites[type][id] = (device.onDashboard==1);
break;
case "scene":
var scene = MultiBox.getSceneByAltuiID( id );
_favorites[type][id] = ( scene.onDashboard==1);
}
}
return _favorites[type][id] || false;
};
return {
set: _set,
get: _get,
save: _save
}
})( );
var EventBus = ( function (undefined) {
var _subscriptions = {
// altui specific ones // parameters
"on_altui_deviceTypeLoaded" : [], // table of { func, object }
// global ones
"on_ui_deviceStatusChanged" : [], // table of { func, object }
"on_ui_initFinished": [],
"on_ui_userDataFirstLoaded" : [],
"on_ui_userDataLoaded" : [],
"on_startup_luStatusLoaded" : [],
// ctrl specific ones , 0 is the master then other are going to be added dynamically
"on_ui_userDataFirstLoaded_0" : [],
"on_ui_userDataLoaded_0" : [],
"on_startup_luStatusLoaded_0" : [],
};
function _allSet(tbl) {
var bResult = true;
$.each(tbl, function(k,v) {
if (v==false)
bResult = false;
return bResult;
});
return bResult;
};
function _registerEventHandler(eventname, object, funcname ) {
if (_subscriptions[eventname] == undefined)
_subscriptions[eventname] = [];
var bFound = false;
$.each(_subscriptions[eventname], function (idx,sub) {
if ((sub.object==object) && (sub.funcname==funcname)) {
bFound = true;
return false;
}
});
if (bFound==false)
_subscriptions[eventname].push( {object: object , funcname: funcname} );
};
function _waitForAll(event, eventtbl, object, funcname ) {
var _state = {};
function _signal(eventname/*, args */) {
var theArgs = arguments;
_state[eventname] = true;
// if all are true, call the object,funcname
if (_allSet(_state)) {
theArgs[0] = event;
if ($.isFunction(funcname)) {
(funcname).apply(object,theArgs);
} else {
// theArgs.unshift(eventname);
var func = object[funcname];
func.apply( object , theArgs );
}
}
};
$.each(eventtbl , function( idx, event) {
_state[event] = false;
_registerEventHandler(event, this, _signal );
})
};
function _publishEvent(eventname/*, args */) {
// console.log(eventname);
if (_subscriptions[eventname]) {
// var theArgs = [].slice.call(arguments, 1); // remove first argument
var theArgs = arguments;
$.each(_subscriptions[eventname], function (idx,sub) {
if ($.isFunction(sub.funcname)) {
(sub.funcname).apply(sub.object,theArgs);
} else {
// theArgs.unshift(eventname);
var func = sub.object[sub.funcname];
func.apply( sub.object , theArgs );
}
});
} else {
_subscriptions[eventname] = [];
}
};
return {
registerEventHandler : _registerEventHandler, //(eventname, object, funcname )
waitForAll : _waitForAll, //(events, object, funcname )
publishEvent : _publishEvent, //(eventname, args)
getEventSupported : function() {
return Object.keys(_subscriptions);
},
}
})();
// function myFunc(device) {
// console.log("Device {0} state changed".format(device.id));
// }
//on_ui_initFinished
// EventBus.registerEventHandler("on_ui_deviceStatusChanged",window,"myFunc");
var PageManager = (function() {
var _pages = null;
// var pages = [
// { id:1, name:'test' },
// { id:2, name:'page2' },
// ];
function _fixMyPage(page) {
if (page.children)
$.each(page.children, function(idx,child) {
if (child.properties.deviceid) {
if (child.properties.deviceid.indexOf('-') == -1) {
child.properties.deviceid = "0-"+child.properties.deviceid;
}
}
if (child.properties.sceneid) {
if (child.properties.sceneid.indexOf('-') == -1) {
child.properties.sceneid = "0-"+child.properties.sceneid;
}
}
if (child.zindex == undefined )
child.zindex = 0;
});
};
function _init(pages) {
if (_pages==null) // otherwise, already initialized
{
AltuiDebug.debug("PageManager.init(), pages="+JSON.stringify(pages));
_pages = [];
$.each( pages, function(idx,page) {
_fixMyPage(page); // temporary code to fix the page definition
_pages.push( $.extend( true, {id:0, name:'', background:''}, page) );
});
}
};
function _recoverFromStorage() {
_pages = MyLocalStorage.get("Pages");
};
function _clearStorage() {
MyLocalStorage.clear("Pages");
};
function _savePages() {
AltuiDebug.debug("PageManager.savePages(), pages="+JSON.stringify(_pages));
MyLocalStorage.set("Pages",_pages);
var names = $.map( _pages, function(page,idx) { return page.name; } );
MultiBox.saveData( "CustomPages", JSON.stringify(names), function(data) {
if (data!="")
PageMessage.message("Save Pages success", "success");
else
PageMessage.message("Save Pages failure", "danger");
});
$.each(_pages, function(idx,page) {
MultiBox.saveData( page.name, JSON.stringify(page), function(data) {
if (data!="")
PageMessage.message("Save for "+page.name+" succeeded.", "success");
else
PageMessage.message( "Save for "+page.name+" did not succeed." , "danger");
});
});
};
function _addPage() {
var id = 0;
$.each(_pages, function(idx,page) {
id = Math.max(id, page.id );
});
id++;
_pages.push({
id:id,
name:'page'+id,
background: 'rgb(232, 231, 231)'
});
return _pages;
};
function _deletePage(name) {
$.each( _pages, function( idx,page) {
if ( page.name==name) {
_pages.splice(idx,1);
return false;
}
});
return _pages;
};
function _getPageFromName( name ) {
var result = null;
if (name)
$.each( _pages, function( idx,page) {
if ( page.name==name) {
result = page;
return false;
}
});
return result;
};
function _updateChildrenInPage( page, widgetid, position , size, zindex )
{
if (page.children)
$.each(page.children, function(idx,child) {
if (child.id == widgetid) {
if (position)
child.position = jQuery.extend(true, {}, position);
if (size)
child.size = jQuery.extend(true, {}, size);
if (zindex)
child.zindex = zindex;
}
});
};
function _insertChildrenInPage( page, tool, position, zindex )
{
var id = 0;
if (page !=null) {
if (page.children == undefined)
page.children = new Array();
$.each(page.children, function(idx,child) {
id = Math.max(id, child.id );
});
id++;
page.children.push( {
id: id,
cls: tool.cls,
position: jQuery.extend(true, {}, position),
properties : jQuery.extend(true, {}, tool.properties), // default values
size: jQuery.extend(true, { }, tool.defaultSize),
zindex : (zindex || tool.zindex || 0),
});
}
return id; //0 if error
};
function _removeChildrenInPage( page, widgetid )
{
var widget = null;
$.each(page.children, function(idx,child) {
if (child.id==widgetid)
{
widget = child;
page.children.splice(idx,1);
return false; // break loop
}
});
return widget;
};
function _getWidgetByID( page, widgetid ) {
var widget=null;
$.each(page.children, function(idx,child) {
if (child.id == widgetid) {
widget = child;
return false;
}
});
return widget;
};
function _forEachPage( func ) {
$.each(_pages, func);
};
return {
init :_init,
recoverFromStorage : _recoverFromStorage,
clearStorage : _clearStorage,
forEachPage: _forEachPage,
getPageFromName: _getPageFromName,
savePages: _savePages,
addPage: _addPage,
deletePage: _deletePage,
updateChildrenInPage: _updateChildrenInPage,
insertChildrenInPage: _insertChildrenInPage,
removeChildrenInPage: _removeChildrenInPage,
getWidgetByID: _getWidgetByID
};
})();
var IconDB = ( function (window, undefined) {
var _dbIcon = null;
function _getIconContent( controllerid, name , cbfunc ) {
if (_dbIcon == null) {
_dbIcon = MyLocalStorage.get("IconDB");
if (_dbIcon==null)
_dbIcon={}
}
// do not load http based sources from the VERA itself
if (name.startsWith("http"))
return name;
// if undefined and not yet started to fetch, then go fetch it
if (_dbIcon[name]==undefined) {
_dbIcon[name]="pending"
MultiBox.getIcon(controllerid, name, function(data) {
// store in cache and call callback
_dbIcon[name]=data;
if ($.isFunction(cbfunc))
cbfunc(data);
});
}
// if not yet there, or still pending , return nothing - it will arrive later in a callback
return ((_dbIcon[name]!=undefined) && (_dbIcon[name]!="pending")) ? _dbIcon[name] : "" ;
};
return {
getIconContent : _getIconContent, // ( controllerid, name , cbfunc )
isDB : function() { return MyLocalStorage.get("IconDB")!=null; },
saveDB : function() { MyLocalStorage.set("IconDB", _dbIcon); },
resetDB : function() { MyLocalStorage.clear("IconDB"); _dbIcon = {}; }
}
} )( window );
var FileDB = ( function (window, undefined) {
var _dbFile = null;
function _getFileContent( controllerid, name, cbfunc ) {
AltuiDebug.debug("_getFileContent( {0},{1} )".format(controllerid,name));
if (_dbFile == null) {
_dbFile = MyLocalStorage.get("FileDB");
if (_dbFile==null)
_dbFile={}
}
if ($.isFunction(cbfunc)==false)
return null;
if (_dbFile[name]!=undefined)
if (_dbFile[name]=="pending")
{
AltuiDebug.debug("_getFileContent( {0} ) ==> not yet here, defered in 200ms".format(name));
setTimeout( FileDB.getFileContent, 200, controllerid,name,cbfunc );
}
else {
AltuiDebug.debug("_getFileContent( {0} ) ==> returning content from cache".format(name));
cbfunc(_dbFile[name]);
}
else {
_dbFile[name]="pending";
// console.log("getting file "+name);
AltuiDebug.debug("_getFileContent( {0} ) ==> asking content to Vera".format(name));
MultiBox.getFileContent( controllerid, name, function(data,jqXHR) {
AltuiDebug.debug("_getFileContent( {0} ) ==> returning async content from Controller #{1}".format(name,controllerid));
_dbFile[name] = data;
cbfunc(data,jqXHR);
});
}
return 0;
};
return {
getFileContent : _getFileContent, // ( controllerid, name, cbfunc )
isDB : function() { return MyLocalStorage.get("FileDB")!=null; },
saveDB : function(db) { MyLocalStorage.set("FileDB", _dbFile); },
resetDB : function(db) { MyLocalStorage.clear("FileDB"); _dbFile = {}; }
}
} )( window );
function _sortByVariableName(a,b)
{
if (a.variable > b.variable)
return 1;
if (a.variable < b.variable)
return -1;
// a doit être égale à b
return 0;
};
/* ========================================================================
* Bootstrap (plugin): validator.js v0.8.0
* ========================================================================
* The MIT License (MIT)
*
* Copyright (c) 2013 Cina Saffary.
* Made by @1000hz in the style of Bootstrap 3 era @fat
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* ======================================================================== */
+function ($) {
'use strict';
// VALIDATOR CLASS DEFINITION
// ==========================
var Validator = function (element, options) {
this.$element = $(element)
this.options = options
options.errors = $.extend({}, Validator.DEFAULTS.errors, options.errors)
for (var custom in options.custom) {
if (!options.errors[custom]) throw new Error('Missing default error message for custom validator: ' + custom)
}
$.extend(Validator.VALIDATORS, options.custom)
this.$element.attr('novalidate', true) // disable automatic native validation
this.toggleSubmit()
this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.validateInput, this))
this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this))
this.$element.find('[data-match]').each(function () {
var $this = $(this)
var target = $this.data('match')
$(target).on('input.bs.validator', function (e) {
$this.val() && $this.trigger('input.bs.validator')
})
})
}
Validator.DEFAULTS = {
delay: 500,
html: false,
disable: true,
custom: {},
errors: {
match: 'Does not match',
minlength: 'Not long enough'
}
}
Validator.VALIDATORS = {
native: function ($el) {
var el = $el[0]
return el.checkValidity ? el.checkValidity() : true
},
match: function ($el) {
var target = $el.data('match')
return !$el.val() || $el.val() === $(target).val()
},
minlength: function ($el) {
var minlength = $el.data('minlength')
return !$el.val() || $el.val().length >= minlength
}
}
Validator.prototype.validateInput = function (e) {
var $el = $(e.target)
var prevErrors = $el.data('bs.validator.errors')
var errors
if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]')
this.$element.trigger(e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}))
if (e.isDefaultPrevented()) return
var self = this
this.runValidators($el).done(function (errors) {
$el.data('bs.validator.errors', errors)
errors.length ? self.showErrors($el) : self.clearErrors($el)
if (!prevErrors || errors.toString() !== prevErrors.toString()) {
e = errors.length
? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors})
: $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors})
self.$element.trigger(e)
}
self.toggleSubmit()
self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]}))
})
}
Validator.prototype.runValidators = function ($el) {
var errors = []
var deferred = $.Deferred()
var options = this.options
$el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject()
$el.data('bs.validator.deferred', deferred)
function getErrorMessage(key) {
return $el.data(key + '-error')
|| $el.data('error')
|| key == 'native' && $el[0].validationMessage
|| options.errors[key]
}
$.each(Validator.VALIDATORS, $.proxy(function (key, validator) {
if (($el.data(key) || key == 'native') && !validator.call(this, $el)) {
var error = getErrorMessage(key)
!~errors.indexOf(error) && errors.push(error)
}
}, this))
if (!errors.length && $el.val() && $el.data('remote')) {
this.defer($el, function () {
var data = {}
data[$el.attr('name')] = $el.val()
$.get($el.data('remote'), data)
.fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) })
.always(function () { deferred.resolve(errors)})
})
} else deferred.resolve(errors)
return deferred.promise()
}
Validator.prototype.validate = function () {
var delay = this.options.delay
this.options.delay = 0
this.$element.find(':input:not([type="hidden"])').trigger('input.bs.validator')
this.options.delay = delay
return this
}
Validator.prototype.showErrors = function ($el) {
var method = this.options.html ? 'html' : 'text'
this.defer($el, function () {
var $group = $el.closest('.form-group')
var $block = $group.find('.help-block.with-errors')
var $feedback = $group.find('.form-control-feedback')
var errors = $el.data('bs.validator.errors')
if (!errors.length) return
errors = $('
')
.addClass('list-unstyled')
.append($.map(errors, function (error) { return $('')[method](error) }))
$block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html())
$block.empty().append(errors)
$group.addClass('has-error')
$feedback.length
&& $feedback.removeClass('glyphicon-ok')
&& $feedback.addClass('glyphicon-warning-sign')
&& $group.removeClass('has-success')
})
}
Validator.prototype.clearErrors = function ($el) {
var $group = $el.closest('.form-group')
var $block = $group.find('.help-block.with-errors')
var $feedback = $group.find('.form-control-feedback')
$block.html($block.data('bs.validator.originalContent'))
$group.removeClass('has-error')
$feedback.length
&& $feedback.removeClass('glyphicon-warning-sign')
&& $feedback.addClass('glyphicon-ok')
&& $group.addClass('has-success')
}
Validator.prototype.hasErrors = function () {
function fieldErrors() {
return !!($(this).data('bs.validator.errors') || []).length
}
return !!this.$element.find(':input:enabled').filter(fieldErrors).length
}
Validator.prototype.isIncomplete = function () {
function fieldIncomplete() {
return this.type === 'checkbox' ? !this.checked :
this.type === 'radio' ? !$('[name="' + this.name + '"]:checked').length :
$.trim(this.value) === ''
}
return !!this.$element.find(':input[required]:enabled').filter(fieldIncomplete).length
}
Validator.prototype.onSubmit = function (e) {
this.validate()
if (this.isIncomplete() || this.hasErrors()) e.preventDefault()
}
Validator.prototype.toggleSubmit = function () {
if(!this.options.disable) return
var $btn = this.$element.find('input[type="submit"], button[type="submit"]')
$btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
.css({'pointer-events': 'all', 'cursor': 'pointer'})
}
Validator.prototype.defer = function ($el, callback) {
if (!this.options.delay) return callback()
window.clearTimeout($el.data('bs.validator.timeout'))
$el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay))
}
Validator.prototype.destroy = function () {
this.$element
.removeAttr('novalidate')
.removeData('bs.validator')
.off('.bs.validator')
this.$element.find(':input')
.off('.bs.validator')
.removeData(['bs.validator.errors', 'bs.validator.deferred'])
.each(function () {
var $this = $(this)
var timeout = $this.data('bs.validator.timeout')
window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout')
})
this.$element.find('.help-block.with-errors').each(function () {
var $this = $(this)
var originalContent = $this.data('bs.validator.originalContent')
$this
.removeData('bs.validator.originalContent')
.html(originalContent)
})
this.$element.find('input[type="submit"], button[type="submit"]').removeClass('disabled')
this.$element.find('.has-error').removeClass('has-error')
return this
}
// VALIDATOR PLUGIN DEFINITION
// ===========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option)
var data = $this.data('bs.validator')
if (!data && option == 'destroy') return
if (!data) $this.data('bs.validator', (data = new Validator(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.validator
$.fn.validator = Plugin
$.fn.validator.Constructor = Validator
// VALIDATOR NO CONFLICT
// =====================
$.fn.validator.noConflict = function () {
$.fn.validator = old
return this
}
// VALIDATOR DATA-API
// ==================
$(window).on('load', function () {
$('form[data-toggle="validator"]').each(function () {
var $form = $(this)
Plugin.call($form, $form.data())
})
})
}(jQuery);
/* ========================================================================
* http://www.kunalbabre.com/projects/table2CSV.php
* ======================================================================== */
jQuery.fn.table2CSV = function(options) {
var options = jQuery.extend({
separator: ',',
header: [],
delivery: 'popup' // popup, value
},
options);
var csvData = [];
var headerArr = [];
var el = this;
//header
var numCols = options.header.length;
var tmpRow = []; // construct header avalible array
if (numCols > 0) {
for (var i = 0; i < numCols; i++) {
tmpRow[tmpRow.length] = formatData(options.header[i]);
}
} else {
$(el).filter(':visible').find('th').each(function() {
if ($(this).css('display') != 'none') tmpRow[tmpRow.length] = formatData($(this).html());
});
}
row2CSV(tmpRow);
// actual data
$(el).find('tr').each(function() {
var tmpRow = [];
$(this).filter(':visible').find('td').each(function() {
if ($(this).css('display') != 'none') tmpRow[tmpRow.length] = formatData($(this).html());
});
row2CSV(tmpRow);
});
if (options.delivery == 'popup') {
var mydata = csvData.join('\n');
return popup(mydata);
} else {
var mydata = csvData.join('\n');
if ($.isFunction(options.delivery)) {
(options.delivery)(mydata);
}
return mydata;
}
function row2CSV(tmpRow) {
var tmp = tmpRow.join('') // to remove any blank rows
// alert(tmp);
if (tmpRow.length > 0 && tmp != '') {
var mystr = tmpRow.join(options.separator);
csvData[csvData.length] = mystr;
}
}
function formatData(input) {
// replace " with “
var regexp = new RegExp(/["]/g);
var output = input.replace(regexp, "“");
//HTML
var regexp = new RegExp(/\<[^\<]+\>/g);
var output = output.replace(regexp, "");
if (output == "") return '';
return '"' + output + '"';
}
function popup(data) {
// $("").appendTo("body");
// $("textarea#altui-divtemp").focus();
// $("textarea#altui-divtemp").text(data)
// $("textarea#altui-divtemp").select();
// document.execCommand('copy');
// $("textarea#altui-divtemp").remove();
// alert(_T("Data copied in clipboard"));
var generator = window.open('', 'csv', 'height=400,width=600');
generator.document.write('CSV');
generator.document.write('');
generator.document.write('');
generator.document.write('');
generator.document.close();
return true;
}
};
/**
* Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect)
*
* Apache License, Version 2.0:
* Copyright (c) 2012 - 2015 David Stutz
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* BSD 3-Clause License:
* Copyright (c) 2012 - 2015 David Stutz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of David Stutz nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var bootstrap_multiselect_css = ".multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px 3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 10px 3px 25px}.multiselect-container>li>a>label.radio,.multiselect-container>li>a>label.checkbox{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 10px 3px 25px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0}";
!function ($) {
"use strict";// jshint ;_;
if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
ko.bindingHandlers.multiselect = {
after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var config = ko.toJS(valueAccessor());
$element.multiselect(config);
if (allBindings.has('options')) {
var options = allBindings.get('options');
if (ko.isObservable(options)) {
ko.computed({
read: function() {
options();
setTimeout(function() {
var ms = $element.data('multiselect');
if (ms)
ms.updateOriginalOptions();//Not sure how beneficial this is.
$element.multiselect('rebuild');
}, 1);
},
disposeWhenNodeIsRemoved: element
});
}
}
//value and selectedOptions are two-way, so these will be triggered even by our own actions.
//It needs some way to tell if they are triggered because of us or because of outside change.
//It doesn't loop but it's a waste of processing.
if (allBindings.has('value')) {
var value = allBindings.get('value');
if (ko.isObservable(value)) {
ko.computed({
read: function() {
value();
setTimeout(function() {
$element.multiselect('refresh');
}, 1);
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
}
}
//Switched from arrayChange subscription to general subscription using 'refresh'.
//Not sure performance is any better using 'select' and 'deselect'.
if (allBindings.has('selectedOptions')) {
var selectedOptions = allBindings.get('selectedOptions');
if (ko.isObservable(selectedOptions)) {
ko.computed({
read: function() {
selectedOptions();
setTimeout(function() {
$element.multiselect('refresh');
}, 1);
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
}
}
var setEnabled = function (enable) {
setTimeout(function () {
if (enable)
$element.multiselect('enable');
else
$element.multiselect('disable');
});
};
if (allBindings.has('enable')) {
var enable = allBindings.get('enable');
if (ko.isObservable(enable)) {
ko.computed({
read: function () {
setEnabled(enable());
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
} else {
setEnabled(enable);
}
}
if (allBindings.has('disable')) {
var disable = allBindings.get('disable');
if (ko.isObservable(disable)) {
ko.computed({
read: function () {
setEnabled(!disable());
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
} else {
setEnabled(!disable);
}
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$element.multiselect('destroy');
});
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var config = ko.toJS(valueAccessor());
$element.multiselect('setOptions', config);
$element.multiselect('rebuild');
}
};
}
function forEach(array, callback) {
for (var index = 0; index < array.length; ++index) {
callback(array[index], index);
}
}
/**
* Constructor to create a new multiselect using the given select.
*
* @param {jQuery} select
* @param {Object} options
* @returns {Multiselect}
*/
function Multiselect(select, options) {
this.$select = $(select);
// Placeholder via data attributes
if (this.$select.attr("data-placeholder")) {
options.nonSelectedText = this.$select.data("placeholder");
}
this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
// Initialization.
// We have to clone to create a new reference.
this.originalOptions = this.$select.clone()[0].options;
this.query = '';
this.searchTimeout = null;
this.lastToggledInput = null;
this.options.multiple = this.$select.attr('multiple') === "multiple";
this.options.onChange = $.proxy(this.options.onChange, this);
this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this);
this.options.onInitialized = $.proxy(this.options.onInitialized, this);
// Build select all if enabled.
this.buildContainer();
this.buildButton();
this.buildDropdown();
this.buildSelectAll();
this.buildDropdownOptions();
this.buildFilter();
this.updateButtonText();
this.updateSelectAll(true);
if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
this.disable();
}
this.$select.hide().after(this.$container);
this.options.onInitialized(this.$select, this.$container);
}
Multiselect.prototype = {
defaults: {
/**
* Default text function will either print 'None selected' in case no
* option is selected or a list of the selected options up to a length
* of 3 selected options.
*
* @param {jQuery} options
* @param {jQuery} select
* @returns {String}
*/
buttonText: function(options, select) {
if (this.disabledText.length > 0
&& (this.disableIfEmpty || select.prop('disabled'))
&& options.length == 0) {
return this.disabledText;
}
else if (options.length === 0) {
return this.nonSelectedText;
}
else if (this.allSelectedText
&& options.length === $('option', $(select)).length
&& $('option', $(select)).length !== 1
&& this.multiple) {
if (this.selectAllNumber) {
return this.allSelectedText + ' (' + options.length + ')';
}
else {
return this.allSelectedText;
}
}
else if (options.length > this.numberDisplayed) {
return options.length + ' ' + this.nSelectedText;
}
else {
var selected = '';
var delimiter = this.delimiterText;
options.each(function() {
var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
selected += label + delimiter;
});
return selected.substr(0, selected.length - 2);
}
},
/**
* Updates the title of the button similar to the buttonText function.
*
* @param {jQuery} options
* @param {jQuery} select
* @returns {@exp;selected@call;substr}
*/
buttonTitle: function(options, select) {
if (options.length === 0) {
return this.nonSelectedText;
}
else {
var selected = '';
var delimiter = this.delimiterText;
options.each(function () {
var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
selected += label + delimiter;
});
return selected.substr(0, selected.length - 2);
}
},
/**
* Create a label.
*
* @param {jQuery} element
* @returns {String}
*/
optionLabel: function(element){
return $(element).attr('label') || $(element).text();
},
/**
* Create a class.
*
* @param {jQuery} element
* @returns {String}
*/
optionClass: function(element) {
return $(element).attr('class') || '';
},
/**
* Triggered on change of the multiselect.
*
* Not triggered when selecting/deselecting options manually.
*
* @param {jQuery} option
* @param {Boolean} checked
*/
onChange : function(option, checked) {
},
/**
* Triggered when the dropdown is shown.
*
* @param {jQuery} event
*/
onDropdownShow: function(event) {
},
/**
* Triggered when the dropdown is hidden.
*
* @param {jQuery} event
*/
onDropdownHide: function(event) {
},
/**
* Triggered after the dropdown is shown.
*
* @param {jQuery} event
*/
onDropdownShown: function(event) {
},
/**
* Triggered after the dropdown is hidden.
*
* @param {jQuery} event
*/
onDropdownHidden: function(event) {
},
/**
* Triggered on select all.
*/
onSelectAll: function(checked) {
},
/**
* Triggered after initializing.
*
* @param {jQuery} $select
* @param {jQuery} $container
*/
onInitialized: function($select, $container) {
},
enableHTML: false,
buttonClass: 'btn btn-default',
inheritClass: false,
buttonWidth: 'auto',
buttonContainer: '',
dropRight: false,
dropUp: false,
selectedClass: 'active',
// Maximum height of the dropdown menu.
// If maximum height is exceeded a scrollbar will be displayed.
maxHeight: false,
checkboxName: false,
includeSelectAllOption: false,
includeSelectAllIfMoreThan: 0,
selectAllText: ' Select all',
selectAllValue: 'multiselect-all',
selectAllName: false,
selectAllNumber: true,
selectAllJustVisible: true,
enableFiltering: false,
enableCaseInsensitiveFiltering: false,
enableFullValueFiltering: false,
enableClickableOptGroups: false,
enableCollapsibelOptGroups: false,
filterPlaceholder: 'Search',
// possible options: 'text', 'value', 'both'
filterBehavior: 'text',
includeFilterClearBtn: true,
preventInputChangeEvent: false,
nonSelectedText: 'None selected',
nSelectedText: 'selected',
allSelectedText: 'All selected',
numberDisplayed: 3,
disableIfEmpty: false,
disabledText: '',
delimiterText: ', ',
templates: {
button: '',
ul: '
',
filter: '
',
filterClearBtn: '',
li: '
',
divider: '',
liGroup: ''
}
},
constructor: Multiselect,
/**
* Builds the container of the multiselect.
*/
buildContainer: function() {
this.$container = $(this.options.buttonContainer);
this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
},
/**
* Builds the button of the multiselect.
*/
buildButton: function() {
this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
if (this.$select.attr('class') && this.options.inheritClass) {
this.$button.addClass(this.$select.attr('class'));
}
// Adopt active state.
if (this.$select.prop('disabled')) {
this.disable();
}
else {
this.enable();
}
// Manually add button width if set.
if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
this.$button.css({
'width' : this.options.buttonWidth,
'overflow' : 'hidden',
'text-overflow' : 'ellipsis'
});
this.$container.css({
'width': this.options.buttonWidth
});
}
// Keep the tab index from the select.
var tabindex = this.$select.attr('tabindex');
if (tabindex) {
this.$button.attr('tabindex', tabindex);
}
this.$container.prepend(this.$button);
},
/**
* Builds the ul representing the dropdown menu.
*/
buildDropdown: function() {
// Build ul.
this.$ul = $(this.options.templates.ul);
if (this.options.dropRight) {
this.$ul.addClass('pull-right');
}
// Set max height of dropdown menu to activate auto scrollbar.
if (this.options.maxHeight) {
// TODO: Add a class for this option to move the css declarations.
this.$ul.css({
'max-height': this.options.maxHeight + 'px',
'overflow-y': 'auto',
'overflow-x': 'hidden'
});
}
if (this.options.dropUp) {
var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0));
var moveCalc = height + 34;
this.$ul.css({
'max-height': height + 'px',
'overflow-y': 'auto',
'overflow-x': 'hidden',
'margin-top': "-" + moveCalc + 'px'
});
}
this.$container.append(this.$ul);
},
/**
* Build the dropdown options and binds all nessecary events.
*
* Uses createDivider and createOptionValue to create the necessary options.
*/
buildDropdownOptions: function() {
this.$select.children().each($.proxy(function(index, element) {
var $element = $(element);
// Support optgroups and options without a group simultaneously.
var tag = $element.prop('tagName')
.toLowerCase();
if ($element.prop('value') === this.options.selectAllValue) {
return;
}
if (tag === 'optgroup') {
this.createOptgroup(element);
}
else if (tag === 'option') {
if ($element.data('role') === 'divider') {
this.createDivider();
}
else {
this.createOptionValue(element);
}
}
// Other illegal tags will be ignored.
}, this));
// Bind the change event on the dropdown elements.
$('li input', this.$ul).on('change', $.proxy(function(event) {
var $target = $(event.target);
var checked = $target.prop('checked') || false;
var isSelectAllOption = $target.val() === this.options.selectAllValue;
// Apply or unapply the configured selected class.
if (this.options.selectedClass) {
if (checked) {
$target.closest('li')
.addClass(this.options.selectedClass);
}
else {
$target.closest('li')
.removeClass(this.options.selectedClass);
}
}
// Get the corresponding option.
var value = $target.val();
var $option = this.getOptionByValue(value);
var $optionsNotThis = $('option', this.$select).not($option);
var $checkboxesNotThis = $('input', this.$container).not($target);
if (isSelectAllOption) {
if (checked) {
this.selectAll(this.options.selectAllJustVisible);
}
else {
this.deselectAll(this.options.selectAllJustVisible);
}
}
else {
if (checked) {
$option.prop('selected', true);
if (this.options.multiple) {
// Simply select additional option.
$option.prop('selected', true);
}
else {
// Unselect all other options and corresponding checkboxes.
if (this.options.selectedClass) {
$($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
}
$($checkboxesNotThis).prop('checked', false);
$optionsNotThis.prop('selected', false);
// It's a single selection, so close.
this.$button.click();
}
if (this.options.selectedClass === "active") {
$optionsNotThis.closest("a").css("outline", "");
}
}
else {
// Unselect option.
$option.prop('selected', false);
}
// To prevent select all from firing onChange: #575
this.options.onChange($option, checked);
}
this.$select.change();
this.updateButtonText();
this.updateSelectAll();
if(this.options.preventInputChangeEvent) {
return false;
}
}, this));
$('li a', this.$ul).on('mousedown', function(e) {
if (e.shiftKey) {
// Prevent selecting text by Shift+click
return false;
}
});
$('li a', this.$ul).on('touchstart click', $.proxy(function(event) {
event.stopPropagation();
var $target = $(event.target);
if (event.shiftKey && this.options.multiple) {
if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
event.preventDefault();
$target = $target.find("input");
$target.prop("checked", !$target.prop("checked"));
}
var checked = $target.prop('checked') || false;
if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
var from = $target.closest("li").index();
var to = this.lastToggledInput.closest("li").index();
if (from > to) { // Swap the indices
var tmp = to;
to = from;
from = tmp;
}
// Make sure we grab all elements since slice excludes the last index
++to;
// Change the checkboxes and underlying options
var range = this.$ul.find("li").slice(from, to).find("input");
range.prop('checked', checked);
if (this.options.selectedClass) {
range.closest('li')
.toggleClass(this.options.selectedClass, checked);
}
for (var i = 0, j = range.length; i < j; i++) {
var $checkbox = $(range[i]);
var $option = this.getOptionByValue($checkbox.val());
$option.prop('selected', checked);
}
}
// Trigger the select "change" event
$target.trigger("change");
}
// Remembers last clicked option
if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
this.lastToggledInput = $target;
}
$target.blur();
}, this));
// Keyboard support.
this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
if ($('input[type="text"]', this.$container).is(':focus')) {
return;
}
if (event.keyCode === 9 && this.$container.hasClass('open')) {
this.$button.click();
}
else {
var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
if (!$items.length) {
return;
}
var index = $items.index($items.filter(':focus'));
// Navigation up.
if (event.keyCode === 38 && index > 0) {
index--;
}
// Navigate down.
else if (event.keyCode === 40 && index < $items.length - 1) {
index++;
}
else if (!~index) {
index = 0;
}
var $current = $items.eq(index);
$current.focus();
if (event.keyCode === 32 || event.keyCode === 13) {
var $checkbox = $current.find('input');
$checkbox.prop("checked", !$checkbox.prop("checked"));
$checkbox.change();
}
event.stopPropagation();
event.preventDefault();
}
}, this));
if(this.options.enableClickableOptGroups && this.options.multiple) {
$('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) {
event.stopPropagation();
console.log('test');
var group = $(event.target).parent();
// Search all option in optgroup
var $options = group.nextUntil('li.multiselect-group');
var $visibleOptions = $options.filter(":visible:not(.disabled)");
// check or uncheck items
var allChecked = true;
var optionInputs = $visibleOptions.find('input');
var values = [];
optionInputs.each(function() {
allChecked = allChecked && $(this).prop('checked');
values.push($(this).val());
});
if (!allChecked) {
this.select(values, false);
}
else {
this.deselect(values, false);
}
this.options.onChange(optionInputs, !allChecked);
}, this));
}
if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
$("li.multiselect-group input", this.$ul).off();
$("li.multiselect-group", this.$ul).siblings().not("li.multiselect-group, li.multiselect-all", this.$ul).each( function () {
$(this).toggleClass('hidden', true);
});
$("li.multiselect-group", this.$ul).on("click", $.proxy(function(group) {
group.stopPropagation();
}, this));
$("li.multiselect-group > a > b", this.$ul).on("click", $.proxy(function(t) {
t.stopPropagation();
var n = $(t.target).closest('li');
var r = n.nextUntil("li.multiselect-group");
var i = true;
r.each(function() {
i = i && $(this).hasClass('hidden');
});
r.toggleClass('hidden', !i);
}, this));
$("li.multiselect-group > a > input", this.$ul).on("change", $.proxy(function(t) {
t.stopPropagation();
var n = $(t.target).closest('li');
var r = n.nextUntil("li.multiselect-group", ':not(.disabled)');
var s = r.find("input");
var i = true;
s.each(function() {
i = i && $(this).prop("checked");
});
s.prop("checked", !i).trigger("change");
}, this));
// Set the initial selection state of the groups.
$('li.multiselect-group', this.$ul).each(function() {
var r = $(this).nextUntil("li.multiselect-group", ':not(.disabled)');
var s = r.find("input");
var i = true;
s.each(function() {
i = i && $(this).prop("checked");
});
$(this).find('input').prop("checked", i);
});
// Update the group checkbox based on new selections among the
// corresponding children.
$("li input", this.$ul).on("change", $.proxy(function(t) {
t.stopPropagation();
var n = $(t.target).closest('li');
var r1 = n.prevUntil("li.multiselect-group", ':not(.disabled)');
var r2 = n.nextUntil("li.multiselect-group", ':not(.disabled)');
var s1 = r1.find("input");
var s2 = r2.find("input");
var i = $(t.target).prop('checked');
s1.each(function() {
i = i && $(this).prop("checked");
});
s2.each(function() {
i = i && $(this).prop("checked");
});
n.prevAll('.multiselect-group').find('input').prop('checked', i);
}, this));
$("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea');
$("li.multiselect-group > a, li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px');
$("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px');
}
},
/**
* Create an option using the given select option.
*
* @param {jQuery} element
*/
createOptionValue: function(element) {
var $element = $(element);
if ($element.is(':selected')) {
$element.prop('selected', true);
}
// Support the label attribute on options.
var label = this.options.optionLabel(element);
var classes = this.options.optionClass(element);
var value = $element.val();
var inputType = this.options.multiple ? "checkbox" : "radio";
var $li = $(this.options.templates.li);
var $label = $('label', $li);
$label.addClass(inputType);
$li.addClass(classes);
if (this.options.enableHTML) {
$label.html(" " + label);
}
else {
$label.text(" " + label);
}
var $checkbox = $('').attr('type', inputType);
if (this.options.checkboxName) {
$checkbox.attr('name', this.options.checkboxName);
}
$label.prepend($checkbox);
var selected = $element.prop('selected') || false;
$checkbox.val(value);
if (value === this.options.selectAllValue) {
$li.addClass("multiselect-item multiselect-all");
$checkbox.parent().parent()
.addClass('multiselect-all');
}
$label.attr('title', $element.attr('title'));
this.$ul.append($li);
if ($element.is(':disabled')) {
$checkbox.attr('disabled', 'disabled')
.prop('disabled', true)
.closest('a')
.attr("tabindex", "-1")
.closest('li')
.addClass('disabled');
}
$checkbox.prop('checked', selected);
if (selected && this.options.selectedClass) {
$checkbox.closest('li')
.addClass(this.options.selectedClass);
}
},
/**
* Creates a divider using the given select option.
*
* @param {jQuery} element
*/
createDivider: function(element) {
var $divider = $(this.options.templates.divider);
this.$ul.append($divider);
},
/**
* Creates an optgroup.
*
* @param {jQuery} group
*/
createOptgroup: function(group) {
if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
var label = $(group).attr("label");
var value = $(group).attr("value");
var r = $('