magic_suggest 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/magic_suggest.rb +6 -0
- data/lib/magic_suggest/version.rb +3 -0
- data/vendor/assets/javascripts/magicsuggest-min.js +1 -0
- data/vendor/assets/javascripts/magicsuggest.js +1565 -0
- data/vendor/assets/stylesheets/magicsuggest-min.css +1 -0
- data/vendor/assets/stylesheets/magicsuggest.css +266 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2a65f5eea68f7c103a1ab460495ffec9670982c2
|
4
|
+
data.tar.gz: 4c2b1070ef3d9cb331d1bb35a3620d122d955358
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77d10dbf8321b564c14a108ee23c5268a515ba731b89b32a197abd6303dcb5feae4664acb6cfcda706cfb96cfa3911369f6b42eeb8965f0e8cae8ef5d7ba5bd4
|
7
|
+
data.tar.gz: f82a295710327f4420b00b36050a056cab32d30ecd9fa809bbf5c6b0ff6e05b6f805c91f968991fc87163240e5094bea4bdeaa2a5a2585abf8537a41bec1a701
|
@@ -0,0 +1 @@
|
|
1
|
+
(function($){"use strict";var MagicSuggest=function(element,options){var ms=this;var defaults={allowFreeEntries:true,allowDuplicates:false,ajaxConfig:{},autoSelect:true,selectFirst:false,queryParam:"query",beforeSend:function(){},cls:"",data:null,dataUrlParams:{},disabled:false,disabledField:null,displayField:"name",editable:true,expanded:false,expandOnFocus:false,groupBy:null,hideTrigger:false,highlight:true,id:null,infoMsgCls:"",inputCfg:{},invalidCls:"ms-inv",matchCase:false,maxDropHeight:290,maxEntryLength:null,maxEntryRenderer:function(v){return"Please reduce your entry by "+v+" character"+(v>1?"s":"")},maxSuggestions:null,maxSelection:10,maxSelectionRenderer:function(v){return"You cannot choose more than "+v+" item"+(v>1?"s":"")},method:"POST",minChars:0,minCharsRenderer:function(v){return"Please type "+v+" more character"+(v>1?"s":"")},mode:"local",name:null,noSuggestionText:"No suggestions",placeholder:"Type or click here",renderer:null,required:false,resultAsString:false,resultAsStringDelimiter:",",resultsField:"results",selectionCls:"",selectionContainer:null,selectionPosition:"inner",selectionRenderer:null,selectionStacked:false,sortDir:"asc",sortOrder:null,strictSuggest:false,style:"",toggleOnClick:false,typeDelay:400,useTabKey:false,useCommaKey:true,useZebraStyle:false,value:null,valueField:"id",vregex:null,vtype:null};var conf=$.extend({},options);var cfg=$.extend(true,{},defaults,conf);this.addToSelection=function(items,isSilent){if(!cfg.maxSelection||_selection.length<cfg.maxSelection){if(!$.isArray(items)){items=[items]}var valuechanged=false;$.each(items,function(index,json){if(cfg.allowDuplicates||$.inArray(json[cfg.valueField],ms.getValue())===-1){_selection.push(json);valuechanged=true}});if(valuechanged===true){self._renderSelection();this.empty();if(isSilent!==true){$(this).trigger("selectionchange",[this,this.getSelection()])}}}this.input.attr("placeholder",cfg.selectionPosition==="inner"&&this.getValue().length>0?"":cfg.placeholder)};this.clear=function(isSilent){this.removeFromSelection(_selection.slice(0),isSilent)};this.collapse=function(){if(cfg.expanded===true){this.combobox.detach();cfg.expanded=false;$(this).trigger("collapse",[this])}};this.disable=function(){this.container.addClass("ms-ctn-disabled");cfg.disabled=true;ms.input.attr("disabled",true)};this.empty=function(){this.input.val("")};this.enable=function(){this.container.removeClass("ms-ctn-disabled");cfg.disabled=false;ms.input.attr("disabled",false)};this.expand=function(){if(!cfg.expanded&&(this.input.val().length>=cfg.minChars||this.combobox.children().size()>0)){this.combobox.appendTo(this.container);self._processSuggestions();cfg.expanded=true;$(this).trigger("expand",[this])}};this.isDisabled=function(){return cfg.disabled};this.isValid=function(){var valid=cfg.required===false||_selection.length>0;if(cfg.vtype||cfg.vregex){$.each(_selection,function(index,item){valid=valid&&self._validateSingleItem(item[cfg.valueField])})}return valid};this.getDataUrlParams=function(){return cfg.dataUrlParams};this.getName=function(){return cfg.name};this.getSelection=function(){return _selection};this.getRawValue=function(){return ms.input.val()};this.getValue=function(){return $.map(_selection,function(o){return o[cfg.valueField]})};this.removeFromSelection=function(items,isSilent){if(!$.isArray(items)){items=[items]}var valuechanged=false;$.each(items,function(index,json){var i=$.inArray(json[cfg.valueField],ms.getValue());if(i>-1){_selection.splice(i,1);valuechanged=true}});if(valuechanged===true){self._renderSelection();if(isSilent!==true){$(this).trigger("selectionchange",[this,this.getSelection()])}if(cfg.expandOnFocus){ms.expand()}if(cfg.expanded){self._processSuggestions()}}this.input.attr("placeholder",cfg.selectionPosition==="inner"&&this.getValue().length>0?"":cfg.placeholder)};this.getData=function(){return _cbData};this.setData=function(data){cfg.data=data;self._processSuggestions()};this.setName=function(name){cfg.name=name;if(name){cfg.name+=name.indexOf("[]")>0?"":"[]"}if(ms._valueContainer){$.each(ms._valueContainer.children(),function(i,el){el.name=cfg.name})}};this.setSelection=function(items){this.clear();this.addToSelection(items)};this.setValue=function(values){var items=[];$.each(values,function(index,value){var found=false;$.each(_cbData,function(i,item){if(item[cfg.valueField]==value){items.push(item);found=true;return false}});if(!found){if(typeof value==="object"){items.push(value)}else{var json={};json[cfg.valueField]=value;json[cfg.displayField]=value;items.push(json)}}});if(items.length>0){this.addToSelection(items)}};this.setDataUrlParams=function(params){cfg.dataUrlParams=$.extend({},params)};var _selection=[],_comboItemHeight=0,_timer,_hasFocus=false,_groups=null,_cbData=[],_ctrlDown=false,KEYCODES={BACKSPACE:8,TAB:9,ENTER:13,CTRL:17,ESC:27,SPACE:32,UPARROW:38,DOWNARROW:40,COMMA:188};var self={_displaySuggestions:function(data){ms.combobox.show();ms.combobox.empty();var resHeight=0,nbGroups=0;if(_groups===null){self._renderComboItems(data);resHeight=_comboItemHeight*data.length}else{for(var grpName in _groups){nbGroups+=1;$("<div/>",{"class":"ms-res-group",html:grpName}).appendTo(ms.combobox);self._renderComboItems(_groups[grpName].items,true)}var _groupItemHeight=ms.combobox.find(".ms-res-group").outerHeight();if(_groupItemHeight!==null){var tmpResHeight=nbGroups*_groupItemHeight;resHeight=_comboItemHeight*data.length+tmpResHeight}else{resHeight=_comboItemHeight*(data.length+nbGroups)}}if(resHeight<ms.combobox.height()||resHeight<=cfg.maxDropHeight){ms.combobox.height(resHeight)}else if(resHeight>=ms.combobox.height()&&resHeight>cfg.maxDropHeight){ms.combobox.height(cfg.maxDropHeight)}if(data.length===1&&cfg.autoSelect===true){ms.combobox.children().filter(":not(.ms-res-item-disabled):last").addClass("ms-res-item-active")}if(cfg.selectFirst===true){ms.combobox.children().filter(":not(.ms-res-item-disabled):first").addClass("ms-res-item-active")}if(data.length===0&&ms.getRawValue()!==""){var noSuggestionText=cfg.noSuggestionText.replace(/\{\{.*\}\}/,ms.input.val());self._updateHelper(noSuggestionText);ms.collapse()}if(cfg.allowFreeEntries===false){if(data.length===0){$(ms.input).addClass(cfg.invalidCls);ms.combobox.hide()}else{$(ms.input).removeClass(cfg.invalidCls)}}},_getEntriesFromStringArray:function(data){var json=[];$.each(data,function(index,s){var entry={};entry[cfg.displayField]=entry[cfg.valueField]=$.trim(s);json.push(entry)});return json},_highlightSuggestion:function(html){var q=ms.input.val();var specialCharacters=["^","$","*","+","?",".","(",")",":","!","|","{","}","[","]"];$.each(specialCharacters,function(index,value){q=q.replace(value,"\\"+value)});if(q.length===0){return html}var glob=cfg.matchCase===true?"g":"gi";return html.replace(new RegExp("("+q+")(?!([^<]+)?>)",glob),"<em>$1</em>")},_moveSelectedRow:function(dir){if(!cfg.expanded){ms.expand()}var list,start,active,scrollPos;list=ms.combobox.find(".ms-res-item:not(.ms-res-item-disabled)");if(dir==="down"){start=list.eq(0)}else{start=list.filter(":last")}active=ms.combobox.find(".ms-res-item-active:not(.ms-res-item-disabled):first");if(active.length>0){if(dir==="down"){start=active.nextAll(".ms-res-item:not(.ms-res-item-disabled)").first();if(start.length===0){start=list.eq(0)}scrollPos=ms.combobox.scrollTop();ms.combobox.scrollTop(0);if(start[0].offsetTop+start.outerHeight()>ms.combobox.height()){ms.combobox.scrollTop(scrollPos+_comboItemHeight)}}else{start=active.prevAll(".ms-res-item:not(.ms-res-item-disabled)").first();if(start.length===0){start=list.filter(":last");ms.combobox.scrollTop(_comboItemHeight*list.length)}if(start[0].offsetTop<ms.combobox.scrollTop()){ms.combobox.scrollTop(ms.combobox.scrollTop()-_comboItemHeight)}}}list.removeClass("ms-res-item-active");start.addClass("ms-res-item-active")},_processSuggestions:function(source){var json=null,data=source||cfg.data;if(data!==null){if(typeof data==="function"){data=data.call(ms,ms.getRawValue())}if(typeof data==="string"){$(ms).trigger("beforeload",[ms]);var queryParams={};queryParams[cfg.queryParam]=ms.input.val();var params=$.extend(queryParams,cfg.dataUrlParams);$.ajax($.extend({type:cfg.method,url:data,data:params,beforeSend:cfg.beforeSend,success:function(asyncData){json=typeof asyncData==="string"?JSON.parse(asyncData):asyncData;self._processSuggestions(json);$(ms).trigger("load",[ms,json]);if(self._asyncValues){ms.setValue(typeof self._asyncValues==="string"?JSON.parse(self._asyncValues):self._asyncValues);self._renderSelection();delete self._asyncValues}},error:function(){throw"Could not reach server"}},cfg.ajaxConfig));return}else{if(data.length>0&&typeof data[0]==="string"){_cbData=self._getEntriesFromStringArray(data)}else{_cbData=data[cfg.resultsField]||data}}var sortedData=cfg.mode==="remote"?_cbData:self._sortAndTrim(_cbData);self._displaySuggestions(self._group(sortedData))}},_render:function(el){ms.setName(cfg.name);ms.container=$("<div/>",{"class":"ms-ctn form-control "+(cfg.resultAsString?"ms-as-string ":"")+cfg.cls+($(el).hasClass("input-lg")?" input-lg":"")+($(el).hasClass("input-sm")?" input-sm":"")+(cfg.disabled===true?" ms-ctn-disabled":"")+(cfg.editable===true?"":" ms-ctn-readonly")+(cfg.hideTrigger===false?"":" ms-no-trigger"),style:cfg.style,id:cfg.id});ms.container.focus($.proxy(handlers._onFocus,this));ms.container.blur($.proxy(handlers._onBlur,this));ms.container.keydown($.proxy(handlers._onKeyDown,this));ms.container.keyup($.proxy(handlers._onKeyUp,this));ms.input=$("<input/>",$.extend({type:"text","class":cfg.editable===true?"":" ms-input-readonly",readonly:!cfg.editable,placeholder:cfg.placeholder,disabled:cfg.disabled},cfg.inputCfg));ms.input.focus($.proxy(handlers._onInputFocus,this));ms.input.click($.proxy(handlers._onInputClick,this));ms.combobox=$("<div/>",{"class":"ms-res-ctn dropdown-menu"}).height(cfg.maxDropHeight);ms.combobox.on("click","div.ms-res-item",$.proxy(handlers._onComboItemSelected,this));ms.combobox.on("mouseover","div.ms-res-item",$.proxy(handlers._onComboItemMouseOver,this));if(cfg.selectionContainer){ms.selectionContainer=cfg.selectionContainer;$(ms.selectionContainer).addClass("ms-sel-ctn")}else{ms.selectionContainer=$("<div/>",{"class":"ms-sel-ctn"})}ms.selectionContainer.click($.proxy(handlers._onFocus,this));if(cfg.selectionPosition==="inner"&&!cfg.selectionContainer){ms.selectionContainer.append(ms.input)}else{ms.container.append(ms.input)}ms.helper=$("<span/>",{"class":"ms-helper "+cfg.infoMsgCls});self._updateHelper();ms.container.append(ms.helper);$(el).replaceWith(ms.container);if(!cfg.selectionContainer){switch(cfg.selectionPosition){case"bottom":ms.selectionContainer.insertAfter(ms.container);if(cfg.selectionStacked===true){ms.selectionContainer.width(ms.container.width());ms.selectionContainer.addClass("ms-stacked")}break;case"right":ms.selectionContainer.insertAfter(ms.container);ms.container.css("float","left");break;default:ms.container.append(ms.selectionContainer);break}}if(cfg.hideTrigger===false){ms.trigger=$("<div/>",{"class":"ms-trigger",html:'<div class="ms-trigger-ico"></div>'});ms.trigger.click($.proxy(handlers._onTriggerClick,this));ms.container.append(ms.trigger)}$(window).resize($.proxy(handlers._onWindowResized,this));if(cfg.value!==null||cfg.data!==null){if(typeof cfg.data==="string"){self._asyncValues=cfg.value;self._processSuggestions()}else{self._processSuggestions();if(cfg.value!==null){ms.setValue(cfg.value);self._renderSelection()}}}$("body").click(function(e){if(ms.container.hasClass("ms-ctn-focus")&&ms.container.has(e.target).length===0&&e.target.className.indexOf("ms-res-item")<0&&e.target.className.indexOf("ms-close-btn")<0&&ms.container[0]!==e.target){handlers._onBlur()}});if(cfg.expanded===true){cfg.expanded=false;ms.expand()}},_renderComboItems:function(items,isGrouped){var ref=this,html="";$.each(items,function(index,value){var displayed=cfg.renderer!==null?cfg.renderer.call(ref,value):value[cfg.displayField];var disabled=cfg.disabledField!==null&&value[cfg.disabledField]===true;var resultItemEl=$("<div/>",{"class":"ms-res-item "+(isGrouped?"ms-res-item-grouped ":"")+(disabled?"ms-res-item-disabled ":"")+(index%2===1&&cfg.useZebraStyle===true?"ms-res-odd":""),html:cfg.highlight===true?self._highlightSuggestion(displayed):displayed,"data-json":JSON.stringify(value)});html+=$("<div/>").append(resultItemEl).html()});ms.combobox.append(html);_comboItemHeight=ms.combobox.find(".ms-res-item:first").outerHeight()},_renderSelection:function(){var ref=this,w=0,inputOffset=0,items=[],asText=cfg.resultAsString===true&&!_hasFocus;ms.selectionContainer.find(".ms-sel-item").remove();if(ms._valueContainer!==undefined){ms._valueContainer.remove()}$.each(_selection,function(index,value){var selectedItemEl,delItemEl,selectedItemHtml=cfg.selectionRenderer!==null?cfg.selectionRenderer.call(ref,value):value[cfg.displayField];var validCls=self._validateSingleItem(value[cfg.displayField])?"":" ms-sel-invalid";if(asText===true){selectedItemEl=$("<div/>",{"class":"ms-sel-item ms-sel-text "+cfg.selectionCls+validCls,html:selectedItemHtml+(index===_selection.length-1?"":cfg.resultAsStringDelimiter)}).data("json",value)}else{selectedItemEl=$("<div/>",{"class":"ms-sel-item "+cfg.selectionCls+validCls,html:selectedItemHtml}).data("json",value);if(cfg.disabled===false){delItemEl=$("<span/>",{"class":"ms-close-btn"}).data("json",value).appendTo(selectedItemEl);delItemEl.click($.proxy(handlers._onTagTriggerClick,ref))}}items.push(selectedItemEl)});ms.selectionContainer.prepend(items);ms._valueContainer=$("<div/>",{style:"display: none;"});$.each(ms.getValue(),function(i,val){var el=$("<input/>",{type:"hidden",name:cfg.name,value:val});el.appendTo(ms._valueContainer)});ms._valueContainer.appendTo(ms.selectionContainer);if(cfg.selectionPosition==="inner"&&!cfg.selectionContainer){ms.input.width(0);inputOffset=ms.input.offset().left-ms.selectionContainer.offset().left;w=ms.container.width()-inputOffset-42;ms.input.width(w)}if(_selection.length===cfg.maxSelection){self._updateHelper(cfg.maxSelectionRenderer.call(this,_selection.length))}else{ms.helper.hide()}},_selectItem:function(item){if(cfg.maxSelection===1){_selection=[]}ms.addToSelection(item.data("json"));item.removeClass("ms-res-item-active");if(cfg.expandOnFocus===false||_selection.length===cfg.maxSelection){ms.collapse()}if(!_hasFocus){ms.input.focus()}else if(_hasFocus&&(cfg.expandOnFocus||_ctrlDown)){self._processSuggestions();if(_ctrlDown){ms.expand()}}},_sortAndTrim:function(data){var q=ms.getRawValue(),filtered=[],newSuggestions=[],selectedValues=ms.getValue();if(q.length>0){$.each(data,function(index,obj){var name=obj[cfg.displayField];if(cfg.matchCase===true&&name.indexOf(q)>-1||cfg.matchCase===false&&name.toLowerCase().indexOf(q.toLowerCase())>-1){if(cfg.strictSuggest===false||name.toLowerCase().indexOf(q.toLowerCase())===0){filtered.push(obj)}}})}else{filtered=data}$.each(filtered,function(index,obj){if(cfg.allowDuplicates||$.inArray(obj[cfg.valueField],selectedValues)===-1){newSuggestions.push(obj)}});if(cfg.sortOrder!==null){newSuggestions.sort(function(a,b){if(a[cfg.sortOrder]<b[cfg.sortOrder]){return cfg.sortDir==="asc"?-1:1}if(a[cfg.sortOrder]>b[cfg.sortOrder]){return cfg.sortDir==="asc"?1:-1}return 0})}if(cfg.maxSuggestions&&cfg.maxSuggestions>0){newSuggestions=newSuggestions.slice(0,cfg.maxSuggestions)}return newSuggestions},_group:function(data){if(cfg.groupBy!==null){_groups={};$.each(data,function(index,value){var props=cfg.groupBy.indexOf(".")>-1?cfg.groupBy.split("."):cfg.groupBy;var prop=value[cfg.groupBy];if(typeof props!="string"){prop=value;while(props.length>0){prop=prop[props.shift()]}}if(_groups[prop]===undefined){_groups[prop]={title:prop,items:[value]}}else{_groups[prop].items.push(value)}})}return data},_updateHelper:function(html){ms.helper.html(html);if(!ms.helper.is(":visible")){ms.helper.fadeIn()}},_validateSingleItem:function(value){if(cfg.vregex!==null&&cfg.vregex instanceof RegExp){return cfg.vregex.test(value)}else if(cfg.vtype!==null){switch(cfg.vtype){case"alpha":return/^[a-zA-Z_]+$/.test(value);case"alphanum":return/^[a-zA-Z0-9_]+$/.test(value);case"email":return/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/.test(value);case"url":return/(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i.test(value);case"ipaddress":return/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(value)}}return true}};var handlers={_onBlur:function(){ms.container.removeClass("ms-ctn-focus");ms.collapse();_hasFocus=false;if(ms.getRawValue()!==""&&cfg.allowFreeEntries===true){var obj={};obj[cfg.displayField]=obj[cfg.valueField]=ms.getRawValue().trim();ms.addToSelection(obj)}self._renderSelection();if(ms.isValid()===false){ms.container.addClass(cfg.invalidCls)}else if(ms.input.val()!==""&&cfg.allowFreeEntries===false){ms.empty();self._updateHelper("")}$(ms).trigger("blur",[ms])},_onComboItemMouseOver:function(e){var target=$(e.currentTarget);if(!target.hasClass("ms-res-item-disabled")){ms.combobox.children().removeClass("ms-res-item-active");target.addClass("ms-res-item-active")}},_onComboItemSelected:function(e){var target=$(e.currentTarget);if(!target.hasClass("ms-res-item-disabled")){self._selectItem($(e.currentTarget))}},_onFocus:function(){ms.input.focus()},_onInputClick:function(){if(ms.isDisabled()===false&&_hasFocus){if(cfg.toggleOnClick===true){if(cfg.expanded){ms.collapse()}else{ms.expand()}}}},_onInputFocus:function(){if(ms.isDisabled()===false&&!_hasFocus){_hasFocus=true;ms.container.addClass("ms-ctn-focus");ms.container.removeClass(cfg.invalidCls);var curLength=ms.getRawValue().length;if(cfg.expandOnFocus===true){ms.expand()}if(_selection.length===cfg.maxSelection){self._updateHelper(cfg.maxSelectionRenderer.call(this,_selection.length))}else if(curLength<cfg.minChars){self._updateHelper(cfg.minCharsRenderer.call(this,cfg.minChars-curLength))}self._renderSelection();$(ms).trigger("focus",[ms])}},_onKeyDown:function(e){var active=ms.combobox.find(".ms-res-item-active:not(.ms-res-item-disabled):first"),freeInput=ms.input.val();$(ms).trigger("keydown",[ms,e]);if(e.keyCode===KEYCODES.TAB&&(cfg.useTabKey===false||cfg.useTabKey===true&&active.length===0&&ms.input.val().length===0)){handlers._onBlur();return}switch(e.keyCode){case KEYCODES.BACKSPACE:if(freeInput.length===0&&ms.getSelection().length>0&&cfg.selectionPosition==="inner"){_selection.pop();self._renderSelection();$(ms).trigger("selectionchange",[ms,ms.getSelection()]);ms.input.attr("placeholder",cfg.selectionPosition==="inner"&&ms.getValue().length>0?"":cfg.placeholder);ms.input.focus();e.preventDefault()}break;case KEYCODES.TAB:case KEYCODES.ESC:e.preventDefault();break;case KEYCODES.ENTER:if(freeInput!==""||cfg.expanded){e.preventDefault()}break;case KEYCODES.COMMA:if(cfg.useCommaKey===true){e.preventDefault()}break;case KEYCODES.CTRL:_ctrlDown=true;break;case KEYCODES.DOWNARROW:e.preventDefault();self._moveSelectedRow("down");break;case KEYCODES.UPARROW:e.preventDefault();self._moveSelectedRow("up");break;default:if(_selection.length===cfg.maxSelection){e.preventDefault()}break}},_onKeyUp:function(e){var freeInput=ms.getRawValue(),inputValid=$.trim(ms.input.val()).length>0&&(!cfg.maxEntryLength||$.trim(ms.input.val()).length<=cfg.maxEntryLength),selected,obj={};$(ms).trigger("keyup",[ms,e]);clearTimeout(_timer);if(e.keyCode===KEYCODES.ESC&&cfg.expanded){ms.combobox.hide()}if(e.keyCode===KEYCODES.TAB&&cfg.useTabKey===false||e.keyCode>KEYCODES.ENTER&&e.keyCode<KEYCODES.SPACE){if(e.keyCode===KEYCODES.CTRL){_ctrlDown=false}return}switch(e.keyCode){case KEYCODES.UPARROW:case KEYCODES.DOWNARROW:e.preventDefault();break;case KEYCODES.ENTER:case KEYCODES.TAB:case KEYCODES.COMMA:if(e.keyCode!==KEYCODES.COMMA||cfg.useCommaKey===true){e.preventDefault();if(cfg.expanded===true){selected=ms.combobox.find(".ms-res-item-active:not(.ms-res-item-disabled):first");if(selected.length>0){self._selectItem(selected);return}}if(inputValid===true&&cfg.allowFreeEntries===true){obj[cfg.displayField]=obj[cfg.valueField]=freeInput.trim();ms.addToSelection(obj);ms.collapse();ms.input.focus()}break}default:if(_selection.length===cfg.maxSelection){self._updateHelper(cfg.maxSelectionRenderer.call(this,_selection.length))}else{if(freeInput.length<cfg.minChars){self._updateHelper(cfg.minCharsRenderer.call(this,cfg.minChars-freeInput.length));if(cfg.expanded===true){ms.collapse()}}else if(cfg.maxEntryLength&&freeInput.length>cfg.maxEntryLength){self._updateHelper(cfg.maxEntryRenderer.call(this,freeInput.length-cfg.maxEntryLength));if(cfg.expanded===true){ms.collapse()}}else{ms.helper.hide();if(cfg.minChars<=freeInput.length){_timer=setTimeout(function(){if(cfg.expanded===true){self._processSuggestions()}else{ms.expand()}},cfg.typeDelay)}}}break}},_onTagTriggerClick:function(e){ms.removeFromSelection($(e.currentTarget).data("json"))},_onTriggerClick:function(){if(ms.isDisabled()===false&&!(cfg.expandOnFocus===true&&_selection.length===cfg.maxSelection)){$(ms).trigger("triggerclick",[ms]);if(cfg.expanded===true){ms.collapse()}else{var curLength=ms.getRawValue().length;if(curLength>=cfg.minChars){ms.input.focus();ms.expand()}else{self._updateHelper(cfg.minCharsRenderer.call(this,cfg.minChars-curLength))}}}},_onWindowResized:function(){self._renderSelection()}};if(element!==null){self._render(element)}};$.fn.magicSuggest=function(options){var obj=$(this);if(obj.size()===1&&obj.data("magicSuggest")){return obj.data("magicSuggest")}obj.each(function(i){var cntr=$(this);if(cntr.data("magicSuggest")){return}if(this.nodeName.toLowerCase()==="select"){options.data=[];options.value=[];$.each(this.children,function(index,child){if(child.nodeName&&child.nodeName.toLowerCase()==="option"){options.data.push({id:child.value,name:child.text});if($(child).attr("selected")){options.value.push(child.value)}}})}var def={};$.each(this.attributes,function(i,att){def[att.name]=att.name==="value"&&att.value!==""?JSON.parse(att.value):att.value});var field=new MagicSuggest(this,$.extend([],$.fn.magicSuggest.defaults,options,def));cntr.data("magicSuggest",field);field.container.data("magicSuggest",field)});if(obj.size()===1){return obj.data("magicSuggest")}return obj};$.fn.magicSuggest.defaults={}})(jQuery);
|
@@ -0,0 +1,1565 @@
|
|
1
|
+
/**
|
2
|
+
* Multiple Selection Component for Bootstrap
|
3
|
+
* Check nicolasbize.github.io/magicsuggest/ for latest updates.
|
4
|
+
*
|
5
|
+
* Author: Nicolas Bize
|
6
|
+
* Created: Feb 8th 2013
|
7
|
+
* Last Updated: Oct 16th 2014
|
8
|
+
* Version: 2.1.4
|
9
|
+
* Licence: MagicSuggest is licenced under MIT licence (http://opensource.org/licenses/MIT)
|
10
|
+
*/
|
11
|
+
(function($)
|
12
|
+
{
|
13
|
+
"use strict";
|
14
|
+
var MagicSuggest = function(element, options)
|
15
|
+
{
|
16
|
+
var ms = this;
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Initializes the MagicSuggest component
|
20
|
+
*/
|
21
|
+
var defaults = {
|
22
|
+
/********** CONFIGURATION PROPERTIES ************/
|
23
|
+
/**
|
24
|
+
* Restricts or allows the user to validate typed entries.
|
25
|
+
* Defaults to true.
|
26
|
+
*/
|
27
|
+
allowFreeEntries: true,
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Restricts or allows the user to add the same entry more than once
|
31
|
+
* Defaults to false.
|
32
|
+
*/
|
33
|
+
allowDuplicates: false,
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Additional config object passed to each $.ajax call
|
37
|
+
*/
|
38
|
+
ajaxConfig: {},
|
39
|
+
|
40
|
+
/**
|
41
|
+
* If a single suggestion comes out, it is preselected.
|
42
|
+
*/
|
43
|
+
autoSelect: true,
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Auto select the first matching item with multiple items shown
|
47
|
+
*/
|
48
|
+
selectFirst: false,
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Allow customization of query parameter
|
52
|
+
*/
|
53
|
+
queryParam: 'query',
|
54
|
+
|
55
|
+
/**
|
56
|
+
* A function triggered just before the ajax request is sent, similar to jQuery
|
57
|
+
*/
|
58
|
+
beforeSend: function(){ },
|
59
|
+
|
60
|
+
/**
|
61
|
+
* A custom CSS class to apply to the field's underlying element.
|
62
|
+
*/
|
63
|
+
cls: '',
|
64
|
+
|
65
|
+
/**
|
66
|
+
* JSON Data source used to populate the combo box. 3 options are available here:
|
67
|
+
* No Data Source (default)
|
68
|
+
* When left null, the combo box will not suggest anything. It can still enable the user to enter
|
69
|
+
* multiple entries if allowFreeEntries is * set to true (default).
|
70
|
+
* Static Source
|
71
|
+
* You can pass an array of JSON objects, an array of strings or even a single CSV string as the
|
72
|
+
* data source.For ex. data: [* {id:0,name:"Paris"}, {id: 1, name: "New York"}]
|
73
|
+
* You can also pass any json object with the results property containing the json array.
|
74
|
+
* Url
|
75
|
+
* You can pass the url from which the component will fetch its JSON data.Data will be fetched
|
76
|
+
* using a POST ajax request that will * include the entered text as 'query' parameter. The results
|
77
|
+
* fetched from the server can be:
|
78
|
+
* - an array of JSON objects (ex: [{id:...,name:...},{...}])
|
79
|
+
* - a string containing an array of JSON objects ready to be parsed (ex: "[{id:...,name:...},{...}]")
|
80
|
+
* - a JSON object whose data will be contained in the results property
|
81
|
+
* (ex: {results: [{id:...,name:...},{...}]
|
82
|
+
* Function
|
83
|
+
* You can pass a function which returns an array of JSON objects (ex: [{id:...,name:...},{...}])
|
84
|
+
* The function can return the JSON data or it can use the first argument as function to handle the data.
|
85
|
+
* Only one (callback function or return value) is needed for the function to succeed.
|
86
|
+
* See the following example:
|
87
|
+
* function (response) { var myjson = [{name: 'test', id: 1}]; response(myjson); return myjson; }
|
88
|
+
*/
|
89
|
+
data: null,
|
90
|
+
|
91
|
+
/**
|
92
|
+
* Additional parameters to the ajax call
|
93
|
+
*/
|
94
|
+
dataUrlParams: {},
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Start the component in a disabled state.
|
98
|
+
*/
|
99
|
+
disabled: false,
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Name of JSON object property that defines the disabled behaviour
|
103
|
+
*/
|
104
|
+
disabledField: null,
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Name of JSON object property displayed in the combo list
|
108
|
+
*/
|
109
|
+
displayField: 'name',
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Set to false if you only want mouse interaction. In that case the combo will
|
113
|
+
* automatically expand on focus.
|
114
|
+
*/
|
115
|
+
editable: true,
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Set starting state for combo.
|
119
|
+
*/
|
120
|
+
expanded: false,
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Automatically expands combo on focus.
|
124
|
+
*/
|
125
|
+
expandOnFocus: false,
|
126
|
+
|
127
|
+
/**
|
128
|
+
* JSON property by which the list should be grouped
|
129
|
+
*/
|
130
|
+
groupBy: null,
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Set to true to hide the trigger on the right
|
134
|
+
*/
|
135
|
+
hideTrigger: false,
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Set to true to highlight search input within displayed suggestions
|
139
|
+
*/
|
140
|
+
highlight: true,
|
141
|
+
|
142
|
+
/**
|
143
|
+
* A custom ID for this component
|
144
|
+
*/
|
145
|
+
id: null,
|
146
|
+
|
147
|
+
/**
|
148
|
+
* A class that is added to the info message appearing on the top-right part of the component
|
149
|
+
*/
|
150
|
+
infoMsgCls: '',
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Additional parameters passed out to the INPUT tag. Enables usage of AngularJS's custom tags for ex.
|
154
|
+
*/
|
155
|
+
inputCfg: {},
|
156
|
+
|
157
|
+
/**
|
158
|
+
* The class that is applied to show that the field is invalid
|
159
|
+
*/
|
160
|
+
invalidCls: 'ms-inv',
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Set to true to filter data results according to case. Useless if the data is fetched remotely
|
164
|
+
*/
|
165
|
+
matchCase: false,
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Once expanded, the combo's height will take as much room as the # of available results.
|
169
|
+
* In case there are too many results displayed, this will fix the drop down height.
|
170
|
+
*/
|
171
|
+
maxDropHeight: 290,
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Defines how long the user free entry can be. Set to null for no limit.
|
175
|
+
*/
|
176
|
+
maxEntryLength: null,
|
177
|
+
|
178
|
+
/**
|
179
|
+
* A function that defines the helper text when the max entry length has been surpassed.
|
180
|
+
*/
|
181
|
+
maxEntryRenderer: function(v) {
|
182
|
+
return 'Please reduce your entry by ' + v + ' character' + (v > 1 ? 's':'');
|
183
|
+
},
|
184
|
+
|
185
|
+
/**
|
186
|
+
* The maximum number of results displayed in the combo drop down at once.
|
187
|
+
*/
|
188
|
+
maxSuggestions: null,
|
189
|
+
|
190
|
+
/**
|
191
|
+
* The maximum number of items the user can select if multiple selection is allowed.
|
192
|
+
* Set to null to remove the limit.
|
193
|
+
*/
|
194
|
+
maxSelection: 10,
|
195
|
+
|
196
|
+
/**
|
197
|
+
* A function that defines the helper text when the max selection amount has been reached. The function has a single
|
198
|
+
* parameter which is the number of selected elements.
|
199
|
+
*/
|
200
|
+
maxSelectionRenderer: function(v) {
|
201
|
+
return 'You cannot choose more than ' + v + ' item' + (v > 1 ? 's':'');
|
202
|
+
},
|
203
|
+
|
204
|
+
/**
|
205
|
+
* The method used by the ajax request.
|
206
|
+
*/
|
207
|
+
method: 'POST',
|
208
|
+
|
209
|
+
/**
|
210
|
+
* The minimum number of characters the user must type before the combo expands and offers suggestions.
|
211
|
+
*/
|
212
|
+
minChars: 0,
|
213
|
+
|
214
|
+
/**
|
215
|
+
* A function that defines the helper text when not enough letters are set. The function has a single
|
216
|
+
* parameter which is the difference between the required amount of letters and the current one.
|
217
|
+
*/
|
218
|
+
minCharsRenderer: function(v) {
|
219
|
+
return 'Please type ' + v + ' more character' + (v > 1 ? 's':'');
|
220
|
+
},
|
221
|
+
|
222
|
+
/**
|
223
|
+
* Whether or not sorting / filtering should be done remotely or locally.
|
224
|
+
* Use either 'local' or 'remote'
|
225
|
+
*/
|
226
|
+
mode: 'local',
|
227
|
+
|
228
|
+
/**
|
229
|
+
* The name used as a form element.
|
230
|
+
*/
|
231
|
+
name: null,
|
232
|
+
|
233
|
+
/**
|
234
|
+
* The text displayed when there are no suggestions.
|
235
|
+
*/
|
236
|
+
noSuggestionText: 'No suggestions',
|
237
|
+
|
238
|
+
/**
|
239
|
+
* The default placeholder text when nothing has been entered
|
240
|
+
*/
|
241
|
+
placeholder: 'Type or click here',
|
242
|
+
|
243
|
+
/**
|
244
|
+
* A function used to define how the items will be presented in the combo
|
245
|
+
*/
|
246
|
+
renderer: null,
|
247
|
+
|
248
|
+
/**
|
249
|
+
* Whether or not this field should be required
|
250
|
+
*/
|
251
|
+
required: false,
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Set to true to render selection as a delimited string
|
255
|
+
*/
|
256
|
+
resultAsString: false,
|
257
|
+
|
258
|
+
/**
|
259
|
+
* Text delimiter to use in a delimited string.
|
260
|
+
*/
|
261
|
+
resultAsStringDelimiter: ',',
|
262
|
+
|
263
|
+
/**
|
264
|
+
* Name of JSON object property that represents the list of suggested objects
|
265
|
+
*/
|
266
|
+
resultsField: 'results',
|
267
|
+
|
268
|
+
/**
|
269
|
+
* A custom CSS class to add to a selected item
|
270
|
+
*/
|
271
|
+
selectionCls: '',
|
272
|
+
|
273
|
+
/**
|
274
|
+
* An optional element replacement in which the selection is rendered
|
275
|
+
*/
|
276
|
+
selectionContainer: null,
|
277
|
+
|
278
|
+
/**
|
279
|
+
* Where the selected items will be displayed. Only 'right', 'bottom' and 'inner' are valid values
|
280
|
+
*/
|
281
|
+
selectionPosition: 'inner',
|
282
|
+
|
283
|
+
/**
|
284
|
+
* A function used to define how the items will be presented in the tag list
|
285
|
+
*/
|
286
|
+
selectionRenderer: null,
|
287
|
+
|
288
|
+
/**
|
289
|
+
* Set to true to stack the selectioned items when positioned on the bottom
|
290
|
+
* Requires the selectionPosition to be set to 'bottom'
|
291
|
+
*/
|
292
|
+
selectionStacked: false,
|
293
|
+
|
294
|
+
/**
|
295
|
+
* Direction used for sorting. Only 'asc' and 'desc' are valid values
|
296
|
+
*/
|
297
|
+
sortDir: 'asc',
|
298
|
+
|
299
|
+
/**
|
300
|
+
* name of JSON object property for local result sorting.
|
301
|
+
* Leave null if you do not wish the results to be ordered or if they are already ordered remotely.
|
302
|
+
*/
|
303
|
+
sortOrder: null,
|
304
|
+
|
305
|
+
/**
|
306
|
+
* If set to true, suggestions will have to start by user input (and not simply contain it as a substring)
|
307
|
+
*/
|
308
|
+
strictSuggest: false,
|
309
|
+
|
310
|
+
/**
|
311
|
+
* Custom style added to the component container.
|
312
|
+
*/
|
313
|
+
style: '',
|
314
|
+
|
315
|
+
/**
|
316
|
+
* If set to true, the combo will expand / collapse when clicked upon
|
317
|
+
*/
|
318
|
+
toggleOnClick: false,
|
319
|
+
|
320
|
+
|
321
|
+
/**
|
322
|
+
* Amount (in ms) between keyboard registers.
|
323
|
+
*/
|
324
|
+
typeDelay: 400,
|
325
|
+
|
326
|
+
/**
|
327
|
+
* If set to true, tab won't blur the component but will be registered as the ENTER key
|
328
|
+
*/
|
329
|
+
useTabKey: false,
|
330
|
+
|
331
|
+
/**
|
332
|
+
* If set to true, using comma will validate the user's choice
|
333
|
+
*/
|
334
|
+
useCommaKey: true,
|
335
|
+
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Determines whether or not the results will be displayed with a zebra table style
|
339
|
+
*/
|
340
|
+
useZebraStyle: false,
|
341
|
+
|
342
|
+
/**
|
343
|
+
* initial value for the field
|
344
|
+
*/
|
345
|
+
value: null,
|
346
|
+
|
347
|
+
/**
|
348
|
+
* name of JSON object property that represents its underlying value
|
349
|
+
*/
|
350
|
+
valueField: 'id',
|
351
|
+
|
352
|
+
/**
|
353
|
+
* regular expression to validate the values against
|
354
|
+
*/
|
355
|
+
vregex: null,
|
356
|
+
|
357
|
+
/**
|
358
|
+
* type to validate against
|
359
|
+
*/
|
360
|
+
vtype: null
|
361
|
+
};
|
362
|
+
|
363
|
+
var conf = $.extend({},options);
|
364
|
+
var cfg = $.extend(true, {}, defaults, conf);
|
365
|
+
|
366
|
+
/********** PUBLIC METHODS ************/
|
367
|
+
/**
|
368
|
+
* Add one or multiple json items to the current selection
|
369
|
+
* @param items - json object or array of json objects
|
370
|
+
* @param isSilent - (optional) set to true to suppress 'selectionchange' event from being triggered
|
371
|
+
*/
|
372
|
+
this.addToSelection = function(items, isSilent)
|
373
|
+
{
|
374
|
+
if (!cfg.maxSelection || _selection.length < cfg.maxSelection) {
|
375
|
+
if (!$.isArray(items)) {
|
376
|
+
items = [items];
|
377
|
+
}
|
378
|
+
var valuechanged = false;
|
379
|
+
$.each(items, function(index, json) {
|
380
|
+
if (cfg.allowDuplicates || $.inArray(json[cfg.valueField], ms.getValue()) === -1) {
|
381
|
+
_selection.push(json);
|
382
|
+
valuechanged = true;
|
383
|
+
}
|
384
|
+
});
|
385
|
+
if(valuechanged === true) {
|
386
|
+
self._renderSelection();
|
387
|
+
this.empty();
|
388
|
+
if (isSilent !== true) {
|
389
|
+
$(this).trigger('selectionchange', [this, this.getSelection()]);
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
this.input.attr('placeholder', (cfg.selectionPosition === 'inner' && this.getValue().length > 0) ? '' : cfg.placeholder);
|
394
|
+
};
|
395
|
+
|
396
|
+
/**
|
397
|
+
* Clears the current selection
|
398
|
+
* @param isSilent - (optional) set to true to suppress 'selectionchange' event from being triggered
|
399
|
+
*/
|
400
|
+
this.clear = function(isSilent)
|
401
|
+
{
|
402
|
+
this.removeFromSelection(_selection.slice(0), isSilent); // clone array to avoid concurrency issues
|
403
|
+
};
|
404
|
+
|
405
|
+
/**
|
406
|
+
* Collapse the drop down part of the combo
|
407
|
+
*/
|
408
|
+
this.collapse = function()
|
409
|
+
{
|
410
|
+
if (cfg.expanded === true) {
|
411
|
+
this.combobox.detach();
|
412
|
+
cfg.expanded = false;
|
413
|
+
$(this).trigger('collapse', [this]);
|
414
|
+
}
|
415
|
+
};
|
416
|
+
|
417
|
+
/**
|
418
|
+
* Set the component in a disabled state.
|
419
|
+
*/
|
420
|
+
this.disable = function()
|
421
|
+
{
|
422
|
+
this.container.addClass('ms-ctn-disabled');
|
423
|
+
cfg.disabled = true;
|
424
|
+
ms.input.attr('disabled', true);
|
425
|
+
};
|
426
|
+
|
427
|
+
/**
|
428
|
+
* Empties out the combo user text
|
429
|
+
*/
|
430
|
+
this.empty = function(){
|
431
|
+
this.input.val('');
|
432
|
+
};
|
433
|
+
|
434
|
+
/**
|
435
|
+
* Set the component in a enable state.
|
436
|
+
*/
|
437
|
+
this.enable = function()
|
438
|
+
{
|
439
|
+
this.container.removeClass('ms-ctn-disabled');
|
440
|
+
cfg.disabled = false;
|
441
|
+
ms.input.attr('disabled', false);
|
442
|
+
};
|
443
|
+
|
444
|
+
/**
|
445
|
+
* Expand the drop drown part of the combo.
|
446
|
+
*/
|
447
|
+
this.expand = function()
|
448
|
+
{
|
449
|
+
if (!cfg.expanded && (this.input.val().length >= cfg.minChars || this.combobox.children().size() > 0)) {
|
450
|
+
this.combobox.appendTo(this.container);
|
451
|
+
self._processSuggestions();
|
452
|
+
cfg.expanded = true;
|
453
|
+
$(this).trigger('expand', [this]);
|
454
|
+
}
|
455
|
+
};
|
456
|
+
|
457
|
+
/**
|
458
|
+
* Retrieve component enabled status
|
459
|
+
*/
|
460
|
+
this.isDisabled = function()
|
461
|
+
{
|
462
|
+
return cfg.disabled;
|
463
|
+
};
|
464
|
+
|
465
|
+
/**
|
466
|
+
* Checks whether the field is valid or not
|
467
|
+
* @return {boolean}
|
468
|
+
*/
|
469
|
+
this.isValid = function()
|
470
|
+
{
|
471
|
+
var valid = cfg.required === false || _selection.length > 0;
|
472
|
+
if(cfg.vtype || cfg.vregex){
|
473
|
+
$.each(_selection, function(index, item){
|
474
|
+
valid = valid && self._validateSingleItem(item[cfg.valueField]);
|
475
|
+
});
|
476
|
+
}
|
477
|
+
return valid;
|
478
|
+
};
|
479
|
+
|
480
|
+
/**
|
481
|
+
* Gets the data params for current ajax request
|
482
|
+
*/
|
483
|
+
this.getDataUrlParams = function()
|
484
|
+
{
|
485
|
+
return cfg.dataUrlParams;
|
486
|
+
};
|
487
|
+
|
488
|
+
/**
|
489
|
+
* Gets the name given to the form input
|
490
|
+
*/
|
491
|
+
this.getName = function()
|
492
|
+
{
|
493
|
+
return cfg.name;
|
494
|
+
};
|
495
|
+
|
496
|
+
/**
|
497
|
+
* Retrieve an array of selected json objects
|
498
|
+
* @return {Array}
|
499
|
+
*/
|
500
|
+
this.getSelection = function()
|
501
|
+
{
|
502
|
+
return _selection;
|
503
|
+
};
|
504
|
+
|
505
|
+
/**
|
506
|
+
* Retrieve the current text entered by the user
|
507
|
+
*/
|
508
|
+
this.getRawValue = function(){
|
509
|
+
return ms.input.val();
|
510
|
+
};
|
511
|
+
|
512
|
+
/**
|
513
|
+
* Retrieve an array of selected values
|
514
|
+
*/
|
515
|
+
this.getValue = function()
|
516
|
+
{
|
517
|
+
return $.map(_selection, function(o) {
|
518
|
+
return o[cfg.valueField];
|
519
|
+
});
|
520
|
+
};
|
521
|
+
|
522
|
+
/**
|
523
|
+
* Remove one or multiples json items from the current selection
|
524
|
+
* @param items - json object or array of json objects
|
525
|
+
* @param isSilent - (optional) set to true to suppress 'selectionchange' event from being triggered
|
526
|
+
*/
|
527
|
+
this.removeFromSelection = function(items, isSilent)
|
528
|
+
{
|
529
|
+
if (!$.isArray(items)) {
|
530
|
+
items = [items];
|
531
|
+
}
|
532
|
+
var valuechanged = false;
|
533
|
+
$.each(items, function(index, json) {
|
534
|
+
var i = $.inArray(json[cfg.valueField], ms.getValue());
|
535
|
+
if (i > -1) {
|
536
|
+
_selection.splice(i, 1);
|
537
|
+
valuechanged = true;
|
538
|
+
}
|
539
|
+
});
|
540
|
+
if (valuechanged === true) {
|
541
|
+
self._renderSelection();
|
542
|
+
if(isSilent !== true){
|
543
|
+
$(this).trigger('selectionchange', [this, this.getSelection()]);
|
544
|
+
}
|
545
|
+
if(cfg.expandOnFocus){
|
546
|
+
ms.expand();
|
547
|
+
}
|
548
|
+
if(cfg.expanded) {
|
549
|
+
self._processSuggestions();
|
550
|
+
}
|
551
|
+
}
|
552
|
+
this.input.attr('placeholder', (cfg.selectionPosition === 'inner' && this.getValue().length > 0) ? '' : cfg.placeholder);
|
553
|
+
};
|
554
|
+
|
555
|
+
/**
|
556
|
+
* Get current data
|
557
|
+
*/
|
558
|
+
this.getData = function(){
|
559
|
+
return _cbData;
|
560
|
+
};
|
561
|
+
|
562
|
+
/**
|
563
|
+
* Set up some combo data after it has been rendered
|
564
|
+
* @param data
|
565
|
+
*/
|
566
|
+
this.setData = function(data){
|
567
|
+
cfg.data = data;
|
568
|
+
self._processSuggestions();
|
569
|
+
};
|
570
|
+
|
571
|
+
/**
|
572
|
+
* Sets the name for the input field so it can be fetched in the form
|
573
|
+
* @param name
|
574
|
+
*/
|
575
|
+
this.setName = function(name){
|
576
|
+
cfg.name = name;
|
577
|
+
if(name){
|
578
|
+
cfg.name += name.indexOf('[]') > 0 ? '' : '[]';
|
579
|
+
}
|
580
|
+
if(ms._valueContainer){
|
581
|
+
$.each(ms._valueContainer.children(), function(i, el){
|
582
|
+
el.name = cfg.name;
|
583
|
+
});
|
584
|
+
}
|
585
|
+
};
|
586
|
+
|
587
|
+
/**
|
588
|
+
* Sets the current selection with the JSON items provided
|
589
|
+
* @param items
|
590
|
+
*/
|
591
|
+
this.setSelection = function(items){
|
592
|
+
this.clear();
|
593
|
+
this.addToSelection(items);
|
594
|
+
};
|
595
|
+
|
596
|
+
/**
|
597
|
+
* Sets a value for the combo box. Value must be an array of values with data type matching valueField one.
|
598
|
+
* @param data
|
599
|
+
*/
|
600
|
+
this.setValue = function(values)
|
601
|
+
{
|
602
|
+
var items = [];
|
603
|
+
|
604
|
+
$.each(values, function(index, value) {
|
605
|
+
// first try to see if we have the full objects from our data set
|
606
|
+
var found = false;
|
607
|
+
$.each(_cbData, function(i,item){
|
608
|
+
if(item[cfg.valueField] == value){
|
609
|
+
items.push(item);
|
610
|
+
found = true;
|
611
|
+
return false;
|
612
|
+
}
|
613
|
+
});
|
614
|
+
if(!found){
|
615
|
+
if(typeof(value) === 'object'){
|
616
|
+
items.push(value);
|
617
|
+
} else {
|
618
|
+
var json = {};
|
619
|
+
json[cfg.valueField] = value;
|
620
|
+
json[cfg.displayField] = value;
|
621
|
+
items.push(json);
|
622
|
+
}
|
623
|
+
}
|
624
|
+
});
|
625
|
+
if(items.length > 0) {
|
626
|
+
this.addToSelection(items);
|
627
|
+
}
|
628
|
+
};
|
629
|
+
|
630
|
+
/**
|
631
|
+
* Sets data params for subsequent ajax requests
|
632
|
+
* @param params
|
633
|
+
*/
|
634
|
+
this.setDataUrlParams = function(params)
|
635
|
+
{
|
636
|
+
cfg.dataUrlParams = $.extend({},params);
|
637
|
+
};
|
638
|
+
|
639
|
+
/********** PRIVATE ************/
|
640
|
+
var _selection = [], // selected objects
|
641
|
+
_comboItemHeight = 0, // height for each combo item.
|
642
|
+
_timer,
|
643
|
+
_hasFocus = false,
|
644
|
+
_groups = null,
|
645
|
+
_cbData = [],
|
646
|
+
_ctrlDown = false,
|
647
|
+
KEYCODES = {
|
648
|
+
BACKSPACE: 8,
|
649
|
+
TAB: 9,
|
650
|
+
ENTER: 13,
|
651
|
+
CTRL: 17,
|
652
|
+
ESC: 27,
|
653
|
+
SPACE: 32,
|
654
|
+
UPARROW: 38,
|
655
|
+
DOWNARROW: 40,
|
656
|
+
COMMA: 188
|
657
|
+
};
|
658
|
+
|
659
|
+
var self = {
|
660
|
+
|
661
|
+
/**
|
662
|
+
* Empties the result container and refills it with the array of json results in input
|
663
|
+
* @private
|
664
|
+
*/
|
665
|
+
_displaySuggestions: function(data) {
|
666
|
+
ms.combobox.show();
|
667
|
+
ms.combobox.empty();
|
668
|
+
|
669
|
+
var resHeight = 0, // total height taken by displayed results.
|
670
|
+
nbGroups = 0;
|
671
|
+
|
672
|
+
if(_groups === null) {
|
673
|
+
self._renderComboItems(data);
|
674
|
+
resHeight = _comboItemHeight * data.length;
|
675
|
+
}
|
676
|
+
else {
|
677
|
+
for(var grpName in _groups) {
|
678
|
+
nbGroups += 1;
|
679
|
+
$('<div/>', {
|
680
|
+
'class': 'ms-res-group',
|
681
|
+
html: grpName
|
682
|
+
}).appendTo(ms.combobox);
|
683
|
+
self._renderComboItems(_groups[grpName].items, true);
|
684
|
+
}
|
685
|
+
var _groupItemHeight = ms.combobox.find('.ms-res-group').outerHeight();
|
686
|
+
if(_groupItemHeight !== null) {
|
687
|
+
var tmpResHeight = nbGroups * _groupItemHeight;
|
688
|
+
resHeight = (_comboItemHeight * data.length) + tmpResHeight;
|
689
|
+
} else {
|
690
|
+
resHeight = _comboItemHeight * (data.length + nbGroups);
|
691
|
+
}
|
692
|
+
}
|
693
|
+
|
694
|
+
if(resHeight < ms.combobox.height() || resHeight <= cfg.maxDropHeight) {
|
695
|
+
ms.combobox.height(resHeight);
|
696
|
+
}
|
697
|
+
else if(resHeight >= ms.combobox.height() && resHeight > cfg.maxDropHeight) {
|
698
|
+
ms.combobox.height(cfg.maxDropHeight);
|
699
|
+
}
|
700
|
+
|
701
|
+
if(data.length === 1 && cfg.autoSelect === true) {
|
702
|
+
ms.combobox.children().filter(':not(.ms-res-item-disabled):last').addClass('ms-res-item-active');
|
703
|
+
}
|
704
|
+
|
705
|
+
if (cfg.selectFirst === true) {
|
706
|
+
ms.combobox.children().filter(':not(.ms-res-item-disabled):first').addClass('ms-res-item-active');
|
707
|
+
}
|
708
|
+
|
709
|
+
if(data.length === 0 && ms.getRawValue() !== "") {
|
710
|
+
var noSuggestionText = cfg.noSuggestionText.replace(/\{\{.*\}\}/, ms.input.val());
|
711
|
+
self._updateHelper(noSuggestionText);
|
712
|
+
ms.collapse();
|
713
|
+
}
|
714
|
+
|
715
|
+
// When free entry is off, add invalid class to input if no data matches
|
716
|
+
if(cfg.allowFreeEntries === false) {
|
717
|
+
if(data.length === 0) {
|
718
|
+
$(ms.input).addClass(cfg.invalidCls);
|
719
|
+
ms.combobox.hide();
|
720
|
+
} else {
|
721
|
+
$(ms.input).removeClass(cfg.invalidCls);
|
722
|
+
}
|
723
|
+
}
|
724
|
+
},
|
725
|
+
|
726
|
+
/**
|
727
|
+
* Returns an array of json objects from an array of strings.
|
728
|
+
* @private
|
729
|
+
*/
|
730
|
+
_getEntriesFromStringArray: function(data) {
|
731
|
+
var json = [];
|
732
|
+
$.each(data, function(index, s) {
|
733
|
+
var entry = {};
|
734
|
+
entry[cfg.displayField] = entry[cfg.valueField] = $.trim(s);
|
735
|
+
json.push(entry);
|
736
|
+
});
|
737
|
+
return json;
|
738
|
+
},
|
739
|
+
|
740
|
+
/**
|
741
|
+
* Replaces html with highlighted html according to case
|
742
|
+
* @param html
|
743
|
+
* @private
|
744
|
+
*/
|
745
|
+
_highlightSuggestion: function(html) {
|
746
|
+
var q = ms.input.val();
|
747
|
+
|
748
|
+
//escape special regex characters
|
749
|
+
var specialCharacters = ['^', '$', '*', '+', '?', '.', '(', ')', ':', '!', '|', '{', '}', '[', ']'];
|
750
|
+
|
751
|
+
$.each(specialCharacters, function (index, value) {
|
752
|
+
q = q.replace(value, "\\" + value);
|
753
|
+
})
|
754
|
+
|
755
|
+
if(q.length === 0) {
|
756
|
+
return html; // nothing entered as input
|
757
|
+
}
|
758
|
+
|
759
|
+
var glob = cfg.matchCase === true ? 'g' : 'gi';
|
760
|
+
return html.replace(new RegExp('(' + q + ')(?!([^<]+)?>)', glob), '<em>$1</em>');
|
761
|
+
},
|
762
|
+
|
763
|
+
/**
|
764
|
+
* Moves the selected cursor amongst the list item
|
765
|
+
* @param dir - 'up' or 'down'
|
766
|
+
* @private
|
767
|
+
*/
|
768
|
+
_moveSelectedRow: function(dir) {
|
769
|
+
if(!cfg.expanded) {
|
770
|
+
ms.expand();
|
771
|
+
}
|
772
|
+
var list, start, active, scrollPos;
|
773
|
+
list = ms.combobox.find(".ms-res-item:not(.ms-res-item-disabled)");
|
774
|
+
if(dir === 'down') {
|
775
|
+
start = list.eq(0);
|
776
|
+
}
|
777
|
+
else {
|
778
|
+
start = list.filter(':last');
|
779
|
+
}
|
780
|
+
active = ms.combobox.find('.ms-res-item-active:not(.ms-res-item-disabled):first');
|
781
|
+
if(active.length > 0) {
|
782
|
+
if(dir === 'down') {
|
783
|
+
start = active.nextAll('.ms-res-item:not(.ms-res-item-disabled)').first();
|
784
|
+
if(start.length === 0) {
|
785
|
+
start = list.eq(0);
|
786
|
+
}
|
787
|
+
scrollPos = ms.combobox.scrollTop();
|
788
|
+
ms.combobox.scrollTop(0);
|
789
|
+
if(start[0].offsetTop + start.outerHeight() > ms.combobox.height()) {
|
790
|
+
ms.combobox.scrollTop(scrollPos + _comboItemHeight);
|
791
|
+
}
|
792
|
+
}
|
793
|
+
else {
|
794
|
+
start = active.prevAll('.ms-res-item:not(.ms-res-item-disabled)').first();
|
795
|
+
if(start.length === 0) {
|
796
|
+
start = list.filter(':last');
|
797
|
+
ms.combobox.scrollTop(_comboItemHeight * list.length);
|
798
|
+
}
|
799
|
+
if(start[0].offsetTop < ms.combobox.scrollTop()) {
|
800
|
+
ms.combobox.scrollTop(ms.combobox.scrollTop() - _comboItemHeight);
|
801
|
+
}
|
802
|
+
}
|
803
|
+
}
|
804
|
+
list.removeClass("ms-res-item-active");
|
805
|
+
start.addClass("ms-res-item-active");
|
806
|
+
},
|
807
|
+
|
808
|
+
/**
|
809
|
+
* According to given data and query, sort and add suggestions in their container
|
810
|
+
* @private
|
811
|
+
*/
|
812
|
+
_processSuggestions: function(source) {
|
813
|
+
var json = null, data = source || cfg.data;
|
814
|
+
if(data !== null) {
|
815
|
+
if(typeof(data) === 'function'){
|
816
|
+
data = data.call(ms, ms.getRawValue());
|
817
|
+
}
|
818
|
+
if(typeof(data) === 'string') { // get results from ajax
|
819
|
+
$(ms).trigger('beforeload', [ms]);
|
820
|
+
var queryParams = {}
|
821
|
+
queryParams[cfg.queryParam] = ms.input.val();
|
822
|
+
var params = $.extend(queryParams, cfg.dataUrlParams);
|
823
|
+
$.ajax($.extend({
|
824
|
+
type: cfg.method,
|
825
|
+
url: data,
|
826
|
+
data: params,
|
827
|
+
beforeSend: cfg.beforeSend,
|
828
|
+
success: function(asyncData){
|
829
|
+
json = typeof(asyncData) === 'string' ? JSON.parse(asyncData) : asyncData;
|
830
|
+
self._processSuggestions(json);
|
831
|
+
$(ms).trigger('load', [ms, json]);
|
832
|
+
if(self._asyncValues){
|
833
|
+
ms.setValue(typeof(self._asyncValues) === 'string' ? JSON.parse(self._asyncValues) : self._asyncValues);
|
834
|
+
self._renderSelection();
|
835
|
+
delete(self._asyncValues);
|
836
|
+
}
|
837
|
+
},
|
838
|
+
error: function(){
|
839
|
+
throw("Could not reach server");
|
840
|
+
}
|
841
|
+
}, cfg.ajaxConfig));
|
842
|
+
return;
|
843
|
+
} else { // results from local array
|
844
|
+
if(data.length > 0 && typeof(data[0]) === 'string') { // results from array of strings
|
845
|
+
_cbData = self._getEntriesFromStringArray(data);
|
846
|
+
} else { // regular json array or json object with results property
|
847
|
+
_cbData = data[cfg.resultsField] || data;
|
848
|
+
}
|
849
|
+
}
|
850
|
+
var sortedData = cfg.mode === 'remote' ? _cbData : self._sortAndTrim(_cbData);
|
851
|
+
self._displaySuggestions(self._group(sortedData));
|
852
|
+
|
853
|
+
}
|
854
|
+
},
|
855
|
+
|
856
|
+
/**
|
857
|
+
* Render the component to the given input DOM element
|
858
|
+
* @private
|
859
|
+
*/
|
860
|
+
_render: function(el) {
|
861
|
+
ms.setName(cfg.name); // make sure the form name is correct
|
862
|
+
// holds the main div, will relay the focus events to the contained input element.
|
863
|
+
ms.container = $('<div/>', {
|
864
|
+
'class': 'ms-ctn form-control ' + (cfg.resultAsString ? 'ms-as-string ' : '') + cfg.cls +
|
865
|
+
($(el).hasClass('input-lg') ? ' input-lg' : '') +
|
866
|
+
($(el).hasClass('input-sm') ? ' input-sm' : '') +
|
867
|
+
(cfg.disabled === true ? ' ms-ctn-disabled' : '') +
|
868
|
+
(cfg.editable === true ? '' : ' ms-ctn-readonly') +
|
869
|
+
(cfg.hideTrigger === false ? '' : ' ms-no-trigger'),
|
870
|
+
style: cfg.style,
|
871
|
+
id: cfg.id
|
872
|
+
});
|
873
|
+
ms.container.focus($.proxy(handlers._onFocus, this));
|
874
|
+
ms.container.blur($.proxy(handlers._onBlur, this));
|
875
|
+
ms.container.keydown($.proxy(handlers._onKeyDown, this));
|
876
|
+
ms.container.keyup($.proxy(handlers._onKeyUp, this));
|
877
|
+
|
878
|
+
// holds the input field
|
879
|
+
ms.input = $('<input/>', $.extend({
|
880
|
+
type: 'text',
|
881
|
+
'class': cfg.editable === true ? '' : ' ms-input-readonly',
|
882
|
+
readonly: !cfg.editable,
|
883
|
+
placeholder: cfg.placeholder,
|
884
|
+
disabled: cfg.disabled
|
885
|
+
}, cfg.inputCfg));
|
886
|
+
|
887
|
+
ms.input.focus($.proxy(handlers._onInputFocus, this));
|
888
|
+
ms.input.click($.proxy(handlers._onInputClick, this));
|
889
|
+
|
890
|
+
// holds the suggestions. will always be placed on focus
|
891
|
+
ms.combobox = $('<div/>', {
|
892
|
+
'class': 'ms-res-ctn dropdown-menu'
|
893
|
+
}).height(cfg.maxDropHeight);
|
894
|
+
|
895
|
+
// bind the onclick and mouseover using delegated events (needs jQuery >= 1.7)
|
896
|
+
ms.combobox.on('click', 'div.ms-res-item', $.proxy(handlers._onComboItemSelected, this));
|
897
|
+
ms.combobox.on('mouseover', 'div.ms-res-item', $.proxy(handlers._onComboItemMouseOver, this));
|
898
|
+
|
899
|
+
if(cfg.selectionContainer){
|
900
|
+
ms.selectionContainer = cfg.selectionContainer;
|
901
|
+
$(ms.selectionContainer).addClass('ms-sel-ctn');
|
902
|
+
} else {
|
903
|
+
ms.selectionContainer = $('<div/>', {
|
904
|
+
'class': 'ms-sel-ctn'
|
905
|
+
});
|
906
|
+
}
|
907
|
+
ms.selectionContainer.click($.proxy(handlers._onFocus, this));
|
908
|
+
|
909
|
+
if(cfg.selectionPosition === 'inner' && !cfg.selectionContainer) {
|
910
|
+
ms.selectionContainer.append(ms.input);
|
911
|
+
}
|
912
|
+
else {
|
913
|
+
ms.container.append(ms.input);
|
914
|
+
}
|
915
|
+
|
916
|
+
ms.helper = $('<span/>', {
|
917
|
+
'class': 'ms-helper ' + cfg.infoMsgCls
|
918
|
+
});
|
919
|
+
self._updateHelper();
|
920
|
+
ms.container.append(ms.helper);
|
921
|
+
|
922
|
+
|
923
|
+
// Render the whole thing
|
924
|
+
$(el).replaceWith(ms.container);
|
925
|
+
|
926
|
+
if(!cfg.selectionContainer){
|
927
|
+
switch(cfg.selectionPosition) {
|
928
|
+
case 'bottom':
|
929
|
+
ms.selectionContainer.insertAfter(ms.container);
|
930
|
+
if(cfg.selectionStacked === true) {
|
931
|
+
ms.selectionContainer.width(ms.container.width());
|
932
|
+
ms.selectionContainer.addClass('ms-stacked');
|
933
|
+
}
|
934
|
+
break;
|
935
|
+
case 'right':
|
936
|
+
ms.selectionContainer.insertAfter(ms.container);
|
937
|
+
ms.container.css('float', 'left');
|
938
|
+
break;
|
939
|
+
default:
|
940
|
+
ms.container.append(ms.selectionContainer);
|
941
|
+
break;
|
942
|
+
}
|
943
|
+
}
|
944
|
+
|
945
|
+
|
946
|
+
// holds the trigger on the right side
|
947
|
+
if(cfg.hideTrigger === false) {
|
948
|
+
ms.trigger = $('<div/>', {
|
949
|
+
'class': 'ms-trigger',
|
950
|
+
html: '<div class="ms-trigger-ico"></div>'
|
951
|
+
});
|
952
|
+
ms.trigger.click($.proxy(handlers._onTriggerClick, this));
|
953
|
+
ms.container.append(ms.trigger);
|
954
|
+
}
|
955
|
+
|
956
|
+
$(window).resize($.proxy(handlers._onWindowResized, this));
|
957
|
+
|
958
|
+
// do not perform an initial call if we are using ajax unless we have initial values
|
959
|
+
if(cfg.value !== null || cfg.data !== null){
|
960
|
+
if(typeof(cfg.data) === 'string'){
|
961
|
+
self._asyncValues = cfg.value;
|
962
|
+
self._processSuggestions();
|
963
|
+
} else {
|
964
|
+
self._processSuggestions();
|
965
|
+
if(cfg.value !== null){
|
966
|
+
ms.setValue(cfg.value);
|
967
|
+
self._renderSelection();
|
968
|
+
}
|
969
|
+
}
|
970
|
+
|
971
|
+
}
|
972
|
+
|
973
|
+
$("body").click(function(e) {
|
974
|
+
if(ms.container.hasClass('ms-ctn-focus') &&
|
975
|
+
ms.container.has(e.target).length === 0 &&
|
976
|
+
e.target.className.indexOf('ms-res-item') < 0 &&
|
977
|
+
e.target.className.indexOf('ms-close-btn') < 0 &&
|
978
|
+
ms.container[0] !== e.target) {
|
979
|
+
handlers._onBlur();
|
980
|
+
}
|
981
|
+
});
|
982
|
+
|
983
|
+
if(cfg.expanded === true) {
|
984
|
+
cfg.expanded = false;
|
985
|
+
ms.expand();
|
986
|
+
}
|
987
|
+
},
|
988
|
+
|
989
|
+
/**
|
990
|
+
* Renders each element within the combo box
|
991
|
+
* @private
|
992
|
+
*/
|
993
|
+
_renderComboItems: function(items, isGrouped) {
|
994
|
+
var ref = this, html = '';
|
995
|
+
$.each(items, function(index, value) {
|
996
|
+
var displayed = cfg.renderer !== null ? cfg.renderer.call(ref, value) : value[cfg.displayField];
|
997
|
+
var disabled = cfg.disabledField !== null && value[cfg.disabledField] === true;
|
998
|
+
var resultItemEl = $('<div/>', {
|
999
|
+
'class': 'ms-res-item ' + (isGrouped ? 'ms-res-item-grouped ':'') +
|
1000
|
+
(disabled ? 'ms-res-item-disabled ':'') +
|
1001
|
+
(index % 2 === 1 && cfg.useZebraStyle === true ? 'ms-res-odd' : ''),
|
1002
|
+
html: cfg.highlight === true ? self._highlightSuggestion(displayed) : displayed,
|
1003
|
+
'data-json': JSON.stringify(value)
|
1004
|
+
});
|
1005
|
+
html += $('<div/>').append(resultItemEl).html();
|
1006
|
+
});
|
1007
|
+
ms.combobox.append(html);
|
1008
|
+
_comboItemHeight = ms.combobox.find('.ms-res-item:first').outerHeight();
|
1009
|
+
},
|
1010
|
+
|
1011
|
+
/**
|
1012
|
+
* Renders the selected items into their container.
|
1013
|
+
* @private
|
1014
|
+
*/
|
1015
|
+
_renderSelection: function() {
|
1016
|
+
var ref = this, w = 0, inputOffset = 0, items = [],
|
1017
|
+
asText = cfg.resultAsString === true && !_hasFocus;
|
1018
|
+
|
1019
|
+
ms.selectionContainer.find('.ms-sel-item').remove();
|
1020
|
+
if(ms._valueContainer !== undefined) {
|
1021
|
+
ms._valueContainer.remove();
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
$.each(_selection, function(index, value){
|
1025
|
+
|
1026
|
+
var selectedItemEl, delItemEl,
|
1027
|
+
selectedItemHtml = cfg.selectionRenderer !== null ? cfg.selectionRenderer.call(ref, value) : value[cfg.displayField];
|
1028
|
+
|
1029
|
+
var validCls = self._validateSingleItem(value[cfg.displayField]) ? '' : ' ms-sel-invalid';
|
1030
|
+
|
1031
|
+
// tag representing selected value
|
1032
|
+
if(asText === true) {
|
1033
|
+
selectedItemEl = $('<div/>', {
|
1034
|
+
'class': 'ms-sel-item ms-sel-text ' + cfg.selectionCls + validCls,
|
1035
|
+
html: selectedItemHtml + (index === (_selection.length - 1) ? '' : cfg.resultAsStringDelimiter)
|
1036
|
+
}).data('json', value);
|
1037
|
+
}
|
1038
|
+
else {
|
1039
|
+
selectedItemEl = $('<div/>', {
|
1040
|
+
'class': 'ms-sel-item ' + cfg.selectionCls + validCls,
|
1041
|
+
html: selectedItemHtml
|
1042
|
+
}).data('json', value);
|
1043
|
+
|
1044
|
+
if(cfg.disabled === false){
|
1045
|
+
// small cross img
|
1046
|
+
delItemEl = $('<span/>', {
|
1047
|
+
'class': 'ms-close-btn'
|
1048
|
+
}).data('json', value).appendTo(selectedItemEl);
|
1049
|
+
|
1050
|
+
delItemEl.click($.proxy(handlers._onTagTriggerClick, ref));
|
1051
|
+
}
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
items.push(selectedItemEl);
|
1055
|
+
});
|
1056
|
+
ms.selectionContainer.prepend(items);
|
1057
|
+
|
1058
|
+
// store the values, behaviour of multiple select
|
1059
|
+
ms._valueContainer = $('<div/>', {
|
1060
|
+
style: 'display: none;'
|
1061
|
+
});
|
1062
|
+
$.each(ms.getValue(), function(i, val){
|
1063
|
+
var el = $('<input/>', {
|
1064
|
+
type: 'hidden',
|
1065
|
+
name: cfg.name,
|
1066
|
+
value: val
|
1067
|
+
});
|
1068
|
+
el.appendTo(ms._valueContainer);
|
1069
|
+
});
|
1070
|
+
ms._valueContainer.appendTo(ms.selectionContainer);
|
1071
|
+
|
1072
|
+
if(cfg.selectionPosition === 'inner' && !cfg.selectionContainer) {
|
1073
|
+
ms.input.width(0);
|
1074
|
+
inputOffset = ms.input.offset().left - ms.selectionContainer.offset().left;
|
1075
|
+
w = ms.container.width() - inputOffset - 42;
|
1076
|
+
ms.input.width(w);
|
1077
|
+
}
|
1078
|
+
|
1079
|
+
if(_selection.length === cfg.maxSelection){
|
1080
|
+
self._updateHelper(cfg.maxSelectionRenderer.call(this, _selection.length));
|
1081
|
+
} else {
|
1082
|
+
ms.helper.hide();
|
1083
|
+
}
|
1084
|
+
},
|
1085
|
+
|
1086
|
+
/**
|
1087
|
+
* Select an item either through keyboard or mouse
|
1088
|
+
* @param item
|
1089
|
+
* @private
|
1090
|
+
*/
|
1091
|
+
_selectItem: function(item) {
|
1092
|
+
if(cfg.maxSelection === 1){
|
1093
|
+
_selection = [];
|
1094
|
+
}
|
1095
|
+
ms.addToSelection(item.data('json'));
|
1096
|
+
item.removeClass('ms-res-item-active');
|
1097
|
+
if(cfg.expandOnFocus === false || _selection.length === cfg.maxSelection){
|
1098
|
+
ms.collapse();
|
1099
|
+
}
|
1100
|
+
if(!_hasFocus){
|
1101
|
+
ms.input.focus();
|
1102
|
+
} else if(_hasFocus && (cfg.expandOnFocus || _ctrlDown)){
|
1103
|
+
self._processSuggestions();
|
1104
|
+
if(_ctrlDown){
|
1105
|
+
ms.expand();
|
1106
|
+
}
|
1107
|
+
}
|
1108
|
+
},
|
1109
|
+
|
1110
|
+
/**
|
1111
|
+
* Sorts the results and cut them down to max # of displayed results at once
|
1112
|
+
* @private
|
1113
|
+
*/
|
1114
|
+
_sortAndTrim: function(data) {
|
1115
|
+
var q = ms.getRawValue(),
|
1116
|
+
filtered = [],
|
1117
|
+
newSuggestions = [],
|
1118
|
+
selectedValues = ms.getValue();
|
1119
|
+
// filter the data according to given input
|
1120
|
+
if(q.length > 0) {
|
1121
|
+
$.each(data, function(index, obj) {
|
1122
|
+
var name = obj[cfg.displayField];
|
1123
|
+
if((cfg.matchCase === true && name.indexOf(q) > -1) ||
|
1124
|
+
(cfg.matchCase === false && name.toLowerCase().indexOf(q.toLowerCase()) > -1)) {
|
1125
|
+
if(cfg.strictSuggest === false || name.toLowerCase().indexOf(q.toLowerCase()) === 0) {
|
1126
|
+
filtered.push(obj);
|
1127
|
+
}
|
1128
|
+
}
|
1129
|
+
});
|
1130
|
+
}
|
1131
|
+
else {
|
1132
|
+
filtered = data;
|
1133
|
+
}
|
1134
|
+
// take out the ones that have already been selected
|
1135
|
+
$.each(filtered, function(index, obj) {
|
1136
|
+
if (cfg.allowDuplicates || $.inArray(obj[cfg.valueField], selectedValues) === -1) {
|
1137
|
+
newSuggestions.push(obj);
|
1138
|
+
}
|
1139
|
+
});
|
1140
|
+
// sort the data
|
1141
|
+
if(cfg.sortOrder !== null) {
|
1142
|
+
newSuggestions.sort(function(a,b) {
|
1143
|
+
if(a[cfg.sortOrder] < b[cfg.sortOrder]) {
|
1144
|
+
return cfg.sortDir === 'asc' ? -1 : 1;
|
1145
|
+
}
|
1146
|
+
if(a[cfg.sortOrder] > b[cfg.sortOrder]) {
|
1147
|
+
return cfg.sortDir === 'asc' ? 1 : -1;
|
1148
|
+
}
|
1149
|
+
return 0;
|
1150
|
+
});
|
1151
|
+
}
|
1152
|
+
// trim it down
|
1153
|
+
if(cfg.maxSuggestions && cfg.maxSuggestions > 0) {
|
1154
|
+
newSuggestions = newSuggestions.slice(0, cfg.maxSuggestions);
|
1155
|
+
}
|
1156
|
+
return newSuggestions;
|
1157
|
+
|
1158
|
+
},
|
1159
|
+
|
1160
|
+
_group: function(data){
|
1161
|
+
// build groups
|
1162
|
+
if(cfg.groupBy !== null) {
|
1163
|
+
_groups = {};
|
1164
|
+
|
1165
|
+
$.each(data, function(index, value) {
|
1166
|
+
var props = cfg.groupBy.indexOf('.') > -1 ? cfg.groupBy.split('.') : cfg.groupBy;
|
1167
|
+
var prop = value[cfg.groupBy];
|
1168
|
+
if(typeof(props) != 'string'){
|
1169
|
+
prop = value;
|
1170
|
+
while(props.length > 0){
|
1171
|
+
prop = prop[props.shift()];
|
1172
|
+
}
|
1173
|
+
}
|
1174
|
+
if(_groups[prop] === undefined) {
|
1175
|
+
_groups[prop] = {title: prop, items: [value]};
|
1176
|
+
}
|
1177
|
+
else {
|
1178
|
+
_groups[prop].items.push(value);
|
1179
|
+
}
|
1180
|
+
});
|
1181
|
+
}
|
1182
|
+
return data;
|
1183
|
+
},
|
1184
|
+
|
1185
|
+
/**
|
1186
|
+
* Update the helper text
|
1187
|
+
* @private
|
1188
|
+
*/
|
1189
|
+
_updateHelper: function(html) {
|
1190
|
+
ms.helper.html(html);
|
1191
|
+
if(!ms.helper.is(":visible")) {
|
1192
|
+
ms.helper.fadeIn();
|
1193
|
+
}
|
1194
|
+
},
|
1195
|
+
|
1196
|
+
/**
|
1197
|
+
* Validate an item against vtype or vregex
|
1198
|
+
* @private
|
1199
|
+
*/
|
1200
|
+
_validateSingleItem: function(value){
|
1201
|
+
if(cfg.vregex !== null && cfg.vregex instanceof RegExp){
|
1202
|
+
return cfg.vregex.test(value);
|
1203
|
+
} else if(cfg.vtype !== null) {
|
1204
|
+
switch(cfg.vtype){
|
1205
|
+
case 'alpha':
|
1206
|
+
return (/^[a-zA-Z_]+$/).test(value);
|
1207
|
+
case 'alphanum':
|
1208
|
+
return (/^[a-zA-Z0-9_]+$/).test(value);
|
1209
|
+
case 'email':
|
1210
|
+
return (/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/).test(value);
|
1211
|
+
case 'url':
|
1212
|
+
return (/(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i).test(value);
|
1213
|
+
case 'ipaddress':
|
1214
|
+
return (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/).test(value);
|
1215
|
+
}
|
1216
|
+
}
|
1217
|
+
return true;
|
1218
|
+
}
|
1219
|
+
};
|
1220
|
+
|
1221
|
+
var handlers = {
|
1222
|
+
/**
|
1223
|
+
* Triggered when blurring out of the component
|
1224
|
+
* @private
|
1225
|
+
*/
|
1226
|
+
_onBlur: function() {
|
1227
|
+
ms.container.removeClass('ms-ctn-focus');
|
1228
|
+
ms.collapse();
|
1229
|
+
_hasFocus = false;
|
1230
|
+
if(ms.getRawValue() !== '' && cfg.allowFreeEntries === true){
|
1231
|
+
var obj = {};
|
1232
|
+
obj[cfg.displayField] = obj[cfg.valueField] = ms.getRawValue().trim();
|
1233
|
+
ms.addToSelection(obj);
|
1234
|
+
}
|
1235
|
+
self._renderSelection();
|
1236
|
+
|
1237
|
+
if(ms.isValid() === false) {
|
1238
|
+
ms.container.addClass(cfg.invalidCls);
|
1239
|
+
}
|
1240
|
+
|
1241
|
+
else if(ms.input.val() !== '' && cfg.allowFreeEntries === false) {
|
1242
|
+
ms.empty();
|
1243
|
+
self._updateHelper('');
|
1244
|
+
}
|
1245
|
+
|
1246
|
+
$(ms).trigger('blur', [ms]);
|
1247
|
+
},
|
1248
|
+
|
1249
|
+
/**
|
1250
|
+
* Triggered when hovering an element in the combo
|
1251
|
+
* @param e
|
1252
|
+
* @private
|
1253
|
+
*/
|
1254
|
+
_onComboItemMouseOver: function(e) {
|
1255
|
+
var target = $(e.currentTarget);
|
1256
|
+
if(!target.hasClass('ms-res-item-disabled')){
|
1257
|
+
ms.combobox.children().removeClass('ms-res-item-active');
|
1258
|
+
target.addClass('ms-res-item-active');
|
1259
|
+
}
|
1260
|
+
},
|
1261
|
+
|
1262
|
+
/**
|
1263
|
+
* Triggered when an item is chosen from the list
|
1264
|
+
* @param e
|
1265
|
+
* @private
|
1266
|
+
*/
|
1267
|
+
_onComboItemSelected: function(e) {
|
1268
|
+
var target = $(e.currentTarget);
|
1269
|
+
if(!target.hasClass('ms-res-item-disabled')){
|
1270
|
+
self._selectItem($(e.currentTarget));
|
1271
|
+
}
|
1272
|
+
},
|
1273
|
+
|
1274
|
+
/**
|
1275
|
+
* Triggered when focusing on the container div. Will focus on the input field instead.
|
1276
|
+
* @private
|
1277
|
+
*/
|
1278
|
+
_onFocus: function() {
|
1279
|
+
ms.input.focus();
|
1280
|
+
},
|
1281
|
+
|
1282
|
+
/**
|
1283
|
+
* Triggered when clicking on the input text field
|
1284
|
+
* @private
|
1285
|
+
*/
|
1286
|
+
_onInputClick: function(){
|
1287
|
+
if (ms.isDisabled() === false && _hasFocus) {
|
1288
|
+
if (cfg.toggleOnClick === true) {
|
1289
|
+
if (cfg.expanded){
|
1290
|
+
ms.collapse();
|
1291
|
+
} else {
|
1292
|
+
ms.expand();
|
1293
|
+
}
|
1294
|
+
}
|
1295
|
+
}
|
1296
|
+
},
|
1297
|
+
|
1298
|
+
/**
|
1299
|
+
* Triggered when focusing on the input text field.
|
1300
|
+
* @private
|
1301
|
+
*/
|
1302
|
+
_onInputFocus: function() {
|
1303
|
+
if(ms.isDisabled() === false && !_hasFocus) {
|
1304
|
+
_hasFocus = true;
|
1305
|
+
ms.container.addClass('ms-ctn-focus');
|
1306
|
+
ms.container.removeClass(cfg.invalidCls);
|
1307
|
+
|
1308
|
+
var curLength = ms.getRawValue().length;
|
1309
|
+
if(cfg.expandOnFocus === true){
|
1310
|
+
ms.expand();
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
if(_selection.length === cfg.maxSelection) {
|
1314
|
+
self._updateHelper(cfg.maxSelectionRenderer.call(this, _selection.length));
|
1315
|
+
} else if(curLength < cfg.minChars) {
|
1316
|
+
self._updateHelper(cfg.minCharsRenderer.call(this, cfg.minChars - curLength));
|
1317
|
+
}
|
1318
|
+
|
1319
|
+
self._renderSelection();
|
1320
|
+
$(ms).trigger('focus', [ms]);
|
1321
|
+
}
|
1322
|
+
},
|
1323
|
+
|
1324
|
+
/**
|
1325
|
+
* Triggered when the user presses a key while the component has focus
|
1326
|
+
* This is where we want to handle all keys that don't require the user input field
|
1327
|
+
* since it hasn't registered the key hit yet
|
1328
|
+
* @param e keyEvent
|
1329
|
+
* @private
|
1330
|
+
*/
|
1331
|
+
_onKeyDown: function(e) {
|
1332
|
+
// check how tab should be handled
|
1333
|
+
var active = ms.combobox.find('.ms-res-item-active:not(.ms-res-item-disabled):first'),
|
1334
|
+
freeInput = ms.input.val();
|
1335
|
+
$(ms).trigger('keydown', [ms, e]);
|
1336
|
+
|
1337
|
+
if(e.keyCode === KEYCODES.TAB && (cfg.useTabKey === false ||
|
1338
|
+
(cfg.useTabKey === true && active.length === 0 && ms.input.val().length === 0))) {
|
1339
|
+
handlers._onBlur();
|
1340
|
+
return;
|
1341
|
+
}
|
1342
|
+
switch(e.keyCode) {
|
1343
|
+
case KEYCODES.BACKSPACE:
|
1344
|
+
if(freeInput.length === 0 && ms.getSelection().length > 0 && cfg.selectionPosition === 'inner') {
|
1345
|
+
_selection.pop();
|
1346
|
+
self._renderSelection();
|
1347
|
+
$(ms).trigger('selectionchange', [ms, ms.getSelection()]);
|
1348
|
+
ms.input.attr('placeholder', (cfg.selectionPosition === 'inner' && ms.getValue().length > 0) ? '' : cfg.placeholder);
|
1349
|
+
ms.input.focus();
|
1350
|
+
e.preventDefault();
|
1351
|
+
}
|
1352
|
+
break;
|
1353
|
+
case KEYCODES.TAB:
|
1354
|
+
case KEYCODES.ESC:
|
1355
|
+
e.preventDefault();
|
1356
|
+
break;
|
1357
|
+
case KEYCODES.ENTER:
|
1358
|
+
if(freeInput !== '' || cfg.expanded){
|
1359
|
+
e.preventDefault();
|
1360
|
+
}
|
1361
|
+
break;
|
1362
|
+
case KEYCODES.COMMA:
|
1363
|
+
if(cfg.useCommaKey === true){
|
1364
|
+
e.preventDefault();
|
1365
|
+
}
|
1366
|
+
break;
|
1367
|
+
case KEYCODES.CTRL:
|
1368
|
+
_ctrlDown = true;
|
1369
|
+
break;
|
1370
|
+
case KEYCODES.DOWNARROW:
|
1371
|
+
e.preventDefault();
|
1372
|
+
self._moveSelectedRow("down");
|
1373
|
+
break;
|
1374
|
+
case KEYCODES.UPARROW:
|
1375
|
+
e.preventDefault();
|
1376
|
+
self._moveSelectedRow("up");
|
1377
|
+
break;
|
1378
|
+
default:
|
1379
|
+
if(_selection.length === cfg.maxSelection) {
|
1380
|
+
e.preventDefault();
|
1381
|
+
}
|
1382
|
+
break;
|
1383
|
+
}
|
1384
|
+
},
|
1385
|
+
|
1386
|
+
/**
|
1387
|
+
* Triggered when a key is released while the component has focus
|
1388
|
+
* @param e
|
1389
|
+
* @private
|
1390
|
+
*/
|
1391
|
+
_onKeyUp: function(e) {
|
1392
|
+
var freeInput = ms.getRawValue(),
|
1393
|
+
inputValid = $.trim(ms.input.val()).length > 0 &&
|
1394
|
+
(!cfg.maxEntryLength || $.trim(ms.input.val()).length <= cfg.maxEntryLength),
|
1395
|
+
selected,
|
1396
|
+
obj = {};
|
1397
|
+
|
1398
|
+
$(ms).trigger('keyup', [ms, e]);
|
1399
|
+
|
1400
|
+
clearTimeout(_timer);
|
1401
|
+
|
1402
|
+
// collapse if escape, but keep focus.
|
1403
|
+
if(e.keyCode === KEYCODES.ESC && cfg.expanded) {
|
1404
|
+
ms.combobox.hide();
|
1405
|
+
}
|
1406
|
+
// ignore a bunch of keys
|
1407
|
+
if((e.keyCode === KEYCODES.TAB && cfg.useTabKey === false) || (e.keyCode > KEYCODES.ENTER && e.keyCode < KEYCODES.SPACE)) {
|
1408
|
+
if(e.keyCode === KEYCODES.CTRL){
|
1409
|
+
_ctrlDown = false;
|
1410
|
+
}
|
1411
|
+
return;
|
1412
|
+
}
|
1413
|
+
switch(e.keyCode) {
|
1414
|
+
case KEYCODES.UPARROW:
|
1415
|
+
case KEYCODES.DOWNARROW:
|
1416
|
+
e.preventDefault();
|
1417
|
+
break;
|
1418
|
+
case KEYCODES.ENTER:
|
1419
|
+
case KEYCODES.TAB:
|
1420
|
+
case KEYCODES.COMMA:
|
1421
|
+
if(e.keyCode !== KEYCODES.COMMA || cfg.useCommaKey === true) {
|
1422
|
+
e.preventDefault();
|
1423
|
+
if(cfg.expanded === true){ // if a selection is performed, select it and reset field
|
1424
|
+
selected = ms.combobox.find('.ms-res-item-active:not(.ms-res-item-disabled):first');
|
1425
|
+
if(selected.length > 0) {
|
1426
|
+
self._selectItem(selected);
|
1427
|
+
return;
|
1428
|
+
}
|
1429
|
+
}
|
1430
|
+
// if no selection or if freetext entered and free entries allowed, add new obj to selection
|
1431
|
+
if(inputValid === true && cfg.allowFreeEntries === true) {
|
1432
|
+
obj[cfg.displayField] = obj[cfg.valueField] = freeInput.trim();
|
1433
|
+
ms.addToSelection(obj);
|
1434
|
+
ms.collapse(); // reset combo suggestions
|
1435
|
+
ms.input.focus();
|
1436
|
+
}
|
1437
|
+
break;
|
1438
|
+
}
|
1439
|
+
default:
|
1440
|
+
if(_selection.length === cfg.maxSelection){
|
1441
|
+
self._updateHelper(cfg.maxSelectionRenderer.call(this, _selection.length));
|
1442
|
+
}
|
1443
|
+
else {
|
1444
|
+
if(freeInput.length < cfg.minChars) {
|
1445
|
+
self._updateHelper(cfg.minCharsRenderer.call(this, cfg.minChars - freeInput.length));
|
1446
|
+
if(cfg.expanded === true) {
|
1447
|
+
ms.collapse();
|
1448
|
+
}
|
1449
|
+
}
|
1450
|
+
else if(cfg.maxEntryLength && freeInput.length > cfg.maxEntryLength) {
|
1451
|
+
self._updateHelper(cfg.maxEntryRenderer.call(this, freeInput.length - cfg.maxEntryLength));
|
1452
|
+
if(cfg.expanded === true) {
|
1453
|
+
ms.collapse();
|
1454
|
+
}
|
1455
|
+
}
|
1456
|
+
else {
|
1457
|
+
ms.helper.hide();
|
1458
|
+
if(cfg.minChars <= freeInput.length){
|
1459
|
+
_timer = setTimeout(function() {
|
1460
|
+
if(cfg.expanded === true) {
|
1461
|
+
self._processSuggestions();
|
1462
|
+
} else {
|
1463
|
+
ms.expand();
|
1464
|
+
}
|
1465
|
+
}, cfg.typeDelay);
|
1466
|
+
}
|
1467
|
+
}
|
1468
|
+
}
|
1469
|
+
break;
|
1470
|
+
}
|
1471
|
+
},
|
1472
|
+
|
1473
|
+
/**
|
1474
|
+
* Triggered when clicking upon cross for deletion
|
1475
|
+
* @param e
|
1476
|
+
* @private
|
1477
|
+
*/
|
1478
|
+
_onTagTriggerClick: function(e) {
|
1479
|
+
ms.removeFromSelection($(e.currentTarget).data('json'));
|
1480
|
+
},
|
1481
|
+
|
1482
|
+
/**
|
1483
|
+
* Triggered when clicking on the small trigger in the right
|
1484
|
+
* @private
|
1485
|
+
*/
|
1486
|
+
_onTriggerClick: function() {
|
1487
|
+
if(ms.isDisabled() === false && !(cfg.expandOnFocus === true && _selection.length === cfg.maxSelection)) {
|
1488
|
+
$(ms).trigger('triggerclick', [ms]);
|
1489
|
+
if(cfg.expanded === true) {
|
1490
|
+
ms.collapse();
|
1491
|
+
} else {
|
1492
|
+
var curLength = ms.getRawValue().length;
|
1493
|
+
if(curLength >= cfg.minChars){
|
1494
|
+
ms.input.focus();
|
1495
|
+
ms.expand();
|
1496
|
+
} else {
|
1497
|
+
self._updateHelper(cfg.minCharsRenderer.call(this, cfg.minChars - curLength));
|
1498
|
+
}
|
1499
|
+
}
|
1500
|
+
}
|
1501
|
+
},
|
1502
|
+
|
1503
|
+
/**
|
1504
|
+
* Triggered when the browser window is resized
|
1505
|
+
* @private
|
1506
|
+
*/
|
1507
|
+
_onWindowResized: function() {
|
1508
|
+
self._renderSelection();
|
1509
|
+
}
|
1510
|
+
};
|
1511
|
+
|
1512
|
+
// startup point
|
1513
|
+
if(element !== null) {
|
1514
|
+
self._render(element);
|
1515
|
+
}
|
1516
|
+
};
|
1517
|
+
|
1518
|
+
$.fn.magicSuggest = function(options) {
|
1519
|
+
var obj = $(this);
|
1520
|
+
|
1521
|
+
if(obj.size() === 1 && obj.data('magicSuggest')) {
|
1522
|
+
return obj.data('magicSuggest');
|
1523
|
+
}
|
1524
|
+
|
1525
|
+
obj.each(function(i) {
|
1526
|
+
// assume $(this) is an element
|
1527
|
+
var cntr = $(this);
|
1528
|
+
|
1529
|
+
// Return early if this element already has a plugin instance
|
1530
|
+
if(cntr.data('magicSuggest')){
|
1531
|
+
return;
|
1532
|
+
}
|
1533
|
+
|
1534
|
+
if(this.nodeName.toLowerCase() === 'select'){ // rendering from select
|
1535
|
+
options.data = [];
|
1536
|
+
options.value = [];
|
1537
|
+
$.each(this.children, function(index, child){
|
1538
|
+
if(child.nodeName && child.nodeName.toLowerCase() === 'option'){
|
1539
|
+
options.data.push({id: child.value, name: child.text});
|
1540
|
+
if($(child).attr('selected')){
|
1541
|
+
options.value.push(child.value);
|
1542
|
+
}
|
1543
|
+
}
|
1544
|
+
});
|
1545
|
+
}
|
1546
|
+
|
1547
|
+
var def = {};
|
1548
|
+
// set values from DOM container element
|
1549
|
+
$.each(this.attributes, function(i, att){
|
1550
|
+
def[att.name] = att.name === 'value' && att.value !== '' ? JSON.parse(att.value) : att.value;
|
1551
|
+
});
|
1552
|
+
|
1553
|
+
var field = new MagicSuggest(this, $.extend([], $.fn.magicSuggest.defaults, options, def));
|
1554
|
+
cntr.data('magicSuggest', field);
|
1555
|
+
field.container.data('magicSuggest', field);
|
1556
|
+
});
|
1557
|
+
|
1558
|
+
if(obj.size() === 1) {
|
1559
|
+
return obj.data('magicSuggest');
|
1560
|
+
}
|
1561
|
+
return obj;
|
1562
|
+
};
|
1563
|
+
|
1564
|
+
$.fn.magicSuggest.defaults = {};
|
1565
|
+
})(jQuery);
|