first commit

This commit is contained in:
2025-01-06 20:47:25 +01:00
commit 3bdbd78c2f
25591 changed files with 3586440 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,417 @@
/*
* jQuery blockUI plugin
* Version 2.20 (19-MAY-2009)
* @requires jQuery v1.2.3 or later
*
* Examples at: http://malsup.com/jquery/block/
* Copyright (c) 2007-2008 M. Alsup
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Thanks to Amir-Hossein Sobhi for some excellent contributions!
*/
;(function($) {
$.fn._fadeIn = $.fn.fadeIn;
var setExpr = (function() {
if (!$.browser.msie) return false;
var div = document.createElement('div');
try { div.style.setExpression('width','0+0'); }
catch(e) { return false; }
return true;
})();
// global $ methods for blocking/unblocking the entire page
$.blockUI = function(opts) { install(window, opts); };
$.unblockUI = function(opts) { remove(window, opts); };
// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
$.growlUI = function(title, message, timeout, onClose) {
var $m = $('<div class="growlUI"></div>');
if (title) $m.append('<h1>'+title+'</h1>');
if (message) $m.append('<h2>'+message+'</h2>');
if (timeout == undefined) timeout = 3000;
$.blockUI({
message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
timeout: timeout, showOverlay: false,
onUnblock: onClose,
css: $.blockUI.defaults.growlCSS
});
};
// plugin method for blocking element content
$.fn.block = function(opts) {
return this.unblock({ fadeOut: 0 }).each(function() {
if ($.css(this,'position') == 'static')
this.style.position = 'relative';
if ($.browser.msie)
this.style.zoom = 1; // force 'hasLayout'
install(this, opts);
});
};
// plugin method for unblocking element content
$.fn.unblock = function(opts) {
return this.each(function() {
remove(this, opts);
});
};
$.blockUI.version = 2.20; // 2nd generation blocking at no extra cost!
// override these in your code to change the default behavior and style
$.blockUI.defaults = {
// message displayed when blocking (use null for no message)
message: '<h1>Please wait...</h1>',
// styles for the message when blocking; if you wish to disable
// these and use an external stylesheet then do this in your code:
// $.blockUI.defaults.css = {};
css: {
padding: 0,
margin: 0,
width: '30%',
top: '40%',
left: '35%',
textAlign: 'center',
color: '#000',
border: '3px solid #aaa',
backgroundColor:'#fff',
cursor: 'wait'
},
// styles for the overlay
overlayCSS: {
backgroundColor: '#000',
opacity: 0.6,
cursor: 'wait'
},
// styles applied when using $.growlUI
growlCSS: {
width: '350px',
top: '10px',
left: '',
right: '10px',
border: 'none',
padding: '5px',
opacity: 0.6,
cursor: null,
color: '#fff',
backgroundColor: '#000',
'-webkit-border-radius': '10px',
'-moz-border-radius': '10px'
},
// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
// (hat tip to Jorge H. N. de Vasconcelos)
iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
// force usage of iframe in non-IE browsers (handy for blocking applets)
forceIframe: false,
// z-index for the blocking overlay
baseZ: 1000,
// set these to true to have the message automatically centered
centerX: true, // <-- only effects element blocking (page block controlled via css above)
centerY: true,
// allow body element to be stetched in ie6; this makes blocking look better
// on "short" pages. disable if you wish to prevent changes to the body height
allowBodyStretch: true,
// enable if you want key and mouse events to be disabled for content that is blocked
bindEvents: true,
// be default blockUI will supress tab navigation from leaving blocking content
// (if bindEvents is true)
constrainTabKey: true,
// fadeIn time in millis; set to 0 to disable fadeIn on block
fadeIn: 200,
// fadeOut time in millis; set to 0 to disable fadeOut on unblock
fadeOut: 400,
// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
timeout: 0,
// disable if you don't want to show the overlay
showOverlay: true,
// if true, focus will be placed in the first available input field when
// page blocking
focusInput: true,
// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
applyPlatformOpacityRules: true,
// callback method invoked when unblocking has completed; the callback is
// passed the element that has been unblocked (which is the window object for page
// blocks) and the options that were passed to the unblock call:
// onUnblock(element, options)
onUnblock: null,
// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
quirksmodeOffsetHack: 4
};
// private data and functions follow...
var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent);
var pageBlock = null;
var pageBlockEls = [];
function install(el, opts) {
var full = (el == window);
var msg = opts && opts.message !== undefined ? opts.message : undefined;
opts = $.extend({}, $.blockUI.defaults, opts || {});
opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
msg = msg === undefined ? opts.message : msg;
// remove the current block (if there is one)
if (full && pageBlock)
remove(window, {fadeOut:0});
// if an existing element is being used as the blocking content then we capture
// its current place in the DOM (and current display style) so we can restore
// it when we unblock
if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
var node = msg.jquery ? msg[0] : msg;
var data = {};
$(el).data('blockUI.history', data);
data.el = node;
data.parent = node.parentNode;
data.display = node.style.display;
data.position = node.style.position;
if (data.parent)
data.parent.removeChild(node);
}
var z = opts.baseZ;
// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
// layer1 is the iframe layer which is used to supress bleed through of underlying content
// layer2 is the overlay layer which has opacity and a wait cursor (by default)
// layer3 is the message content that is displayed while blocking
var lyr1 = ($.browser.msie || opts.forceIframe)
? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
: $('<div class="blockUI" style="display:none"></div>');
var lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
var lyr3 = full ? $('<div class="blockUI blockMsg blockPage" style="z-index:'+z+';display:none;position:fixed"></div>')
: $('<div class="blockUI blockMsg blockElement" style="z-index:'+z+';display:none;position:absolute"></div>');
// if we have a message, style it
if (msg)
lyr3.css(css);
// style the overlay
if (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))
lyr2.css(opts.overlayCSS);
lyr2.css('position', full ? 'fixed' : 'absolute');
// make iframe layer transparent in IE
if ($.browser.msie || opts.forceIframe)
lyr1.css('opacity',0.0);
$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
var expr = $.browser.msie && ($.browser.version < 8 || !$.boxModel) && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
if (ie6 || (expr && setExpr)) {
// give body 100% height
if (full && opts.allowBodyStretch && $.boxModel)
$('html,body').css('height','100%');
// fix ie6 issue when blocked element has a border width
if ((ie6 || !$.boxModel) && !full) {
var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
var fixT = t ? '(0 - '+t+')' : 0;
var fixL = l ? '(0 - '+l+')' : 0;
}
// simulate fixed position
$.each([lyr1,lyr2,lyr3], function(i,o) {
var s = o[0].style;
s.position = 'absolute';
if (i < 2) {
full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
: s.setExpression('height','this.parentNode.offsetHeight + "px"');
full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
: s.setExpression('width','this.parentNode.offsetWidth + "px"');
if (fixL) s.setExpression('left', fixL);
if (fixT) s.setExpression('top', fixT);
}
else if (opts.centerY) {
if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
s.marginTop = 0;
}
else if (!opts.centerY && full) {
var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
s.setExpression('top',expression);
}
});
}
// show the message
if (msg) {
lyr3.append(msg);
if (msg.jquery || msg.nodeType)
$(msg).show();
}
if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
lyr1.show(); // opacity is zero
if (opts.fadeIn) {
if (opts.showOverlay)
lyr2._fadeIn(opts.fadeIn);
if (msg)
lyr3.fadeIn(opts.fadeIn);
}
else {
if (opts.showOverlay)
lyr2.show();
if (msg)
lyr3.show();
}
// bind key and mouse events
bind(1, el, opts);
if (full) {
pageBlock = lyr3[0];
pageBlockEls = $(':input:enabled:visible',pageBlock);
if (opts.focusInput)
setTimeout(focus, 20);
}
else
center(lyr3[0], opts.centerX, opts.centerY);
if (opts.timeout) {
// auto-unblock
var to = setTimeout(function() {
full ? $.unblockUI(opts) : $(el).unblock(opts);
}, opts.timeout);
$(el).data('blockUI.timeout', to);
}
};
// remove the block
function remove(el, opts) {
var full = el == window;
var $el = $(el);
var data = $el.data('blockUI.history');
var to = $el.data('blockUI.timeout');
if (to) {
clearTimeout(to);
$el.removeData('blockUI.timeout');
}
opts = $.extend({}, $.blockUI.defaults, opts || {});
bind(0, el, opts); // unbind events
var els = full ? $('body').children().filter('.blockUI') : $('.blockUI', el);
if (full)
pageBlock = pageBlockEls = null;
if (opts.fadeOut) {
els.fadeOut(opts.fadeOut);
setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
}
else
reset(els, data, opts, el);
};
// move blocking element back into the DOM where it started
function reset(els,data,opts,el) {
els.each(function(i,o) {
// remove via DOM calls so we don't lose event handlers
if (this.parentNode)
this.parentNode.removeChild(this);
});
if (data && data.el) {
data.el.style.display = data.display;
data.el.style.position = data.position;
if (data.parent)
data.parent.appendChild(data.el);
$(data.el).removeData('blockUI.history');
}
if (typeof opts.onUnblock == 'function')
opts.onUnblock(el,opts);
};
// bind/unbind the handler
function bind(b, el, opts) {
var full = el == window, $el = $(el);
// don't bother unbinding if there is nothing to unbind
if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
return;
if (!full)
$el.data('blockUI.isBlocked', b);
// don't bind events when overlay is not in use or if bindEvents is false
if (!opts.bindEvents || (b && !opts.showOverlay))
return;
// bind anchors and inputs for mouse and key events
var events = 'mousedown mouseup keydown keypress';
b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);
// former impl...
// var $e = $('a,:input');
// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
};
// event handler to suppress keyboard/mouse events when blocking
function handler(e) {
// allow tab navigation (conditionally)
if (e.keyCode && e.keyCode == 9) {
if (pageBlock && e.data.constrainTabKey) {
var els = pageBlockEls;
var fwd = !e.shiftKey && e.target == els[els.length-1];
var back = e.shiftKey && e.target == els[0];
if (fwd || back) {
setTimeout(function(){focus(back)},10);
return false;
}
}
}
// allow events within the message content
if ($(e.target).parents('div.blockMsg').length > 0)
return true;
// allow events for content that is not being blocked
return $(e.target).parents().children().filter('div.blockUI').length == 0;
};
function focus(back) {
if (!pageBlockEls)
return;
var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
if (e)
e.focus();
};
function center(el, x, y) {
var p = el.parentNode, s = el.style;
var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
if (x) s.left = l > 0 ? (l+'px') : '0';
if (y) s.top = t > 0 ? (t+'px') : '0';
};
function sz(el, p) {
return parseInt($.css(el,p))||0;
};
})($);

