From 2cd5f1525f0275442f31b5559d05073dbed0738c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3na=20Lore?= <lonalore@freemail.hu> Date: Wed, 2 Dec 2015 14:23:48 +0100 Subject: [PATCH] Issue #991: Removed all Selectize JS code from PHP. Additional option (settings) for e107::js(). Integrates Selectize.js using e107::js(settings) and JavaScript behaviors with jQuery Once. --- e107_admin/footer.php | 4 + e107_core/templates/footer_default.php | 4 + e107_handlers/e107_class.php | 4 + e107_handlers/e_parse_class.php | 131 ++++++++++++++++ e107_handlers/form_handler.php | 113 ++------------ e107_handlers/js_manager.php | 77 ++++++++- e107_web/js/core/all.jquery.js | 123 ++++++++++++++- e107_web/js/selectize/js/selectize.init.js | 173 +++++++++++++++++++++ 8 files changed, 522 insertions(+), 107 deletions(-) create mode 100644 e107_web/js/selectize/js/selectize.init.js diff --git a/e107_admin/footer.php b/e107_admin/footer.php index a388c6773..553339423 100644 --- a/e107_admin/footer.php +++ b/e107_admin/footer.php @@ -348,6 +348,10 @@ if (abs($_serverTime - $lastSet) > 120) // </script>\n"; } +// All JavaScript settings are placed in the footer of the page with the library weight so that inline scripts appear +// afterwards. +e107::getJs()->renderJs('settings'); + e107::getJs()->renderJs('footer_inline', true); // diff --git a/e107_core/templates/footer_default.php b/e107_core/templates/footer_default.php index b061d1f85..045695121 100644 --- a/e107_core/templates/footer_default.php +++ b/e107_core/templates/footer_default.php @@ -326,6 +326,10 @@ e107::getJs()->renderJs('footer', true); e107::getJs()->renderCached('js'); +// All JavaScript settings are placed in the footer of the page with the library weight so that inline scripts appear +// afterwards. +e107::getJs()->renderJs('settings'); + // [JSManager] Load JS Footer inline code by priority e107::getJs()->renderJs('footer_inline', true); diff --git a/e107_handlers/e107_class.php b/e107_handlers/e107_class.php index a71ca63d7..4c51a853e 100644 --- a/e107_handlers/e107_class.php +++ b/e107_handlers/e107_class.php @@ -1668,6 +1668,10 @@ class e107 switch ($type) { + case 'settings': + $jshandler->jsSettings($data); + break; + case 'core': // data is e.g. 'core/tabs.js' if(null !== $zone) $jshandler->requireCoreLib($data, $zone); diff --git a/e107_handlers/e_parse_class.php b/e107_handlers/e_parse_class.php index 76e3b6629..4ce2f1b4a 100644 --- a/e107_handlers/e_parse_class.php +++ b/e107_handlers/e_parse_class.php @@ -1954,6 +1954,137 @@ class e_parse extends e_parser } + /** + * Converts a PHP variable into its JavaScript equivalent. + * We use HTML-safe strings, with several characters escaped. + * + * @param mixed $var + * @return string + */ + public function toJSON($var) + { + // The PHP version cannot change within a request. + static $php530; + + if(!isset($php530)) + { + $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); + } + + if($php530) + { + // Encode <, >, ', &, and " using the json_encode() options parameter. + return json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + } + + return $this->toJSONhelper($var); + } + + + /** + * Encodes a PHP variable to HTML-safe JSON for PHP versions below 5.3.0. + * + * @param mixed $var + * @return string + */ + public function toJSONhelper($var) + { + switch(gettype($var)) + { + case 'boolean': + return $var ? 'true' : 'false'; // Lowercase necessary! + + case 'integer': + case 'double': + return $var; + + case 'resource': + case 'string': + // Always use Unicode escape sequences (\u0022) over JSON escape + // sequences (\") to prevent browsers interpreting these as + // special characters. + $replace_pairs = array( + // ", \ and U+0000 - U+001F must be escaped according to RFC 4627. + '\\' => '\u005C', + '"' => '\u0022', + "\x00" => '\u0000', + "\x01" => '\u0001', + "\x02" => '\u0002', + "\x03" => '\u0003', + "\x04" => '\u0004', + "\x05" => '\u0005', + "\x06" => '\u0006', + "\x07" => '\u0007', + "\x08" => '\u0008', + "\x09" => '\u0009', + "\x0a" => '\u000A', + "\x0b" => '\u000B', + "\x0c" => '\u000C', + "\x0d" => '\u000D', + "\x0e" => '\u000E', + "\x0f" => '\u000F', + "\x10" => '\u0010', + "\x11" => '\u0011', + "\x12" => '\u0012', + "\x13" => '\u0013', + "\x14" => '\u0014', + "\x15" => '\u0015', + "\x16" => '\u0016', + "\x17" => '\u0017', + "\x18" => '\u0018', + "\x19" => '\u0019', + "\x1a" => '\u001A', + "\x1b" => '\u001B', + "\x1c" => '\u001C', + "\x1d" => '\u001D', + "\x1e" => '\u001E', + "\x1f" => '\u001F', + // Prevent browsers from interpreting these as as special. + "'" => '\u0027', + '<' => '\u003C', + '>' => '\u003E', + '&' => '\u0026', + // Prevent browsers from interpreting the solidus as special and + // non-compliant JSON parsers from interpreting // as a comment. + '/' => '\u002F', + // While these are allowed unescaped according to ECMA-262, section + // 15.12.2, they cause problems in some JSON parsers. + "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator. + "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator. + ); + + return '"' . strtr($var, $replace_pairs) . '"'; + + case 'array': + // Arrays in JSON can't be associative. If the array is empty or if it + // has sequential whole number keys starting with 0, it's not associative + // so we can go ahead and convert it as an array. + if(empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) + { + $output = array(); + foreach($var as $v) + { + $output[] = $this->toJSONhelper($v); + } + return '[ ' . implode(', ', $output) . ' ]'; + } + break; + + // Otherwise, fall through to convert the array as an object. + case 'object': + $output = array(); + foreach($var as $k => $v) + { + $output[] = $this->toJSONhelper(strval($k)) . ':' . $this->toJSONhelper($v); + } + return '{' . implode(', ', $output) . '}'; + + default: + return 'null'; + } + } + + /** * Convert Text for RSS/XML use. * diff --git a/e107_handlers/form_handler.php b/e107_handlers/form_handler.php index f5104504d..a7cc055b5 100644 --- a/e107_handlers/form_handler.php +++ b/e107_handlers/form_handler.php @@ -422,109 +422,20 @@ class e_form e107::css('core', 'selectize/css/selectize.bootstrap2.css', 'jquery'); } - $ac = $options['selectize']; - $fieldID = vartrue($options['id'], $this->name2id($name)); + // Load selectize behavior. + e107::js('core', 'selectize/js/selectize.init.js', 'jquery'); - // TODO: better method to create suffix as unique identifier. - $optionSuffix = $fieldID . vartrue($options['selectize']['e_editable']); - $optionSuffix = md5($optionSuffix); + $jsSettings = array( + 'id' => vartrue($options['id'], $this->name2id($name)), + 'options' => $options['selectize'], + // Multilingual support. + 'strings' => array( + 'anonymous' => LAN_ANONYMOUS, + ), + ); - // We need to declare an options array with unique ID for the selectize.js field, because we have to store - // the latest default values, because if the selectize() would be reinitialized, e.g. in x-editable popover, - // we have to get the latest value(s). - $jsOptions = 'var options' . $optionSuffix . ' = '; - $jsOptions .= (vartrue($ac['options']) ? json_encode($ac['options']) : "[]") . ";\n"; - - // TODO: more options and callbacks. - $js = "$('#" . $fieldID . "').selectize({ - // General options. - items: " . (vartrue($ac['items']) ? json_encode($ac['items']) : '[]') . ", - delimiter: '" . (vartrue($ac['delimiter'], ',')) . "', - diacritics: " . (vartrue($ac['diacritics'], true) ? 'true' : 'false') . ", - create: " . (vartrue($ac['create'], false) ? 'true' : 'false') . ", - createOnBlur: " . (vartrue($ac['createOnBlur'], true) ? 'true' : 'false') . ", - highlight: " . (vartrue($ac['highlight'], false) ? 'true' : 'false') . ", - persist: " . (vartrue($ac['persist'], false) ? 'true' : 'false') . ", - openOnFocus: " . (vartrue($ac['openOnFocus'], false) ? 'true' : 'false') . ", - maxOptions: " . vartrue($ac['maxOptions'], 'null') . ", - maxItems: " . vartrue($ac['maxItems'], 'null') . ", - hideSelected: " . (vartrue($ac['hideSelected'], false) ? 'true' : 'false') . ", - closeAfterSelect: " . (vartrue($ac['closeAfterSelect'], true) ? 'true' : 'false') . ", - allowEmptyOption: " . (vartrue($ac['allowEmptyOption'], false) ? 'true' : 'false') . ", - scrollDuration: " . vartrue($ac['scrollDuration'], 60) . ", - loadThrottle: " . vartrue($ac['loadThrottle'], 300) . ", - loadingClass: '" . vartrue($ac['loadingClass'], 'loading') . "', - preload: " . (vartrue($ac['preload'], false) ? 'true' : 'false') . ", - dropdownParent: " . vartrue($ac['dropdownParent'], 'null') . ", - addPrecedence: " . (vartrue($ac['addPrecedence'], false) ? 'true' : 'false') . ", - selectOnTab: " . (vartrue($ac['selectOnTab'], false) ? 'true' : 'false') . ", - mode: '" . (vartrue($ac['mode'], 'multi')) . "', - plugins: " . (vartrue($ac['plugins']) ? json_encode($ac['plugins']) : '[]') . ", - - // Data / Searching. - options: options" . $optionSuffix . ", - valueField: '" . vartrue($ac['valueField'], 'value') . "', - labelField: '" . vartrue($ac['labelField'], 'label') . "', - searchField: '" . vartrue($ac['searchField'], 'label') . "', - - // Callbacks. - load: function(query, callback) { - var loadPath = '" . vartrue($ac['loadPath'], '') . "'; - if (loadPath == '') return callback([]); - if (!query.length) return callback([]); - $.ajax({ - url: loadPath, - type: 'POST', - dataType: 'json', - data: { - q: query, - l: " . vartrue($ac['maxOptions'], 10) . " - }, - error: function() { - callback([]); - }, - success: function(data) { - // Update items in options array of this field. - options" . $optionSuffix . " = data; - callback(data); - } - }); - } - });"; - - // We have to generate JS for x-editable field, so we have to initialize selectize.js after popover opens. - if(vartrue($options['selectize']['e_editable'])) - { - // TODO: instead of setTimeout, we should use an x-editable callback. - $click = "$('#" . $options['selectize']['e_editable'] . "').click(function(){ - // Attach success callback for replacing user id with name. - $.fn.editable.defaults.success = function(response, newValue) { - if(response.status == 'error') return; - if ($('#" . $options['selectize']['e_editable'] . "').hasClass('editable-userpicker')) { - $('#" . $options['selectize']['e_editable'] . "').hide(); - options" . $optionSuffix . " = options" . $optionSuffix . " || []; - userName = '" . LAN_ANONYMOUS . "'; - $.each(options" . $optionSuffix . ", function(key, value) { - if (value." . vartrue($ac['valueField'], 'value') . " == newValue) { - userName = value." . vartrue($ac['labelField'], 'label') . "; - } - }); - setTimeout(function(){ - $('#" . $options['selectize']['e_editable'] . "').html(userName).show(); - $.fn.editable.defaults.success = function(response, newValue) {} - }, 300); - } - }; - setTimeout(function(){ " . $js . " }, 300); - })"; - - e107::js('footer-inline', "$(document).ready(function(){" . $jsOptions . $click . "});"); - } - // We have to render JS for a simple form element. - else - { - e107::js('footer-inline', "$(document).ready(function(){" . $jsOptions . $js . "});"); - } + // Merge field settings with other selectize field settings. + e107::js('settings', array('selectize' => array($jsSettings))); } // TODO: remove typeahead. diff --git a/e107_handlers/js_manager.php b/e107_handlers/js_manager.php index 9536606b1..c047f1d4b 100644 --- a/e107_handlers/js_manager.php +++ b/e107_handlers/js_manager.php @@ -45,7 +45,10 @@ class e_jsmanager // "http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js", // "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" // "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" - "https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js" + "https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js", + // jQuery Once filters out all elements that had the same filter applied on them before. It can be used to + // ensure that a function is only applied once to an element. jQuery Once is used in e107.behaviors. + "https://cdnjs.cloudflare.com/ajax/libs/jquery-once/2.1.1/jquery.once.min.js" // , // "http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.min.js", // "http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/themes/base/jquery-ui.css", @@ -66,6 +69,15 @@ class e_jsmanager protected $_core_prefs = array(); + + /** + * Array to store JavaScript options will be rendered in footer as JSON object. + * + * @var array + */ + protected $_e_js_settings = array( + 'basePath' => e_HTTP, + ); /** * Core JS library files, loaded via e_jslib.php @@ -243,6 +255,9 @@ class e_jsmanager { $this->_libraries['jquery'] = array( "https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js", + // jQuery Once filters out all elements that had the same filter applied on them before. It can be used + // to ensure that a function is only applied once to an element. jQuery Once is used in e107.behaviors. + "https://cdnjs.cloudflare.com/ajax/libs/jquery-once/2.1.1/jquery.once.min.js", "https://cdn.jsdelivr.net/jquery.ui/1.11.4/jquery-ui.min.js", "https://cdn.jsdelivr.net/jquery.ui/1.11.4/themes/smoothness/jquery-ui.min.css" ); @@ -634,6 +649,18 @@ class e_jsmanager $this->addJs('footer_inline', $js_content, $priority); return $this; } + + /** + * Add JS settings to site footer + * + * @param array $js_settings + * @return e_jsmanager + */ + public function jsSettings(array $js_settings) + { + $this->addJs('settings', $js_settings); + return $this; + } function setDependency($dep) @@ -816,7 +843,7 @@ class e_jsmanager // FIXME - this could break something after CSS support was added, move it to separate method(s), recursion by type! // Causes the css error on jquery-ui as a css file is loaded as a js. - if(is_array($file_path) ) + if(is_array($file_path) && $type != 'settings') { // print_a($file_path); foreach ($file_path as $fp => $loc) @@ -960,6 +987,11 @@ class e_jsmanager return $this; break; + case 'settings': + $this->_e_js_settings = array_merge_recursive($this->_e_js_settings, $file_path); + return $this; + break; + default: return $this; break; @@ -985,7 +1017,7 @@ class e_jsmanager * @param boolean $return * @return string JS content - only if $return is true */ - public function renderJs($mod, $zone, $external = true, $return = false) + public function renderJs($mod, $zone = null, $external = true, $return = false) { if($return) { @@ -994,6 +1026,15 @@ class e_jsmanager switch($mod) { + case 'settings': + $tp = e107::getParser(); + $options = $this->arrayMergeDeepArray(array($this->_e_js_settings)); + $json = $tp->toJSON($options); + $js = 'jQuery.extend(e107.settings, ' . $json . ');'; + echo '<script>' . $js . '</script>'; + echo "\n"; + break; + case 'framework': // CDN frameworks - rendered before consolidation script (if enabled) $fw = array(); foreach ($this->_libraries as $lib) @@ -1109,6 +1150,36 @@ class e_jsmanager } } + + /** + * Merges multiple arrays, recursively, and returns the merged array. + */ + public function arrayMergeDeepArray($arrays) { + $result = array(); + + foreach ($arrays as $array) { + foreach ($array as $key => $value) { + // Renumber integer keys as array_merge_recursive() does. Note that PHP + // automatically converts array keys that are integer strings (e.g., '1') + // to integers. + if (is_integer($key)) { + $result[] = $value; + } + // Recurse when both values are arrays. + elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) { + $result[$key] = $this->arrayMergeDeepArray(array($result[$key], $value)); + } + // Otherwise, use the latter value, overriding any previous value. + else { + $result[$key] = $value; + } + } + } + + return $result; + } + + /** * Render JS/CSS file array * diff --git a/e107_web/js/core/all.jquery.js b/e107_web/js/core/all.jquery.js index 07ab2da55..9f27ee9b4 100644 --- a/e107_web/js/core/all.jquery.js +++ b/e107_web/js/core/all.jquery.js @@ -1,9 +1,126 @@ -// handle secured json string - the Prototype implementation +var e107 = e107 || {'settings': {}, 'behaviors': {}}; +// Allow other JavaScript libraries to use $. +// TODO: Use jQuery.noConflict(), but for this, need to rewrite all e107 javascript to use wrapper: (function ($) { ... })(jQuery); +// jQuery.noConflict(); + +(function ($) { + + /** + * Attach all registered behaviors to a page element. + * + * Behaviors are event-triggered actions that attach to page elements, enhancing + * default non-JavaScript UIs. Behaviors are registered in the e107.behaviors + * object using the method 'attach' and optionally also 'detach' as follows: + * @code + * e107.behaviors.behaviorName = { + * attach: function (context, settings) { + * ... + * }, + * detach: function (context, settings, trigger) { + * ... + * } + * }; + * @endcode + * + * e107.attachBehaviors is added below to the jQuery ready event and so + * runs on initial page load. Developers implementing Ajax in their + * solutions should also call this function after new page content has been + * loaded, feeding in an element to be processed, in order to attach all + * behaviors to the new content. + * + * Behaviors should use + * @code + * $(selector).once('behavior-name', function () { + * ... + * }); + * @endcode + * to ensure the behavior is attached only once to a given element. (Doing so + * enables the reprocessing of given elements, which may be needed on occasion + * despite the ability to limit behavior attachment to a particular element.) + * + * @param context + * An element to attach behaviors to. If none is given, the document element + * is used. + * @param settings + * An object containing settings for the current context. If none given, the + * global e107.settings object is used. + */ + e107.attachBehaviors = function (context, settings) { + context = context || document; + settings = settings || e107.settings; + // Execute all of them. + $.each(e107.behaviors, function () { + if ($.isFunction(this.attach)) { + this.attach(context, settings); + } + }); + }; + + /** + * Detach registered behaviors from a page element. + * + * Developers implementing AHAH/Ajax in their solutions should call this + * function before page content is about to be removed, feeding in an element + * to be processed, in order to allow special behaviors to detach from the + * content. + * + * Such implementations should look for the class name that was added in their + * corresponding e107.behaviors.behaviorName.attach implementation, i.e. + * behaviorName-processed, to ensure the behavior is detached only from + * previously processed elements. + * + * @param context + * An element to detach behaviors from. If none is given, the document element + * is used. + * @param settings + * An object containing settings for the current context. If none given, the + * global e107.settings object is used. + * @param trigger + * A string containing what's causing the behaviors to be detached. The + * possible triggers are: + * - unload: (default) The context element is being removed from the DOM. + * - move: The element is about to be moved within the DOM (for example, + * during a tabledrag row swap). After the move is completed, + * e107.attachBehaviors() is called, so that the behavior can undo + * whatever it did in response to the move. Many behaviors won't need to + * do anything simply in response to the element being moved, but because + * IFRAME elements reload their "src" when being moved within the DOM, + * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to + * take some action. + * - serialize: E.g. when an Ajax form is submitted, this is called with the + * form as the context. This provides every behavior within the form an + * opportunity to ensure that the field elements have correct content + * in them before the form is serialized. The canonical use-case is so + * that WYSIWYG editors can update the hidden textarea to which they are + * bound. + * + * @see e107.attachBehaviors + */ + e107.detachBehaviors = function (context, settings, trigger) { + context = context || document; + settings = settings || e107.settings; + trigger = trigger || 'unload'; + // Execute all of them. + $.each(e107.behaviors, function () { + if ($.isFunction(this.detach)) { + this.detach(context, settings, trigger); + } + }); + }; + + // Attach all behaviors. + $(function () { + e107.attachBehaviors(document, e107.settings); + }); + +})(jQuery); $.ajaxSetup({ - dataFilter: function(data, type) { - if(type != 'json' || !data) return data; + dataFilter: function (data, type) { + if (type != 'json' || !data) { + return data; + } return data.replace(/^\/\*-secure-([\s\S]*)\*\/\s*$/, '$1'); }, cache: false // Was Really NEeded! diff --git a/e107_web/js/selectize/js/selectize.init.js b/e107_web/js/selectize/js/selectize.init.js new file mode 100644 index 000000000..1fdb12c0d --- /dev/null +++ b/e107_web/js/selectize/js/selectize.init.js @@ -0,0 +1,173 @@ +(function ($) { + + /** + * Behavior to initialize autocomplete fields with selectize.js + * + * @type {{attach: e107.behaviors.selectizeInit.attach}} + */ + e107.behaviors.selectizeInit = { + attach: function (context, settings) { + if (e107.settings.selectize) { + $.each(e107.settings.selectize, function (index, item) { + var key = item.id; + + // Inline, popup editor. Initialize selectize after opening popup. + if (item.options.e_editable) { + $(context).find('#' + item.options.e_editable).once('selectize-editable-init').each(function () { + var $eEditable = $('#' + item.options.e_editable); + + $eEditable.click(function () { + // Attach success callback for replacing user id with name. + $.fn.editable.defaults.success = function (response, newValue) { + if (response.status == 'error') return; + if ($eEditable.hasClass('editable-userpicker')) { + $eEditable.hide(); + var options = item.options.options ? item.options.options : []; + var userName = item.strings.anonymous ? item.strings.anonymous : ''; + var valueField = item.options.valueField ? item.options.valueField : 'value'; + var labelField = item.options.labelField ? item.options.labelField : 'label'; + $.each(options, function (key, value) { + if (value[valueField] == newValue) { + userName = value[labelField]; + } + }); + setTimeout(function () { + $eEditable.html(userName).show(); + $.fn.editable.defaults.success = function (response, newValue) { + } + }, 300); + } + }; + + setTimeout(function () { + $(context).find('#' + key).once('selectize-editable-init').each(function () { + var $item = $(this); + + $item.selectize({ + // General options. + items: item.options.items ? item.options.items : [], + delimiter: item.options.delimiter ? item.options.delimiter : ',', + diacritics: item.options.diacritics ? item.options.diacritics : false, + create: item.options.create ? item.options.create : false, + createOnBlur: item.options.createOnBlur ? item.options.createOnBlur : false, + highlight: item.options.highlight ? item.options.highlight : false, + persist: item.options.persist ? item.options.persist : false, + openOnFocus: item.options.openOnFocus ? item.options.openOnFocus : false, + maxOptions: item.options.maxOptions ? item.options.maxOptions : null, + maxItems: item.options.maxItems ? item.options.maxItems : null, + hideSelected: item.options.hideSelected ? item.options.hideSelected : false, + closeAfterSelect: item.options.closeAfterSelect ? item.options.closeAfterSelect : false, + allowEmptyOption: item.options.allowEmptyOption ? item.options.allowEmptyOption : false, + scrollDuration: item.options.scrollDuration ? item.options.scrollDuration : 60, + loadThrottle: item.options.loadThrottle ? item.options.loadThrottle : 300, + loadingClass: item.options.loadingClass ? item.options.loadingClass : 'loading', + preload: item.options.preload ? item.options.preload : false, + dropdownParent: item.options.dropdownParent ? item.options.dropdownParent : null, + addPrecedence: item.options.addPrecedence ? item.options.addPrecedence : false, + selectOnTab: item.options.selectOnTab ? item.options.selectOnTab : false, + mode: item.options.mode ? item.options.mode : 'multi', + plugins: item.options.plugins ? item.options.plugins : [], + + // Data / Searching. + options: item.options.options ? item.options.options : [], + valueField: item.options.valueField ? item.options.valueField : 'value', + labelField: item.options.labelField ? item.options.labelField : 'label', + searchField: item.options.searchField ? item.options.searchField : 'label', + + // Callbacks. + load: function (query, callback) { + var loadPath = item.options.loadPath ? item.options.loadPath : ''; + if (loadPath == '') return callback([]); + if (!query.length) return callback([]); + $.ajax({ + url: loadPath, + type: 'POST', + dataType: 'json', + data: { + q: query, + l: item.options.maxOptions ? item.options.maxOptions : 10 + }, + error: function () { + callback([]); + }, + success: function (data) { + // Update items in options array of this field. + item.options.options = data; + callback(data); + } + }); + } + }); + }); + }, 300); + }); + }); + } + else { + $(context).find('#' + key).once('selectize-init').each(function () { + var $item = $(this); + + $item.selectize({ + // General options. + items: item.options.items ? item.options.items : [], + delimiter: item.options.delimiter ? item.options.delimiter : ',', + diacritics: item.options.diacritics ? item.options.diacritics : false, + create: item.options.create ? item.options.create : false, + createOnBlur: item.options.createOnBlur ? item.options.createOnBlur : false, + highlight: item.options.highlight ? item.options.highlight : false, + persist: item.options.persist ? item.options.persist : false, + openOnFocus: item.options.openOnFocus ? item.options.openOnFocus : false, + maxOptions: item.options.maxOptions ? item.options.maxOptions : null, + maxItems: item.options.maxItems ? item.options.maxItems : null, + hideSelected: item.options.hideSelected ? item.options.hideSelected : false, + closeAfterSelect: item.options.closeAfterSelect ? item.options.closeAfterSelect : false, + allowEmptyOption: item.options.allowEmptyOption ? item.options.allowEmptyOption : false, + scrollDuration: item.options.scrollDuration ? item.options.scrollDuration : 60, + loadThrottle: item.options.loadThrottle ? item.options.loadThrottle : 300, + loadingClass: item.options.loadingClass ? item.options.loadingClass : 'loading', + preload: item.options.preload ? item.options.preload : false, + dropdownParent: item.options.dropdownParent ? item.options.dropdownParent : null, + addPrecedence: item.options.addPrecedence ? item.options.addPrecedence : false, + selectOnTab: item.options.selectOnTab ? item.options.selectOnTab : false, + mode: item.options.mode ? item.options.mode : 'multi', + plugins: item.options.plugins ? item.options.plugins : [], + + // Data / Searching. + options: item.options.options ? item.options.options : [], + valueField: item.options.valueField ? item.options.valueField : 'value', + labelField: item.options.labelField ? item.options.labelField : 'label', + searchField: item.options.searchField ? item.options.searchField : 'label', + + // Callbacks. + load: function (query, callback) { + var loadPath = item.options.loadPath ? item.options.loadPath : ''; + if (loadPath == '') return callback([]); + if (!query.length) return callback([]); + $.ajax({ + url: loadPath, + type: 'POST', + dataType: 'json', + data: { + q: query, + l: item.options.maxOptions ? item.options.maxOptions : 10 + }, + error: function () { + callback([]); + }, + success: function (data) { + // Update items in options array of this field. + item.options.options = data; + callback(data); + } + }); + } + }); + }); + } + + }); + } + } + }; + +})(jQuery);