/*!
* jquery.sumoselect - v3.0.3
* http://hemantnegi.github.io/jquery.sumoselect
* 2016-12-12
*
* copyright 2015 hemant negi
* email : hemant.frnz@gmail.com
* compressor http://refresh-sf.com/
*/
(function(factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports !== 'undefined') {
module.exports = factory(require('jquery'));
} else {
factory(jquery);
}
})(function ($) {
'namespace sumo';
$.fn.sumoselect = function (options) {
// this is the easiest way to have default options.
var settings = $.extend({
placeholder: '', // dont change it here.
csvdispcount: 3, // display no. of items in multiselect. 0 to display all.
captionformat:'{0} selected', // format of caption text. you can set your locale.
captionformatallselected:'{0} all selected!', // format of caption text when all elements are selected. set null to use captionformat. it will not work if there are disabled elements in select.
floatwidth: 400, // screen width of device at which the list is rendered in floating popup fashion.
forcecustomrendering: false, // force the custom modal on all devices below floatwidth resolution.
nativeondevice: ['android', 'blackberry', 'iphone', 'ipad', 'ipod', 'opera mini', 'iemobile', 'silk'], //
outputascsv: false, // true to post data as csv ( false for html control array ie. default select )
csvsepchar: ',', // separation char in csv mode
okcancelinmulti: false, // display ok cancel buttons in desktop mode multiselect also.
isclickawayok: false, // for okcancelinmulti=true. sets whether click outside will trigger ok or cancel (default is cancel).
triggerchangecombined: true, // im multi select mode whether to trigger change event on individual selection or combined selection.
selectall: false, // to display select all button in multiselect mode.|| also select all will not be available on mobile devices.
search: true, // to display input for filtering content. selectalltext will be input text placeholder
searchtext: '����', // placeholder for search input
nomatch: 'û��������"{0}"',
prefix: '', // some prefix usually the field name. eg. 'hello'
locale: ['ok', 'cancel', 'select all'], // all text that is used. don't change the index.
up: false, // set true to open upside.
showtitle: true // set to false to prevent title (tooltip) from appearing
}, options);
var ret = this.each(function () {
var selobj = this; // the original select object.
if (this.sumo || !$(this).is('select')) return; //already initialized
this.sumo = {
e: $(selobj), //the jquery object of original select element.
is_multi: $(selobj).attr('multiple'), //if its a multiple select
select: '',
caption: '',
placeholder: '',
optdiv: '',
captioncont: '',
ul:'',
is_floating: false,
is_opened: false,
//backdrop: '',
mob:false, // if to open device default select
pstate: [],
createelems: function () {
var o = this;
o.e.wrap('
');
o.select = o.e.parent();
o.caption = $('');
o.captioncont = $('')
.attr('style', o.e.attr('style'))
.prepend(o.caption);
o.select.append(o.captioncont);
// default turn off if no multiselect
if(!o.is_multi)settings.okcancelinmulti = false
if(o.e.attr('disabled'))
o.select.addclass('disabled').removeattr('tabindex');
//if output as csv and is a multiselect.
if (settings.outputascsv && o.is_multi && o.e.attr('name')) {
//create a hidden field to store csv value.
o.select.append($('').attr('name', o.e.attr('name')).val(o.getselstr()));
// so it can not post the original select.
o.e.removeattr('name');
}
//break for mobile rendring.. if forcecustomrendering is false
if (o.ismobile() && !settings.forcecustomrendering) {
o.setnativemobile();
return;
}
// if there is a name attr in select add a class to container div
if(o.e.attr('name')) o.select.addclass('sumo_'+o.e.attr('name').replace(/\[\]/, ''))
//hide original select
o.e.addclass('sumounder').attr('tabindex', '-1');
//## creating the list...
o.optdiv = $('
');
//branch for floating list in low res devices.
o.floatinglist();
//creating the markup for the available options
o.ul = $('
');
o.optdiv.append(o.ul);
// select all functionality
if(settings.selectall && o.is_multi) o.selall();
// search functionality
if(settings.search) o.search();
o.ul.append(o.prepitems(o.e.children()));
//if multiple then add the class multiple and add ok / cancel button
if (o.is_multi) o.multiselelect();
o.select.append(o.optdiv);
o.basicevents();
o.selallstate();
},
prepitems: function (opts, d) {
var lis = [], o=this;
$(opts).each(function (i, opt) { // parsing options to li
opt = $(opt);
lis.push(opt.is('optgroup')?
$('
')
.find('ul')
.append(o.prepitems(opt.children(), opt[0].disabled))
.end()
:
o.createli(opt, d)
);
});
return lis;
},
//## creates a li element from a given option and binds events to it
//## returns the jquery instance of li (not inserted in dom)
createli: function (opt, d) {
var o = this;
if(!opt.attr('value'))opt.attr('value',opt.val());
var li = $('');
li.data('opt', opt); // store a direct reference to option.
opt.data('li', li); // store a direct reference to list item.
if (o.is_multi) li.prepend('');
if (opt[0].disabled || d)
li = li.addclass('disabled');
o.onoptclick(li);
if (opt[0].selected)
li.addclass('selected');
if (opt.attr('class'))
li.addclass(opt.attr('class'));
if (opt.attr('title'))
li.attr('title', opt.attr('title'));
return li;
},
//## returns the selected items as string in a multiselect.
getselstr: function () {
// get the pre selected items.
sopt = [];
this.e.find('option:selected').each(function () { sopt.push($(this).val()); });
return sopt.join(settings.csvsepchar);
},
//## those ok/cancel buttons on multiple select.
multiselelect: function () {
var o = this;
o.optdiv.addclass('multiple');
o.okbtn = $('
').append(btns));
// handling keyboard navigation on ok cancel buttons.
btns.on('keydown.sumo', function (e) {
var el = $(this);
switch (e.which) {
case 32: // space
case 13: // enter
el.trigger('click');
break;
case 9: //tab
if(el.hasclass('btnok'))return;
case 27: // esc
o._cnbtn();
o.hideopts();
return;
}
e.stoppropagation();
e.preventdefault();
});
},
_okbtn:function(){
var o = this, cg = 0;
//if combined change event is set.
if (settings.triggerchangecombined) {
//check for a change in the selection.
if (o.e.find('option:selected').length != o.pstate.length) {
cg = 1;
}
else {
o.e.find('option').each(function (i,e) {
if(e.selected && o.pstate.indexof(i) < 0) cg = 1;
});
}
if (cg) {
o.callchange();
o.settext();
}
}
},
_cnbtn:function(){
var o = this;
//remove all selections
o.e.find('option:selected').each(function () { this.selected = false; });
o.optdiv.find('li.selected').removeclass('selected')
//restore selections from saved state.
for(var i = 0; i < o.pstate.length; i++) {
o.e.find('option')[o.pstate[i]].selected = true;
o.ul.find('li.opt').eq(o.pstate[i]).addclass('selected');
}
o.selallstate();
},
selall: function () {
var o = this;
if(!o.is_multi)return;
o.selall = $('
');
o.optdiv.addclass('selall');
o.selall.on('click',function(){
o.selall.toggleclass('selected');
o.toggselall(o.selall.hasclass('selected'), 1);
//o.selallstate();
});
o.optdiv.prepend(o.selall);
},
// search module (can be removed if not required.)
search: function(){
var o = this,
cc = o.captioncont.addclass('search'),
p = $('
');
o.ftxt = $('')
.on('click', function(e){
e.stoppropagation();
});
cc.append(o.ftxt);
o.optdiv.children('ul').after(p);
o.ftxt.on('keyup.sumo',function(){
var hid = o.optdiv.find('ul.options li.opt').each(function(ix,e){
var e = $(e),
opt = e.data('opt')[0];
opt.hidden = e.text().tolowercase().indexof(o.ftxt.val().tolowercase()) < 0;
e.toggleclass('hidden', opt.hidden);
}).not('.hidden');
p.html(settings.nomatch.replace(/\{0\}/g, '')).toggle(!hid.length);
p.find('em').text(o.ftxt.val());
o.selallstate();
});
},
selallstate: function () {
var o = this;
if (settings.selectall && o.is_multi) {
var sc = 0, vc = 0;
o.optdiv.find('li.opt').not('.hidden').each(function (ix, e) {
if ($(e).hasclass('selected')) sc++;
if (!$(e).hasclass('disabled')) vc++;
});
//select all checkbox state change.
if (sc == vc) o.selall.removeclass('partial').addclass('selected');
else if (sc == 0) o.selall.removeclass('selected partial');
else o.selall.addclass('partial')//.removeclass('selected');
}
},
showopts: function () {
var o = this;
if (o.e.attr('disabled')) return; // if select is disabled then retrun
o.e.trigger('sumo:opening', o);
o.is_opened = true;
o.select.addclass('open').attr('aria-expanded', 'true');
o.e.trigger('sumo:opened', o);
if(o.ftxt)o.ftxt.focus();
else o.select.focus();
// hide options on click outside.
$(document).on('click.sumo', function (e) {
if (!o.select.is(e.target) // if the target of the click isn't the container...
&& o.select.has(e.target).length === 0){ // ... nor a descendant of the container
if(!o.is_opened)return;
o.hideopts();
if (settings.okcancelinmulti){
if(settings.isclickawayok)
o._okbtn();
else
o._cnbtn();
}
}
});
if (o.is_floating) {
h = o.optdiv.children('ul').outerheight() + 2; // +2 is clear fix
if (o.is_multi) h = h + parseint(o.optdiv.css('padding-bottom'));
o.optdiv.css('height', h);
$('body').addclass('sumostopscroll');
}
o.setpstate();
},
//maintain state when ok/cancel buttons are available storing the indexes.
setpstate: function(){
var o = this;
if (o.is_multi && (o.is_floating || settings.okcancelinmulti)){
o.pstate = [];
// assuming that find returns elements in tree order
o.e.find('option').each(function (i, e){if(e.selected) o.pstate.push(i);});
}
},
callchange: function () {
//this.e.trigger('change').trigger('click');
this.e.change(function () {
$(this).is(":checked");
});
var tck = $("select[data-field-name='studprofessionalseries2']").val();
if (tck == null) {
$("input[data-field-name='studprofessionalseries']").val("");
} else {
$("input[data-field-name='studprofessionalseries']").val(tck);
}
},
hideopts: function () {
var o = this;
if(o.is_opened){
o.e.trigger('sumo:closing', o);
o.is_opened = false;
o.select.removeclass('open').attr('aria-expanded', 'true').find('ul li.sel').removeclass('sel');
o.e.trigger('sumo:closed', o);
$(document).off('click.sumo');
o.select.focus();
$('body').removeclass('sumostopscroll');
// clear the search
if(settings.search){
o.ftxt.val('');
o.ftxt.trigger('keyup.sumo');
}
}
},
setonopen: function () {
var o = this,
li = o.optdiv.find('li.opt:not(.hidden)').eq(settings.search?0:o.e[0].selectedindex);
if(li.hasclass('disabled')){
li = li.next(':not(disabled)')
if(!li.length) return;
}
o.optdiv.find('li.sel').removeclass('sel');
li.addclass('sel');
o.showopts();
},
nav: function (up) {
var o = this, c,
s=o.ul.find('li.opt:not(.disabled, .hidden)'),
sel = o.ul.find('li.opt.sel:not(.hidden)'),
idx = s.index(sel);
if (o.is_opened && sel.length) {
if (up && idx > 0)
c = s.eq(idx-1);
else if(!up && idx < s.length-1 && idx > -1)
c = s.eq(idx+1);
else return; // if no items before or after
sel.removeclass('sel');
sel = c.addclass('sel');
// setting sel item to visible view.
var ul = o.ul,
st = ul.scrolltop(),
t = sel.position().top + st;
if(t >= st + ul.height()-sel.outerheight())
ul.scrolltop(t - ul.height() + sel.outerheight());
if(t= settings.csvdispcount && settings.csvdispcount) {
if (sels.length == o.e.find('option').length && settings.captionformatallselected) {
o.placeholder = settings.captionformatallselected.replace(/\{0\}/g, sels.length)+',';
} else {
o.placeholder = settings.captionformat.replace(/\{0\}/g, sels.length)+',';
}
break;
}
else o.placeholder += $(sels[i]).text() + ", ";
}
o.placeholder = o.placeholder.replace(/,([^,]*)$/, '$1'); //remove unexpected "," from last.
}
else {
o.placeholder = o.e.find(':selected').not(':disabled').text();
}
var is_placeholder = false;
if (!o.placeholder) {
is_placeholder = true;
o.placeholder = o.e.attr('placeholder');
if (!o.placeholder) //if placeholder is there then set it
o.placeholder = o.e.find('option:disabled:selected').text();
}
o.placeholder = o.placeholder ? (settings.prefix + ' ' + o.placeholder) : settings.placeholder
//set display text
o.caption.html(o.placeholder);
if (settings.showtitle) o.captioncont.attr('title', o.placeholder);
//set the hidden field if post as csv is true.
var csvfield = o.select.find('input.hemant123');
if (csvfield.length) csvfield.val(o.getselstr());
//add class placeholder if its a placeholder text.
if (is_placeholder) o.caption.addclass('placeholder'); else o.caption.removeclass('placeholder');
return o.placeholder;
},
ismobile: function () {
// adapted from http://www.detectmobilebrowsers.com
var ua = navigator.useragent || navigator.vendor || window.opera;
// checks for ios, android, blackberry, opera mini, and windows mobile devices
for (var i = 0; i < settings.nativeondevice.length; i++) if (ua.tostring().tolowercase().indexof(settings.nativeondevice[i].tolowercase()) > 0) return settings.nativeondevice[i];
return false;
},
setnativemobile: function () {
var o = this;
o.e.addclass('selectclass')//.css('height', o.select.outerheight());
o.mob = true;
o.e.change(function () {
o.settext();
});
},
floatinglist: function () {
var o = this;
//called on init and also on resize.
//o.is_floating = true if window width is < specified float width
o.is_floating = $(window).width() <= settings.floatwidth;
//set class isfloating
o.optdiv.toggleclass('isfloating', o.is_floating);
//remove height if not floating
if (!o.is_floating) o.optdiv.css('height', '');
//toggle class according to okcancelinmulti flag only when it is not floating
o.optdiv.toggleclass('okcancelinmulti', settings.okcancelinmulti && !o.is_floating);
},
//helpers for outsiders
// validates range of given item operations
vrange: function (i) {
var o = this;
var opts = o.e.find('option');
if (opts.length <= i || i < 0) throw "index out of bounds"
return o;
},
//toggles selection on c as boolean.
toggsel: function (c, i) {
var o = this;
var opt;
if (typeof(i) === "number"){
o.vrange(i);
opt = o.e.find('option')[i];
}
else{
opt = o.e.find('option[value="'+i+'"]')[0]||0;
}
if (!opt || opt.disabled)
return;
if(opt.selected != c){
opt.selected = c;
if(!o.mob) $(opt).data('li').toggleclass('selected',c);
o.callchange();
o.setpstate();
o.settext();
o.selallstate();
}
},
//toggles disabled on c as boolean.
toggdis: function (c, i) {
var o = this.vrange(i);
o.e.find('option')[i].disabled = c;
if(c)o.e.find('option')[i].selected = false;
if(!o.mob)o.optdiv.find('ul.options li').eq(i).toggleclass('disabled', c).removeclass('selected');
o.settext();
},
// toggle disable/enable on complete select control
toggsumo: function(val) {
var o = this;
o.enabled = val;
o.select.toggleclass('disabled', val);
if (val) {
o.e.attr('disabled', 'disabled');
o.select.removeattr('tabindex');
}
else{
o.e.removeattr('disabled');
o.select.attr('tabindex','0');
}
return o;
},
// toggles all option on c as boolean.
// set direct=false/0 bypasses okcancelinmulti behaviour.
toggselall: function (c, direct) {
var o = this;
o.e.find('option:not(:disabled,:hidden)')
.each(function(ix,e){
var is_selected=e.selected,
e = $(e).data('li');
if(e.hasclass('hidden'))return;
if(!!c){
if(!is_selected)e.trigger('click');
}
else{
if(is_selected)e.trigger('click');
}
});
if(!direct){
if(!o.mob && o.selall)o.selall.removeclass('partial').toggleclass('selected',!!c);
o.callchange();
o.settext();
o.setpstate();
}
},
/* outside accessibility options
which can be accessed from the element instance.
*/
reload:function(){
var elm = this.unload();
return $(elm).sumoselect(settings);
},
unload: function () {
var o = this;
o.select.before(o.e);
o.e.show();
if (settings.outputascsv && o.is_multi && o.select.find('input.hemant123').length) {
o.e.attr('name', o.select.find('input.hemant123').attr('name')); // restore the name;
}
o.select.remove();
delete selobj.sumo;
return selobj;
},
//## add a new option to select at a given index.
add: function (val, txt, i) {
if (typeof val == "undefined") throw "no value to add"
var o = this;
opts=o.e.find('option')
if (typeof txt == "number") { i = txt; txt = val; }
if (typeof txt == "undefined") { txt = val; }
opt = $("").val(val).html(txt);
if (opts.length < i) throw "index out of bounds"
if (typeof i == "undefined" || opts.length == i) { // add it to the last if given index is last no or no index provides.
o.e.append(opt);
if(!o.mob)o.ul.append(o.createli(opt));
}
else {
opts.eq(i).before(opt);
if(!o.mob)o.ul.find('li.opt').eq(i).before(o.createli(opt));
}
return selobj;
},
//## removes an item at a given index.
remove: function (i) {
var o = this.vrange(i);
o.e.find('option').eq(i).remove();
if(!o.mob)o.optdiv.find('ul.options li').eq(i).remove();
o.settext();
},
//## select an item at a given index.
selectitem: function (i) { this.toggsel(true, i); },
//## unselect an iten at a given index.
unselectitem: function (i) { this.toggsel(false, i); },
//## select all items of the select.
selectall: function () { this.toggselall(true); },
//## unselect all items of the select.
unselectall: function () { this.toggselall(false); },
//## disable an iten at a given index.
disableitem: function (i) { this.toggdis(true, i) },
//## removes disabled an iten at a given index.
enableitem: function (i) { this.toggdis(false, i) },
//## new simple methods as getter and setter are not working fine in ie8-
//## variable to check state of control if enabled or disabled.
enabled : true,
//## enables the control
enable: function(){return this.toggsumo(false)},
//## disables the control
disable: function(){return this.toggsumo(true)},
init: function () {
var o = this;
o.createelems();
o.settext();
return o
}
};
selobj.sumo.init();
});
return ret.length == 1 ? ret[0] : ret;
};
});