View File

@@ -0,0 +1,38 @@
/*
* jQuery Simple Templates plugin 1.1.1
*
* http://andrew.hedges.name/tmpl/
* http://docs.jquery.com/Plugins/Tmpl
*
* Copyright (c) 2008 Andrew Hedges, andrew@hedges.name
*
* Usage: $.tmpl('<div class="#{classname}">#{content}</div>', { 'classname' : 'my-class', 'content' : 'My content.' });
* $.tmpl('<div class="#{1}">#{0}</div>', 'My content', 'my-class'); // placeholder order not important
*
* The changes for version 1.1 were inspired by the discussion at this thread:
* http://groups.google.com/group/jquery-ui/browse_thread/thread/45d0f5873dad0178/0f3c684499d89ff4
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
(function($) {
// regular expression for matching our placeholders; e.g., #{my-cLaSs_name77}
var regx = /#\{([^{}]*)}/g;
$.extend({
// public interface: $.tmpl
tmpl : function(tmpl) {
// default to doing no harm
tmpl = tmpl || '';
var vals = (2 === arguments.length && 'object' === typeof arguments[1] ? arguments[1] : Array.prototype.slice.call(arguments,1));
// function to making replacements
var repr = function (str, match) {
return typeof vals[match] === 'string' || typeof vals[match] === 'number' ? vals[match] : str;
};
return tmpl.replace(regx, repr);
}
});
})($);

View File

@@ -0,0 +1,967 @@
/*
* jQuery UI Multiselect
*
* Authors:
* Michael Aufreiter (quasipartikel.at)
* Yanick Rochon (yanick.rochon[at]gmail[dot]com)
*
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* http://yanickrochon.uuuq.com/multiselect/
*
*
* Depends:
* ui.core.js
* ui.draggable.js
* ui.droppable.js
* ui.sortable.js
* jquery.blockUI (http://github.com/malsup/blockui/)
* jquery.tmpl (http://andrew.hedges.name/blog/2008/09/03/introducing-jquery-simple-templates
*
* Optional:
* localization (http://plugins.jquery.com/project/localisation)
*
* Notes:
* The strings in this plugin use a templating engine to enable localization
* and allow flexibility in the messages. Read the documentation for more details.
*
* Todo:
* restore selected items on remote searchable multiselect upon page reload (same behavior as local mode)
* (is it worth it??) add a public function to apply the nodeComparator to all items (when using nodeComparator setter)
* support for option groups, disabled options, etc.
* speed improvements
* tests and optimizations
* - test getters/setters (including options from the defaults)
*/
// objectKeys return
$.extend({ objectKeys: function(obj){ if (typeof Object.keys == 'function') return Object.keys(obj); var a = []; $.each(obj, function(k){ a.push(k) }); return a; }});
/********************************
* Default callbacks
********************************/
// expect data to be "val1=text1[\nval2=text2[\n...]]"
var defaultDataParser = function(data) {
if ( typeof data == 'string' ) {
var pattern = /^(\s\n\r\t)*\+?$/;
var selected, line, lines = data.split(/\n/);
data = {};
for (var i in lines) {
line = lines[i].split("=");
// make sure the key is not empty
if (!pattern.test(line[0])) {
selected = (line[0].lastIndexOf('+') == line.length - 1);
if (selected) line[0] = line.substr(0,line.length-1);
// if no value is specified, default to the key value
data[line[0]] = {
selected: false,
value: line[1] || line[0]
};
}
}
} else {
this._messages($.ui.multiselect.constante.MESSAGE_ERROR, $.ui.multiselect.locale.errorDataFormat);
data = false;
}
return data;
};
var defaultNodeComparator = function(node1,node2) {
var text1 = node1.text(),
text2 = node2.text();
return text1 == text2 ? 0 : (text1 < text2 ? -1 : 1);
};
(function($) {
$.widget("ui.multiselect", {
options: {
// sortable and droppable
sortable: 'none',
droppable: 'none',
// searchable
searchable: true,
searchDelay: 400,
searchAtStart: false,
remoteUrl: null,
remoteParams: {},
remoteLimit: 50,
remoteLimitIncrement: 50,
remoteStart: 0,
displayMore: false,
// animated
animated: 'fast',
show: 'slideDown',
hide: 'slideUp',
// ui
dividerLocation: 0.6,
// callbacks
dataParser: defaultDataParser,
nodeComparator: defaultNodeComparator,
nodeInserted: null,
// Add Vincent - Permet le click sur le li pour le passer d'un côté à un autre
triggerOnLiClick: false,
// Add Vincent - Permet le choix de la langue... en fonction de l'iso_code de PrestaShop (en, fr)
localeIsoCode: 'en'
},
_create: function() {
if (this.options.locale != undefined) {
$.ui.multiselect.locale = this.options.locale;
} else {
$.ui.multiselect.locale = {
addAll:'Add all',
removeAll:'Remove all',
itemsCount:'#{count} items selected',
itemsTotal:'#{count} items total',
busy:'please wait...',
errorDataFormat:"Cannot add options, unknown data format",
errorInsertNode:"There was a problem trying to add the item:\n\n\t[#{key}] => #{value}\n\nThe operation was aborted.",
errorReadonly:"The option #{option} is readonly",
errorRequest:"Sorry! There seemed to be a problem with the remote call. (Type: #{status})",
sInputSearch:'Please enter the first letters of the search item',
sInputShowMore: 'Show more'
};
}
this.element.hide();
this.busy = false; // busy state
this.idMultiSelect = this._uniqid(); // busy state
this.container = $('<div class="ui-multiselect ui-helper-clearfix ui-widget"></div>').insertAfter(this.element);
this.selectedContainer = $('<div class="ui-widget-content list-container selected"></div>').appendTo(this.container);
this.availableContainer = $('<div class="ui-widget-content list-container available"></div>').appendTo(this.container);
this.selectedActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="count">'+$.tmpl($.ui.multiselect.locale.itemsCount,{count:0})+'</span><a href="#" class="remove-all">'+$.tmpl($.ui.multiselect.locale.removeAll)+'</a></div>').appendTo(this.selectedContainer);
this.availableActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="busy">'+$.tmpl($.ui.multiselect.locale.busy)+'</span><input type="text" class="search ui-widget-content ui-corner-all" value="'+$.tmpl($.ui.multiselect.locale.sInputSearch)+'" onfocus="javascript:if(this.value==\''+$.tmpl($.ui.multiselect.locale.sInputSearch)+'\')this.value=\'\';" onblur="javascript:if(this.value==\'\')this.value=\''+$.tmpl($.ui.multiselect.locale.sInputSearch)+'\';" /><a href="#" class="add-all">'+$.tmpl($.ui.multiselect.locale.addAll)+'</a></div>').appendTo(this.availableContainer);
this.selectedList = $('<ul class="list selected"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.selectedContainer);
this.availableList = $('<ul class="list available"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.availableContainer);
var that = this;
// initialize data cache
this.availableList.data('multiselect.cache', {});
this.selectedList.data('multiselect.cache', {});
if ( !this.options.animated ) {
this.options.show = 'show';
this.options.hide = 'hide';
}
// sortable / droppable / draggable
var dragOptions = {
selected: {
sortable: ('both' == this.options.sortable || 'left' == this.options.sortable),
droppable: ('both' == this.options.droppable || 'left' == this.options.droppable)
},
available: {
sortable: ('both' == this.options.sortable || 'right' == this.options.sortable),
droppable: ('both' == this.options.droppable || 'right' == this.options.droppable)
}
};
this._prepareLists('selected', 'available', dragOptions);
this._prepareLists('available', 'selected', dragOptions);
// set up livesearch
this._registerSearchEvents(this.availableContainer.find('input.search'), this.options.searchAtStart);
//
// make sure that we're not busy yet
this._setBusy(false);
// batch actions
this.container.find(".remove-all").bind('click.multiselect', function() { that.selectNone(); return false; });
this.container.find(".add-all").bind('click.multiselect', function() { that.selectAll(); return false; });
// set dimensions
this.container.width(this.element.width()+1);
this._refreshDividerLocation();
// set max width of search input dynamically
this.availableActions.find('input').width(Math.max(this.availableActions.width() - this.availableActions.find('a.add-all').width() - 30, 20));
// fix list height to match <option> depending on their individual header's heights
this.selectedList.height(Math.max(this.element.height()-this.selectedActions.height(),1));
this.availableList.height(Math.max(this.element.height()-this.availableActions.height(),1));
// init lists
this._populateLists(this.element.find('option'));
},
_uniqid: function() {
var newDate = new Date;
return newDate.getTime();
},
/**************************************
* Public
**************************************/
destroy: function() {
this.container.remove();
this.element.show();
$.widget.prototype.destroy.apply(this, arguments);
},
isBusy: function() {
return !!this.busy;
},
isSelected: function(item) {
if (this.enabled()) {
return !!this._findItem(item, this.selectedList);
} else {
return null;
}
},
// get all selected values in an array
selectedValues: function() {
return $.map( this.element.find('option[selected]'), function(item,i) { return $(item).val(); });
},
// get/set enable state
enabled: function(state, msg) {
if (undefined !== state) {
if (state) {
this.container.unblock();
this.element.removeAttr('disabled');
} else {
this.container.block({message:msg||null,overlayCSS:{backgroundColor:'#fff',opacity:0.4,cursor:'default'}});
this.element.attr('disabled', true);
}
}
return !this.element.attr('disabled');
},
selectAll: function() {
if (this.enabled()) {
this._batchSelect(this.availableList.children('li.ui-element:visible'), true);
}
},
selectNone: function() {
if (this.enabled()) {
this._batchSelect(this.selectedList.children('li.ui-element:visible'), false);
}
},
select: function(text) {
if (this.enabled()) {
var available = this._findItem(text, this.availableList);
if ( available ) {
this._setSelected(available, true);
}
}
},
deselect: function(text) {
if (this.enabled()) {
var selected = this._findItem(text, this.selectedList);
if ( selected ) {
this._setSelected(selected, false);
}
}
},
search: function(query) {
if (!this.busy && this.enabled() && this.options.searchable) {
var input = this.availableActions.children('input:first');
input.val(query);
input.trigger('keydown.multiselect');
}
},
// insert new <option> and _populate
// @return int the number of options added
addOptions: function(data) {
if (this.enabled()) {
this._setBusy(true);
// format data
var elements = [];
if (data = this.options.dataParser.call(this, data)) {
for (var key in data) {
// check if the option does not exist already
if (this.element.find('option[value="'+key+'"]').size()==0) {
elements.push( $('<option value="'+key+'"/>').text(data[key].value).appendTo(this.element)[0] );
}
}
}
if (elements.length>0) {
this._populateLists($(elements));
}
this._filter(this.availableList.children('li.ui-element'), data);
var availableItemsCount = $.objectKeys(data).length;
if(availableItemsCount >= this.options.remoteLimit && this.options.displayMore == true) {
if (!$('#multiselectShowMore_'+this.idMultiSelect).length) {
var showMoreLink = $('<p id="multiselectShowMore_'+this.idMultiSelect+'"><a href="javascript:void(0);">'+$.tmpl($.ui.multiselect.locale.sInputShowMore)+'</a></p>');
this.availableList.after(showMoreLink);
} else {
var showMoreLink = $('#multiselectShowMore_'+this.idMultiSelect);
}
var that = this;
showMoreLink.unbind('click').bind('click', function() {
that.options.remoteStart += that.options.remoteLimit;
that.options.remoteLimit = that.options.remoteLimitIncrement;
that._registerSearchEvents(that.availableContainer.find('input.search'), true);
});
}
else if(this.options.displayMore == false || availableItemsCount < this.options.remoteLimit) {
$('#multiselectShowMore_'+this.idMultiSelect).fadeOut('fast',function() {$(this).remove();});
}
this._setBusy(false);
return elements.length;
} else {
return false;
}
},
/**************************************
* Private
**************************************/
_setData: function(key, value) {
switch (key) {
// special treatement must be done for theses values when changed
case 'dividerLocation':
this.options.dividerLocation = value;
this._refreshDividerLocation();
break;
case 'searchable':
this.options.searchable = value;
this._registerSearchEvents(this.availableContainer.find('input.search'), false);
break;
case 'droppable':
case 'sortable':
// readonly options
this._messages(
$.ui.multiselect.constants.MESSAGE_WARNING,
$.ui.multiselect.locale.errorReadonly,
{option: key}
);
default:
// default behavior
this.options[key] = value;
break;
}
},
_ui: function(type) {
var uiObject = {sender: this.element};
switch (type) {
// events: messages
case 'message':
uiObject.type = arguments[1];
uiObject.message = arguments[2];
break;
// events: selected, deselected
case 'selection':
uiObject.option = arguments[1];
break;
}
return uiObject;
},
_messages: function(type, msg, params) {
this._trigger('messages', null, this._ui('message', type, $.tmpl(msg, params)));
},
_refreshDividerLocation: function() {
this.selectedContainer.width(Math.floor(this.element.width()*this.options.dividerLocation));
this.availableContainer.width(Math.floor(this.element.width()*(1-this.options.dividerLocation)));
},
_prepareLists: function(side, otherSide, opts) {
var that = this;
var itemSelected = ('selected' == side);
var list = this[side+'List'];
var otherList = this[otherSide+'List'];
var listDragHelper = opts[otherSide].sortable ? _dragHelper : 'clone';
list
.data('multiselect.sortable', opts[side].sortable )
.data('multiselect.droppable', opts[side].droppable )
.data('multiselect.draggable', !opts[side].sortable && (opts[otherSide].sortable || opts[otherSide].droppable) );
if (opts[side].sortable) {
list.sortable({
appendTo: this.container,
connectWith: otherList,
containment: this.container,
helper: listDragHelper,
items: 'li.ui-element',
revert: !(opts[otherSide].sortable || opts[otherSide].droppable),
receive: function(event, ui) {
// DEBUG
//that._messages(0, "Receive : " + ui.item.data('multiselect.optionLink') + ":" + ui.item.parent()[0].className + " = " + itemSelected);
// we received an element from a sortable to another sortable...
if (opts[otherSide].sortable) {
var optionLink = ui.item.data('multiselect.optionLink');
that._applyItemState(ui.item.hide(), itemSelected);
// if the cache already contain an element, remove it
if (otherList.data('multiselect.cache')[optionLink.val()]) {
delete otherList.data('multiselect.cache')[optionLink.val()];
}
ui.item.hide();
that._setSelected(ui.item, itemSelected, true);
} else {
// the other is droppable only, so merely select the element...
setTimeout(function() {
that._setSelected(ui.item, itemSelected);
}, 10);
}
},
stop: function(event, ui) {
// DEBUG
//that._messages(0, "Stop : " + (ui.item.parent()[0] == otherList[0]));
that._moveOptionNode(ui.item);
}
});
}
// cannot be droppable if both lists are sortable, it breaks the receive function
if (!(opts[side].sortable && opts[otherSide].sortable)
&& (opts[side].droppable || opts[otherSide].sortable || opts[otherSide].droppable)) {
//alert( side + " is droppable ");
list.droppable({
accept: '.ui-multiselect li.ui-element',
hoverClass: 'ui-state-highlight',
revert: !(opts[otherSide].sortable || opts[otherSide].droppable),
greedy: true,
drop: function(event, ui) {
// DEBUG
//that._messages(0, "drop " + side + " = " + ui.draggable.data('multiselect.optionLink') + ":" + ui.draggable.parent()[0].className);
//alert( "drop " + itemSelected );
// if no optionLink is defined, it was dragged in
if (!ui.draggable.data('multiselect.optionLink')) {
var optionLink = ui.helper.data('multiselect.optionLink');
ui.draggable.data('multiselect.optionLink', optionLink);
// if the cache already contain an element, remove it
if (list.data('multiselect.cache')[optionLink.val()]) {
delete list.data('multiselect.cache')[optionLink.val()];
}
list.data('multiselect.cache')[optionLink.val()] = ui.draggable;
that._applyItemState(ui.draggable, itemSelected);
// received an item from a sortable to a droppable
} else if (!opts[side].sortable) {
setTimeout(function() {
ui.draggable.hide();
that._setSelected(ui.draggable, itemSelected);
}, 10);
}
}
});
}
},
_populateLists: function(options) {
this._setBusy(true);
var that = this;
// do this async so the browser actually display the waiting message
setTimeout(function() {
$(options.each(function(i) {
var list = (this.selected ? that.selectedList : that.availableList);
var item = that._getOptionNode(this).show();
that._applyItemState(item, this.selected);
item.data('multiselect.idx', i);
// cache
list.data('multiselect.cache')[item.data('multiselect.optionLink').val()] = item;
that._insertToList(item, list);
}));
// update count
that._setBusy(false);
that._updateCount();
}, 1);
},
_insertToList: function(node, list) {
// Add Vincent - Permet le click sur le li pour le passer d'un côté à un autre
if (this.options.triggerOnLiClick == true) {
node.unbind('click.multiselect').bind('click.multiselect', function() { node.find('a.action').trigger('click.multiselect'); });
}
// End Vincent - Permet le click sur le li pour le passer d'un côté à un autre
var that = this;
this._setBusy(true);
// the browsers don't like batch node insertion...
var _addNodeRetry = 0;
var _addNode = function() {
var succ = (that.options.nodeComparator ? that._getSuccessorNode(node, list) : null);
try {
if (succ) {
node.insertBefore(succ);
} else {
list.append(node);
}
if (list === that.selectedList) that._moveOptionNode(node);
// callback after node insertion
if ('function' == typeof that.options.nodeInserted) that.options.nodeInserted(node);
that._setBusy(false);
} catch (e) {
// if this problem did not occur too many times already
if ( _addNodeRetry++ < 10 ) {
// try again later (let the browser cool down first)
setTimeout(function() { _addNode(); }, 1);
} else {
that._messages(
$.ui.multiselect.constants.MESSAGE_EXCEPTION,
$.ui.multiselect.locale.errorInsertNode,
{key:node.data('multiselect.optionLink').val(), value:node.text()}
);
that._setBusy(false);
}
}
};
_addNode();
},
_updateCount: function() {
var that = this;
// defer until system is not busy
if (this.busy) setTimeout(function() { that._updateCount(); }, 100);
// count only visible <li> (less .ui-helper-hidden*)
var count = this.selectedList.children('li:not(.ui-helper-hidden-accessible,.ui-sortable-placeholder):visible').size();
var total = this.availableList.children('li:not(.ui-helper-hidden-accessible,.ui-sortable-placeholder,.shadowed)').size() + count;
this.selectedContainer.find('span.count')
.html($.tmpl($.ui.multiselect.locale.itemsCount, {count:count}))
.attr('title', $.tmpl($.ui.multiselect.locale.itemsTotal, {count:total}));
},
_getOptionNode: function(option) {
option = $(option);
var node = $('<li class="ui-state-default ui-element"><span class="ui-icon"/>'+option.text()+'<a href="#" class="ui-state-default action"><span class="ui-corner-all ui-icon"/></a></li>').hide();
// Add Vincent - Permet le click sur le li pour le passer d'un côté à un autre
if (this.options.triggerOnLiClick == true) {
node.unbind('click.multiselect').bind('click.multiselect', function() { node.find('a.action').trigger('click.multiselect'); });
}
// End Vincent - Permet le click sur le li pour le passer d'un côté à un autre
node.data('multiselect.optionLink', option);
return node;
},
_moveOptionNode: function(item) {
// call this async to let the item be placed correctly
setTimeout( function() {
var optionLink = item.data('multiselect.optionLink');
if (optionLink) {
var prevItem = item.prev('li:not(.ui-helper-hidden-accessible,.ui-sortable-placeholder):visible');
var prevOptionLink = prevItem.size() ? prevItem.data('multiselect.optionLink') : null;
if (prevOptionLink) {
optionLink.insertAfter(prevOptionLink);
} else {
optionLink.prependTo(optionLink.parent());
}
}
}, 100);
},
// used by select and deselect, etc.
_findItem: function(text, list) {
var found = null;
list.children('li.ui-element:visible').each(function(i,el) {
el = $(el);
if (el.text().toLowerCase() === text.toLowerCase()) {
found = el;
}
});
if (found && found.size()) {
return found;
} else {
return false;
}
},
// clones an item with
// didn't find a smarter away around this (michael)
// now using cache to speed up the process (yr)
_cloneWithData: function(clonee, cacheName, insertItem) {
var that = this;
var id = clonee.data('multiselect.optionLink').val();
var selected = ('selected' == cacheName);
var list = (selected ? this.selectedList : this.availableList);
var clone = list.data('multiselect.cache')[id];
if (!clone) {
clone = clonee.clone().hide();
this._applyItemState(clone, selected);
// update cache
list.data('multiselect.cache')[id] = clone;
// update <option> and idx
clone.data('multiselect.optionLink', clonee.data('multiselect.optionLink'));
// need this here because idx is needed in _getSuccessorNode
clone.data('multiselect.idx', clonee.data('multiselect.idx'));
// insert the node into it's list
if (insertItem) {
this._insertToList(clone, list);
}
} else {
// update idx
clone.data('multiselect.idx', clonee.data('multiselect.idx'));
}
return clone;
},
_batchSelect: function(elements, state) {
this._setBusy(true);
var that = this;
// do this async so the browser actually display the waiting message
setTimeout(function() {
var _backup = {
animated: that.options.animated,
hide: that.options.hide,
show: that.options.show
};
that.options.animated = null;
that.options.hide = 'hide';
that.options.show = 'show';
elements.each(function(i,element) {
that._setSelected($(element), state);
});
// filter available items
if (!state) that._filter(that.availableList.find('li.ui-element'));
// restore
$.extend(that.options, _backup);
that._updateCount();
that._setBusy(false);
}, 10);
},
// find the best successor the given item in the specified list
// TODO implement a faster sorting algorithm (and remove the idx dependancy)
_getSuccessorNode: function(item, list) {
// look for successor based on initial option index
var items = list.find('li.ui-element'), comparator = this.options.nodeComparator;
var itemsSize = items.size();
// no successor, list is null
if (items.size() == 0) return null;
var succ, i = Math.min(item.data('multiselect.idx'),itemsSize-1), direction = comparator(item, $(items[i]));
if ( direction ) {
// quick checks
if (0>direction && 0>=i) {
succ = items[0];
} else if (0<direction && itemsSize-1<=i) {
i++;
succ = null;
} else {
while (i>=0 && i<items.length) {
direction > 0 ? i++ : i--;
if (i<0) {
succ = item[0]
}
if ( direction != comparator(item, $(items[i])) ) {
// going up, go back one item down, otherwise leave as is
succ = items[direction > 0 ? i : i+1];
break;
}
}
}
} else {
succ = items[i];
}
// update idx
item.data('multiselect.idx', i);
return succ;
},
// @param DOMElement item is the item to set
// @param bool selected true|false (state)
// @param bool noclone (optional) true only if item should not be cloned on the other list
_setSelected: function(item, selected, noclone) {
var that = this, otherItem;
var optionLink = item.data('multiselect.optionLink');
if (selected) {
// already selected
if (optionLink.attr('selected')) return;
optionLink.attr('selected','selected');
if (noclone) {
otherItem = item;
} else {
// retrieve associatd or cloned item
otherItem = this._cloneWithData(item, 'selected', true).hide();
item.addClass('shadowed')[this.options.hide](this.options.animated, function() { that._updateCount(); });
}
otherItem[this.options.show](this.options.animated);
} else {
// already deselected
if (!optionLink.attr('selected')) return;
optionLink.removeAttr('selected');
if (noclone) {
otherItem = item;
} else {
// retrieve associated or clone the item
otherItem = this._cloneWithData(item, 'available', true).hide().removeClass('shadowed');
item[this.options.hide](this.options.animated, function() { that._updateCount() });
}
if (!otherItem.is('.filtered')) otherItem[this.options.show](this.options.animated);
}
if (!this.busy) {
if (this.options.animated) {
// pulse
//otherItem.effect("pulsate", { times: 1, mode: 'show' }, 400); // pulsate twice???
otherItem.fadeTo('fast', 0.3, function() { $(this).fadeTo('fast', 1); });
}
}
// fire selection event
this._trigger(selected ? 'selected' : 'deselected', null, this._ui('selection', optionLink));
return otherItem;
},
_setBusy: function(state) {
var input = this.availableActions.children('input.search');
var busy = this.availableActions.children('.busy');
this.busy = Math.max(state ? ++this.busy : --this.busy, 0);
this.container.find("a.remove-all, a.add-all")[this.busy ? 'hide' : 'show']();
if (state && (1 == this.busy)) {
if (this.options.searchable) {
// backup input state
input.data('multiselect.hadFocus', input.data('multiselect.hasFocus'));
// webkit needs to blur before hiding or it won't fire focus again in the else block
input.blur().hide();
}
busy.show();
} else if(!this.busy) {
if (this.options.searchable) {
input.show();
if (input.data('multiselect.hadFocus')) input.focus();
}
busy.hide();
}
// DEBUG
//this._messages(0, "Busy state changed to : " + this.busy);
},
_applyItemState: function(item, selected) {
if (selected) {
item.children('span').addClass('ui-helper-hidden').removeClass('ui-icon');
item.find('a.action span').addClass('ui-icon-minus').removeClass('ui-icon-plus');
this._registerRemoveEvents(item.find('a.action'));
} else {
item.children('span').addClass('ui-helper-hidden').removeClass('ui-icon');
item.find('a.action span').addClass('ui-icon-plus').removeClass('ui-icon-minus');
this._registerAddEvents(item.find('a.action'));
}
this._registerHoverEvents(item);
return item;
},
// apply filter and return elements
_filter: function(elements, data) {
var input = this.availableActions.children('input.search');
var term = $.trim( input.val().toLowerCase() );
if ( !term ) {
elements.removeClass('filtered');
} else {
elements.each(function(i,element) {
element = $(element);
if (data != undefined) {
if (data[element.data('multiselect.optionLink').val()] != undefined) {
element['removeClass']('filtered');
} else {
element['addClass']('filtered');
}
} else {
element[(element.text().toLowerCase().indexOf(term)>=0 ? 'remove' : 'add')+'Class']('filtered');
}
});
}
return elements.not('.filtered, .shadowed').show().end().filter('.filtered, .shadowed').hide().end();
},
_registerHoverEvents: function(elements) {
elements
.unbind('mouseover.multiselect').bind('mouseover.multiselect', function() {
$(this).find('a').andSelf().addClass('ui-state-hover');
})
.unbind('mouseout.multiselect').bind('mouseout.multiselect', function() {
$(this).find('a').andSelf().removeClass('ui-state-hover');
})
.find('a').andSelf().removeClass('ui-state-hover')
;
},
_registerAddEvents: function(elements) {
var that = this;
elements.unbind('click.multiselect').bind('click.multiselect', function() {
// ignore if busy...
if (!this.busy) {
that._setSelected($(this).parent(), true);
}
return false;
});
if (this.availableList.data('multiselect.draggable')) {
// make draggable
elements.each(function() {
$(this).parent().draggable({
connectToSortable: that.selectedList,
helper: _dragHelper,
appendTo: that.container,
containment: that.container,
revert: 'invalid'
});
});
// refresh the selected list or the draggable will not connect to it first hand
if (this.selectedList.data('multiselect.sortable')) {
this.selectedList.sortable('refresh');
}
}
},
_registerRemoveEvents: function(elements) {
var that = this;
elements.unbind('click.multiselect').bind('click.multiselect', function() {
// ignore if busy...
if (!that.busy) {
that._setSelected($(this).parent(), false);
}
return false;
});
if (this.selectedList.data('multiselect.draggable')) {
// make draggable
elements.each(function() {
$(this).parent().draggable({
connectToSortable: that.availableList,
helper: _dragHelper,
appendTo: that.container,
containment: that.container,
revert: 'invalid'
});
});
// refresh the selected list or the draggable will not connect to it first hand
if (this.availableList.data('multiselect.sortable')) {
this.availableList.sortable('refresh');
}
}
},
_registerSearchEvents: function(input, searchNow) {
var that = this;
var previousValue = input.val(), timer;
var _searchNow = function(forceUpdate) {
if (that.busy) return;
var value = input.val();
if ((value != previousValue) || (forceUpdate)) {
that._setBusy(true);
if (that.options.remoteUrl) {
var params = $.extend({}, that.options.remoteParams);
try {
$.ajax({
url: that.options.remoteUrl,
data: $.extend(params, {q:value, start:that.options.remoteStart, limit:that.options.remoteLimit}),
success: function(data) {
that.addOptions(data);
that._setBusy(false);
},
error: function(request,status,e) {
that._messages(
$.ui.multiselect.constants.MESSAGE_ERROR,
$.ui.multiselect.locale.errorRequest,
{status:status}
);
that._setBusy(false);
}
});
} catch (e) {
that._messages($.ui.multiselect.constants.MESSAGE_EXCEPTION, e.message); // error message template ??
that._setBusy(false);
}
} else {
that._filter(that.availableList.children('li.ui-element'));
that._setBusy(false);
}
previousValue = value;
}
};
// reset any events... if any
input.unbind('focus.multiselect blur.multiselect keydown.multiselect keypress.multiselect');
if (this.options.searchable) {
input
.bind('focus.multiselect', function() {
$(this).addClass('ui-state-active').data('multiselect.hasFocus', true);
})
.bind('blur.multiselect', function() {
$(this).removeClass('ui-state-active').data('multiselect.hasFocus', false);
})
.bind('keydown.multiselect keypress.multiselect', function(e) {
if (timer) clearTimeout(timer);
switch (e.which) {
case 13: // enter
_searchNow(true);
return false;
default:
timer = setTimeout(function() { _searchNow(); }, Math.max(that.options.searchDelay,1));
}
})
.show();
} else {
input.val('').hide();
this._filter(that.availableList.find('li.ui-element'))
}
// initiate search filter (delayed)
var _initSearch = function() {
if (that.busy) {
setTimeout(function() { _initSearch(); }, 100);
}
_searchNow(true);
};
if (searchNow) _initSearch();
}
});
// END ui.multiselect
/********************************
* Internal functions
********************************/
var _dragHelper = function(event, ui) {
var item = $(event.target);
var clone = item.clone().width(item.width());
if (clone.data('multiselect.optionLink') != undefined && item.data('multiselect.optionLink') != undefined) {
clone
.data('multiselect.optionLink', item.data('multiselect.optionLink'))
.data('multiselect.list', item.parent() )
// node ui cleanup
.find('a').remove()
;
}
return clone;
};
/****************************
* Settings
****************************/
$.extend($.ui.multiselect, {
getter: 'selectedValues enabled isBusy',
constants: {
MESSAGE_WARNING: 0,
MESSAGE_EXCEPTION: 1,
MESSAGE_ERROR: 2
}
});
})($);