From 7a251be650aeaf8a1cfd6d12a970a517bcd0ca07 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Wed, 28 Jun 2023 15:38:28 +0100 Subject: [PATCH 1/4] allow to provide a filename for assets created from URL --- src/fields/Assets.php | 27 +++++++++++-- src/helpers/DataHelper.php | 4 +- src/templates/_includes/fields/assets.html | 10 +++++ src/web/assets/feedme/dist/FeedMe.js | 2 +- src/web/assets/feedme/dist/FeedMe.js.map | 2 +- src/web/assets/feedme/dist/css/FeedMe.css | 4 +- src/web/assets/feedme/dist/css/FeedMe.css.map | 2 +- src/web/assets/feedme/src/js/feed-me.js | 5 +++ src/web/assets/feedme/src/scss/feed-me.scss | 38 ++++++++++++------- 9 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/fields/Assets.php b/src/fields/Assets.php index 9acd8293..d3d2bec8 100644 --- a/src/fields/Assets.php +++ b/src/fields/Assets.php @@ -112,6 +112,8 @@ public function parseField() $urlsToUpload = []; $base64ToUpload = []; + $filenamesFromFeed = $upload ? DataHelper::fetchArrayValue($this->feedData, $this->fieldInfo, 'options.filenameNode') : null; + foreach ($value as $key => $dataValue) { // Prevent empty or blank values (string or array), which match all elements if (empty($dataValue) && empty($default)) { @@ -154,8 +156,15 @@ public function parseField() // If we're uploading files, this will need to be an absolute URL. If it is, save until later. // We also don't check for existing assets here, so break out instantly. if ($upload && UrlHelper::isAbsoluteUrl($dataValue)) { - $urlsToUpload[$key] = $dataValue; - $filename = AssetHelper::getRemoteUrlFilename($dataValue); + $urlsToUpload[$key]['value'] = $dataValue; + + if (isset($filenamesFromFeed[$key])) { + $filename = $filenamesFromFeed[$key] . '.' . AssetHelper::getRemoteUrlExtension($urlsToUpload[$key]['value']); + $urlsToUpload[$key]['newFilename'] = $filename; + } else { + $filename = AssetHelper::getRemoteUrlFilename($dataValue); + $urlsToUpload[$key]['newFilename'] = null; + } } else { $filename = basename($dataValue); } @@ -189,8 +198,18 @@ public function parseField() if ($upload) { if ($urlsToUpload) { - $uploadedElements = AssetHelper::fetchRemoteImage($urlsToUpload, $this->fieldInfo, $this->feed, $this->field, $this->element); - $foundElements = array_merge($foundElements, $uploadedElements); + foreach ($urlsToUpload as $item) { + $uploadedElements = AssetHelper::fetchRemoteImage( + [$item['value']], + $this->fieldInfo, + $this->feed, + $this->field, + $this->element, + null, + $item['newFilename'] + ); + $foundElements = array_merge($foundElements, $uploadedElements); + } } if ($base64ToUpload) { diff --git a/src/helpers/DataHelper.php b/src/helpers/DataHelper.php index 7f2a7a51..c4b26251 100644 --- a/src/helpers/DataHelper.php +++ b/src/helpers/DataHelper.php @@ -58,11 +58,11 @@ public static function fetchSimpleValue($feedData, $fieldInfo) * @param $fieldInfo * @return array|ArrayAccess|mixed */ - public static function fetchArrayValue($feedData, $fieldInfo) + public static function fetchArrayValue($feedData, $fieldInfo, $nodeName = 'node') { $value = null; - $node = Hash::get($fieldInfo, 'node'); + $node = Hash::get($fieldInfo, $nodeName); $dataDelimiter = Plugin::$plugin->service->getConfig('dataDelimiter'); diff --git a/src/templates/_includes/fields/assets.html b/src/templates/_includes/fields/assets.html index 90e9cfa8..fb005480 100644 --- a/src/templates/_includes/fields/assets.html +++ b/src/templates/_includes/fields/assets.html @@ -55,5 +55,15 @@ ], value: hash_get(feed.fieldMapping, optionsPath ~ '.conflict') ?: '', }) }} + +
+ {{ forms.selectField({ + label: 'Use this filename for assets created from URL:'|t('feed-me'), + name: 'options[filenameNode]', + value: hash_get(feed.fieldMapping, optionsPath ~ '.filenameNode') ?: '', + options: feedData, + class: 'selectize fullwidth', + }) }} +
{% endblock %} diff --git a/src/web/assets/feedme/dist/FeedMe.js b/src/web/assets/feedme/dist/FeedMe.js index 9e35126f..253bb9eb 100644 --- a/src/web/assets/feedme/dist/FeedMe.js +++ b/src/web/assets/feedme/dist/FeedMe.js @@ -1,2 +1,2 @@ -!function(){var e={611:function(){function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}$((function(){"undefined"===e(Craft.FeedMe)&&(Craft.FeedMe={}),$(document).on("click","#feeds .settings",(function(e){e.preventDefault();var t=$(this).parents("tr").data("id")+"-settings";$('tr[data-settings-id="'+t+'"] .settings-pane').toggle()}));var t=$('input[name="duplicateHandle[]"][value="disable"]').next("label"),n=t.text(),i=t.siblings(".instructions"),o=i.text();$(document).on("change","#elementType",(function(){$(".element-select").hide();var e=$(this).val().replace(/\\/g,"-");$('.element-select[data-type="'+e+'"]').show(),"craft-elements-User"===e?(t.text(Craft.t("feed-me","Suspend missing users")),i.text(Craft.t("feed-me","Suspends any users that are missing from the feed."))):(t.text(n),i.text(o))})),$("#elementType").trigger("change"),$(document).on("change",".element-parent-group select",(function(){var e=$(this).parents(".element-sub-group").data("items")||{},t=$(this).val(),n=e["item_"+t]||[],i=$(".element-child-group select").val(),o='";$.each(n,(function(e,t){e&&(o+='")})),$(".element-child-group select").html(o),i?$(".element-child-group select").val(i):$($(".element-child-group select").children()[1]).attr("selected",!0);var s=$("#elementType").val();Craft.FeedMe.elementTypes[s]&&Craft.FeedMe.elementTypes[s].groups[t]&&Craft.FeedMe.elementTypes[s].groups[t].isSingleton?$("#singleton").val()||($("#singleton").val("1"),$("#is-create").attr({checked:!1,disabled:!0}),$("#is-update").attr({checked:!0,disabled:!0}),$("#is-disable-globally").attr({checked:!1,disabled:!0}),$("#is-disable-site").attr({checked:!1,disabled:!0}),$("#is-delete").attr({checked:!1,disabled:!0})):$("#singleton").val()&&($("#singleton").val(""),$("#is-create").attr({checked:!0,disabled:!1}),$("#is-update").attr({checked:!1,disabled:!1}),$("#is-disable-globally").attr({checked:!1,disabled:!1}),$("#is-disable-site").attr({checked:!1,disabled:!1}),$("#is-delete").attr({checked:!1,disabled:!1}))})),$(".element-parent-group select:visible").trigger("change"),$(".feedme-uniques").length&&($('.feedme-uniques input[type="checkbox"]:checked').length||$('.feedme-uniques input[type="checkbox"]:first').prop("checked",!0)),$(".assets-uploads input").on("change",(function(e){var t=$(this).parents(".field-extra-settings").find(".select"),n=$(this).parents(".field-extra-settings").find(".asset-label-hide");$(this).prop("checked")?(n.css({opacity:1,visibility:"visible"}),t.css({opacity:1,visibility:"visible"})):(n.css({opacity:0,visibility:"hidden"}),t.css({opacity:0,visibility:"hidden"}))})),$(".assets-uploads input").trigger("change"),$(".field-extra-settings .element-create input").on("change",(function(e){var t=$(this).parents(".field-extra-settings").find(".element-groups");$(this).prop("checked")?t.show():t.hide()})),$(".field-extra-settings .element-create input").trigger("change"),$(".field-extra-settings .element-group-section select").on("change",(function(e){var t=$(this).parents(".field-extra-settings").find(".element-group-entrytype"),n=t.data("items")["item_"+$(this).val()],i="";$.each(n,(function(e,t){e&&(i+='")})),t.find("select").html(i)})),$(".field-extra-settings .element-group-section select").trigger("change"),$(".feedme-mapping .selectize select").selectize({allowEmptyOption:!0}),$(".subelement-toggle label").on("click",(function(e){$(this).parents(".subelement-toggle").find(".lightswitch").data("lightswitch").toggle()})),$(".subelement-toggle .lightswitch").on("change",(function(e){$(this).data("lightswitch"),$(this).parents("tr").nextUntil(":not(.element-sub-field)").toggle()})),$(".element-sub-field").each((function(e,t){var n=[$(this).find(".col-map select").val(),$(this).find(".col-default input").val()],i=!1;$.each(n,(function(e,t){""!=t&&"noimport"!=t&&void 0!==t&&(i=!0)})),i&&$(this).prevUntil(":not(.element-sub-field)").addBack().prev().find(".lightswitch").data("lightswitch").turnOn()})),$(document).on("click",".log-detail-link",(function(e){e.preventDefault();var t=$(this).data("key");$('tr[data-key="'+t+'"]').toggleClass("hidden")})),$(document).on("click","input[data-action]",(function(e){var t=$(this).parents("form"),n=$(this).data("action");t.find('input[name="action"]').val(n),t.submit()})),$(document).on("change",".log-type-form .select",(function(e){e.preventDefault(),$(this).parents("form").submit()}))}))},622:function(e,t,n){var i,o,s,r,a,l,c,p,u;function d(e){return d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},d(e)}o=function(){var e=function(e,t){this.items=e,this.settings=t||{diacritics:!0}};e.prototype.tokenize=function(e){if(!(e=i(String(e||"").toLowerCase()))||!e.length)return[];var t,n,s,a,l=[],c=e.split(/ +/);for(t=0,n=c.length;t0)&&i.items.push({score:n,id:o})})):r.iterator(r.items,(function(e,t){i.items.push({score:1,id:t})})),(o=r.getSortFunction(i,t))&&i.items.sort(o),i.total=i.items.length,"number"==typeof t.limit&&(i.items=i.items.slice(0,t.limit)),i};var t=function(e,t){return"number"==typeof e&&"number"==typeof t?e>t?1:e(t=a(String(t||"")))?1:t>e?-1:0},n=function(e,t){var n,i,o,s;for(n=1,i=arguments.length;n=0&&t.data.length>0){var s=t.data.match(n),r=document.createElement("span");r.className="highlight";var a=t.splitText(o),l=(a.splitText(s[0].length),a.cloneNode(!0));r.appendChild(l),a.parentNode.replaceChild(r,a),i=1}}else if(1===t.nodeType&&t.childNodes&&!/(script|style)/i.test(t.tagName))for(var c=0;c/g,">").replace(/"/g,""")},h=function(e){return(e+"").replace(/\$/g,"$$$$")},f=function(e,t,n){var i=e[t];e[t]=function(){var t=i.apply(e,arguments);return n.apply(e,arguments),t}},g=function(e,t,n){var i,o=e.trigger,s={};for(i in e.trigger=function(){var n=arguments[0];if(-1===t.indexOf(n))return o.apply(e,arguments);s[n]=arguments},n.apply(e,[]),e.trigger=o,s)s.hasOwnProperty(i)&&o.apply(e,s[i])},v=function(e){var t={};if("selectionStart"in e)t.start=e.selectionStart,t.length=e.selectionEnd-t.start;else if(document.selection){e.focus();var n=document.selection.createRange(),i=document.selection.createRange().text.length;n.moveStart("character",-e.value.length),t.start=n.text.length-i,t.length=i}return t},m=function(t){var n=null,i=function(i,o){var s,r,a,l,c,p,u,d;o=o||{},(i=i||window.event||{}).metaKey||i.altKey||(o.force||!1!==t.data("grow"))&&(s=t.val(),i.type&&"keydown"===i.type.toLowerCase()&&(a=(r=i.keyCode)>=97&&r<=122||r>=65&&r<=90||r>=48&&r<=57||32===r,46===r||8===r?(d=v(t[0])).length?s=s.substring(0,d.start)+s.substring(d.start+d.length):8===r&&d.start?s=s.substring(0,d.start-1)+s.substring(d.start+1):46===r&&void 0!==d.start&&(s=s.substring(0,d.start)+s.substring(d.start+1)):a&&(p=i.shiftKey,u=String.fromCharCode(i.keyCode),s+=u=p?u.toUpperCase():u.toLowerCase())),l=t.attr("placeholder"),!s&&l&&(s=l),(c=function(t,n){if(!t)return 0;var i=e("").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).text(t).appendTo("body");!function(e,t,n){var i,o,s={};if(n)for(i=0,o=n.length;i").addClass(w.wrapperClass).addClass(d).addClass(u),n=e("
").addClass(w.inputClass).addClass("items").appendTo(t),i=e('').appendTo(n).attr("tabindex",x.is(":disabled")?"-1":$.tabIndex),p=e(w.dropdownParent||t),o=e("
").addClass(w.dropdownClass).addClass(u).hide().appendTo(p),c=e("
").addClass(w.dropdownContentClass).appendTo(o),$.settings.copyClassesToDropdown&&o.addClass(d),t.css({width:x[0].style.width}),$.plugins.names.length&&(h="plugin-"+$.plugins.names.join(" plugin-"),t.addClass(h),o.addClass(h)),(null===w.maxItems||w.maxItems>1)&&1===$.tagType&&x.attr("multiple","multiple"),$.settings.placeholder&&i.attr("placeholder",w.placeholder),!$.settings.splitOn&&$.settings.delimiter){var S=$.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");$.settings.splitOn=new RegExp("\\s*"+S+"+\\s*")}x.attr("autocorrect")&&i.attr("autocorrect",x.attr("autocorrect")),x.attr("autocapitalize")&&i.attr("autocapitalize",x.attr("autocapitalize")),$.$wrapper=t,$.$control=n,$.$control_input=i,$.$dropdown=o,$.$dropdown_content=c,o.on("mouseenter","[data-selectable]",(function(){return $.onOptionHover.apply($,arguments)})),o.on("mousedown click","[data-selectable]",(function(){return $.onOptionSelect.apply($,arguments)})),g="mousedown",v="*:not(input)",y=function(){return $.onItemSelect.apply($,arguments)},(f=n).on(g,v,(function(e){for(var t=e.target;t&&t.parentNode!==f[0];)t=t.parentNode;return e.currentTarget=t,y.apply(this,[e])})),m(i),n.on({mousedown:function(){return $.onMouseDown.apply($,arguments)},click:function(){return $.onClick.apply($,arguments)}}),i.on({mousedown:function(e){e.stopPropagation()},keydown:function(){return $.onKeyDown.apply($,arguments)},keyup:function(){return $.onKeyUp.apply($,arguments)},keypress:function(){return $.onKeyPress.apply($,arguments)},resize:function(){$.positionDropdown.apply($,[])},blur:function(){return $.onBlur.apply($,arguments)},focus:function(){return $.ignoreBlur=!1,$.onFocus.apply($,arguments)},paste:function(){return $.onPaste.apply($,arguments)}}),C.on("keydown"+b,(function(e){$.isCmdDown=e[s?"metaKey":"ctrlKey"],$.isCtrlDown=e[s?"altKey":"ctrlKey"],$.isShiftDown=e.shiftKey})),C.on("keyup"+b,(function(e){e.keyCode===a&&($.isCtrlDown=!1),16===e.keyCode&&($.isShiftDown=!1),e.keyCode===r&&($.isCmdDown=!1)})),C.on("mousedown"+b,(function(e){if($.isFocused){if(e.target===$.$dropdown[0]||e.target.parentNode===$.$dropdown[0])return!1;$.$control.has(e.target).length||e.target===$.$control[0]||$.blur(e.target)}})),O.on(["scroll"+b,"resize"+b].join(" "),(function(){$.isOpen&&$.positionDropdown.apply($,arguments)})),O.on("mousemove"+b,(function(){$.ignoreHover=!1})),this.revertSettings={$children:x.children().detach(),tabindex:x.attr("tabindex")},x.attr("tabindex",-1).hide().after($.$wrapper),e.isArray(w.items)&&($.setValue(w.items),delete w.items),l&&x.on("invalid"+b,(function(e){e.preventDefault(),$.isInvalid=!0,$.refreshState()})),$.updateOriginalInput(),$.refreshItems(),$.refreshState(),$.updatePlaceholder(),$.isSetup=!0,x.is(":disabled")&&$.disable(),$.on("change",this.onChange),x.data("selectize",$),x.addClass("selectized"),$.trigger("initialize"),!0===w.preload&&$.onSearchChange("")},setupTemplates:function(){var t=this,n=t.settings.labelField,i=t.settings.optgroupLabelField,o={optgroup:function(e){return'
'+e.html+"
"},optgroup_header:function(e,t){return'
'+t(e[i])+"
"},option:function(e,t){return'
'+t(e[n])+"
"},item:function(e,t){return'
'+t(e[n])+"
"},option_create:function(e,t){return'
Add '+t(e.input)+"
"}};t.settings.render=e.extend({},o,t.settings.render)},setupCallbacks:function(){var e,t,n={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in n)n.hasOwnProperty(e)&&(t=this.settings[n[e]])&&this.on(e,t)},onClick:function(e){this.isFocused||(this.focus(),e.preventDefault())},onMouseDown:function(t){var n=this,i=t.isDefaultPrevented();if(e(t.target),n.isFocused){if(t.target!==n.$control_input[0])return"single"===n.settings.mode?n.isOpen?n.close():n.open():i||n.setActiveItem(null),!1}else i||window.setTimeout((function(){n.focus()}),0)},onChange:function(){this.$input.trigger("change")},onPaste:function(t){var n=this;n.isFull()||n.isInputHidden||n.isLocked?t.preventDefault():n.settings.splitOn&&setTimeout((function(){for(var t=e.trim(n.$control_input.val()||"").split(n.settings.splitOn),i=0,o=t.length;i(a=Array.prototype.indexOf.apply(u.$control[0].childNodes,[t[0]]))&&(c=r,r=a,a=c),o=r;o<=a;o++)l=u.$control[0].childNodes[o],-1===u.$activeItems.indexOf(l)&&(e(l).addClass("active"),u.$activeItems.push(l));n.preventDefault()}else"mousedown"===i&&u.isCtrlDown||"keydown"===i&&this.isShiftDown?t.hasClass("active")?(s=u.$activeItems.indexOf(t[0]),u.$activeItems.splice(s,1),t.removeClass("active")):u.$activeItems.push(t.addClass("active")[0]):(e(u.$activeItems).removeClass("active"),u.$activeItems=[t.addClass("active")[0]]);u.hideInput(),this.isFocused||u.focus()}},setActiveOption:function(t,n,i){var o,s,r,a,l,p=this;p.$activeOption&&p.$activeOption.removeClass("active"),p.$activeOption=null,(t=e(t)).length&&(p.$activeOption=t.addClass("active"),!n&&c(n)||(o=p.$dropdown_content.height(),s=p.$activeOption.outerHeight(!0),n=p.$dropdown_content.scrollTop()||0,a=r=p.$activeOption.offset().top-p.$dropdown_content.offset().top+n,l=r-o+s,r+s>o+n?p.$dropdown_content.stop().animate({scrollTop:l},i?p.settings.scrollDuration:0):r=0;n--)-1!==s.items.indexOf(p(i.items[n].id))&&i.items.splice(n,1);return i},refreshOptions:function(t){var n,o,s,r,a,l,c,u,d,h,f,g,v,m,y,$;void 0===t&&(t=!0);var w=this,b=e.trim(w.$control_input.val()),O=w.search(b),C=w.$dropdown_content,x=w.$activeOption&&p(w.$activeOption.attr("data-value"));for(r=O.items.length,"number"==typeof w.settings.maxOptions&&(r=Math.min(r,w.settings.maxOptions)),a={},l=[],n=0;n0||v,w.hasOptions?(O.items.length>0?((y=x&&w.getOption(x))&&y.length?m=y:"single"===w.settings.mode&&w.items.length&&(m=w.getOption(w.items[0])),m&&m.length||(m=$&&!w.settings.addPrecedence?w.getAdjacentOption($,1):C.find("[data-selectable]:first"))):m=$,w.setActiveOption(m),t&&!w.isOpen&&w.open()):(w.setActiveOption(null),t&&w.isOpen&&w.close())},addOption:function(t){var n,i,o,s=this;if(e.isArray(t))for(n=0,i=t.length;n=0&&o0),t.$control_input.data("grow",!n&&!i)},isFull:function(){return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems},updateOriginalInput:function(e){var t,n,i,o,s=this;if(e=e||{},1===s.tagType){for(i=[],t=0,n=s.items.length;t'+u(o)+"");i.length||this.$input.attr("multiple")||i.push(''),s.$input.html(i.join(""))}else s.$input.val(s.getValue()),s.$input.attr("value",s.$input.val());s.isSetup&&(e.silent||s.trigger("change",s.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var e=this.$control_input;this.items.length?e.removeAttr("placeholder"):e.attr("placeholder",this.settings.placeholder),e.triggerHandler("update",{force:!0})}},open:function(){var e=this;e.isLocked||e.isOpen||"multi"===e.settings.mode&&e.isFull()||(e.focus(),e.isOpen=!0,e.refreshState(),e.$dropdown.css({visibility:"hidden",display:"block"}),e.positionDropdown(),e.$dropdown.css({visibility:"visible"}),e.trigger("dropdown_open",e.$dropdown))},close:function(){var e=this,t=e.isOpen;"single"===e.settings.mode&&e.items.length&&e.hideInput(),e.isOpen=!1,e.$dropdown.hide(),e.setActiveOption(null),e.refreshState(),t&&e.trigger("dropdown_close",e.$dropdown)},positionDropdown:function(){var e=this.$control,t="body"===this.settings.dropdownParent?e.offset():e.position();t.top+=e.outerHeight(!0),this.$dropdown.css({width:e.outerWidth(),top:t.top,left:t.left})},clear:function(e){var t=this;t.items.length&&(t.$control.children(":not(input)").remove(),t.items=[],t.lastQuery=null,t.setCaret(0),t.setActiveItem(null),t.updatePlaceholder(),t.updateOriginalInput({silent:e}),t.refreshState(),t.showInput(),t.trigger("clear"))},insertAtCaret:function(t){var n=Math.min(this.caretPos,this.items.length);0===n?this.$control.prepend(t):e(this.$control[0].childNodes[n]).before(t),this.setCaret(n+1)},deleteSelection:function(t){var n,i,o,s,r,a,l,c,p,u=this;if(o=t&&8===t.keyCode?-1:1,s=v(u.$control_input[0]),u.$activeOption&&!u.settings.hideSelected&&(l=u.getAdjacentOption(u.$activeOption,-1).attr("data-value")),r=[],u.$activeItems.length){for(p=u.$control.children(".active:"+(o>0?"last":"first")),a=u.$control.children(":not(input)").index(p),o>0&&a++,n=0,i=u.$activeItems.length;n0&&s.start===u.$control_input.val().length&&r.push(u.items[u.caretPos]));if(!r.length||"function"==typeof u.settings.onDelete&&!1===u.settings.onDelete.apply(u,[r]))return!1;for(void 0!==a&&u.setCaret(a);r.length;)u.removeItem(r.pop());return u.showInput(),u.positionDropdown(),u.refreshOptions(!0),l&&(c=u.getOption(l)).length&&u.setActiveOption(c),!0},advanceSelection:function(e,t){var n,i,o,s,r,a=this;0!==e&&(a.rtl&&(e*=-1),n=e>0?"last":"first",i=v(a.$control_input[0]),a.isFocused&&!a.isInputHidden?(s=a.$control_input.val().length,(e<0?0===i.start&&0===i.length:i.start===s)&&!s&&a.advanceCaret(e,t)):(r=a.$control.children(".active:"+n)).length&&(o=a.$control.children(":not(input)").index(r),a.setActiveItem(null),a.setCaret(e>0?o+1:o)))},advanceCaret:function(e,t){var n,i,o=this;0!==e&&(n=e>0?"next":"prev",o.isShiftDown?(i=o.$control_input[n]()).length&&(o.hideInput(),o.setActiveItem(i),t&&t.preventDefault()):o.setCaret(o.caretPos+e))},setCaret:function(t){var n,i,o,s,r=this;if(t="single"===r.settings.mode?r.items.length:Math.max(0,Math.min(r.items.length,t)),!r.isPending)for(n=0,i=(o=r.$control.children(":not(input)")).length;n
'+e.title+'×
'}},t),i.setup=(n=i.setup,function(){n.apply(i,arguments),i.$dropdown_header=e(t.html(t)),i.$dropdown.prepend(i.$dropdown_header)})})),y.define("optgroup_columns",(function(t){var n,i=this;t=e.extend({equalizeWidth:!0,equalizeHeight:!0},t),this.getAdjacentOption=function(t,n){var i=t.closest("[data-group]").find("[data-selectable]"),o=i.index(t)+n;return o>=0&&o
',t=t.firstChild,i.body.appendChild(t),n=e.width=t.offsetWidth-t.clientWidth,i.body.removeChild(t)),n},s=function(){var n,s,r,a,l,c,p;if((s=(p=e("[data-group]",i.$dropdown_content)).length)&&i.$dropdown_content.width()){if(t.equalizeHeight){for(r=0,n=0;n1&&(l=c-a*(s-1),p.eq(s-1).css({width:l})))}};(t.equalizeHeight||t.equalizeWidth)&&(f(this,"positionDropdown",s),f(this,"refreshOptions",s))})),y.define("remove_button",(function(t){if("single"!==this.settings.mode){t=e.extend({label:"×",title:"Remove",className:"remove",append:!0},t);var n,i=this,o=''+t.label+"";this.setup=(n=i.setup,function(){if(t.append){var s=i.settings.render.item;i.settings.render.item=function(e){return t=s.apply(this,arguments),n=o,i=t.search(/(<\/[^>]+>\s*)$/),t.substring(0,i)+n+t.substring(i);var t,n,i}}n.apply(this,arguments),this.$control.on("click","."+t.className,(function(t){if(t.preventDefault(),!i.isLocked){var n=e(t.currentTarget).parent();i.setActiveItem(n),i.deleteSelection()&&i.setCaret(i.items.length)}}))})}})),y.define("restore_on_backspace",(function(e){var t;e.text=e.text||function(e){return e[this.settings.labelField]},this.onKeyDown=(t=this.onKeyDown,function(n){var i,o;return 8===n.keyCode&&""===this.$control_input.val()&&!this.$activeItems.length&&(i=this.caretPos-1)>=0&&in.parts.length&&(i.parts.length=n.parts.length)}else{var r=[];for(o=0;o";$.each(n,(function(e,t){e&&(o+='")})),$(".element-child-group select").html(o),i?$(".element-child-group select").val(i):$($(".element-child-group select").children()[1]).attr("selected",!0);var s=$("#elementType").val();Craft.FeedMe.elementTypes[s]&&Craft.FeedMe.elementTypes[s].groups[t]&&Craft.FeedMe.elementTypes[s].groups[t].isSingleton?$("#singleton").val()||($("#singleton").val("1"),$("#is-create").attr({checked:!1,disabled:!0}),$("#is-update").attr({checked:!0,disabled:!0}),$("#is-disable-globally").attr({checked:!1,disabled:!0}),$("#is-disable-site").attr({checked:!1,disabled:!0}),$("#is-delete").attr({checked:!1,disabled:!0})):$("#singleton").val()&&($("#singleton").val(""),$("#is-create").attr({checked:!0,disabled:!1}),$("#is-update").attr({checked:!1,disabled:!1}),$("#is-disable-globally").attr({checked:!1,disabled:!1}),$("#is-disable-site").attr({checked:!1,disabled:!1}),$("#is-delete").attr({checked:!1,disabled:!1}))})),$(".element-parent-group select:visible").trigger("change"),$(".feedme-uniques").length&&($('.feedme-uniques input[type="checkbox"]:checked').length||$('.feedme-uniques input[type="checkbox"]:first').prop("checked",!0)),$(".assets-uploads input").on("change",(function(e){var t=$(this).parents(".field-extra-settings").find(".select"),n=$(this).parents(".field-extra-settings").find(".asset-label-hide"),i=$(this).parents(".field-extra-settings").find(".asset-filename-node");$(this).prop("checked")?(n.css({opacity:1,visibility:"visible"}),t.css({opacity:1,visibility:"visible"}),i.show()):(n.css({opacity:0,visibility:"hidden"}),t.css({opacity:0,visibility:"hidden"}),i.hide())})),$(".assets-uploads input").trigger("change"),$(".field-extra-settings .element-create input").on("change",(function(e){var t=$(this).parents(".field-extra-settings").find(".element-groups");$(this).prop("checked")?t.show():t.hide()})),$(".field-extra-settings .element-create input").trigger("change"),$(".field-extra-settings .element-group-section select").on("change",(function(e){var t=$(this).parents(".field-extra-settings").find(".element-group-entrytype"),n=t.data("items")["item_"+$(this).val()],i="";$.each(n,(function(e,t){e&&(i+='")})),t.find("select").html(i)})),$(".field-extra-settings .element-group-section select").trigger("change"),$(".feedme-mapping .selectize select").selectize({allowEmptyOption:!0}),$(".subelement-toggle label").on("click",(function(e){$(this).parents(".subelement-toggle").find(".lightswitch").data("lightswitch").toggle()})),$(".subelement-toggle .lightswitch").on("change",(function(e){$(this).data("lightswitch"),$(this).parents("tr").nextUntil(":not(.element-sub-field)").toggle()})),$(".element-sub-field").each((function(e,t){var n=[$(this).find(".col-map select").val(),$(this).find(".col-default input").val()],i=!1;$.each(n,(function(e,t){""!=t&&"noimport"!=t&&void 0!==t&&(i=!0)})),i&&$(this).prevUntil(":not(.element-sub-field)").addBack().prev().find(".lightswitch").data("lightswitch").turnOn()})),$(document).on("click",".log-detail-link",(function(e){e.preventDefault();var t=$(this).data("key");$('tr[data-key="'+t+'"]').toggleClass("hidden")})),$(document).on("click","input[data-action]",(function(e){var t=$(this).parents("form"),n=$(this).data("action");t.find('input[name="action"]').val(n),t.submit()})),$(document).on("change",".log-type-form .select",(function(e){e.preventDefault(),$(this).parents("form").submit()}))}))},622:function(e,t,n){var i,o,s,r,a,l,c,p,u;function d(e){return d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},d(e)}o=function(){var e=function(e,t){this.items=e,this.settings=t||{diacritics:!0}};e.prototype.tokenize=function(e){if(!(e=i(String(e||"").toLowerCase()))||!e.length)return[];var t,n,s,a,l=[],c=e.split(/ +/);for(t=0,n=c.length;t0)&&i.items.push({score:n,id:o})})):r.iterator(r.items,(function(e,t){i.items.push({score:1,id:t})})),(o=r.getSortFunction(i,t))&&i.items.sort(o),i.total=i.items.length,"number"==typeof t.limit&&(i.items=i.items.slice(0,t.limit)),i};var t=function(e,t){return"number"==typeof e&&"number"==typeof t?e>t?1:e(t=a(String(t||"")))?1:t>e?-1:0},n=function(e,t){var n,i,o,s;for(n=1,i=arguments.length;n=0&&t.data.length>0){var s=t.data.match(n),r=document.createElement("span");r.className="highlight";var a=t.splitText(o),l=(a.splitText(s[0].length),a.cloneNode(!0));r.appendChild(l),a.parentNode.replaceChild(r,a),i=1}}else if(1===t.nodeType&&t.childNodes&&!/(script|style)/i.test(t.tagName))for(var c=0;c/g,">").replace(/"/g,""")},h=function(e){return(e+"").replace(/\$/g,"$$$$")},f=function(e,t,n){var i=e[t];e[t]=function(){var t=i.apply(e,arguments);return n.apply(e,arguments),t}},g=function(e,t,n){var i,o=e.trigger,s={};for(i in e.trigger=function(){var n=arguments[0];if(-1===t.indexOf(n))return o.apply(e,arguments);s[n]=arguments},n.apply(e,[]),e.trigger=o,s)s.hasOwnProperty(i)&&o.apply(e,s[i])},v=function(e){var t={};if("selectionStart"in e)t.start=e.selectionStart,t.length=e.selectionEnd-t.start;else if(document.selection){e.focus();var n=document.selection.createRange(),i=document.selection.createRange().text.length;n.moveStart("character",-e.value.length),t.start=n.text.length-i,t.length=i}return t},m=function(t){var n=null,i=function(i,o){var s,r,a,l,c,p,u,d;o=o||{},(i=i||window.event||{}).metaKey||i.altKey||(o.force||!1!==t.data("grow"))&&(s=t.val(),i.type&&"keydown"===i.type.toLowerCase()&&(a=(r=i.keyCode)>=97&&r<=122||r>=65&&r<=90||r>=48&&r<=57||32===r,46===r||8===r?(d=v(t[0])).length?s=s.substring(0,d.start)+s.substring(d.start+d.length):8===r&&d.start?s=s.substring(0,d.start-1)+s.substring(d.start+1):46===r&&void 0!==d.start&&(s=s.substring(0,d.start)+s.substring(d.start+1)):a&&(p=i.shiftKey,u=String.fromCharCode(i.keyCode),s+=u=p?u.toUpperCase():u.toLowerCase())),l=t.attr("placeholder"),!s&&l&&(s=l),(c=function(t,n){if(!t)return 0;var i=e("").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).text(t).appendTo("body");!function(e,t,n){var i,o,s={};if(n)for(i=0,o=n.length;i").addClass(w.wrapperClass).addClass(d).addClass(u),n=e("
").addClass(w.inputClass).addClass("items").appendTo(t),i=e('').appendTo(n).attr("tabindex",x.is(":disabled")?"-1":$.tabIndex),p=e(w.dropdownParent||t),o=e("
").addClass(w.dropdownClass).addClass(u).hide().appendTo(p),c=e("
").addClass(w.dropdownContentClass).appendTo(o),$.settings.copyClassesToDropdown&&o.addClass(d),t.css({width:x[0].style.width}),$.plugins.names.length&&(h="plugin-"+$.plugins.names.join(" plugin-"),t.addClass(h),o.addClass(h)),(null===w.maxItems||w.maxItems>1)&&1===$.tagType&&x.attr("multiple","multiple"),$.settings.placeholder&&i.attr("placeholder",w.placeholder),!$.settings.splitOn&&$.settings.delimiter){var S=$.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");$.settings.splitOn=new RegExp("\\s*"+S+"+\\s*")}x.attr("autocorrect")&&i.attr("autocorrect",x.attr("autocorrect")),x.attr("autocapitalize")&&i.attr("autocapitalize",x.attr("autocapitalize")),$.$wrapper=t,$.$control=n,$.$control_input=i,$.$dropdown=o,$.$dropdown_content=c,o.on("mouseenter","[data-selectable]",(function(){return $.onOptionHover.apply($,arguments)})),o.on("mousedown click","[data-selectable]",(function(){return $.onOptionSelect.apply($,arguments)})),g="mousedown",v="*:not(input)",y=function(){return $.onItemSelect.apply($,arguments)},(f=n).on(g,v,(function(e){for(var t=e.target;t&&t.parentNode!==f[0];)t=t.parentNode;return e.currentTarget=t,y.apply(this,[e])})),m(i),n.on({mousedown:function(){return $.onMouseDown.apply($,arguments)},click:function(){return $.onClick.apply($,arguments)}}),i.on({mousedown:function(e){e.stopPropagation()},keydown:function(){return $.onKeyDown.apply($,arguments)},keyup:function(){return $.onKeyUp.apply($,arguments)},keypress:function(){return $.onKeyPress.apply($,arguments)},resize:function(){$.positionDropdown.apply($,[])},blur:function(){return $.onBlur.apply($,arguments)},focus:function(){return $.ignoreBlur=!1,$.onFocus.apply($,arguments)},paste:function(){return $.onPaste.apply($,arguments)}}),C.on("keydown"+b,(function(e){$.isCmdDown=e[s?"metaKey":"ctrlKey"],$.isCtrlDown=e[s?"altKey":"ctrlKey"],$.isShiftDown=e.shiftKey})),C.on("keyup"+b,(function(e){e.keyCode===a&&($.isCtrlDown=!1),16===e.keyCode&&($.isShiftDown=!1),e.keyCode===r&&($.isCmdDown=!1)})),C.on("mousedown"+b,(function(e){if($.isFocused){if(e.target===$.$dropdown[0]||e.target.parentNode===$.$dropdown[0])return!1;$.$control.has(e.target).length||e.target===$.$control[0]||$.blur(e.target)}})),O.on(["scroll"+b,"resize"+b].join(" "),(function(){$.isOpen&&$.positionDropdown.apply($,arguments)})),O.on("mousemove"+b,(function(){$.ignoreHover=!1})),this.revertSettings={$children:x.children().detach(),tabindex:x.attr("tabindex")},x.attr("tabindex",-1).hide().after($.$wrapper),e.isArray(w.items)&&($.setValue(w.items),delete w.items),l&&x.on("invalid"+b,(function(e){e.preventDefault(),$.isInvalid=!0,$.refreshState()})),$.updateOriginalInput(),$.refreshItems(),$.refreshState(),$.updatePlaceholder(),$.isSetup=!0,x.is(":disabled")&&$.disable(),$.on("change",this.onChange),x.data("selectize",$),x.addClass("selectized"),$.trigger("initialize"),!0===w.preload&&$.onSearchChange("")},setupTemplates:function(){var t=this,n=t.settings.labelField,i=t.settings.optgroupLabelField,o={optgroup:function(e){return'
'+e.html+"
"},optgroup_header:function(e,t){return'
'+t(e[i])+"
"},option:function(e,t){return'
'+t(e[n])+"
"},item:function(e,t){return'
'+t(e[n])+"
"},option_create:function(e,t){return'
Add '+t(e.input)+"
"}};t.settings.render=e.extend({},o,t.settings.render)},setupCallbacks:function(){var e,t,n={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in n)n.hasOwnProperty(e)&&(t=this.settings[n[e]])&&this.on(e,t)},onClick:function(e){this.isFocused||(this.focus(),e.preventDefault())},onMouseDown:function(t){var n=this,i=t.isDefaultPrevented();if(e(t.target),n.isFocused){if(t.target!==n.$control_input[0])return"single"===n.settings.mode?n.isOpen?n.close():n.open():i||n.setActiveItem(null),!1}else i||window.setTimeout((function(){n.focus()}),0)},onChange:function(){this.$input.trigger("change")},onPaste:function(t){var n=this;n.isFull()||n.isInputHidden||n.isLocked?t.preventDefault():n.settings.splitOn&&setTimeout((function(){for(var t=e.trim(n.$control_input.val()||"").split(n.settings.splitOn),i=0,o=t.length;i(a=Array.prototype.indexOf.apply(u.$control[0].childNodes,[t[0]]))&&(c=r,r=a,a=c),o=r;o<=a;o++)l=u.$control[0].childNodes[o],-1===u.$activeItems.indexOf(l)&&(e(l).addClass("active"),u.$activeItems.push(l));n.preventDefault()}else"mousedown"===i&&u.isCtrlDown||"keydown"===i&&this.isShiftDown?t.hasClass("active")?(s=u.$activeItems.indexOf(t[0]),u.$activeItems.splice(s,1),t.removeClass("active")):u.$activeItems.push(t.addClass("active")[0]):(e(u.$activeItems).removeClass("active"),u.$activeItems=[t.addClass("active")[0]]);u.hideInput(),this.isFocused||u.focus()}},setActiveOption:function(t,n,i){var o,s,r,a,l,p=this;p.$activeOption&&p.$activeOption.removeClass("active"),p.$activeOption=null,(t=e(t)).length&&(p.$activeOption=t.addClass("active"),!n&&c(n)||(o=p.$dropdown_content.height(),s=p.$activeOption.outerHeight(!0),n=p.$dropdown_content.scrollTop()||0,a=r=p.$activeOption.offset().top-p.$dropdown_content.offset().top+n,l=r-o+s,r+s>o+n?p.$dropdown_content.stop().animate({scrollTop:l},i?p.settings.scrollDuration:0):r=0;n--)-1!==s.items.indexOf(p(i.items[n].id))&&i.items.splice(n,1);return i},refreshOptions:function(t){var n,o,s,r,a,l,c,u,d,h,f,g,v,m,y,$;void 0===t&&(t=!0);var w=this,b=e.trim(w.$control_input.val()),O=w.search(b),C=w.$dropdown_content,x=w.$activeOption&&p(w.$activeOption.attr("data-value"));for(r=O.items.length,"number"==typeof w.settings.maxOptions&&(r=Math.min(r,w.settings.maxOptions)),a={},l=[],n=0;n0||v,w.hasOptions?(O.items.length>0?((y=x&&w.getOption(x))&&y.length?m=y:"single"===w.settings.mode&&w.items.length&&(m=w.getOption(w.items[0])),m&&m.length||(m=$&&!w.settings.addPrecedence?w.getAdjacentOption($,1):C.find("[data-selectable]:first"))):m=$,w.setActiveOption(m),t&&!w.isOpen&&w.open()):(w.setActiveOption(null),t&&w.isOpen&&w.close())},addOption:function(t){var n,i,o,s=this;if(e.isArray(t))for(n=0,i=t.length;n=0&&o0),t.$control_input.data("grow",!n&&!i)},isFull:function(){return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems},updateOriginalInput:function(e){var t,n,i,o,s=this;if(e=e||{},1===s.tagType){for(i=[],t=0,n=s.items.length;t'+u(o)+"");i.length||this.$input.attr("multiple")||i.push(''),s.$input.html(i.join(""))}else s.$input.val(s.getValue()),s.$input.attr("value",s.$input.val());s.isSetup&&(e.silent||s.trigger("change",s.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var e=this.$control_input;this.items.length?e.removeAttr("placeholder"):e.attr("placeholder",this.settings.placeholder),e.triggerHandler("update",{force:!0})}},open:function(){var e=this;e.isLocked||e.isOpen||"multi"===e.settings.mode&&e.isFull()||(e.focus(),e.isOpen=!0,e.refreshState(),e.$dropdown.css({visibility:"hidden",display:"block"}),e.positionDropdown(),e.$dropdown.css({visibility:"visible"}),e.trigger("dropdown_open",e.$dropdown))},close:function(){var e=this,t=e.isOpen;"single"===e.settings.mode&&e.items.length&&e.hideInput(),e.isOpen=!1,e.$dropdown.hide(),e.setActiveOption(null),e.refreshState(),t&&e.trigger("dropdown_close",e.$dropdown)},positionDropdown:function(){var e=this.$control,t="body"===this.settings.dropdownParent?e.offset():e.position();t.top+=e.outerHeight(!0),this.$dropdown.css({width:e.outerWidth(),top:t.top,left:t.left})},clear:function(e){var t=this;t.items.length&&(t.$control.children(":not(input)").remove(),t.items=[],t.lastQuery=null,t.setCaret(0),t.setActiveItem(null),t.updatePlaceholder(),t.updateOriginalInput({silent:e}),t.refreshState(),t.showInput(),t.trigger("clear"))},insertAtCaret:function(t){var n=Math.min(this.caretPos,this.items.length);0===n?this.$control.prepend(t):e(this.$control[0].childNodes[n]).before(t),this.setCaret(n+1)},deleteSelection:function(t){var n,i,o,s,r,a,l,c,p,u=this;if(o=t&&8===t.keyCode?-1:1,s=v(u.$control_input[0]),u.$activeOption&&!u.settings.hideSelected&&(l=u.getAdjacentOption(u.$activeOption,-1).attr("data-value")),r=[],u.$activeItems.length){for(p=u.$control.children(".active:"+(o>0?"last":"first")),a=u.$control.children(":not(input)").index(p),o>0&&a++,n=0,i=u.$activeItems.length;n0&&s.start===u.$control_input.val().length&&r.push(u.items[u.caretPos]));if(!r.length||"function"==typeof u.settings.onDelete&&!1===u.settings.onDelete.apply(u,[r]))return!1;for(void 0!==a&&u.setCaret(a);r.length;)u.removeItem(r.pop());return u.showInput(),u.positionDropdown(),u.refreshOptions(!0),l&&(c=u.getOption(l)).length&&u.setActiveOption(c),!0},advanceSelection:function(e,t){var n,i,o,s,r,a=this;0!==e&&(a.rtl&&(e*=-1),n=e>0?"last":"first",i=v(a.$control_input[0]),a.isFocused&&!a.isInputHidden?(s=a.$control_input.val().length,(e<0?0===i.start&&0===i.length:i.start===s)&&!s&&a.advanceCaret(e,t)):(r=a.$control.children(".active:"+n)).length&&(o=a.$control.children(":not(input)").index(r),a.setActiveItem(null),a.setCaret(e>0?o+1:o)))},advanceCaret:function(e,t){var n,i,o=this;0!==e&&(n=e>0?"next":"prev",o.isShiftDown?(i=o.$control_input[n]()).length&&(o.hideInput(),o.setActiveItem(i),t&&t.preventDefault()):o.setCaret(o.caretPos+e))},setCaret:function(t){var n,i,o,s,r=this;if(t="single"===r.settings.mode?r.items.length:Math.max(0,Math.min(r.items.length,t)),!r.isPending)for(n=0,i=(o=r.$control.children(":not(input)")).length;n
'+e.title+'×
'}},t),i.setup=(n=i.setup,function(){n.apply(i,arguments),i.$dropdown_header=e(t.html(t)),i.$dropdown.prepend(i.$dropdown_header)})})),y.define("optgroup_columns",(function(t){var n,i=this;t=e.extend({equalizeWidth:!0,equalizeHeight:!0},t),this.getAdjacentOption=function(t,n){var i=t.closest("[data-group]").find("[data-selectable]"),o=i.index(t)+n;return o>=0&&o
',t=t.firstChild,i.body.appendChild(t),n=e.width=t.offsetWidth-t.clientWidth,i.body.removeChild(t)),n},s=function(){var n,s,r,a,l,c,p;if((s=(p=e("[data-group]",i.$dropdown_content)).length)&&i.$dropdown_content.width()){if(t.equalizeHeight){for(r=0,n=0;n1&&(l=c-a*(s-1),p.eq(s-1).css({width:l})))}};(t.equalizeHeight||t.equalizeWidth)&&(f(this,"positionDropdown",s),f(this,"refreshOptions",s))})),y.define("remove_button",(function(t){if("single"!==this.settings.mode){t=e.extend({label:"×",title:"Remove",className:"remove",append:!0},t);var n,i=this,o=''+t.label+"";this.setup=(n=i.setup,function(){if(t.append){var s=i.settings.render.item;i.settings.render.item=function(e){return t=s.apply(this,arguments),n=o,i=t.search(/(<\/[^>]+>\s*)$/),t.substring(0,i)+n+t.substring(i);var t,n,i}}n.apply(this,arguments),this.$control.on("click","."+t.className,(function(t){if(t.preventDefault(),!i.isLocked){var n=e(t.currentTarget).parent();i.setActiveItem(n),i.deleteSelection()&&i.setCaret(i.items.length)}}))})}})),y.define("restore_on_backspace",(function(e){var t;e.text=e.text||function(e){return e[this.settings.labelField]},this.onKeyDown=(t=this.onKeyDown,function(n){var i,o;return 8===n.keyCode&&""===this.$control_input.val()&&!this.$activeItems.length&&(i=this.caretPos-1)>=0&&in.parts.length&&(i.parts.length=n.parts.length)}else{var r=[];for(o=0;o' + Craft.t('feed-me', 'None') + '';\n $.each(entryTypes, function (index, value) {\n if (index) {\n newOptions += '';\n }\n });\n\n $('.element-child-group select').html(newOptions);\n\n // Select the first non-empty, or pre-selected\n if (currentValue) {\n $('.element-child-group select').val(currentValue);\n } else {\n $($('.element-child-group select').children()[1]).attr('selected', true);\n }\n\n // Show/hide the import settings depending on whether this group is a singleton\n var elementType = $('#elementType').val();\n if (\n Craft.FeedMe.elementTypes[elementType] &&\n Craft.FeedMe.elementTypes[elementType].groups[groupId] &&\n Craft.FeedMe.elementTypes[elementType].groups[groupId].isSingleton\n ) {\n if (!$('#singleton').val()) {\n $('#singleton').val('1');\n $('#is-create').attr({checked: false, disabled: true});\n $('#is-update').attr({checked: true, disabled: true});\n $('#is-disable-globally').attr({checked: false, disabled: true});\n $('#is-disable-site').attr({checked: false, disabled: true});\n $('#is-delete').attr({checked: false, disabled: true});\n }\n } else if ($('#singleton').val()) {\n $('#singleton').val('');\n $('#is-create').attr({checked: true, disabled: false});\n $('#is-update').attr({checked: false, disabled: false});\n $('#is-disable-globally').attr({checked: false, disabled: false});\n $('#is-disable-site').attr({checked: false, disabled: false});\n $('#is-delete').attr({checked: false, disabled: false});\n }\n });\n\n $('.element-parent-group select:visible').trigger('change');\n\n //\n // Field Mapping\n //\n\n // For field-mapping, auto-select Title if no unique checkboxes are set\n if ($('.feedme-uniques').length) {\n var checked = $('.feedme-uniques input[type=\"checkbox\"]:checked').length;\n\n if (!checked) {\n $('.feedme-uniques input[type=\"checkbox\"]:first').prop('checked', true);\n }\n }\n\n // For Assets, only show the upload options if we decide to upload\n $('.assets-uploads input').on('change', function (e) {\n var $options = $(this).parents('.field-extra-settings').find('.select');\n var $label = $(this)\n .parents('.field-extra-settings')\n .find('.asset-label-hide');\n\n if ($(this).prop('checked')) {\n $label.css({opacity: 1, visibility: 'visible'});\n $options.css({opacity: 1, visibility: 'visible'});\n } else {\n $label.css({opacity: 0, visibility: 'hidden'});\n $options.css({opacity: 0, visibility: 'hidden'});\n }\n });\n\n // On-load, hide/show upload options\n $('.assets-uploads input').trigger('change');\n\n // For elements, show the grouping select(s)\n $('.field-extra-settings .element-create input').on('change', function (e) {\n var $container = $(this)\n .parents('.field-extra-settings')\n .find('.element-groups');\n\n if ($(this).prop('checked')) {\n $container.show();\n } else {\n $container.hide();\n }\n });\n\n $('.field-extra-settings .element-create input').trigger('change');\n\n // Toggle various field when changing element type\n $('.field-extra-settings .element-group-section select').on(\n 'change',\n function (e) {\n var $container = $(this)\n .parents('.field-extra-settings')\n .find('.element-group-entrytype');\n var sections = $container.data('items');\n\n // var sections = $(this).parents('.element-sub-group').data('items');\n var entryType = 'item_' + $(this).val();\n var entryTypes = sections[entryType];\n\n var newOptions = '';\n $.each(entryTypes, function (index, value) {\n if (index) {\n newOptions += '';\n }\n });\n\n $container.find('select').html(newOptions);\n }\n );\n\n $('.field-extra-settings .element-group-section select').trigger('change');\n\n // Selectize inputs\n $('.feedme-mapping .selectize select').selectize({\n allowEmptyOption: true,\n });\n\n // Help with sub-element field toggle\n $('.subelement-toggle label').on('click', function (e) {\n var $lightswitch = $(this)\n .parents('.subelement-toggle')\n .find('.lightswitch')\n .data('lightswitch');\n\n $lightswitch.toggle();\n });\n\n // Show initially hidden element sub-fields. A little tricky because they're in a table, and all equal siblings\n $('.subelement-toggle .lightswitch').on('change', function (e) {\n var $lightswitch = $(this).data('lightswitch');\n var $tr = $(this).parents('tr');\n var $directSiblings = $tr.nextUntil(':not(.element-sub-field)');\n\n $directSiblings.toggle();\n });\n\n // If we have any element sub-fields that are being mapped, we want to show the panel to notify users they're mapping stuff\n $('.element-sub-field').each(function (index, element) {\n var mappingValue = $(this).find('.col-map select').val();\n var defaultValue = $(this).find('.col-default input').val();\n\n var rowValues = [mappingValue, defaultValue];\n var rowHasValue = false;\n\n // Check for inputs and selects which have a value\n $.each(rowValues, function (i, v) {\n if (v != '' && v != 'noimport' && v !== undefined) {\n rowHasValue = true;\n }\n });\n\n if (rowHasValue) {\n var $parentRow = $(this)\n .prevUntil(':not(.element-sub-field)')\n .addBack()\n .prev();\n var $lightswitch = $parentRow.find('.lightswitch').data('lightswitch');\n\n $lightswitch.turnOn();\n }\n });\n\n //\n // Logs\n //\n $(document).on('click', '.log-detail-link', function (e) {\n e.preventDefault();\n\n var key = $(this).data('key');\n\n $('tr[data-key=\"' + key + '\"]').toggleClass('hidden');\n });\n\n // Allow multiple submit actions, that trigger different actions as required\n $(document).on('click', 'input[data-action]', function (e) {\n var $form = $(this).parents('form');\n var action = $(this).data('action');\n\n $form.find('input[name=\"action\"]').val(action);\n $form.submit();\n });\n\n $(document).on('change', '.log-type-form .select', function (e) {\n e.preventDefault();\n\n $(this).parents('form').submit();\n });\n});\n","/**\n * sifter.js\n * Copyright (c) 2013 Brian Reavis & contributors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this\n * file except in compliance with the License. You may obtain a copy of the License at:\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n * ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n *\n * @author Brian Reavis \n */\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define('sifter', factory);\n } else if (typeof exports === 'object') {\n module.exports = factory();\n } else {\n root.Sifter = factory();\n }\n})(this, function () {\n /**\n * Textually searches arrays and hashes of objects\n * by property (or multiple properties). Designed\n * specifically for autocomplete.\n *\n * @constructor\n * @param {array|object} items\n * @param {object} items\n */\n var Sifter = function (items, settings) {\n this.items = items;\n this.settings = settings || {diacritics: true};\n };\n\n /**\n * Splits a search string into an array of individual\n * regexps to be used to match results.\n *\n * @param {string} query\n * @returns {array}\n */\n Sifter.prototype.tokenize = function (query) {\n query = trim(String(query || '').toLowerCase());\n if (!query || !query.length) return [];\n\n var i, n, regex, letter;\n var tokens = [];\n var words = query.split(/ +/);\n\n for (i = 0, n = words.length; i < n; i++) {\n regex = escape_regex(words[i]);\n if (this.settings.diacritics) {\n for (letter in DIACRITICS) {\n if (DIACRITICS.hasOwnProperty(letter)) {\n regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);\n }\n }\n }\n tokens.push({\n string: words[i],\n regex: new RegExp(regex, 'i'),\n });\n }\n\n return tokens;\n };\n\n /**\n * Iterates over arrays and hashes.\n *\n * ```\n * this.iterator(this.items, function(item, id) {\n * // invoked for each item\n * });\n * ```\n *\n * @param {array|object} object\n */\n Sifter.prototype.iterator = function (object, callback) {\n var iterator;\n if (is_array(object)) {\n iterator =\n Array.prototype.forEach ||\n function (callback) {\n for (var i = 0, n = this.length; i < n; i++) {\n callback(this[i], i, this);\n }\n };\n } else {\n iterator = function (callback) {\n for (var key in this) {\n if (this.hasOwnProperty(key)) {\n callback(this[key], key, this);\n }\n }\n };\n }\n\n iterator.apply(object, [callback]);\n };\n\n /**\n * Returns a function to be used to score individual results.\n *\n * Good matches will have a higher score than poor matches.\n * If an item is not a match, 0 will be returned by the function.\n *\n * @param {object|string} search\n * @param {object} options (optional)\n * @returns {function}\n */\n Sifter.prototype.getScoreFunction = function (search, options) {\n var self, fields, tokens, token_count;\n\n self = this;\n search = self.prepareSearch(search, options);\n tokens = search.tokens;\n fields = search.options.fields;\n token_count = tokens.length;\n\n /**\n * Calculates how close of a match the\n * given value is against a search token.\n *\n * @param {mixed} value\n * @param {object} token\n * @return {number}\n */\n var scoreValue = function (value, token) {\n var score, pos;\n\n if (!value) return 0;\n value = String(value || '');\n pos = value.search(token.regex);\n if (pos === -1) return 0;\n score = token.string.length / value.length;\n if (pos === 0) score += 0.5;\n return score;\n };\n\n /**\n * Calculates the score of an object\n * against the search query.\n *\n * @param {object} token\n * @param {object} data\n * @return {number}\n */\n var scoreObject = (function () {\n var field_count = fields.length;\n if (!field_count) {\n return function () {\n return 0;\n };\n }\n if (field_count === 1) {\n return function (token, data) {\n return scoreValue(data[fields[0]], token);\n };\n }\n return function (token, data) {\n for (var i = 0, sum = 0; i < field_count; i++) {\n sum += scoreValue(data[fields[i]], token);\n }\n return sum / field_count;\n };\n })();\n\n if (!token_count) {\n return function () {\n return 0;\n };\n }\n if (token_count === 1) {\n return function (data) {\n return scoreObject(tokens[0], data);\n };\n }\n\n if (search.options.conjunction === 'and') {\n return function (data) {\n var score;\n for (var i = 0, sum = 0; i < token_count; i++) {\n score = scoreObject(tokens[i], data);\n if (score <= 0) return 0;\n sum += score;\n }\n return sum / token_count;\n };\n } else {\n return function (data) {\n for (var i = 0, sum = 0; i < token_count; i++) {\n sum += scoreObject(tokens[i], data);\n }\n return sum / token_count;\n };\n }\n };\n\n /**\n * Returns a function that can be used to compare two\n * results, for sorting purposes. If no sorting should\n * be performed, `null` will be returned.\n *\n * @param {string|object} search\n * @param {object} options\n * @return function(a,b)\n */\n Sifter.prototype.getSortFunction = function (search, options) {\n var i,\n n,\n self,\n field,\n fields,\n fields_count,\n multiplier,\n multipliers,\n get_field,\n implicit_score,\n sort;\n\n self = this;\n search = self.prepareSearch(search, options);\n sort = (!search.query && options.sort_empty) || options.sort;\n\n /**\n * Fetches the specified sort field value\n * from a search result item.\n *\n * @param {string} name\n * @param {object} result\n * @return {mixed}\n */\n get_field = function (name, result) {\n if (name === '$score') return result.score;\n return self.items[result.id][name];\n };\n\n // parse options\n fields = [];\n if (sort) {\n for (i = 0, n = sort.length; i < n; i++) {\n if (search.query || sort[i].field !== '$score') {\n fields.push(sort[i]);\n }\n }\n }\n\n // the \"$score\" field is implied to be the primary\n // sort field, unless it's manually specified\n if (search.query) {\n implicit_score = true;\n for (i = 0, n = fields.length; i < n; i++) {\n if (fields[i].field === '$score') {\n implicit_score = false;\n break;\n }\n }\n if (implicit_score) {\n fields.unshift({field: '$score', direction: 'desc'});\n }\n } else {\n for (i = 0, n = fields.length; i < n; i++) {\n if (fields[i].field === '$score') {\n fields.splice(i, 1);\n break;\n }\n }\n }\n\n multipliers = [];\n for (i = 0, n = fields.length; i < n; i++) {\n multipliers.push(fields[i].direction === 'desc' ? -1 : 1);\n }\n\n // build function\n fields_count = fields.length;\n if (!fields_count) {\n return null;\n } else if (fields_count === 1) {\n field = fields[0].field;\n multiplier = multipliers[0];\n return function (a, b) {\n return multiplier * cmp(get_field(field, a), get_field(field, b));\n };\n } else {\n return function (a, b) {\n var i, result, a_value, b_value, field;\n for (i = 0; i < fields_count; i++) {\n field = fields[i].field;\n result =\n multipliers[i] * cmp(get_field(field, a), get_field(field, b));\n if (result) return result;\n }\n return 0;\n };\n }\n };\n\n /**\n * Parses a search query and returns an object\n * with tokens and fields ready to be populated\n * with results.\n *\n * @param {string} query\n * @param {object} options\n * @returns {object}\n */\n Sifter.prototype.prepareSearch = function (query, options) {\n if (typeof query === 'object') return query;\n\n options = extend({}, options);\n\n var option_fields = options.fields;\n var option_sort = options.sort;\n var option_sort_empty = options.sort_empty;\n\n if (option_fields && !is_array(option_fields))\n options.fields = [option_fields];\n if (option_sort && !is_array(option_sort)) options.sort = [option_sort];\n if (option_sort_empty && !is_array(option_sort_empty))\n options.sort_empty = [option_sort_empty];\n\n return {\n options: options,\n query: String(query || '').toLowerCase(),\n tokens: this.tokenize(query),\n total: 0,\n items: [],\n };\n };\n\n /**\n * Searches through all items and returns a sorted array of matches.\n *\n * The `options` parameter can contain:\n *\n * - fields {string|array}\n * - sort {array}\n * - score {function}\n * - filter {bool}\n * - limit {integer}\n *\n * Returns an object containing:\n *\n * - options {object}\n * - query {string}\n * - tokens {array}\n * - total {int}\n * - items {array}\n *\n * @param {string} query\n * @param {object} options\n * @returns {object}\n */\n Sifter.prototype.search = function (query, options) {\n var self = this,\n value,\n score,\n search,\n calculateScore;\n var fn_sort;\n var fn_score;\n\n search = this.prepareSearch(query, options);\n options = search.options;\n query = search.query;\n\n // generate result scoring function\n fn_score = options.score || self.getScoreFunction(search);\n\n // perform search and sort\n if (query.length) {\n self.iterator(self.items, function (item, id) {\n score = fn_score(item);\n if (options.filter === false || score > 0) {\n search.items.push({score: score, id: id});\n }\n });\n } else {\n self.iterator(self.items, function (item, id) {\n search.items.push({score: 1, id: id});\n });\n }\n\n fn_sort = self.getSortFunction(search, options);\n if (fn_sort) search.items.sort(fn_sort);\n\n // apply limits\n search.total = search.items.length;\n if (typeof options.limit === 'number') {\n search.items = search.items.slice(0, options.limit);\n }\n\n return search;\n };\n\n // utilities\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n var cmp = function (a, b) {\n if (typeof a === 'number' && typeof b === 'number') {\n return a > b ? 1 : a < b ? -1 : 0;\n }\n a = asciifold(String(a || ''));\n b = asciifold(String(b || ''));\n if (a > b) return 1;\n if (b > a) return -1;\n return 0;\n };\n\n var extend = function (a, b) {\n var i, n, k, object;\n for (i = 1, n = arguments.length; i < n; i++) {\n object = arguments[i];\n if (!object) continue;\n for (k in object) {\n if (object.hasOwnProperty(k)) {\n a[k] = object[k];\n }\n }\n }\n return a;\n };\n\n var trim = function (str) {\n return (str + '').replace(/^\\s+|\\s+$|/g, '');\n };\n\n var escape_regex = function (str) {\n return (str + '').replace(/([.?*+^$[\\]\\\\(){}|-])/g, '\\\\$1');\n };\n\n var is_array =\n Array.isArray ||\n ($ && $.isArray) ||\n function (object) {\n return Object.prototype.toString.call(object) === '[object Array]';\n };\n\n var DIACRITICS = {\n a: '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',\n c: '[cÇçćĆčČ]',\n d: '[dđĐďĎ]',\n e: '[eÈÉÊËèéêëěĚĒēęĘ]',\n i: '[iÌÍÎÏìíîïĪī]',\n l: '[lłŁ]',\n n: '[nÑñňŇńŃ]',\n o: '[oÒÓÔÕÕÖØòóôõöøŌō]',\n r: '[rřŘ]',\n s: '[sŠšśŚ]',\n t: '[tťŤ]',\n u: '[uÙÚÛÜùúûüůŮŪū]',\n y: '[yŸÿýÝ]',\n z: '[zŽžżŻźŹ]',\n };\n\n var asciifold = (function () {\n var i, n, k, chunk;\n var foreignletters = '';\n var lookup = {};\n for (k in DIACRITICS) {\n if (DIACRITICS.hasOwnProperty(k)) {\n chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);\n foreignletters += chunk;\n for (i = 0, n = chunk.length; i < n; i++) {\n lookup[chunk.charAt(i)] = k;\n }\n }\n }\n var regexp = new RegExp('[' + foreignletters + ']', 'g');\n return function (str) {\n return str\n .replace(regexp, function (foreignletter) {\n return lookup[foreignletter];\n })\n .toLowerCase();\n };\n })();\n\n // export\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n return Sifter;\n});\n\n/**\n * microplugin.js\n * Copyright (c) 2013 Brian Reavis & contributors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this\n * file except in compliance with the License. You may obtain a copy of the License at:\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n * ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n *\n * @author Brian Reavis \n */\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define('microplugin', factory);\n } else if (typeof exports === 'object') {\n module.exports = factory();\n } else {\n root.MicroPlugin = factory();\n }\n})(this, function () {\n var MicroPlugin = {};\n\n MicroPlugin.mixin = function (Interface) {\n Interface.plugins = {};\n\n /**\n * Initializes the listed plugins (with options).\n * Acceptable formats:\n *\n * List (without options):\n * ['a', 'b', 'c']\n *\n * List (with options):\n * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]\n *\n * Hash (with options):\n * {'a': { ... }, 'b': { ... }, 'c': { ... }}\n *\n * @param {mixed} plugins\n */\n Interface.prototype.initializePlugins = function (plugins) {\n var i, n, key;\n var self = this;\n var queue = [];\n\n self.plugins = {\n names: [],\n settings: {},\n requested: {},\n loaded: {},\n };\n\n if (utils.isArray(plugins)) {\n for (i = 0, n = plugins.length; i < n; i++) {\n if (typeof plugins[i] === 'string') {\n queue.push(plugins[i]);\n } else {\n self.plugins.settings[plugins[i].name] = plugins[i].options;\n queue.push(plugins[i].name);\n }\n }\n } else if (plugins) {\n for (key in plugins) {\n if (plugins.hasOwnProperty(key)) {\n self.plugins.settings[key] = plugins[key];\n queue.push(key);\n }\n }\n }\n\n while (queue.length) {\n self.require(queue.shift());\n }\n };\n\n Interface.prototype.loadPlugin = function (name) {\n var self = this;\n var plugins = self.plugins;\n var plugin = Interface.plugins[name];\n\n if (!Interface.plugins.hasOwnProperty(name)) {\n throw new Error('Unable to find \"' + name + '\" plugin');\n }\n\n plugins.requested[name] = true;\n plugins.loaded[name] = plugin.fn.apply(self, [\n self.plugins.settings[name] || {},\n ]);\n plugins.names.push(name);\n };\n\n /**\n * Initializes a plugin.\n *\n * @param {string} name\n */\n Interface.prototype.require = function (name) {\n var self = this;\n var plugins = self.plugins;\n\n if (!self.plugins.loaded.hasOwnProperty(name)) {\n if (plugins.requested[name]) {\n throw new Error('Plugin has circular dependency (\"' + name + '\")');\n }\n self.loadPlugin(name);\n }\n\n return plugins.loaded[name];\n };\n\n /**\n * Registers a plugin.\n *\n * @param {string} name\n * @param {function} fn\n */\n Interface.define = function (name, fn) {\n Interface.plugins[name] = {\n name: name,\n fn: fn,\n };\n };\n };\n\n var utils = {\n isArray:\n Array.isArray ||\n function (vArg) {\n return Object.prototype.toString.call(vArg) === '[object Array]';\n },\n };\n\n return MicroPlugin;\n});\n\n/**\n * selectize.js (v0.12.1)\n * Copyright (c) 2013–2015 Brian Reavis & contributors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this\n * file except in compliance with the License. You may obtain a copy of the License at:\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n * ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n *\n * @author Brian Reavis \n */\n\n/*jshint curly:false */\n/*jshint browser:true */\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define('selectize', ['jquery', 'sifter', 'microplugin'], factory);\n } else if (typeof exports === 'object') {\n module.exports = factory(\n require('jquery'),\n require('sifter'),\n require('microplugin')\n );\n } else {\n root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);\n }\n})(this, function ($, Sifter, MicroPlugin) {\n 'use strict';\n\n var highlight = function ($element, pattern) {\n if (typeof pattern === 'string' && !pattern.length) return;\n var regex =\n typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;\n\n var highlight = function (node) {\n var skip = 0;\n if (node.nodeType === 3) {\n var pos = node.data.search(regex);\n if (pos >= 0 && node.data.length > 0) {\n var match = node.data.match(regex);\n var spannode = document.createElement('span');\n spannode.className = 'highlight';\n var middlebit = node.splitText(pos);\n var endbit = middlebit.splitText(match[0].length);\n var middleclone = middlebit.cloneNode(true);\n spannode.appendChild(middleclone);\n middlebit.parentNode.replaceChild(spannode, middlebit);\n skip = 1;\n }\n } else if (\n node.nodeType === 1 &&\n node.childNodes &&\n !/(script|style)/i.test(node.tagName)\n ) {\n for (var i = 0; i < node.childNodes.length; ++i) {\n i += highlight(node.childNodes[i]);\n }\n }\n return skip;\n };\n\n return $element.each(function () {\n highlight(this);\n });\n };\n\n var MicroEvent = function () {};\n MicroEvent.prototype = {\n on: function (event, fct) {\n this._events = this._events || {};\n this._events[event] = this._events[event] || [];\n this._events[event].push(fct);\n },\n off: function (event, fct) {\n var n = arguments.length;\n if (n === 0) return delete this._events;\n if (n === 1) return delete this._events[event];\n\n this._events = this._events || {};\n if (event in this._events === false) return;\n this._events[event].splice(this._events[event].indexOf(fct), 1);\n },\n trigger: function (event /* , args... */) {\n this._events = this._events || {};\n if (event in this._events === false) return;\n for (var i = 0; i < this._events[event].length; i++) {\n this._events[event][i].apply(\n this,\n Array.prototype.slice.call(arguments, 1)\n );\n }\n },\n };\n\n /**\n * Mixin will delegate all MicroEvent.js function in the destination object.\n *\n * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent\n *\n * @param {object} the object which will support MicroEvent\n */\n MicroEvent.mixin = function (destObject) {\n var props = ['on', 'off', 'trigger'];\n for (var i = 0; i < props.length; i++) {\n destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];\n }\n };\n\n var IS_MAC = /Mac/.test(navigator.userAgent);\n\n var KEY_A = 65;\n var KEY_COMMA = 188;\n var KEY_RETURN = 13;\n var KEY_ESC = 27;\n var KEY_LEFT = 37;\n var KEY_UP = 38;\n var KEY_P = 80;\n var KEY_RIGHT = 39;\n var KEY_DOWN = 40;\n var KEY_N = 78;\n var KEY_BACKSPACE = 8;\n var KEY_DELETE = 46;\n var KEY_SHIFT = 16;\n var KEY_CMD = IS_MAC ? 91 : 17;\n var KEY_CTRL = IS_MAC ? 18 : 17;\n var KEY_TAB = 9;\n\n var TAG_SELECT = 1;\n var TAG_INPUT = 2;\n\n // for now, android support in general is too spotty to support validity\n var SUPPORTS_VALIDITY_API =\n !/android/i.test(window.navigator.userAgent) &&\n !!document.createElement('form').validity;\n\n var isset = function (object) {\n return typeof object !== 'undefined';\n };\n\n /**\n * Converts a scalar to its best string representation\n * for hash keys and HTML attribute values.\n *\n * Transformations:\n * 'str' -> 'str'\n * null -> ''\n * undefined -> ''\n * true -> '1'\n * false -> '0'\n * 0 -> '0'\n * 1 -> '1'\n *\n * @param {string} value\n * @returns {string|null}\n */\n var hash_key = function (value) {\n if (typeof value === 'undefined' || value === null) return null;\n if (typeof value === 'boolean') return value ? '1' : '0';\n return value + '';\n };\n\n /**\n * Escapes a string for use within HTML.\n *\n * @param {string} str\n * @returns {string}\n */\n var escape_html = function (str) {\n return (str + '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"');\n };\n\n /**\n * Escapes \"$\" characters in replacement strings.\n *\n * @param {string} str\n * @returns {string}\n */\n var escape_replace = function (str) {\n return (str + '').replace(/\\$/g, '$$$$');\n };\n\n var hook = {};\n\n /**\n * Wraps `method` on `self` so that `fn`\n * is invoked before the original method.\n *\n * @param {object} self\n * @param {string} method\n * @param {function} fn\n */\n hook.before = function (self, method, fn) {\n var original = self[method];\n self[method] = function () {\n fn.apply(self, arguments);\n return original.apply(self, arguments);\n };\n };\n\n /**\n * Wraps `method` on `self` so that `fn`\n * is invoked after the original method.\n *\n * @param {object} self\n * @param {string} method\n * @param {function} fn\n */\n hook.after = function (self, method, fn) {\n var original = self[method];\n self[method] = function () {\n var result = original.apply(self, arguments);\n fn.apply(self, arguments);\n return result;\n };\n };\n\n /**\n * Wraps `fn` so that it can only be invoked once.\n *\n * @param {function} fn\n * @returns {function}\n */\n var once = function (fn) {\n var called = false;\n return function () {\n if (called) return;\n called = true;\n fn.apply(this, arguments);\n };\n };\n\n /**\n * Wraps `fn` so that it can only be called once\n * every `delay` milliseconds (invoked on the falling edge).\n *\n * @param {function} fn\n * @param {int} delay\n * @returns {function}\n */\n var debounce = function (fn, delay) {\n var timeout;\n return function () {\n var self = this;\n var args = arguments;\n window.clearTimeout(timeout);\n timeout = window.setTimeout(function () {\n fn.apply(self, args);\n }, delay);\n };\n };\n\n /**\n * Debounce all fired events types listed in `types`\n * while executing the provided `fn`.\n *\n * @param {object} self\n * @param {array} types\n * @param {function} fn\n */\n var debounce_events = function (self, types, fn) {\n var type;\n var trigger = self.trigger;\n var event_args = {};\n\n // override trigger method\n self.trigger = function () {\n var type = arguments[0];\n if (types.indexOf(type) !== -1) {\n event_args[type] = arguments;\n } else {\n return trigger.apply(self, arguments);\n }\n };\n\n // invoke provided function\n fn.apply(self, []);\n self.trigger = trigger;\n\n // trigger queued events\n for (type in event_args) {\n if (event_args.hasOwnProperty(type)) {\n trigger.apply(self, event_args[type]);\n }\n }\n };\n\n /**\n * A workaround for http://bugs.jquery.com/ticket/6696\n *\n * @param {object} $parent - Parent element to listen on.\n * @param {string} event - Event name.\n * @param {string} selector - Descendant selector to filter by.\n * @param {function} fn - Event handler.\n */\n var watchChildEvent = function ($parent, event, selector, fn) {\n $parent.on(event, selector, function (e) {\n var child = e.target;\n while (child && child.parentNode !== $parent[0]) {\n child = child.parentNode;\n }\n e.currentTarget = child;\n return fn.apply(this, [e]);\n });\n };\n\n /**\n * Determines the current selection within a text input control.\n * Returns an object containing:\n * - start\n * - length\n *\n * @param {object} input\n * @returns {object}\n */\n var getSelection = function (input) {\n var result = {};\n if ('selectionStart' in input) {\n result.start = input.selectionStart;\n result.length = input.selectionEnd - result.start;\n } else if (document.selection) {\n input.focus();\n var sel = document.selection.createRange();\n var selLen = document.selection.createRange().text.length;\n sel.moveStart('character', -input.value.length);\n result.start = sel.text.length - selLen;\n result.length = selLen;\n }\n return result;\n };\n\n /**\n * Copies CSS properties from one element to another.\n *\n * @param {object} $from\n * @param {object} $to\n * @param {array} properties\n */\n var transferStyles = function ($from, $to, properties) {\n var i,\n n,\n styles = {};\n if (properties) {\n for (i = 0, n = properties.length; i < n; i++) {\n styles[properties[i]] = $from.css(properties[i]);\n }\n } else {\n styles = $from.css();\n }\n $to.css(styles);\n };\n\n /**\n * Measures the width of a string within a\n * parent element (in pixels).\n *\n * @param {string} str\n * @param {object} $parent\n * @returns {int}\n */\n var measureString = function (str, $parent) {\n if (!str) {\n return 0;\n }\n\n var $test = $('')\n .css({\n position: 'absolute',\n top: -99999,\n left: -99999,\n width: 'auto',\n padding: 0,\n whiteSpace: 'pre',\n })\n .text(str)\n .appendTo('body');\n\n transferStyles($parent, $test, [\n 'letterSpacing',\n 'fontSize',\n 'fontFamily',\n 'fontWeight',\n 'textTransform',\n ]);\n\n var width = $test.width();\n $test.remove();\n\n return width;\n };\n\n /**\n * Sets up an input to grow horizontally as the user\n * types. If the value is changed manually, you can\n * trigger the \"update\" handler to resize:\n *\n * $input.trigger('update');\n *\n * @param {object} $input\n */\n var autoGrow = function ($input) {\n var currentWidth = null;\n\n var update = function (e, options) {\n var value, keyCode, printable, placeholder, width;\n var shift, character, selection;\n e = e || window.event || {};\n options = options || {};\n\n if (e.metaKey || e.altKey) return;\n if (!options.force && $input.data('grow') === false) return;\n\n value = $input.val();\n if (e.type && e.type.toLowerCase() === 'keydown') {\n keyCode = e.keyCode;\n printable =\n (keyCode >= 97 && keyCode <= 122) || // a-z\n (keyCode >= 65 && keyCode <= 90) || // A-Z\n (keyCode >= 48 && keyCode <= 57) || // 0-9\n keyCode === 32; // space\n\n if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {\n selection = getSelection($input[0]);\n if (selection.length) {\n value =\n value.substring(0, selection.start) +\n value.substring(selection.start + selection.length);\n } else if (keyCode === KEY_BACKSPACE && selection.start) {\n value =\n value.substring(0, selection.start - 1) +\n value.substring(selection.start + 1);\n } else if (\n keyCode === KEY_DELETE &&\n typeof selection.start !== 'undefined'\n ) {\n value =\n value.substring(0, selection.start) +\n value.substring(selection.start + 1);\n }\n } else if (printable) {\n shift = e.shiftKey;\n character = String.fromCharCode(e.keyCode);\n if (shift) character = character.toUpperCase();\n else character = character.toLowerCase();\n value += character;\n }\n }\n\n placeholder = $input.attr('placeholder');\n if (!value && placeholder) {\n value = placeholder;\n }\n\n width = measureString(value, $input) + 4;\n if (width !== currentWidth) {\n currentWidth = width;\n $input.width(width);\n $input.triggerHandler('resize');\n }\n };\n\n $input.on('keydown keyup update blur', update);\n update();\n };\n\n var Selectize = function ($input, settings) {\n var key,\n i,\n n,\n dir,\n input,\n self = this;\n input = $input[0];\n input.selectize = self;\n\n // detect rtl environment\n var computedStyle =\n window.getComputedStyle && window.getComputedStyle(input, null);\n dir = computedStyle\n ? computedStyle.getPropertyValue('direction')\n : input.currentStyle && input.currentStyle.direction;\n dir = dir || $input.parents('[dir]:first').attr('dir') || '';\n\n // setup default state\n $.extend(self, {\n order: 0,\n settings: settings,\n $input: $input,\n tabIndex: $input.attr('tabindex') || '',\n tagType:\n input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,\n rtl: /rtl/i.test(dir),\n\n eventNS: '.selectize' + ++Selectize.count,\n highlightedValue: null,\n isOpen: false,\n isDisabled: false,\n isRequired: $input.is('[required]'),\n isInvalid: false,\n isLocked: false,\n isFocused: false,\n isInputHidden: false,\n isSetup: false,\n isShiftDown: false,\n isCmdDown: false,\n isCtrlDown: false,\n ignoreFocus: false,\n ignoreBlur: false,\n ignoreHover: false,\n hasOptions: false,\n currentResults: null,\n lastValue: '',\n caretPos: 0,\n loading: 0,\n loadedSearches: {},\n\n $activeOption: null,\n $activeItems: [],\n\n optgroups: {},\n options: {},\n userOptions: {},\n items: [],\n renderCache: {},\n onSearchChange:\n settings.loadThrottle === null\n ? self.onSearchChange\n : debounce(self.onSearchChange, settings.loadThrottle),\n });\n\n // search system\n self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});\n\n // build options table\n if (self.settings.options) {\n for (i = 0, n = self.settings.options.length; i < n; i++) {\n self.registerOption(self.settings.options[i]);\n }\n delete self.settings.options;\n }\n\n // build optgroup table\n if (self.settings.optgroups) {\n for (i = 0, n = self.settings.optgroups.length; i < n; i++) {\n self.registerOptionGroup(self.settings.optgroups[i]);\n }\n delete self.settings.optgroups;\n }\n\n // option-dependent defaults\n self.settings.mode =\n self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');\n if (typeof self.settings.hideSelected !== 'boolean') {\n self.settings.hideSelected = self.settings.mode === 'multi';\n }\n\n self.initializePlugins(self.settings.plugins);\n self.setupCallbacks();\n self.setupTemplates();\n self.setup();\n };\n\n // mixins\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n MicroEvent.mixin(Selectize);\n MicroPlugin.mixin(Selectize);\n\n // methods\n // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n $.extend(Selectize.prototype, {\n /**\n * Creates all elements and sets up event bindings.\n */\n setup: function () {\n var self = this;\n var settings = self.settings;\n var eventNS = self.eventNS;\n var $window = $(window);\n var $document = $(document);\n var $input = self.$input;\n\n var $wrapper;\n var $control;\n var $control_input;\n var $dropdown;\n var $dropdown_content;\n var $dropdown_parent;\n var inputMode;\n var timeout_blur;\n var timeout_focus;\n var classes;\n var classes_plugins;\n\n inputMode = self.settings.mode;\n classes = $input.attr('class') || '';\n\n $wrapper = $('
')\n .addClass(settings.wrapperClass)\n .addClass(classes)\n .addClass(inputMode);\n $control = $('
')\n .addClass(settings.inputClass)\n .addClass('items')\n .appendTo($wrapper);\n /* HACK */\n //$control_input = $('').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);\n $control_input = $('')\n .appendTo($control)\n .attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);\n /* END HACK */\n $dropdown_parent = $(settings.dropdownParent || $wrapper);\n $dropdown = $('
')\n .addClass(settings.dropdownClass)\n .addClass(inputMode)\n .hide()\n .appendTo($dropdown_parent);\n $dropdown_content = $('
')\n .addClass(settings.dropdownContentClass)\n .appendTo($dropdown);\n\n if (self.settings.copyClassesToDropdown) {\n $dropdown.addClass(classes);\n }\n\n $wrapper.css({\n width: $input[0].style.width,\n });\n\n if (self.plugins.names.length) {\n classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');\n $wrapper.addClass(classes_plugins);\n $dropdown.addClass(classes_plugins);\n }\n\n if (\n (settings.maxItems === null || settings.maxItems > 1) &&\n self.tagType === TAG_SELECT\n ) {\n $input.attr('multiple', 'multiple');\n }\n\n if (self.settings.placeholder) {\n $control_input.attr('placeholder', settings.placeholder);\n }\n\n // if splitOn was not passed in, construct it from the delimiter to allow pasting universally\n if (!self.settings.splitOn && self.settings.delimiter) {\n var delimiterEscaped = self.settings.delimiter.replace(\n /[-\\/\\\\^$*+?.()|[\\]{}]/g,\n '\\\\$&'\n );\n self.settings.splitOn = new RegExp('\\\\s*' + delimiterEscaped + '+\\\\s*');\n }\n\n if ($input.attr('autocorrect')) {\n $control_input.attr('autocorrect', $input.attr('autocorrect'));\n }\n\n if ($input.attr('autocapitalize')) {\n $control_input.attr('autocapitalize', $input.attr('autocapitalize'));\n }\n\n self.$wrapper = $wrapper;\n self.$control = $control;\n self.$control_input = $control_input;\n self.$dropdown = $dropdown;\n self.$dropdown_content = $dropdown_content;\n\n $dropdown.on('mouseenter', '[data-selectable]', function () {\n return self.onOptionHover.apply(self, arguments);\n });\n $dropdown.on('mousedown click', '[data-selectable]', function () {\n return self.onOptionSelect.apply(self, arguments);\n });\n watchChildEvent($control, 'mousedown', '*:not(input)', function () {\n return self.onItemSelect.apply(self, arguments);\n });\n autoGrow($control_input);\n\n $control.on({\n mousedown: function () {\n return self.onMouseDown.apply(self, arguments);\n },\n click: function () {\n return self.onClick.apply(self, arguments);\n },\n });\n\n $control_input.on({\n mousedown: function (e) {\n e.stopPropagation();\n },\n keydown: function () {\n return self.onKeyDown.apply(self, arguments);\n },\n keyup: function () {\n return self.onKeyUp.apply(self, arguments);\n },\n keypress: function () {\n return self.onKeyPress.apply(self, arguments);\n },\n resize: function () {\n self.positionDropdown.apply(self, []);\n },\n blur: function () {\n return self.onBlur.apply(self, arguments);\n },\n focus: function () {\n self.ignoreBlur = false;\n return self.onFocus.apply(self, arguments);\n },\n paste: function () {\n return self.onPaste.apply(self, arguments);\n },\n });\n\n $document.on('keydown' + eventNS, function (e) {\n self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];\n self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];\n self.isShiftDown = e.shiftKey;\n });\n\n $document.on('keyup' + eventNS, function (e) {\n if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;\n if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;\n if (e.keyCode === KEY_CMD) self.isCmdDown = false;\n });\n\n $document.on('mousedown' + eventNS, function (e) {\n if (self.isFocused) {\n // prevent events on the dropdown scrollbar from causing the control to blur\n if (\n e.target === self.$dropdown[0] ||\n e.target.parentNode === self.$dropdown[0]\n ) {\n return false;\n }\n // blur on click outside\n if (\n !self.$control.has(e.target).length &&\n e.target !== self.$control[0]\n ) {\n self.blur(e.target);\n }\n }\n });\n\n $window.on(\n ['scroll' + eventNS, 'resize' + eventNS].join(' '),\n function () {\n if (self.isOpen) {\n self.positionDropdown.apply(self, arguments);\n }\n }\n );\n $window.on('mousemove' + eventNS, function () {\n self.ignoreHover = false;\n });\n\n // store original children and tab index so that they can be\n // restored when the destroy() method is called.\n this.revertSettings = {\n $children: $input.children().detach(),\n tabindex: $input.attr('tabindex'),\n };\n\n $input.attr('tabindex', -1).hide().after(self.$wrapper);\n\n if ($.isArray(settings.items)) {\n self.setValue(settings.items);\n delete settings.items;\n }\n\n // feature detect for the validation API\n if (SUPPORTS_VALIDITY_API) {\n $input.on('invalid' + eventNS, function (e) {\n e.preventDefault();\n self.isInvalid = true;\n self.refreshState();\n });\n }\n\n self.updateOriginalInput();\n self.refreshItems();\n self.refreshState();\n self.updatePlaceholder();\n self.isSetup = true;\n\n if ($input.is(':disabled')) {\n self.disable();\n }\n\n self.on('change', this.onChange);\n\n $input.data('selectize', self);\n $input.addClass('selectized');\n self.trigger('initialize');\n\n // preload options\n if (settings.preload === true) {\n self.onSearchChange('');\n }\n },\n\n /**\n * Sets up default rendering functions.\n */\n setupTemplates: function () {\n var self = this;\n var field_label = self.settings.labelField;\n var field_optgroup = self.settings.optgroupLabelField;\n\n var templates = {\n optgroup: function (data) {\n return '
' + data.html + '
';\n },\n optgroup_header: function (data, escape) {\n return (\n '
' +\n escape(data[field_optgroup]) +\n '
'\n );\n },\n option: function (data, escape) {\n return '
' + escape(data[field_label]) + '
';\n },\n item: function (data, escape) {\n return '
' + escape(data[field_label]) + '
';\n },\n option_create: function (data, escape) {\n return (\n '
Add ' +\n escape(data.input) +\n '
'\n );\n },\n };\n\n self.settings.render = $.extend({}, templates, self.settings.render);\n },\n\n /**\n * Maps fired events to callbacks provided\n * in the settings used when creating the control.\n */\n setupCallbacks: function () {\n var key,\n fn,\n callbacks = {\n initialize: 'onInitialize',\n change: 'onChange',\n item_add: 'onItemAdd',\n item_remove: 'onItemRemove',\n clear: 'onClear',\n option_add: 'onOptionAdd',\n option_remove: 'onOptionRemove',\n option_clear: 'onOptionClear',\n optgroup_add: 'onOptionGroupAdd',\n optgroup_remove: 'onOptionGroupRemove',\n optgroup_clear: 'onOptionGroupClear',\n dropdown_open: 'onDropdownOpen',\n dropdown_close: 'onDropdownClose',\n type: 'onType',\n load: 'onLoad',\n focus: 'onFocus',\n blur: 'onBlur',\n };\n\n for (key in callbacks) {\n if (callbacks.hasOwnProperty(key)) {\n fn = this.settings[callbacks[key]];\n if (fn) this.on(key, fn);\n }\n }\n },\n\n /**\n * Triggered when the main control element\n * has a click event.\n *\n * @param {object} e\n * @return {boolean}\n */\n onClick: function (e) {\n var self = this;\n\n // necessary for mobile webkit devices (manual focus triggering\n // is ignored unless invoked within a click event)\n if (!self.isFocused) {\n self.focus();\n e.preventDefault();\n }\n },\n\n /**\n * Triggered when the main control element\n * has a mouse down event.\n *\n * @param {object} e\n * @return {boolean}\n */\n onMouseDown: function (e) {\n var self = this;\n var defaultPrevented = e.isDefaultPrevented();\n var $target = $(e.target);\n\n if (self.isFocused) {\n // retain focus by preventing native handling. if the\n // event target is the input it should not be modified.\n // otherwise, text selection within the input won't work.\n if (e.target !== self.$control_input[0]) {\n if (self.settings.mode === 'single') {\n // toggle dropdown\n self.isOpen ? self.close() : self.open();\n } else if (!defaultPrevented) {\n self.setActiveItem(null);\n }\n return false;\n }\n } else {\n // give control focus\n if (!defaultPrevented) {\n window.setTimeout(function () {\n self.focus();\n }, 0);\n }\n }\n },\n\n /**\n * Triggered when the value of the control has been changed.\n * This should propagate the event to the original DOM\n * input / select element.\n */\n onChange: function () {\n this.$input.trigger('change');\n },\n\n /**\n * Triggered on paste.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onPaste: function (e) {\n var self = this;\n if (self.isFull() || self.isInputHidden || self.isLocked) {\n e.preventDefault();\n } else {\n // If a regex or string is included, this will split the pasted\n // input and create Items for each separate value\n if (self.settings.splitOn) {\n setTimeout(function () {\n var splitInput = $.trim(self.$control_input.val() || '').split(\n self.settings.splitOn\n );\n for (var i = 0, n = splitInput.length; i < n; i++) {\n self.createItem(splitInput[i]);\n }\n }, 0);\n }\n }\n },\n\n /**\n * Triggered on keypress.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyPress: function (e) {\n if (this.isLocked) return e && e.preventDefault();\n var character = String.fromCharCode(e.keyCode || e.which);\n if (\n this.settings.create &&\n this.settings.mode === 'multi' &&\n character === this.settings.delimiter\n ) {\n this.createItem();\n e.preventDefault();\n return false;\n }\n },\n\n /**\n * Triggered on keydown.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyDown: function (e) {\n var isInput = e.target === this.$control_input[0];\n var self = this;\n\n if (self.isLocked) {\n if (e.keyCode !== KEY_TAB) {\n e.preventDefault();\n }\n return;\n }\n\n switch (e.keyCode) {\n case KEY_A:\n if (self.isCmdDown) {\n self.selectAll();\n return;\n }\n break;\n case KEY_ESC:\n if (self.isOpen) {\n e.preventDefault();\n e.stopPropagation();\n self.close();\n }\n return;\n case KEY_N:\n if (!e.ctrlKey || e.altKey) break;\n case KEY_DOWN:\n if (!self.isOpen && self.hasOptions) {\n self.open();\n } else if (self.$activeOption) {\n self.ignoreHover = true;\n var $next = self.getAdjacentOption(self.$activeOption, 1);\n if ($next.length) self.setActiveOption($next, true, true);\n }\n e.preventDefault();\n return;\n case KEY_P:\n if (!e.ctrlKey || e.altKey) break;\n case KEY_UP:\n if (self.$activeOption) {\n self.ignoreHover = true;\n var $prev = self.getAdjacentOption(self.$activeOption, -1);\n if ($prev.length) self.setActiveOption($prev, true, true);\n }\n e.preventDefault();\n return;\n case KEY_RETURN:\n if (self.isOpen && self.$activeOption) {\n self.onOptionSelect({currentTarget: self.$activeOption});\n e.preventDefault();\n }\n return;\n case KEY_LEFT:\n self.advanceSelection(-1, e);\n return;\n case KEY_RIGHT:\n self.advanceSelection(1, e);\n return;\n case KEY_TAB:\n if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {\n self.onOptionSelect({currentTarget: self.$activeOption});\n\n // Default behaviour is to jump to the next field, we only want this\n // if the current field doesn't accept any more entries\n if (!self.isFull()) {\n e.preventDefault();\n }\n }\n if (self.settings.create && self.createItem()) {\n e.preventDefault();\n }\n return;\n case KEY_BACKSPACE:\n case KEY_DELETE:\n self.deleteSelection(e);\n return;\n }\n\n if (\n (self.isFull() || self.isInputHidden) &&\n !(IS_MAC ? e.metaKey : e.ctrlKey)\n ) {\n e.preventDefault();\n return;\n }\n },\n\n /**\n * Triggered on keyup.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyUp: function (e) {\n var self = this;\n\n if (self.isLocked) return e && e.preventDefault();\n var value = self.$control_input.val() || '';\n if (self.lastValue !== value) {\n self.lastValue = value;\n self.onSearchChange(value);\n self.refreshOptions();\n self.trigger('type', value);\n }\n },\n\n /**\n * Invokes the user-provide option provider / loader.\n *\n * Note: this function is debounced in the Selectize\n * constructor (by `settings.loadDelay` milliseconds)\n *\n * @param {string} value\n */\n onSearchChange: function (value) {\n var self = this;\n var fn = self.settings.load;\n if (!fn) return;\n if (self.loadedSearches.hasOwnProperty(value)) return;\n self.loadedSearches[value] = true;\n self.load(function (callback) {\n fn.apply(self, [value, callback]);\n });\n },\n\n /**\n * Triggered on focus.\n *\n * @param {object} e (optional)\n * @returns {boolean}\n */\n onFocus: function (e) {\n var self = this;\n var wasFocused = self.isFocused;\n\n if (self.isDisabled) {\n self.blur();\n e && e.preventDefault();\n return false;\n }\n\n if (self.ignoreFocus) return;\n self.isFocused = true;\n if (self.settings.preload === 'focus') self.onSearchChange('');\n\n if (!wasFocused) self.trigger('focus');\n\n if (!self.$activeItems.length) {\n self.showInput();\n self.setActiveItem(null);\n self.refreshOptions(!!self.settings.openOnFocus);\n }\n\n self.refreshState();\n },\n\n /**\n * Triggered on blur.\n *\n * @param {object} e\n * @param {Element} dest\n */\n onBlur: function (e, dest) {\n var self = this;\n if (!self.isFocused) return;\n self.isFocused = false;\n\n if (self.ignoreFocus) {\n return;\n } else if (\n !self.ignoreBlur &&\n document.activeElement === self.$dropdown_content[0]\n ) {\n // necessary to prevent IE closing the dropdown when the scrollbar is clicked\n self.ignoreBlur = true;\n self.onFocus(e);\n return;\n }\n\n var deactivate = function () {\n self.close();\n self.setTextboxValue('');\n self.setActiveItem(null);\n self.setActiveOption(null);\n self.setCaret(self.items.length);\n self.refreshState();\n\n // IE11 bug: element still marked as active\n (dest || document.body).focus();\n\n self.ignoreFocus = false;\n self.trigger('blur');\n };\n\n self.ignoreFocus = true;\n if (self.settings.create && self.settings.createOnBlur) {\n self.createItem(null, false, deactivate);\n } else {\n deactivate();\n }\n },\n\n /**\n * Triggered when the user rolls over\n * an option in the autocomplete dropdown menu.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onOptionHover: function (e) {\n if (this.ignoreHover) return;\n this.setActiveOption(e.currentTarget, false);\n },\n\n /**\n * Triggered when the user clicks on an option\n * in the autocomplete dropdown menu.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onOptionSelect: function (e) {\n var value,\n $target,\n $option,\n self = this;\n\n if (e.preventDefault) {\n e.preventDefault();\n e.stopPropagation();\n }\n\n $target = $(e.currentTarget);\n if ($target.hasClass('create')) {\n self.createItem(null, function () {\n if (self.settings.closeAfterSelect) {\n self.close();\n }\n });\n } else {\n value = $target.attr('data-value');\n if (typeof value !== 'undefined') {\n self.lastQuery = null;\n self.setTextboxValue('');\n self.addItem(value);\n if (self.settings.closeAfterSelect) {\n self.close();\n } else if (\n !self.settings.hideSelected &&\n e.type &&\n /mouse/.test(e.type)\n ) {\n self.setActiveOption(self.getOption(value));\n }\n }\n }\n },\n\n /**\n * Triggered when the user clicks on an item\n * that has been selected.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onItemSelect: function (e) {\n var self = this;\n\n if (self.isLocked) return;\n if (self.settings.mode === 'multi') {\n e.preventDefault();\n self.setActiveItem(e.currentTarget, e);\n }\n },\n\n /**\n * Invokes the provided method that provides\n * results to a callback---which are then added\n * as options to the control.\n *\n * @param {function} fn\n */\n load: function (fn) {\n var self = this;\n var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);\n\n self.loading++;\n fn.apply(self, [\n function (results) {\n self.loading = Math.max(self.loading - 1, 0);\n if (results && results.length) {\n self.addOption(results);\n self.refreshOptions(self.isFocused && !self.isInputHidden);\n }\n if (!self.loading) {\n $wrapper.removeClass(self.settings.loadingClass);\n }\n self.trigger('load', results);\n },\n ]);\n },\n\n /**\n * Sets the input field of the control to the specified value.\n *\n * @param {string} value\n */\n setTextboxValue: function (value) {\n var $input = this.$control_input;\n var changed = $input.val() !== value;\n if (changed) {\n $input.val(value).triggerHandler('update');\n this.lastValue = value;\n }\n },\n\n /**\n * Returns the value of the control. If multiple items\n * can be selected (e.g. or \n * element to reflect the current state.\n */\n updateOriginalInput: function (opts) {\n var i,\n n,\n options,\n label,\n self = this;\n opts = opts || {};\n\n if (self.tagType === TAG_SELECT) {\n options = [];\n for (i = 0, n = self.items.length; i < n; i++) {\n label = self.options[self.items[i]][self.settings.labelField] || '';\n options.push(\n ''\n );\n }\n if (!options.length && !this.$input.attr('multiple')) {\n options.push('');\n }\n self.$input.html(options.join(''));\n } else {\n self.$input.val(self.getValue());\n self.$input.attr('value', self.$input.val());\n }\n\n if (self.isSetup) {\n if (!opts.silent) {\n self.trigger('change', self.$input.val());\n }\n }\n },\n\n /**\n * Shows/hide the input placeholder depending\n * on if there items in the list already.\n */\n updatePlaceholder: function () {\n if (!this.settings.placeholder) return;\n var $input = this.$control_input;\n\n if (this.items.length) {\n $input.removeAttr('placeholder');\n } else {\n $input.attr('placeholder', this.settings.placeholder);\n }\n $input.triggerHandler('update', {force: true});\n },\n\n /**\n * Shows the autocomplete dropdown containing\n * the available options.\n */\n open: function () {\n var self = this;\n\n if (\n self.isLocked ||\n self.isOpen ||\n (self.settings.mode === 'multi' && self.isFull())\n )\n return;\n self.focus();\n self.isOpen = true;\n self.refreshState();\n self.$dropdown.css({visibility: 'hidden', display: 'block'});\n self.positionDropdown();\n self.$dropdown.css({visibility: 'visible'});\n self.trigger('dropdown_open', self.$dropdown);\n },\n\n /**\n * Closes the autocomplete dropdown menu.\n */\n close: function () {\n var self = this;\n var trigger = self.isOpen;\n\n if (self.settings.mode === 'single' && self.items.length) {\n self.hideInput();\n }\n\n self.isOpen = false;\n self.$dropdown.hide();\n self.setActiveOption(null);\n self.refreshState();\n\n if (trigger) self.trigger('dropdown_close', self.$dropdown);\n },\n\n /**\n * Calculates and applies the appropriate\n * position of the dropdown.\n */\n positionDropdown: function () {\n var $control = this.$control;\n var offset =\n this.settings.dropdownParent === 'body'\n ? $control.offset()\n : $control.position();\n offset.top += $control.outerHeight(true);\n\n this.$dropdown.css({\n width: $control.outerWidth(),\n top: offset.top,\n left: offset.left,\n });\n },\n\n /**\n * Resets / clears all selected items\n * from the control.\n *\n * @param {boolean} silent\n */\n clear: function (silent) {\n var self = this;\n\n if (!self.items.length) return;\n self.$control.children(':not(input)').remove();\n self.items = [];\n self.lastQuery = null;\n self.setCaret(0);\n self.setActiveItem(null);\n self.updatePlaceholder();\n self.updateOriginalInput({silent: silent});\n self.refreshState();\n self.showInput();\n self.trigger('clear');\n },\n\n /**\n * A helper method for inserting an element\n * at the current caret position.\n *\n * @param {object} $el\n */\n insertAtCaret: function ($el) {\n var caret = Math.min(this.caretPos, this.items.length);\n if (caret === 0) {\n this.$control.prepend($el);\n } else {\n $(this.$control[0].childNodes[caret]).before($el);\n }\n this.setCaret(caret + 1);\n },\n\n /**\n * Removes the current selected item(s).\n *\n * @param {object} e (optional)\n * @returns {boolean}\n */\n deleteSelection: function (e) {\n var i,\n n,\n direction,\n selection,\n values,\n caret,\n option_select,\n $option_select,\n $tail;\n var self = this;\n\n direction = e && e.keyCode === KEY_BACKSPACE ? -1 : 1;\n selection = getSelection(self.$control_input[0]);\n\n if (self.$activeOption && !self.settings.hideSelected) {\n option_select = self\n .getAdjacentOption(self.$activeOption, -1)\n .attr('data-value');\n }\n\n // determine items that will be removed\n values = [];\n\n if (self.$activeItems.length) {\n $tail = self.$control.children(\n '.active:' + (direction > 0 ? 'last' : 'first')\n );\n caret = self.$control.children(':not(input)').index($tail);\n if (direction > 0) {\n caret++;\n }\n\n for (i = 0, n = self.$activeItems.length; i < n; i++) {\n values.push($(self.$activeItems[i]).attr('data-value'));\n }\n if (e) {\n e.preventDefault();\n e.stopPropagation();\n }\n } else if (\n (self.isFocused || self.settings.mode === 'single') &&\n self.items.length\n ) {\n if (direction < 0 && selection.start === 0 && selection.length === 0) {\n values.push(self.items[self.caretPos - 1]);\n } else if (\n direction > 0 &&\n selection.start === self.$control_input.val().length\n ) {\n values.push(self.items[self.caretPos]);\n }\n }\n\n // allow the callback to abort\n if (\n !values.length ||\n (typeof self.settings.onDelete === 'function' &&\n self.settings.onDelete.apply(self, [values]) === false)\n ) {\n return false;\n }\n\n // perform removal\n if (typeof caret !== 'undefined') {\n self.setCaret(caret);\n }\n while (values.length) {\n self.removeItem(values.pop());\n }\n\n self.showInput();\n self.positionDropdown();\n self.refreshOptions(true);\n\n // select previous option\n if (option_select) {\n $option_select = self.getOption(option_select);\n if ($option_select.length) {\n self.setActiveOption($option_select);\n }\n }\n\n return true;\n },\n\n /**\n * Selects the previous / next item (depending\n * on the `direction` argument).\n *\n * > 0 - right\n * < 0 - left\n *\n * @param {int} direction\n * @param {object} e (optional)\n */\n advanceSelection: function (direction, e) {\n var tail, selection, idx, valueLength, cursorAtEdge, $tail;\n var self = this;\n\n if (direction === 0) return;\n if (self.rtl) direction *= -1;\n\n tail = direction > 0 ? 'last' : 'first';\n selection = getSelection(self.$control_input[0]);\n\n if (self.isFocused && !self.isInputHidden) {\n valueLength = self.$control_input.val().length;\n cursorAtEdge =\n direction < 0\n ? selection.start === 0 && selection.length === 0\n : selection.start === valueLength;\n\n if (cursorAtEdge && !valueLength) {\n self.advanceCaret(direction, e);\n }\n } else {\n $tail = self.$control.children('.active:' + tail);\n if ($tail.length) {\n idx = self.$control.children(':not(input)').index($tail);\n self.setActiveItem(null);\n self.setCaret(direction > 0 ? idx + 1 : idx);\n }\n }\n },\n\n /**\n * Moves the caret left / right.\n *\n * @param {int} direction\n * @param {object} e (optional)\n */\n advanceCaret: function (direction, e) {\n var self = this,\n fn,\n $adj;\n\n if (direction === 0) return;\n\n fn = direction > 0 ? 'next' : 'prev';\n if (self.isShiftDown) {\n $adj = self.$control_input[fn]();\n if ($adj.length) {\n self.hideInput();\n self.setActiveItem($adj);\n e && e.preventDefault();\n }\n } else {\n self.setCaret(self.caretPos + direction);\n }\n },\n\n /**\n * Moves the caret to the specified index.\n *\n * @param {int} i\n */\n setCaret: function (i) {\n var self = this;\n\n if (self.settings.mode === 'single') {\n i = self.items.length;\n } else {\n i = Math.max(0, Math.min(self.items.length, i));\n }\n\n if (!self.isPending) {\n // the input must be moved by leaving it in place and moving the\n // siblings, due to the fact that focus cannot be restored once lost\n // on mobile webkit devices\n var j, n, fn, $children, $child;\n $children = self.$control.children(':not(input)');\n for (j = 0, n = $children.length; j < n; j++) {\n $child = $($children[j]).detach();\n if (j < i) {\n self.$control_input.before($child);\n } else {\n self.$control.append($child);\n }\n }\n }\n\n self.caretPos = i;\n },\n\n /**\n * Disables user input on the control. Used while\n * items are being asynchronously created.\n */\n lock: function () {\n this.close();\n this.isLocked = true;\n this.refreshState();\n },\n\n /**\n * Re-enables user input on the control.\n */\n unlock: function () {\n this.isLocked = false;\n this.refreshState();\n },\n\n /**\n * Disables user input on the control completely.\n * While disabled, it cannot receive focus.\n */\n disable: function () {\n var self = this;\n self.$input.prop('disabled', true);\n self.$control_input.prop('disabled', true).prop('tabindex', -1);\n self.isDisabled = true;\n self.lock();\n },\n\n /**\n * Enables the control so that it can respond\n * to focus and user input.\n */\n enable: function () {\n var self = this;\n self.$input.prop('disabled', false);\n self.$control_input\n .prop('disabled', false)\n .prop('tabindex', self.tabIndex);\n self.isDisabled = false;\n self.unlock();\n },\n\n /**\n * Completely destroys the control and\n * unbinds all event listeners so that it can\n * be garbage collected.\n */\n destroy: function () {\n var self = this;\n var eventNS = self.eventNS;\n var revertSettings = self.revertSettings;\n\n self.trigger('destroy');\n self.off();\n self.$wrapper.remove();\n self.$dropdown.remove();\n\n self.$input\n .html('')\n .append(revertSettings.$children)\n .removeAttr('tabindex')\n .removeClass('selectized')\n .attr({tabindex: revertSettings.tabindex})\n .show();\n\n self.$control_input.removeData('grow');\n self.$input.removeData('selectize');\n\n $(window).off(eventNS);\n $(document).off(eventNS);\n $(document.body).off(eventNS);\n\n delete self.$input[0].selectize;\n },\n\n /**\n * A helper method for rendering \"item\" and\n * \"option\" templates, given the data.\n *\n * @param {string} templateName\n * @param {object} data\n * @returns {string}\n */\n render: function (templateName, data) {\n var value, id, label;\n var html = '';\n var cache = false;\n var self = this;\n var regex_tag =\n /^[\\t \\r\\n]*<([a-z][a-z0-9\\-_]*(?:\\:[a-z][a-z0-9\\-_]*)?)/i;\n\n if (templateName === 'option' || templateName === 'item') {\n value = hash_key(data[self.settings.valueField]);\n cache = !!value;\n }\n\n // pull markup from cache if it exists\n if (cache) {\n if (!isset(self.renderCache[templateName])) {\n self.renderCache[templateName] = {};\n }\n if (self.renderCache[templateName].hasOwnProperty(value)) {\n return self.renderCache[templateName][value];\n }\n }\n\n // render markup\n html = self.settings.render[templateName].apply(this, [\n data,\n escape_html,\n ]);\n\n // add mandatory attributes\n if (templateName === 'option' || templateName === 'option_create') {\n html = html.replace(regex_tag, '<$1 data-selectable');\n }\n if (templateName === 'optgroup') {\n id = data[self.settings.optgroupValueField] || '';\n html = html.replace(\n regex_tag,\n '<$1 data-group=\"' + escape_replace(escape_html(id)) + '\"'\n );\n }\n if (templateName === 'option' || templateName === 'item') {\n html = html.replace(\n regex_tag,\n '<$1 data-value=\"' + escape_replace(escape_html(value || '')) + '\"'\n );\n }\n\n // update cache\n if (cache) {\n self.renderCache[templateName][value] = html;\n }\n\n return html;\n },\n\n /**\n * Clears the render cache for a template. If\n * no template is given, clears all render\n * caches.\n *\n * @param {string} templateName\n */\n clearCache: function (templateName) {\n var self = this;\n if (typeof templateName === 'undefined') {\n self.renderCache = {};\n } else {\n delete self.renderCache[templateName];\n }\n },\n\n /**\n * Determines whether or not to display the\n * create item prompt, given a user input.\n *\n * @param {string} input\n * @return {boolean}\n */\n canCreate: function (input) {\n var self = this;\n if (!self.settings.create) return false;\n var filter = self.settings.createFilter;\n return (\n input.length &&\n (typeof filter !== 'function' || filter.apply(self, [input])) &&\n (typeof filter !== 'string' || new RegExp(filter).test(input)) &&\n (!(filter instanceof RegExp) || filter.test(input))\n );\n },\n });\n\n Selectize.count = 0;\n Selectize.defaults = {\n options: [],\n optgroups: [],\n\n plugins: [],\n delimiter: ',',\n splitOn: null, // regexp or string for splitting up values from a paste command\n persist: true,\n diacritics: true,\n create: false,\n createOnBlur: false,\n createFilter: null,\n highlight: true,\n openOnFocus: true,\n maxOptions: 1000,\n maxItems: null,\n hideSelected: null,\n addPrecedence: false,\n selectOnTab: false,\n preload: false,\n allowEmptyOption: false,\n closeAfterSelect: false,\n\n scrollDuration: 60,\n loadThrottle: 300,\n loadingClass: 'loading',\n\n dataAttr: 'data-data',\n optgroupField: 'optgroup',\n valueField: 'value',\n labelField: 'text',\n optgroupLabelField: 'label',\n optgroupValueField: 'value',\n lockOptgroupOrder: false,\n\n sortField: '$order',\n searchField: ['text'],\n searchConjunction: 'and',\n\n mode: null,\n wrapperClass: 'selectize-control',\n inputClass: 'selectize-input',\n dropdownClass: 'selectize-dropdown',\n dropdownContentClass: 'selectize-dropdown-content',\n\n dropdownParent: null,\n\n copyClassesToDropdown: true,\n\n /*\n load : null, // function(query, callback) { ... }\n score : null, // function(search) { ... }\n onInitialize : null, // function() { ... }\n onChange : null, // function(value) { ... }\n onItemAdd : null, // function(value, $item) { ... }\n onItemRemove : null, // function(value) { ... }\n onClear : null, // function() { ... }\n onOptionAdd : null, // function(value, data) { ... }\n onOptionRemove : null, // function(value) { ... }\n onOptionClear : null, // function() { ... }\n onOptionGroupAdd : null, // function(id, data) { ... }\n onOptionGroupRemove : null, // function(id) { ... }\n onOptionGroupClear : null, // function() { ... }\n onDropdownOpen : null, // function($dropdown) { ... }\n onDropdownClose : null, // function($dropdown) { ... }\n onType : null, // function(str) { ... }\n onDelete : null, // function(values) { ... }\n */\n\n render: {\n /*\n item: null,\n optgroup: null,\n optgroup_header: null,\n option: null,\n option_create: null\n */\n },\n };\n\n $.fn.selectize = function (settings_user) {\n var defaults = $.fn.selectize.defaults;\n var settings = $.extend({}, defaults, settings_user);\n var attr_data = settings.dataAttr;\n var field_label = settings.labelField;\n var field_value = settings.valueField;\n var field_optgroup = settings.optgroupField;\n var field_optgroup_label = settings.optgroupLabelField;\n var field_optgroup_value = settings.optgroupValueField;\n\n /**\n * Initializes selectize from a element.\n *\n * @param {object} $input\n * @param {object} settings_element\n */\n var init_textbox = function ($input, settings_element) {\n var i, n, values, option;\n\n var data_raw = $input.attr(attr_data);\n\n if (!data_raw) {\n var value = $.trim($input.val() || '');\n if (!settings.allowEmptyOption && !value.length) return;\n values = value.split(settings.delimiter);\n for (i = 0, n = values.length; i < n; i++) {\n option = {};\n option[field_label] = values[i];\n option[field_value] = values[i];\n settings_element.options.push(option);\n }\n settings_element.items = values;\n } else {\n settings_element.options = JSON.parse(data_raw);\n for (i = 0, n = settings_element.options.length; i < n; i++) {\n settings_element.items.push(settings_element.options[i][field_value]);\n }\n }\n };\n\n /**\n * Initializes selectize from a ').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);\n $control_input = $('')\n .appendTo($control)\n .attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);\n /* END HACK */\n $dropdown_parent = $(settings.dropdownParent || $wrapper);\n $dropdown = $('
')\n .addClass(settings.dropdownClass)\n .addClass(inputMode)\n .hide()\n .appendTo($dropdown_parent);\n $dropdown_content = $('
')\n .addClass(settings.dropdownContentClass)\n .appendTo($dropdown);\n\n if (self.settings.copyClassesToDropdown) {\n $dropdown.addClass(classes);\n }\n\n $wrapper.css({\n width: $input[0].style.width,\n });\n\n if (self.plugins.names.length) {\n classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');\n $wrapper.addClass(classes_plugins);\n $dropdown.addClass(classes_plugins);\n }\n\n if (\n (settings.maxItems === null || settings.maxItems > 1) &&\n self.tagType === TAG_SELECT\n ) {\n $input.attr('multiple', 'multiple');\n }\n\n if (self.settings.placeholder) {\n $control_input.attr('placeholder', settings.placeholder);\n }\n\n // if splitOn was not passed in, construct it from the delimiter to allow pasting universally\n if (!self.settings.splitOn && self.settings.delimiter) {\n var delimiterEscaped = self.settings.delimiter.replace(\n /[-\\/\\\\^$*+?.()|[\\]{}]/g,\n '\\\\$&'\n );\n self.settings.splitOn = new RegExp('\\\\s*' + delimiterEscaped + '+\\\\s*');\n }\n\n if ($input.attr('autocorrect')) {\n $control_input.attr('autocorrect', $input.attr('autocorrect'));\n }\n\n if ($input.attr('autocapitalize')) {\n $control_input.attr('autocapitalize', $input.attr('autocapitalize'));\n }\n\n self.$wrapper = $wrapper;\n self.$control = $control;\n self.$control_input = $control_input;\n self.$dropdown = $dropdown;\n self.$dropdown_content = $dropdown_content;\n\n $dropdown.on('mouseenter', '[data-selectable]', function () {\n return self.onOptionHover.apply(self, arguments);\n });\n $dropdown.on('mousedown click', '[data-selectable]', function () {\n return self.onOptionSelect.apply(self, arguments);\n });\n watchChildEvent($control, 'mousedown', '*:not(input)', function () {\n return self.onItemSelect.apply(self, arguments);\n });\n autoGrow($control_input);\n\n $control.on({\n mousedown: function () {\n return self.onMouseDown.apply(self, arguments);\n },\n click: function () {\n return self.onClick.apply(self, arguments);\n },\n });\n\n $control_input.on({\n mousedown: function (e) {\n e.stopPropagation();\n },\n keydown: function () {\n return self.onKeyDown.apply(self, arguments);\n },\n keyup: function () {\n return self.onKeyUp.apply(self, arguments);\n },\n keypress: function () {\n return self.onKeyPress.apply(self, arguments);\n },\n resize: function () {\n self.positionDropdown.apply(self, []);\n },\n blur: function () {\n return self.onBlur.apply(self, arguments);\n },\n focus: function () {\n self.ignoreBlur = false;\n return self.onFocus.apply(self, arguments);\n },\n paste: function () {\n return self.onPaste.apply(self, arguments);\n },\n });\n\n $document.on('keydown' + eventNS, function (e) {\n self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];\n self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];\n self.isShiftDown = e.shiftKey;\n });\n\n $document.on('keyup' + eventNS, function (e) {\n if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;\n if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;\n if (e.keyCode === KEY_CMD) self.isCmdDown = false;\n });\n\n $document.on('mousedown' + eventNS, function (e) {\n if (self.isFocused) {\n // prevent events on the dropdown scrollbar from causing the control to blur\n if (\n e.target === self.$dropdown[0] ||\n e.target.parentNode === self.$dropdown[0]\n ) {\n return false;\n }\n // blur on click outside\n if (\n !self.$control.has(e.target).length &&\n e.target !== self.$control[0]\n ) {\n self.blur(e.target);\n }\n }\n });\n\n $window.on(\n ['scroll' + eventNS, 'resize' + eventNS].join(' '),\n function () {\n if (self.isOpen) {\n self.positionDropdown.apply(self, arguments);\n }\n }\n );\n $window.on('mousemove' + eventNS, function () {\n self.ignoreHover = false;\n });\n\n // store original children and tab index so that they can be\n // restored when the destroy() method is called.\n this.revertSettings = {\n $children: $input.children().detach(),\n tabindex: $input.attr('tabindex'),\n };\n\n $input.attr('tabindex', -1).hide().after(self.$wrapper);\n\n if ($.isArray(settings.items)) {\n self.setValue(settings.items);\n delete settings.items;\n }\n\n // feature detect for the validation API\n if (SUPPORTS_VALIDITY_API) {\n $input.on('invalid' + eventNS, function (e) {\n e.preventDefault();\n self.isInvalid = true;\n self.refreshState();\n });\n }\n\n self.updateOriginalInput();\n self.refreshItems();\n self.refreshState();\n self.updatePlaceholder();\n self.isSetup = true;\n\n if ($input.is(':disabled')) {\n self.disable();\n }\n\n self.on('change', this.onChange);\n\n $input.data('selectize', self);\n $input.addClass('selectized');\n self.trigger('initialize');\n\n // preload options\n if (settings.preload === true) {\n self.onSearchChange('');\n }\n },\n\n /**\n * Sets up default rendering functions.\n */\n setupTemplates: function () {\n var self = this;\n var field_label = self.settings.labelField;\n var field_optgroup = self.settings.optgroupLabelField;\n\n var templates = {\n optgroup: function (data) {\n return '
' + data.html + '
';\n },\n optgroup_header: function (data, escape) {\n return (\n '
' +\n escape(data[field_optgroup]) +\n '
'\n );\n },\n option: function (data, escape) {\n return '
' + escape(data[field_label]) + '
';\n },\n item: function (data, escape) {\n return '
' + escape(data[field_label]) + '
';\n },\n option_create: function (data, escape) {\n return (\n '
Add ' +\n escape(data.input) +\n '
'\n );\n },\n };\n\n self.settings.render = $.extend({}, templates, self.settings.render);\n },\n\n /**\n * Maps fired events to callbacks provided\n * in the settings used when creating the control.\n */\n setupCallbacks: function () {\n var key,\n fn,\n callbacks = {\n initialize: 'onInitialize',\n change: 'onChange',\n item_add: 'onItemAdd',\n item_remove: 'onItemRemove',\n clear: 'onClear',\n option_add: 'onOptionAdd',\n option_remove: 'onOptionRemove',\n option_clear: 'onOptionClear',\n optgroup_add: 'onOptionGroupAdd',\n optgroup_remove: 'onOptionGroupRemove',\n optgroup_clear: 'onOptionGroupClear',\n dropdown_open: 'onDropdownOpen',\n dropdown_close: 'onDropdownClose',\n type: 'onType',\n load: 'onLoad',\n focus: 'onFocus',\n blur: 'onBlur',\n };\n\n for (key in callbacks) {\n if (callbacks.hasOwnProperty(key)) {\n fn = this.settings[callbacks[key]];\n if (fn) this.on(key, fn);\n }\n }\n },\n\n /**\n * Triggered when the main control element\n * has a click event.\n *\n * @param {object} e\n * @return {boolean}\n */\n onClick: function (e) {\n var self = this;\n\n // necessary for mobile webkit devices (manual focus triggering\n // is ignored unless invoked within a click event)\n if (!self.isFocused) {\n self.focus();\n e.preventDefault();\n }\n },\n\n /**\n * Triggered when the main control element\n * has a mouse down event.\n *\n * @param {object} e\n * @return {boolean}\n */\n onMouseDown: function (e) {\n var self = this;\n var defaultPrevented = e.isDefaultPrevented();\n var $target = $(e.target);\n\n if (self.isFocused) {\n // retain focus by preventing native handling. if the\n // event target is the input it should not be modified.\n // otherwise, text selection within the input won't work.\n if (e.target !== self.$control_input[0]) {\n if (self.settings.mode === 'single') {\n // toggle dropdown\n self.isOpen ? self.close() : self.open();\n } else if (!defaultPrevented) {\n self.setActiveItem(null);\n }\n return false;\n }\n } else {\n // give control focus\n if (!defaultPrevented) {\n window.setTimeout(function () {\n self.focus();\n }, 0);\n }\n }\n },\n\n /**\n * Triggered when the value of the control has been changed.\n * This should propagate the event to the original DOM\n * input / select element.\n */\n onChange: function () {\n this.$input.trigger('change');\n },\n\n /**\n * Triggered on paste.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onPaste: function (e) {\n var self = this;\n if (self.isFull() || self.isInputHidden || self.isLocked) {\n e.preventDefault();\n } else {\n // If a regex or string is included, this will split the pasted\n // input and create Items for each separate value\n if (self.settings.splitOn) {\n setTimeout(function () {\n var splitInput = $.trim(self.$control_input.val() || '').split(\n self.settings.splitOn\n );\n for (var i = 0, n = splitInput.length; i < n; i++) {\n self.createItem(splitInput[i]);\n }\n }, 0);\n }\n }\n },\n\n /**\n * Triggered on keypress.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyPress: function (e) {\n if (this.isLocked) return e && e.preventDefault();\n var character = String.fromCharCode(e.keyCode || e.which);\n if (\n this.settings.create &&\n this.settings.mode === 'multi' &&\n character === this.settings.delimiter\n ) {\n this.createItem();\n e.preventDefault();\n return false;\n }\n },\n\n /**\n * Triggered on keydown.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyDown: function (e) {\n var isInput = e.target === this.$control_input[0];\n var self = this;\n\n if (self.isLocked) {\n if (e.keyCode !== KEY_TAB) {\n e.preventDefault();\n }\n return;\n }\n\n switch (e.keyCode) {\n case KEY_A:\n if (self.isCmdDown) {\n self.selectAll();\n return;\n }\n break;\n case KEY_ESC:\n if (self.isOpen) {\n e.preventDefault();\n e.stopPropagation();\n self.close();\n }\n return;\n case KEY_N:\n if (!e.ctrlKey || e.altKey) break;\n case KEY_DOWN:\n if (!self.isOpen && self.hasOptions) {\n self.open();\n } else if (self.$activeOption) {\n self.ignoreHover = true;\n var $next = self.getAdjacentOption(self.$activeOption, 1);\n if ($next.length) self.setActiveOption($next, true, true);\n }\n e.preventDefault();\n return;\n case KEY_P:\n if (!e.ctrlKey || e.altKey) break;\n case KEY_UP:\n if (self.$activeOption) {\n self.ignoreHover = true;\n var $prev = self.getAdjacentOption(self.$activeOption, -1);\n if ($prev.length) self.setActiveOption($prev, true, true);\n }\n e.preventDefault();\n return;\n case KEY_RETURN:\n if (self.isOpen && self.$activeOption) {\n self.onOptionSelect({currentTarget: self.$activeOption});\n e.preventDefault();\n }\n return;\n case KEY_LEFT:\n self.advanceSelection(-1, e);\n return;\n case KEY_RIGHT:\n self.advanceSelection(1, e);\n return;\n case KEY_TAB:\n if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {\n self.onOptionSelect({currentTarget: self.$activeOption});\n\n // Default behaviour is to jump to the next field, we only want this\n // if the current field doesn't accept any more entries\n if (!self.isFull()) {\n e.preventDefault();\n }\n }\n if (self.settings.create && self.createItem()) {\n e.preventDefault();\n }\n return;\n case KEY_BACKSPACE:\n case KEY_DELETE:\n self.deleteSelection(e);\n return;\n }\n\n if (\n (self.isFull() || self.isInputHidden) &&\n !(IS_MAC ? e.metaKey : e.ctrlKey)\n ) {\n e.preventDefault();\n return;\n }\n },\n\n /**\n * Triggered on keyup.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onKeyUp: function (e) {\n var self = this;\n\n if (self.isLocked) return e && e.preventDefault();\n var value = self.$control_input.val() || '';\n if (self.lastValue !== value) {\n self.lastValue = value;\n self.onSearchChange(value);\n self.refreshOptions();\n self.trigger('type', value);\n }\n },\n\n /**\n * Invokes the user-provide option provider / loader.\n *\n * Note: this function is debounced in the Selectize\n * constructor (by `settings.loadDelay` milliseconds)\n *\n * @param {string} value\n */\n onSearchChange: function (value) {\n var self = this;\n var fn = self.settings.load;\n if (!fn) return;\n if (self.loadedSearches.hasOwnProperty(value)) return;\n self.loadedSearches[value] = true;\n self.load(function (callback) {\n fn.apply(self, [value, callback]);\n });\n },\n\n /**\n * Triggered on focus.\n *\n * @param {object} e (optional)\n * @returns {boolean}\n */\n onFocus: function (e) {\n var self = this;\n var wasFocused = self.isFocused;\n\n if (self.isDisabled) {\n self.blur();\n e && e.preventDefault();\n return false;\n }\n\n if (self.ignoreFocus) return;\n self.isFocused = true;\n if (self.settings.preload === 'focus') self.onSearchChange('');\n\n if (!wasFocused) self.trigger('focus');\n\n if (!self.$activeItems.length) {\n self.showInput();\n self.setActiveItem(null);\n self.refreshOptions(!!self.settings.openOnFocus);\n }\n\n self.refreshState();\n },\n\n /**\n * Triggered on blur.\n *\n * @param {object} e\n * @param {Element} dest\n */\n onBlur: function (e, dest) {\n var self = this;\n if (!self.isFocused) return;\n self.isFocused = false;\n\n if (self.ignoreFocus) {\n return;\n } else if (\n !self.ignoreBlur &&\n document.activeElement === self.$dropdown_content[0]\n ) {\n // necessary to prevent IE closing the dropdown when the scrollbar is clicked\n self.ignoreBlur = true;\n self.onFocus(e);\n return;\n }\n\n var deactivate = function () {\n self.close();\n self.setTextboxValue('');\n self.setActiveItem(null);\n self.setActiveOption(null);\n self.setCaret(self.items.length);\n self.refreshState();\n\n // IE11 bug: element still marked as active\n (dest || document.body).focus();\n\n self.ignoreFocus = false;\n self.trigger('blur');\n };\n\n self.ignoreFocus = true;\n if (self.settings.create && self.settings.createOnBlur) {\n self.createItem(null, false, deactivate);\n } else {\n deactivate();\n }\n },\n\n /**\n * Triggered when the user rolls over\n * an option in the autocomplete dropdown menu.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onOptionHover: function (e) {\n if (this.ignoreHover) return;\n this.setActiveOption(e.currentTarget, false);\n },\n\n /**\n * Triggered when the user clicks on an option\n * in the autocomplete dropdown menu.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onOptionSelect: function (e) {\n var value,\n $target,\n $option,\n self = this;\n\n if (e.preventDefault) {\n e.preventDefault();\n e.stopPropagation();\n }\n\n $target = $(e.currentTarget);\n if ($target.hasClass('create')) {\n self.createItem(null, function () {\n if (self.settings.closeAfterSelect) {\n self.close();\n }\n });\n } else {\n value = $target.attr('data-value');\n if (typeof value !== 'undefined') {\n self.lastQuery = null;\n self.setTextboxValue('');\n self.addItem(value);\n if (self.settings.closeAfterSelect) {\n self.close();\n } else if (\n !self.settings.hideSelected &&\n e.type &&\n /mouse/.test(e.type)\n ) {\n self.setActiveOption(self.getOption(value));\n }\n }\n }\n },\n\n /**\n * Triggered when the user clicks on an item\n * that has been selected.\n *\n * @param {object} e\n * @returns {boolean}\n */\n onItemSelect: function (e) {\n var self = this;\n\n if (self.isLocked) return;\n if (self.settings.mode === 'multi') {\n e.preventDefault();\n self.setActiveItem(e.currentTarget, e);\n }\n },\n\n /**\n * Invokes the provided method that provides\n * results to a callback---which are then added\n * as options to the control.\n *\n * @param {function} fn\n */\n load: function (fn) {\n var self = this;\n var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);\n\n self.loading++;\n fn.apply(self, [\n function (results) {\n self.loading = Math.max(self.loading - 1, 0);\n if (results && results.length) {\n self.addOption(results);\n self.refreshOptions(self.isFocused && !self.isInputHidden);\n }\n if (!self.loading) {\n $wrapper.removeClass(self.settings.loadingClass);\n }\n self.trigger('load', results);\n },\n ]);\n },\n\n /**\n * Sets the input field of the control to the specified value.\n *\n * @param {string} value\n */\n setTextboxValue: function (value) {\n var $input = this.$control_input;\n var changed = $input.val() !== value;\n if (changed) {\n $input.val(value).triggerHandler('update');\n this.lastValue = value;\n }\n },\n\n /**\n * Returns the value of the control. If multiple items\n * can be selected (e.g. or \n * element to reflect the current state.\n */\n updateOriginalInput: function (opts) {\n var i,\n n,\n options,\n label,\n self = this;\n opts = opts || {};\n\n if (self.tagType === TAG_SELECT) {\n options = [];\n for (i = 0, n = self.items.length; i < n; i++) {\n label = self.options[self.items[i]][self.settings.labelField] || '';\n options.push(\n ''\n );\n }\n if (!options.length && !this.$input.attr('multiple')) {\n options.push('');\n }\n self.$input.html(options.join(''));\n } else {\n self.$input.val(self.getValue());\n self.$input.attr('value', self.$input.val());\n }\n\n if (self.isSetup) {\n if (!opts.silent) {\n self.trigger('change', self.$input.val());\n }\n }\n },\n\n /**\n * Shows/hide the input placeholder depending\n * on if there items in the list already.\n */\n updatePlaceholder: function () {\n if (!this.settings.placeholder) return;\n var $input = this.$control_input;\n\n if (this.items.length) {\n $input.removeAttr('placeholder');\n } else {\n $input.attr('placeholder', this.settings.placeholder);\n }\n $input.triggerHandler('update', {force: true});\n },\n\n /**\n * Shows the autocomplete dropdown containing\n * the available options.\n */\n open: function () {\n var self = this;\n\n if (\n self.isLocked ||\n self.isOpen ||\n (self.settings.mode === 'multi' && self.isFull())\n )\n return;\n self.focus();\n self.isOpen = true;\n self.refreshState();\n self.$dropdown.css({visibility: 'hidden', display: 'block'});\n self.positionDropdown();\n self.$dropdown.css({visibility: 'visible'});\n self.trigger('dropdown_open', self.$dropdown);\n },\n\n /**\n * Closes the autocomplete dropdown menu.\n */\n close: function () {\n var self = this;\n var trigger = self.isOpen;\n\n if (self.settings.mode === 'single' && self.items.length) {\n self.hideInput();\n }\n\n self.isOpen = false;\n self.$dropdown.hide();\n self.setActiveOption(null);\n self.refreshState();\n\n if (trigger) self.trigger('dropdown_close', self.$dropdown);\n },\n\n /**\n * Calculates and applies the appropriate\n * position of the dropdown.\n */\n positionDropdown: function () {\n var $control = this.$control;\n var offset =\n this.settings.dropdownParent === 'body'\n ? $control.offset()\n : $control.position();\n offset.top += $control.outerHeight(true);\n\n this.$dropdown.css({\n width: $control.outerWidth(),\n top: offset.top,\n left: offset.left,\n });\n },\n\n /**\n * Resets / clears all selected items\n * from the control.\n *\n * @param {boolean} silent\n */\n clear: function (silent) {\n var self = this;\n\n if (!self.items.length) return;\n self.$control.children(':not(input)').remove();\n self.items = [];\n self.lastQuery = null;\n self.setCaret(0);\n self.setActiveItem(null);\n self.updatePlaceholder();\n self.updateOriginalInput({silent: silent});\n self.refreshState();\n self.showInput();\n self.trigger('clear');\n },\n\n /**\n * A helper method for inserting an element\n * at the current caret position.\n *\n * @param {object} $el\n */\n insertAtCaret: function ($el) {\n var caret = Math.min(this.caretPos, this.items.length);\n if (caret === 0) {\n this.$control.prepend($el);\n } else {\n $(this.$control[0].childNodes[caret]).before($el);\n }\n this.setCaret(caret + 1);\n },\n\n /**\n * Removes the current selected item(s).\n *\n * @param {object} e (optional)\n * @returns {boolean}\n */\n deleteSelection: function (e) {\n var i,\n n,\n direction,\n selection,\n values,\n caret,\n option_select,\n $option_select,\n $tail;\n var self = this;\n\n direction = e && e.keyCode === KEY_BACKSPACE ? -1 : 1;\n selection = getSelection(self.$control_input[0]);\n\n if (self.$activeOption && !self.settings.hideSelected) {\n option_select = self\n .getAdjacentOption(self.$activeOption, -1)\n .attr('data-value');\n }\n\n // determine items that will be removed\n values = [];\n\n if (self.$activeItems.length) {\n $tail = self.$control.children(\n '.active:' + (direction > 0 ? 'last' : 'first')\n );\n caret = self.$control.children(':not(input)').index($tail);\n if (direction > 0) {\n caret++;\n }\n\n for (i = 0, n = self.$activeItems.length; i < n; i++) {\n values.push($(self.$activeItems[i]).attr('data-value'));\n }\n if (e) {\n e.preventDefault();\n e.stopPropagation();\n }\n } else if (\n (self.isFocused || self.settings.mode === 'single') &&\n self.items.length\n ) {\n if (direction < 0 && selection.start === 0 && selection.length === 0) {\n values.push(self.items[self.caretPos - 1]);\n } else if (\n direction > 0 &&\n selection.start === self.$control_input.val().length\n ) {\n values.push(self.items[self.caretPos]);\n }\n }\n\n // allow the callback to abort\n if (\n !values.length ||\n (typeof self.settings.onDelete === 'function' &&\n self.settings.onDelete.apply(self, [values]) === false)\n ) {\n return false;\n }\n\n // perform removal\n if (typeof caret !== 'undefined') {\n self.setCaret(caret);\n }\n while (values.length) {\n self.removeItem(values.pop());\n }\n\n self.showInput();\n self.positionDropdown();\n self.refreshOptions(true);\n\n // select previous option\n if (option_select) {\n $option_select = self.getOption(option_select);\n if ($option_select.length) {\n self.setActiveOption($option_select);\n }\n }\n\n return true;\n },\n\n /**\n * Selects the previous / next item (depending\n * on the `direction` argument).\n *\n * > 0 - right\n * < 0 - left\n *\n * @param {int} direction\n * @param {object} e (optional)\n */\n advanceSelection: function (direction, e) {\n var tail, selection, idx, valueLength, cursorAtEdge, $tail;\n var self = this;\n\n if (direction === 0) return;\n if (self.rtl) direction *= -1;\n\n tail = direction > 0 ? 'last' : 'first';\n selection = getSelection(self.$control_input[0]);\n\n if (self.isFocused && !self.isInputHidden) {\n valueLength = self.$control_input.val().length;\n cursorAtEdge =\n direction < 0\n ? selection.start === 0 && selection.length === 0\n : selection.start === valueLength;\n\n if (cursorAtEdge && !valueLength) {\n self.advanceCaret(direction, e);\n }\n } else {\n $tail = self.$control.children('.active:' + tail);\n if ($tail.length) {\n idx = self.$control.children(':not(input)').index($tail);\n self.setActiveItem(null);\n self.setCaret(direction > 0 ? idx + 1 : idx);\n }\n }\n },\n\n /**\n * Moves the caret left / right.\n *\n * @param {int} direction\n * @param {object} e (optional)\n */\n advanceCaret: function (direction, e) {\n var self = this,\n fn,\n $adj;\n\n if (direction === 0) return;\n\n fn = direction > 0 ? 'next' : 'prev';\n if (self.isShiftDown) {\n $adj = self.$control_input[fn]();\n if ($adj.length) {\n self.hideInput();\n self.setActiveItem($adj);\n e && e.preventDefault();\n }\n } else {\n self.setCaret(self.caretPos + direction);\n }\n },\n\n /**\n * Moves the caret to the specified index.\n *\n * @param {int} i\n */\n setCaret: function (i) {\n var self = this;\n\n if (self.settings.mode === 'single') {\n i = self.items.length;\n } else {\n i = Math.max(0, Math.min(self.items.length, i));\n }\n\n if (!self.isPending) {\n // the input must be moved by leaving it in place and moving the\n // siblings, due to the fact that focus cannot be restored once lost\n // on mobile webkit devices\n var j, n, fn, $children, $child;\n $children = self.$control.children(':not(input)');\n for (j = 0, n = $children.length; j < n; j++) {\n $child = $($children[j]).detach();\n if (j < i) {\n self.$control_input.before($child);\n } else {\n self.$control.append($child);\n }\n }\n }\n\n self.caretPos = i;\n },\n\n /**\n * Disables user input on the control. Used while\n * items are being asynchronously created.\n */\n lock: function () {\n this.close();\n this.isLocked = true;\n this.refreshState();\n },\n\n /**\n * Re-enables user input on the control.\n */\n unlock: function () {\n this.isLocked = false;\n this.refreshState();\n },\n\n /**\n * Disables user input on the control completely.\n * While disabled, it cannot receive focus.\n */\n disable: function () {\n var self = this;\n self.$input.prop('disabled', true);\n self.$control_input.prop('disabled', true).prop('tabindex', -1);\n self.isDisabled = true;\n self.lock();\n },\n\n /**\n * Enables the control so that it can respond\n * to focus and user input.\n */\n enable: function () {\n var self = this;\n self.$input.prop('disabled', false);\n self.$control_input\n .prop('disabled', false)\n .prop('tabindex', self.tabIndex);\n self.isDisabled = false;\n self.unlock();\n },\n\n /**\n * Completely destroys the control and\n * unbinds all event listeners so that it can\n * be garbage collected.\n */\n destroy: function () {\n var self = this;\n var eventNS = self.eventNS;\n var revertSettings = self.revertSettings;\n\n self.trigger('destroy');\n self.off();\n self.$wrapper.remove();\n self.$dropdown.remove();\n\n self.$input\n .html('')\n .append(revertSettings.$children)\n .removeAttr('tabindex')\n .removeClass('selectized')\n .attr({tabindex: revertSettings.tabindex})\n .show();\n\n self.$control_input.removeData('grow');\n self.$input.removeData('selectize');\n\n $(window).off(eventNS);\n $(document).off(eventNS);\n $(document.body).off(eventNS);\n\n delete self.$input[0].selectize;\n },\n\n /**\n * A helper method for rendering \"item\" and\n * \"option\" templates, given the data.\n *\n * @param {string} templateName\n * @param {object} data\n * @returns {string}\n */\n render: function (templateName, data) {\n var value, id, label;\n var html = '';\n var cache = false;\n var self = this;\n var regex_tag =\n /^[\\t \\r\\n]*<([a-z][a-z0-9\\-_]*(?:\\:[a-z][a-z0-9\\-_]*)?)/i;\n\n if (templateName === 'option' || templateName === 'item') {\n value = hash_key(data[self.settings.valueField]);\n cache = !!value;\n }\n\n // pull markup from cache if it exists\n if (cache) {\n if (!isset(self.renderCache[templateName])) {\n self.renderCache[templateName] = {};\n }\n if (self.renderCache[templateName].hasOwnProperty(value)) {\n return self.renderCache[templateName][value];\n }\n }\n\n // render markup\n html = self.settings.render[templateName].apply(this, [\n data,\n escape_html,\n ]);\n\n // add mandatory attributes\n if (templateName === 'option' || templateName === 'option_create') {\n html = html.replace(regex_tag, '<$1 data-selectable');\n }\n if (templateName === 'optgroup') {\n id = data[self.settings.optgroupValueField] || '';\n html = html.replace(\n regex_tag,\n '<$1 data-group=\"' + escape_replace(escape_html(id)) + '\"'\n );\n }\n if (templateName === 'option' || templateName === 'item') {\n html = html.replace(\n regex_tag,\n '<$1 data-value=\"' + escape_replace(escape_html(value || '')) + '\"'\n );\n }\n\n // update cache\n if (cache) {\n self.renderCache[templateName][value] = html;\n }\n\n return html;\n },\n\n /**\n * Clears the render cache for a template. If\n * no template is given, clears all render\n * caches.\n *\n * @param {string} templateName\n */\n clearCache: function (templateName) {\n var self = this;\n if (typeof templateName === 'undefined') {\n self.renderCache = {};\n } else {\n delete self.renderCache[templateName];\n }\n },\n\n /**\n * Determines whether or not to display the\n * create item prompt, given a user input.\n *\n * @param {string} input\n * @return {boolean}\n */\n canCreate: function (input) {\n var self = this;\n if (!self.settings.create) return false;\n var filter = self.settings.createFilter;\n return (\n input.length &&\n (typeof filter !== 'function' || filter.apply(self, [input])) &&\n (typeof filter !== 'string' || new RegExp(filter).test(input)) &&\n (!(filter instanceof RegExp) || filter.test(input))\n );\n },\n });\n\n Selectize.count = 0;\n Selectize.defaults = {\n options: [],\n optgroups: [],\n\n plugins: [],\n delimiter: ',',\n splitOn: null, // regexp or string for splitting up values from a paste command\n persist: true,\n diacritics: true,\n create: false,\n createOnBlur: false,\n createFilter: null,\n highlight: true,\n openOnFocus: true,\n maxOptions: 1000,\n maxItems: null,\n hideSelected: null,\n addPrecedence: false,\n selectOnTab: false,\n preload: false,\n allowEmptyOption: false,\n closeAfterSelect: false,\n\n scrollDuration: 60,\n loadThrottle: 300,\n loadingClass: 'loading',\n\n dataAttr: 'data-data',\n optgroupField: 'optgroup',\n valueField: 'value',\n labelField: 'text',\n optgroupLabelField: 'label',\n optgroupValueField: 'value',\n lockOptgroupOrder: false,\n\n sortField: '$order',\n searchField: ['text'],\n searchConjunction: 'and',\n\n mode: null,\n wrapperClass: 'selectize-control',\n inputClass: 'selectize-input',\n dropdownClass: 'selectize-dropdown',\n dropdownContentClass: 'selectize-dropdown-content',\n\n dropdownParent: null,\n\n copyClassesToDropdown: true,\n\n /*\n load : null, // function(query, callback) { ... }\n score : null, // function(search) { ... }\n onInitialize : null, // function() { ... }\n onChange : null, // function(value) { ... }\n onItemAdd : null, // function(value, $item) { ... }\n onItemRemove : null, // function(value) { ... }\n onClear : null, // function() { ... }\n onOptionAdd : null, // function(value, data) { ... }\n onOptionRemove : null, // function(value) { ... }\n onOptionClear : null, // function() { ... }\n onOptionGroupAdd : null, // function(id, data) { ... }\n onOptionGroupRemove : null, // function(id) { ... }\n onOptionGroupClear : null, // function() { ... }\n onDropdownOpen : null, // function($dropdown) { ... }\n onDropdownClose : null, // function($dropdown) { ... }\n onType : null, // function(str) { ... }\n onDelete : null, // function(values) { ... }\n */\n\n render: {\n /*\n item: null,\n optgroup: null,\n optgroup_header: null,\n option: null,\n option_create: null\n */\n },\n };\n\n $.fn.selectize = function (settings_user) {\n var defaults = $.fn.selectize.defaults;\n var settings = $.extend({}, defaults, settings_user);\n var attr_data = settings.dataAttr;\n var field_label = settings.labelField;\n var field_value = settings.valueField;\n var field_optgroup = settings.optgroupField;\n var field_optgroup_label = settings.optgroupLabelField;\n var field_optgroup_value = settings.optgroupValueField;\n\n /**\n * Initializes selectize from a element.\n *\n * @param {object} $input\n * @param {object} settings_element\n */\n var init_textbox = function ($input, settings_element) {\n var i, n, values, option;\n\n var data_raw = $input.attr(attr_data);\n\n if (!data_raw) {\n var value = $.trim($input.val() || '');\n if (!settings.allowEmptyOption && !value.length) return;\n values = value.split(settings.delimiter);\n for (i = 0, n = values.length; i < n; i++) {\n option = {};\n option[field_label] = values[i];\n option[field_value] = values[i];\n settings_element.options.push(option);\n }\n settings_element.items = values;\n } else {\n settings_element.options = JSON.parse(data_raw);\n for (i = 0, n = settings_element.options.length; i < n; i++) {\n settings_element.items.push(settings_element.options[i][field_value]);\n }\n }\n };\n\n /**\n * Initializes selectize from a