Overview:
- Elastic Multiple Select List:
- As the user moves over the select list it expands to accommodate the longest option value text
- All the "normal" multiple select list functionality is maintained
- Filter:
- A text field filter is added to the select list but is hidden until the select list gets focus
- As the user types, a list of matching options are shown
- The results can be clicked to be added or removed from the original select list (which is hidden whilst the filtered list is shown)
- Css styling clearly shows which items have already been selected
- Multiple items can be selected from filtered list by holding down the "shift" key.
- A "close" button hides and resets the filter so that the user can return to using the select list in it's normal manner or move on to the next field.
- Each time the filter is activated (mouseover on the select list) the filter list is regenerated so as to be able to maintain all the currently selected options.
- The minimum number of characters to be typed before the filter is activated can be defined as an option
- Being a class, it can be defined for any number of multiple select lists on the same page.
- More on this:
Dependencies:
Implementation:
var mSelect = new multipleSelectFilter(element,{[options]});
Example (gets all multiple select lists on page):
window.addEvent('domready', function() {
$$('select[multiple]').each(function(el,index){
new multipleSelectFilter(el,{
'initLength':'140',
'initialTxt':'search options...',
'charWidth':'8'
});
});
});
Options:
- initLength : (numeric - default 100) Define width of select list on before expansion (and on mouseout)
- charWidth : (numeric - default 8) Define multiplier for expansion, this will vary according to the font-family and size
- initialTxt : (string) Text to show in filter box before user starts typing
- minChars : (numeric - default 0) Minimum number of characters the user must type before the filter is applied
- iconClose : (img tag) Complete img tag for close button
The Code:
var multipleSelectFilter = new Class({
Implements: [Options],
options: {
initLength:'100',
initialTxt:'type here...',
charWidth:'8',
minChars:'0',
iconClose:'
'
},
initialize: function(element,options){
this.setOptions(options);
this.selectBox = $(element);
this.selectOptions = this.selectBox.getElements('option');
// create array of original options
this.optionsArray=this.getOptions();
// find longest item and make array of options for checking later
var optionLength = 0;
this.selectOptions.each(function(option) {
optText=option.get('text');
lng=optText.length;
if(lng>optionLength) optionLength=lng;
});
this.stretchWidth=optionLength*this.options.charWidth;
// make select box expandable
if(this.stretchWidth>this.options.initLength){
this.selectBox.addEvents({
'mouseover' :function(){ this.expand(this.selectBox,this.stretchWidth);}.bind(this),
'mouseout': function(){ this.expand(this.selectBox,this.options.initLength);}.bind(this)
});
}
// add filter
this.addFilter();
// wrap select list in relative layer to flow "over" items near it rather than pushing them away
// NOTE - do this after we have added the filter
this.addWrapper();
},
addWrapper: function(){
// wrap select list in relative layer to flow "over" items near it rather than pushing them away
// this also prevents any "jumping" when the filter is activted
var wrapper = new Element('div', {
'styles': {
'position': 'relative',
'width':''+this.options.initLength+'px',
'z-index':'1'
}
});
wrapper.wraps(this.selectBox);
},
expand:function(el,newWidth){
el.tween('width', ''+newWidth+'px');
},
// clone the original select box with all it's selected options etc
getOptions:function(){
var arr=[];
this.selectOptions.each(function(option, index){
arr.include(option.get('text').toLowerCase());
});
return arr;
},
// add filter box
addFilter:function(){
var initialTxt = this.options.initialTxt;
var initLength = this.options.initLength;
var optionsArray = this.optionsArray;
var selectBox = this.selectBox;
var selectOptions = this.selectOptions;
var selectBoxName = this.selectBox.get('name');
var maxLength = 0;
var charWidth = this.options.charWidth;
var stretchWidth = this.stretchWidth;
var wrapperWidth = initLength.toInt()+20;
var minChars = this.options.minChars;
// get select box height for filter list
var selectBoxHeight=this.selectBox.getSize().y;
// filter wrapper to hold list and text box
var filterWrapper = new Element('div',{
'class' : 'filterWrapper',
styles:{
'width':wrapperWidth+'px'
}
})
// create ul to hold items
var filterList= new Element('ul',{
styles:{
height:selectBoxHeight+'px',
width:initLength+'px'
}
}).inject(filterWrapper,'bottom').hide();
// add text box
var filterTextbox = new Element('input',{
'class':'search',
'value':initialTxt,
events: {
'focus':function(){
if(this.value==''+initialTxt+'') this.value="";
// get options to create li list for filter
var counter=0;
var listItems="";
selectOptions.each(function(option,index){
// define select option value and text value
//optValue=option.get('value'); // NOT USED
optText =option.get('text');
optLength=optText.length;
// define length of longest item
if(optLength>maxLength) maxLength=optLength;
// check if item is selected
if(option.get('selected')) selected='class="checked"';
else selected="";
// add option to list if value isn't empty
listItems += ''+optText+'';
// increase index counter
counter++;
});
// define stretch width
var stretchWidth=maxLength*charWidth;
// check that stretch width is not less than initial length
if(stretchWidth<=initLength) var stretchWidth=initLength;
// add items to ul
filterList.set('html',listItems);
// add events for li items
filterWrapper.getElements('li').addEvents({
'mouseover':function(event){
if(event.shift){
optId=this.id.replace('opt_'+selectBoxName+'_','');
if(this.hasClass('checked')){
// remove from original select box
selectOptions[optId].setProperty('selected', '');
// remove class
this.removeClass('checked');
}else{
// mark as selected in original select box
selectOptions[optId].setProperty('selected', 'selected');
// add class to show as selected
this.addClass('checked');
}
}else{
bgColor=this.getStyle('backgroundColor');
this.highlight('#FFCC00',bgColor);
}
},
'click':function(){
optId=this.id.replace('opt_'+selectBoxName+'_','');
if(this.hasClass('checked')){
// remove from original select box
selectOptions[optId].setProperty('selected', '');
// remove class
this.removeClass('checked');
}else{
// add class to show as selected
this.addClass('checked');
// mark as selected in original select box
selectOptions[optId].setProperty('selected', 'selected');
}
}
}).setStyle('cursor','pointer');
},
'blur':function(){
// reset deafault text
if(this.value=='') this.value=initialTxt;
},
'keyup':function(){
searchStr=this.value.toLowerCase();
if(searchStr.length>minChars){
// show list once we have at least X chars (option - default 0)
// hide select box for IE
selectBox.set('opacity',0);
// show filter select list and expand to widest item
filterList.reveal().tween('width', ''+stretchWidth+'px');
}else{
//filterList.tween('width', ''+initLength+'px').dissolve();
//selectBox.show();
}
// loop through options array and remove items that aren't in it
//var results=0;
optionsArray.each(function(item, index){
if(!item.contains(searchStr)){
// hide item from list
if($('opt_'+selectBoxName+'_'+index+''))$('opt_'+selectBoxName+'_'+index+'').hide();
}else{
// show option (incase it has been hidden by previous typing)
if($('opt_'+selectBoxName+'_'+index+''))$('opt_'+selectBoxName+'_'+index+'').show();
//results++;
}
});
}
}
}).inject(filterWrapper,'top');
// reset button to hide filter list
var btReset = new Element('div',{
'class':'refresh',
'title':'Close Filter',
'html':this.options.iconClose,
'events':{
'click':function(){
// hide the temp list - do it quickly to avoid errors ;)
filterList.hide();
filterWrapper.hide();
selectBox.set('opacity',1);
filterTextbox.value=initialTxt;
}
}
}).inject(filterTextbox,'after');
var clearLayer = new Element('div',{
styles:{
border:'1px dashed red',
clear:'both',
height:'10px'
}
}).inject(filterWrapper,'after');
// hide filter box
filterWrapper.inject(this.selectBox,'before').hide();
selectBox.addEvents({
mouseover:function(){
filterWrapper.reveal();
}
});
}
});