635 lines
24 KiB
JavaScript
Executable File
635 lines
24 KiB
JavaScript
Executable File
/*
|
|
* @license
|
|
*
|
|
* Multiselect v2.3.1
|
|
* http://crlcu.github.io/multiselect/
|
|
*
|
|
* Copyright (c) 2016 Adrian Crisan
|
|
* Licensed under the MIT license (https://github.com/crlcu/multiselect/blob/master/LICENSE)
|
|
*/
|
|
|
|
if (typeof jQuery === 'undefined') {
|
|
throw new Error('multiselect requires jQuery');
|
|
}
|
|
|
|
;(function ($) {
|
|
'use strict';
|
|
|
|
var version = $.fn.jquery.split(' ')[0].split('.');
|
|
|
|
if (version[0] < 2 && version[1] < 7) {
|
|
throw new Error('multiselect requires jQuery version 1.7 or higher');
|
|
}
|
|
})(jQuery);
|
|
|
|
;(function (factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module depending on jQuery.
|
|
define(['jquery'], factory);
|
|
} else {
|
|
// No AMD. Register plugin with global jQuery object.
|
|
factory(jQuery);
|
|
}
|
|
}(function ($) {
|
|
'use strict';
|
|
|
|
var Multiselect = (function($) {
|
|
/** Multiselect object constructor
|
|
*
|
|
* @class Multiselect
|
|
* @constructor
|
|
**/
|
|
function Multiselect( $select, settings ) {
|
|
var id = $select.prop('id');
|
|
this.$left = $select;
|
|
this.$right = $( settings.right ).length ? $( settings.right ) : $('#' + id + '_to');
|
|
this.actions = {
|
|
$leftAll: $( settings.leftAll ).length ? $( settings.leftAll ) : $('#' + id + '_leftAll'),
|
|
$rightAll: $( settings.rightAll ).length ? $( settings.rightAll ) : $('#' + id + '_rightAll'),
|
|
$leftSelected: $( settings.leftSelected ).length ? $( settings.leftSelected ) : $('#' + id + '_leftSelected'),
|
|
$rightSelected: $( settings.rightSelected ).length ? $( settings.rightSelected ) : $('#' + id + '_rightSelected'),
|
|
|
|
$undo: $( settings.undo ).length ? $( settings.undo ) : $('#' + id + '_undo'),
|
|
$redo: $( settings.redo ).length ? $( settings.redo ) : $('#' + id + '_redo'),
|
|
|
|
$moveUp: $( settings.moveUp ).length ? $( settings.moveUp ) : $('#' + id + '_move_up'),
|
|
$moveDown: $( settings.moveDown ).length ? $( settings.moveDown ) : $('#' + id + '_move_down')
|
|
};
|
|
|
|
delete settings.leftAll;
|
|
delete settings.leftSelected;
|
|
delete settings.right;
|
|
delete settings.rightAll;
|
|
delete settings.rightSelected;
|
|
delete settings.undo;
|
|
delete settings.redo;
|
|
delete settings.moveUp;
|
|
delete settings.moveDown;
|
|
|
|
this.options = {
|
|
keepRenderingSort: settings.keepRenderingSort,
|
|
submitAllLeft: settings.submitAllLeft !== undefined ? settings.submitAllLeft : true,
|
|
submitAllRight: settings.submitAllRight !== undefined ? settings.submitAllRight : true,
|
|
search: settings.search,
|
|
ignoreDisabled: settings.ignoreDisabled !== undefined ? settings.ignoreDisabled : false
|
|
};
|
|
|
|
delete settings.keepRenderingSort, settings.submitAllLeft, settings.submitAllRight, settings.search, settings.ignoreDisabled;
|
|
|
|
this.callbacks = settings;
|
|
|
|
this.init();
|
|
}
|
|
|
|
Multiselect.prototype = {
|
|
init: function() {
|
|
var self = this;
|
|
self.undoStack = [];
|
|
self.redoStack = [];
|
|
|
|
// For the moment disable sort if there is a optgroup element
|
|
if (self.$left.find('optgroup').length || self.$right.find('optgroup').length) {
|
|
self.callbacks.sort = false;
|
|
}
|
|
|
|
if (self.options.keepRenderingSort) {
|
|
self.skipInitSort = true;
|
|
|
|
if (self.callbacks.sort !== false) {
|
|
self.callbacks.sort = function(a, b) {
|
|
return $(a).data('position') > $(b).data('position') ? 1 : -1;
|
|
};
|
|
}
|
|
|
|
self.$left.find('option').each(function(index, option) {
|
|
$(option).data('position', index);
|
|
});
|
|
|
|
self.$right.find('option').each(function(index, option) {
|
|
$(option).data('position', index);
|
|
});
|
|
}
|
|
|
|
if ( typeof self.callbacks.startUp == 'function' ) {
|
|
self.callbacks.startUp( self.$left, self.$right );
|
|
}
|
|
|
|
if ( !self.skipInitSort && typeof self.callbacks.sort == 'function' ) {
|
|
self.$left.mSort(self.callbacks.sort);
|
|
|
|
self.$right.each(function(i, select) {
|
|
$(select).mSort(self.callbacks.sort);
|
|
});
|
|
}
|
|
|
|
// Append left filter
|
|
if (self.options.search && self.options.search.left) {
|
|
self.options.search.$left = $(self.options.search.left);
|
|
self.$left.before(self.options.search.$left);
|
|
}
|
|
|
|
// Append right filter
|
|
if (self.options.search && self.options.search.right) {
|
|
self.options.search.$right = $(self.options.search.right);
|
|
self.$right.before($(self.options.search.$right));
|
|
}
|
|
|
|
// Initialize events
|
|
self.events();
|
|
},
|
|
|
|
events: function() {
|
|
var self = this;
|
|
|
|
// Attach event to left filter
|
|
if (self.options.search && self.options.search.$left) {
|
|
self.options.search.$left.on('keyup', function(e) {
|
|
if (this.value) {
|
|
var $toShow = self.$left.find('option:search("' + this.value + '")').mShow();
|
|
var $toHide = self.$left.find('option:not(:search("' + this.value + '"))').mHide();
|
|
var $grpHide= self.$left.find('option.hidden').parent('optgroup').not($(":visible").parent()).mHide();
|
|
var $grpShow= self.$left.find('option:not(.hidden)').parent('optgroup').mShow();
|
|
} else {
|
|
self.$left.find('option, optgroup').mShow();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Attach event to right filter
|
|
if (self.options.search && self.options.search.$right) {
|
|
self.options.search.$right.on('keyup', function(e) {
|
|
if (this.value) {
|
|
var $toShow = self.$right.find('option:search("' + this.value + '")').mShow();
|
|
var $toHide = self.$right.find('option:not(:search("' + this.value + '"))').mHide();
|
|
var $grpHide= self.$right.find('option.hidden').parent('optgroup').not($(":visible").parent()).mHide();
|
|
var $grpShow= self.$right.find('option:not(.hidden)').parent('optgroup').mShow();
|
|
} else {
|
|
self.$right.find('option, optgroup').mShow();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Select all the options from left and right side when submiting the parent form
|
|
self.$right.closest('form').on('submit', function(e) {
|
|
self.$left.find('option').prop('selected', self.options.submitAllLeft);
|
|
self.$right.find('option').prop('selected', self.options.submitAllRight);
|
|
});
|
|
|
|
// Attach event for double clicking on options from left side
|
|
self.$left.on('dblclick', 'option', function(e) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$left.find('option:selected');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToRight($options, e);
|
|
}
|
|
});
|
|
|
|
// Attach event for pushing ENTER on options from left side
|
|
self.$left.on('keypress', function(e) {
|
|
if (e.keyCode === 13) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$left.find('option:selected');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToRight($options, e);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Attach event for double clicking on options from right side
|
|
self.$right.on('dblclick', 'option', function(e) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$right.find('option:selected');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToLeft($options, e);
|
|
}
|
|
});
|
|
|
|
// Attach event for pushing BACKSPACE or DEL on options from right side
|
|
self.$right.on('keydown', function(e) {
|
|
if (e.keyCode === 8 || e.keyCode === 46) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$right.find('option:selected');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToLeft($options, e);
|
|
}
|
|
}
|
|
});
|
|
|
|
// dblclick support for IE
|
|
if ( navigator.userAgent.match(/MSIE/i) || navigator.userAgent.indexOf('Trident/') > 0 || navigator.userAgent.indexOf('Edge/') > 0) {
|
|
self.$left.dblclick(function(e) {
|
|
self.actions.$rightSelected.trigger('click');
|
|
});
|
|
|
|
self.$right.dblclick(function(e) {
|
|
self.actions.$leftSelected.trigger('click');
|
|
});
|
|
}
|
|
|
|
self.actions.$rightSelected.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$left.find('option:selected');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToRight($options, e);
|
|
}
|
|
|
|
$(this).blur();
|
|
});
|
|
|
|
self.actions.$leftSelected.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$right.find('option:selected');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToLeft($options, e);
|
|
}
|
|
|
|
$(this).blur();
|
|
});
|
|
|
|
self.actions.$rightAll.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$left.children(':not(span):not(.hidden)');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToRight($options, e);
|
|
}
|
|
|
|
$(this).blur();
|
|
});
|
|
|
|
self.actions.$leftAll.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
var $options = self.$right.children(':not(span):not(.hidden)');
|
|
|
|
if ( $options.length ) {
|
|
self.moveToLeft($options, e);
|
|
}
|
|
|
|
$(this).blur();
|
|
});
|
|
|
|
self.actions.$undo.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
self.undo(e);
|
|
});
|
|
|
|
self.actions.$redo.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
self.redo(e);
|
|
});
|
|
|
|
self.actions.$moveUp.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
self.doNotSortRight = true;
|
|
|
|
var $options = self.$right.find(':selected:not(span):not(.hidden)');
|
|
|
|
$options.first().prev().before($options);
|
|
|
|
$(this).blur();
|
|
});
|
|
|
|
self.actions.$moveDown.on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
self.doNotSortRight = true;
|
|
|
|
var $options = self.$right.find(':selected:not(span):not(.hidden)');
|
|
|
|
$options.last().next().after($options);
|
|
|
|
$(this).blur();
|
|
});
|
|
},
|
|
|
|
moveToRight: function( $options, event, silent, skipStack ) {
|
|
var self = this;
|
|
|
|
if ( typeof self.callbacks.moveToRight == 'function' ) {
|
|
return self.callbacks.moveToRight( self, $options, event, silent, skipStack );
|
|
} else {
|
|
if ( typeof self.callbacks.beforeMoveToRight == 'function' && !silent ) {
|
|
if ( !self.callbacks.beforeMoveToRight( self.$left, self.$right, $options ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$options.each(function(index, option) {
|
|
var $option = $(option);
|
|
|
|
if (self.options.ignoreDisabled && $option.is(':disabled')) {
|
|
return true;
|
|
}
|
|
|
|
if ($option.parent().is('optgroup')) {
|
|
var $leftGroup = $option.parent();
|
|
var $rightGroup = self.$right.find('optgroup[label="' + $leftGroup.prop('label') + '"]');
|
|
|
|
if (!$rightGroup.length) {
|
|
$rightGroup = $leftGroup.clone();
|
|
$rightGroup.children().remove();
|
|
}
|
|
|
|
$option = $rightGroup.append($option);
|
|
|
|
$leftGroup.removeIfEmpty();
|
|
}
|
|
|
|
self.$right.move($option);
|
|
});
|
|
|
|
if ( !skipStack ) {
|
|
self.undoStack.push(['right', $options ]);
|
|
self.redoStack = [];
|
|
}
|
|
|
|
if ( typeof self.callbacks.sort == 'function' && !silent && !self.doNotSortRight ) {
|
|
self.$right.mSort(self.callbacks.sort);
|
|
}
|
|
|
|
if ( typeof self.callbacks.afterMoveToRight == 'function' && !silent ) {
|
|
self.callbacks.afterMoveToRight( self.$left, self.$right, $options );
|
|
}
|
|
|
|
return self;
|
|
}
|
|
},
|
|
|
|
moveToLeft: function( $options, event, silent, skipStack ) {
|
|
var self = this;
|
|
|
|
if ( typeof self.callbacks.moveToLeft == 'function' ) {
|
|
return self.callbacks.moveToLeft( self, $options, event, silent, skipStack );
|
|
} else {
|
|
if ( typeof self.callbacks.beforeMoveToLeft == 'function' && !silent ) {
|
|
if ( !self.callbacks.beforeMoveToLeft( self.$left, self.$right, $options ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$options.each(function(index, option) {
|
|
var $option = $(option);
|
|
|
|
if ($option.is('optgroup') || $option.parent().is('optgroup')) {
|
|
var $rightGroup = $option.is('optgroup') ? $option : $option.parent();
|
|
var $leftGroup = self.$left.find('optgroup[label="' + $rightGroup.prop('label') + '"]');
|
|
|
|
if (!$leftGroup.length) {
|
|
$leftGroup = $rightGroup.clone();
|
|
$leftGroup.children().remove();
|
|
}
|
|
|
|
$option = $leftGroup.append($option.is('optgroup') ? $option.children() : $option );
|
|
|
|
$rightGroup.removeIfEmpty();
|
|
}
|
|
|
|
self.$left.move($option);
|
|
});
|
|
|
|
if ( !skipStack ) {
|
|
self.undoStack.push(['left', $options ]);
|
|
self.redoStack = [];
|
|
}
|
|
|
|
if ( typeof self.callbacks.sort == 'function' && !silent ) {
|
|
self.$left.mSort(self.callbacks.sort);
|
|
}
|
|
|
|
if ( typeof self.callbacks.afterMoveToLeft == 'function' && !silent ) {
|
|
self.callbacks.afterMoveToLeft( self.$left, self.$right, $options );
|
|
}
|
|
|
|
return self;
|
|
}
|
|
},
|
|
|
|
undo: function(event) {
|
|
var self = this;
|
|
var last = self.undoStack.pop();
|
|
|
|
if ( last ) {
|
|
self.redoStack.push(last);
|
|
|
|
switch(last[0]) {
|
|
case 'left':
|
|
self.moveToRight(last[1], event, false, true);
|
|
break;
|
|
case 'right':
|
|
self.moveToLeft(last[1], event, false, true);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
redo: function(event) {
|
|
var self = this;
|
|
var last = self.redoStack.pop();
|
|
|
|
if ( last ) {
|
|
self.undoStack.push(last);
|
|
|
|
switch(last[0]) {
|
|
case 'left':
|
|
self.moveToLeft(last[1], event, false, true);
|
|
break;
|
|
case 'right':
|
|
self.moveToRight(last[1], event, false, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Multiselect;
|
|
})($);
|
|
|
|
$.multiselect = {
|
|
defaults: {
|
|
/** will be executed once - remove from $left all options that are already in $right
|
|
*
|
|
* @method startUp
|
|
* @attribute $left jQuery object
|
|
* @attribute $right jQuery object
|
|
**/
|
|
startUp: function( $left, $right ) {
|
|
$right.find('option').each(function(index, option) {
|
|
var $option = $left.find('option[value="' + option.value + '"]');
|
|
var $parent = $option.parent();
|
|
|
|
$option.remove();
|
|
|
|
if ($parent.prop('tagName') == 'OPTGROUP') {
|
|
$parent.removeIfEmpty();
|
|
}
|
|
});
|
|
},
|
|
|
|
/** will be executed each time before moving option[s] to right
|
|
*
|
|
* IMPORTANT : this method must return boolean value
|
|
* true : continue to moveToRight method
|
|
* false : stop
|
|
*
|
|
* @method beforeMoveToRight
|
|
* @attribute $left jQuery object
|
|
* @attribute $right jQuery object
|
|
* @attribute $options HTML object (the option[s] which was selected to be moved)
|
|
*
|
|
* @default true
|
|
* @return {boolean}
|
|
**/
|
|
beforeMoveToRight: function($left, $right, $options) { return true; },
|
|
|
|
/* will be executed each time after moving option[s] to right
|
|
*
|
|
* @method afterMoveToRight
|
|
* @attribute $left jQuery object
|
|
* @attribute $right jQuery object
|
|
* @attribute $options HTML object (the option[s] which was selected to be moved)
|
|
**/
|
|
afterMoveToRight: function($left, $right, $options) {},
|
|
|
|
/** will be executed each time before moving option[s] to left
|
|
*
|
|
* IMPORTANT : this method must return boolean value
|
|
* true : continue to moveToRight method
|
|
* false : stop
|
|
*
|
|
* @method beforeMoveToLeft
|
|
* @attribute $left jQuery object
|
|
* @attribute $right jQuery object
|
|
* @attribute $options HTML object (the option[s] which was selected to be moved)
|
|
*
|
|
* @default true
|
|
* @return {boolean}
|
|
**/
|
|
beforeMoveToLeft: function($left, $right, $options) { return true; },
|
|
|
|
/* will be executed each time after moving option[s] to left
|
|
*
|
|
* @method afterMoveToLeft
|
|
* @attribute $left jQuery object
|
|
* @attribute $right jQuery object
|
|
* @attribute $options HTML object (the option[s] which was selected to be moved)
|
|
**/
|
|
afterMoveToLeft: function($left, $right, $options) {},
|
|
|
|
/** sort options by option text
|
|
*
|
|
* @method sort
|
|
* @attribute a HTML option
|
|
* @attribute b HTML option
|
|
*
|
|
* @return 1/-1
|
|
**/
|
|
sort: function(a, b) {
|
|
if (a.innerHTML == 'NA') {
|
|
return 1;
|
|
} else if (b.innerHTML == 'NA') {
|
|
return -1;
|
|
}
|
|
|
|
return (a.innerHTML > b.innerHTML) ? 1 : -1;
|
|
}
|
|
}
|
|
};
|
|
|
|
var ua = window.navigator.userAgent;
|
|
var isIE = (ua.indexOf("MSIE ") + ua.indexOf("Trident/") + ua.indexOf("Edge/")) > -3;
|
|
|
|
$.fn.multiselect = function( options ) {
|
|
return this.each(function() {
|
|
var $this = $(this),
|
|
data = $this.data('crlcu.multiselect'),
|
|
settings = $.extend({}, $.multiselect.defaults, $this.data(), (typeof options === 'object' && options));
|
|
|
|
if (!data) {
|
|
$this.data('crlcu.multiselect', (data = new Multiselect($this, settings)));
|
|
}
|
|
});
|
|
};
|
|
|
|
// append options
|
|
// then set the selected attribute to false
|
|
$.fn.move = function( $options ) {
|
|
this
|
|
.append($options)
|
|
.find('option')
|
|
.prop('selected', false);
|
|
|
|
return this;
|
|
};
|
|
|
|
$.fn.removeIfEmpty = function() {
|
|
if (!this.children().length) {
|
|
this.remove();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
$.fn.mShow = function() {
|
|
this.removeClass('hidden').show();
|
|
|
|
if ( isIE ) {
|
|
this.each(function(index, option) {
|
|
// Remove <span> to make it compatible with IE
|
|
if($(option).parent().is('span')) {
|
|
$(option).parent().replaceWith(option);
|
|
}
|
|
|
|
$(option).show();
|
|
});
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
$.fn.mHide = function() {
|
|
this.addClass('hidden').hide();
|
|
|
|
if ( isIE ) {
|
|
this.each(function(index, option) {
|
|
// Wrap with <span> to make it compatible with IE
|
|
if(!$(option).parent().is('span')) {
|
|
$(option).wrap('<span>').hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
// sort options then reappend them to the select
|
|
$.fn.mSort = function(callback) {
|
|
this
|
|
.find('option')
|
|
.sort(callback)
|
|
.appendTo(this);
|
|
|
|
return this;
|
|
};
|
|
|
|
$.expr[":"].search = function(elem, index, meta) {
|
|
return $(elem).text().toUpperCase().indexOf(meta[3].toUpperCase()) >= 0;
|
|
}
|
|
}));
|