/*jshint undef: true, unused:true */
/*global jQuery: true */
/*!=========================================================================
* Bootstrap Dual Listbox
* v2.0.1
*
* Responsive dual multiple select with filtering. Designed to work on
* small touch devices.
*
* https://github.com/istvan-meszaros/bootstrap-duallistbox
* http://www.virtuosoft.eu/code/bootstrap-duallistbox/
*
* Copyright 2013 István Ujj-Mészáros
*
* 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.
* ====================================================================== */
(function($) {
"use strict";
$.fn.bootstrapDualListbox = function(options) {
return this.each(function() {
var $this = $(this);
if (!$this.is("select"))
{
return $this.find("select").each(function(index, item) {
$(item).bootstrapDualListbox(options);
});
}
if ($this.data('duallistbox_generated')) {
return this;
}
var settings = $.extend( {
bootstrap2compatible : false,
preserveselectiononmove : false, // 'all' / 'moved' / false
moveonselect : true, // true/false (forced true on androids, see the comment later)
initialfilterfrom : '', // string, filter selectables list on init
initialfilterto : '', // string, filter selected list on init
helperselectnamepostfix : '_helper', // 'string_of_postfix' / false
infotext : 'Showing all {0}',// text when all options are visible / false for no info text
infotextfiltered : '<span class="label label-warning">Filtered</span> {0} from {1}',// when not all of the options are visible due to the filter
infotextempty : 'Empty list', // when there are no options present in the list
selectorminimalheight : 100,
showfilterinputs : true,
filterplaceholder : 'Filter',
filtertextclear : 'show all',
nonselectedlistlabel : false, // 'string', false
selectedlistlabel : false // 'string', false
}, options);
var container;
if (settings.bootstrap2compatible) {
container = $('<div class="row-fluid bootstrap-duallistbox-container bs2compatible"><div class="span6 box1"><span class="info-container"><span class="info"></span><button type="button" class="btn btn-mini clear1 pull-right">' + settings.filtertextclear + '</button></span><input placeholder="' + settings.filterplaceholder + '" class="filter" type="text"><div class="btn-group buttons"><button type="button" class="btn moveall" title="Move all"><i class="fa fa-arrow-right"></i><i class="fa fa-arrow-right"></i></button><button type="button" class="btn move" title="Move selected"><i class="fa fa-arrow-right"></i></button></div><select multiple="multiple" data-duallistbox_generated="true"></select></div><div class="span6 box2"><span class="info-container"><span class="info"></span><button type="button" class="btn btn-mini clear2 pull-right">' + settings.filtertextclear + '</button></span><input placeholder="' + settings.filterplaceholder + '" class="filter" type="text"><div class="btn-group buttons"><button type="button" class="btn remove" title="Remove selected"><i class="fa fa-arrow-left"></i></button><button type="button" class="btn removeall" title="Remove all"><i class="fa fa-arrow-left"></i><i class="fa fa-arrow-left"></i></button></div><select multiple="multiple" data-duallistbox_generated="true"></select></div></div>').insertBefore($this);
}
else {
container = $('<div class="row bootstrap-duallistbox-container"><div class="col-md-6 box1"><span class="info-container"><span class="info"></span><button type="button" class="btn btn-primary btn-xs clear1 pull-right">' + settings.filtertextclear + '</button></span><input placeholder="' + settings.filterplaceholder + '" class="filter" type="text"><div class="btn-group buttons"><button type="button" class="btn btn-primary moveall" title="Move all"><i class="fa fa-arrow-right"></i><i class="fa fa-arrow-right"></i></button><button type="button" class="btn btn-primary move" title="Move selected"><i class="fa fa-arrow-right"></i></button></div><select multiple="multiple" data-duallistbox_generated="true"></select></div><div class="col-md-6 box2"><span class="info-container"><span class="info"></span><button type="button" class="btn btn-primary btn-xs clear2 pull-right">' + settings.filtertextclear + '</button></span><input placeholder="' + settings.filterplaceholder + '" class="filter" type="text"><div class="btn-group buttons"><button type="button" class="btn btn-primary remove" title="Remove selected"><i class="fa fa-arrow-left"></i></button><button type="button" class="btn btn-primary removeall" title="Remove all"><i class="fa fa-arrow-left"></i><i class="fa fa-arrow-left"></i></button></div><select multiple="multiple" data-duallistbox_generated="true"></select></div></div>').insertBefore($this);
}
var elements = {
originalselect: $this,
box1: $('.box1', container),
box2: $('.box2', container),
filterinput1: $('.box1 .filter', container),
filterinput2: $('.box2 .filter', container),
filter1clear: $('.box1 .clear1', container),
filter2clear: $('.box2 .clear2', container),
info1: $('.box1 .info', container),
info2: $('.box2 .info', container),
select1: $('.box1 select', container),
select2: $('.box2 select', container),
movebutton: $('.box1 .move', container),
removebutton: $('.box2 .remove', container),
moveallbutton: $('.box1 .moveall', container),
removeallbutton: $('.box2 .removeall', container),
form: $($('.box1 .filter', container)[0].form)
},
i = 0,
selectedelements = 0,
// Selections are invisible on android if the containing select is styled with CSS
// http://code.google.com/p/android/issues/detail?id=16922
isbuggyandroid = /android/i.test(navigator.userAgent.toLowerCase());
init();
function init()
{
// We are forcing to move on select and disabling preserveselection on Android
if (isbuggyandroid) {
settings.moveonselect = true;
settings.preserveselectiononmove = false;
}
if (settings.moveonselect) {
container.addClass('moveonselect');
}
var originalselectname = elements.originalselect.attr('name') || '';
if (settings.nonselectedlistlabel) {
var nonselectedlistid = 'bootstrap-duallistbox-nonselected-list_' + originalselectname;
elements.box1.prepend('<label for="' + nonselectedlistid + '">' + settings.nonselectedlistlabel + '</label>');
elements.select1.prop('id', nonselectedlistid);
}
if (settings.selectedlistlabel) {
var selectedlistid = 'bootstrap-duallistbox-selected-list_' + originalselectname;
elements.box2.prepend('<label for="' + selectedlistid + '">' + settings.selectedlistlabel + '</label>');
elements.select2.prop('id', selectedlistid);
}
if (!!settings.helperselectnamepostfix)
{
elements.select1.attr('name', originalselectname + settings.helperselectnamepostfix + '1');
elements.select2.attr('name', originalselectname + settings.helperselectnamepostfix + '2');
}
var c = elements.originalselect.attr('class');
if (typeof c !== 'undefined' && c) {
c = c.match(/\bspan[1-9][0-2]?/);
if (!c) {
c = elements.originalselect.attr('class');
c = c.match(/\bcol-md-[1-9][0-2]?/);
}
}
if (!!c) {
container.addClass(c.toString());
}
var height = (elements.originalselect.height() < settings.selectorminimalheight) ? settings.selectorminimalheight : elements.originalselect.height();
elements.select1.height(height);
elements.select2.height(height);
elements.originalselect.addClass('hide');
updateselectionstates();
if (!settings.showfilterinputs) {
elements.filterinput1.hide();
elements.filterinput2.hide();
} else {
elements.filterinput1.val(settings.initialfilterfrom);
elements.filterinput2.val(settings.initialfilterto);
}
bindevents();
refreshselects();
}
function updateselectionstates()
{
elements.originalselect.find('option').each(function(index, item) {
var $item = $(item);
if (typeof($item.data('original-index')) === 'undefined') {
$item.data('original-index', i++);
}
if (typeof($item.data('_selected')) === 'undefined') {
$item.data('_selected', false);
}
});
}
function refreshselects()
{
selectedelements = 0;
elements.select1.empty();
elements.select2.empty();
elements.originalselect.find('option').each(function(index, item) {
var $item = $(item);
if ($item.prop('selected')) {
selectedelements++;
elements.select2.append($item.clone(true).prop('selected', $item.data('_selected')));
}
else {
elements.select1.append($item.clone(true).prop('selected', $item.data('_selected')));
}
});
filter1();
filter2();
refreshinfo();
}
function formatstring(s, args)
{
return s.replace(/\{(\d+)\}/g, function(match, number) {
return typeof args[number] !== 'undefined' ? args[number] : match;
});
}
function refreshinfo()
{
if (!settings.infotext) {
return;
}
var visible1 = elements.select1.find('option').length,
visible2 = elements.select2.find('option').length,
all1 = elements.originalselect.find('option').length - selectedelements,
all2 = selectedelements,
content = '';
if (all1 === 0) {
content = settings.infotextempty;
}
else if (visible1 === all1) {
content = formatstring(settings.infotext, [visible1, all1]);
}
else {
content = formatstring(settings.infotextfiltered, [visible1, all1]);
}
elements.info1.html(content);
elements.box1.toggleClass('filtered', !(visible1 === all1 || all1 === 0));
if (all2 === 0) {
content = settings.infotextempty;
}
else if (visible2 === all2) {
content = formatstring(settings.infotext, [visible2, all2]);
}
else {
content = formatstring(settings.infotextfiltered, [visible2, all2]);
}
elements.info2.html(content);
elements.box2.toggleClass('filtered', !(visible2 === all2 || all2 === 0));
}
function bindevents()
{
elements.form.submit(function(e) {
if (elements.filterinput1.is(":focus"))
{
e.preventDefault();
elements.filterinput1.focusout();
}
else if (elements.filterinput2.is(":focus"))
{
e.preventDefault();
elements.filterinput2.focusout();
}
});
elements.originalselect.on('bootstrapduallistbox.refresh', function(e, clearselections){
updateselectionstates();
if (!clearselections) {
saveselections1();
saveselections2();
}
else {
clearselections12();
}
refreshselects();
});
elements.filter1clear.on('click', function() {
elements.filterinput1.val('');
refreshselects();
});
elements.filter2clear.on('click', function() {
elements.filterinput2.val('');
refreshselects();
});
elements.movebutton.on('click', function() {
move();
});
elements.moveallbutton.on('click', function() {
moveall();
});
elements.removebutton.on('click', function() {
remove();
});
elements.removeallbutton.on('click', function() {
removeall();
});
elements.filterinput1.on('change keyup', function() {
filter1();
});
elements.filterinput2.on('change keyup', function() {
filter2();
});
if (settings.moveonselect) {
settings.preserveselectiononmove = false;
elements.select1.on('change', function() {
move();
});
elements.select2.on('change', function() {
remove();
});
}
}
function saveselections1()
{
elements.select1.find('option').each(function(index, item) {
var $item = $(item);
elements.originalselect.find('option').eq($item.data('original-index')).data('_selected', $item.prop('selected'));
});
}
function saveselections2()
{
elements.select2.find('option').each(function(index, item) {
var $item = $(item);
elements.originalselect.find('option').eq($item.data('original-index')).data('_selected', $item.prop('selected'));
});
}
function clearselections12()
{
elements.select1.find('option').each(function() {
elements.originalselect.find('option').data('_selected', false);
});
}
function filter1() {
saveselections1();
elements.select1.empty().scrollTop(0);
var regex = new RegExp($.trim(elements.filterinput1.val()), "gi");
elements.originalselect.find('option').not(':selected').each(function(index, item) {
var $item = $(item);
var isFiltered = true;
if (item.text.match(regex)) {
isFiltered = false;
elements.select1.append($item.clone(true).prop('selected', $item.data('_selected')));
}
elements.originalselect.find('option').eq($item.data('original-index')).data('filtered1', isFiltered);
});
refreshinfo();
}
function filter2() {
saveselections2();
elements.select2.empty().scrollTop(0);
var regex = new RegExp($.trim(elements.filterinput2.val()), "gi");
elements.originalselect.find('option:selected').each(function(index, item) {
var $item = $(item);
var isFiltered = true;
if (item.text.match(regex)) {
isFiltered = false;
elements.select2.append($item.clone(true).prop('selected', $item.data('_selected')));
}
elements.originalselect.find('option').eq($item.data('original-index')).data('filtered2', isFiltered);
});
refreshinfo();
}
function sortoptions(select)
{
select.find('option').sort(function(a, b) {
return ($(a).data('original-index') > $(b).data('original-index')) ? 1 : -1;
}).appendTo(select);
}
function changeselectionstate(original_index, selected)
{
elements.originalselect.find('option').each(function(index, item) {
var $item = $(item);
if ($item.data('original-index') === original_index) {
$item.prop('selected', selected);
}
});
}
function move()
{
if (settings.preserveselectiononmove === 'all') {
saveselections1();
saveselections2();
}
else if (settings.preserveselectiononmove === 'moved') {
saveselections1();
}
elements.select1.find('option:selected').each(function(index, item) {
var $item = $(item);
if (!$item.data('filtered1')) {
changeselectionstate($item.data('original-index'), true);
}
});
refreshselects();
sortoptions(elements.select2);
}
function remove()
{
if (settings.preserveselectiononmove === 'all') {
saveselections1();
saveselections2();
}
else if (settings.preserveselectiononmove === 'moved') {
saveselections2();
}
elements.select2.find('option:selected').each(function(index, item) {
var $item = $(item);
if (!$item.data('filtered2')) {
changeselectionstate($item.data('original-index'), false);
}
});
refreshselects();
sortoptions(elements.select1);
}
function moveall()
{
if (settings.preserveselectiononmove === 'all') {
saveselections1();
saveselections2();
}
else if (settings.preserveselectiononmove === 'moved') {
saveselections1();
}
elements.originalselect.find('option').each(function(index, item) {
var $item = $(item);
if (!$item.data('filtered1')) {
$item.prop('selected', true);
}
});
refreshselects();
}
function removeall()
{
if (settings.preserveselectiononmove === 'all') {
saveselections1();
saveselections2();
}
else if (settings.preserveselectiononmove === 'moved') {
saveselections2();
}
elements.originalselect.find('option').each(function(index, item) {
var $item = $(item);
if (!$item.data('filtered2')) {
$item.prop('selected', false);
}
});
refreshselects();
}
});
};
})(jQuery);
@KyuuKazami