");
+ $input.on("blur.tt", function($e) {
+ var active, isActive, hasActive;
+ active = document.activeElement;
+ isActive = $menu.is(active);
+ hasActive = $menu.has(active).length > 0;
+ if (_.isMsie() && (isActive || hasActive)) {
+ $e.preventDefault();
+ $e.stopImmediatePropagation();
+ _.defer(function() {
+ $input.focus();
+ });
+ }
+ });
+ $menu.on("mousedown.tt", function($e) {
+ $e.preventDefault();
+ });
+ },
+ _onSelectableClicked: function onSelectableClicked(type, $el) {
+ this.select($el);
+ },
+ _onDatasetCleared: function onDatasetCleared() {
+ this._updateHint();
+ },
+ _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) {
+ this._updateHint();
+ this.eventBus.trigger("render", suggestions, async, dataset);
+ },
+ _onAsyncRequested: function onAsyncRequested(type, dataset, query) {
+ this.eventBus.trigger("asyncrequest", query, dataset);
+ },
+ _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) {
+ this.eventBus.trigger("asynccancel", query, dataset);
+ },
+ _onAsyncReceived: function onAsyncReceived(type, dataset, query) {
+ this.eventBus.trigger("asyncreceive", query, dataset);
+ },
+ _onFocused: function onFocused() {
+ this._minLengthMet() && this.menu.update(this.input.getQuery());
+ },
+ _onBlurred: function onBlurred() {
+ if (this.input.hasQueryChangedSinceLastFocus()) {
+ this.eventBus.trigger("change", this.input.getQuery());
+ }
+ },
+ _onEnterKeyed: function onEnterKeyed(type, $e) {
+ var $selectable;
+ if ($selectable = this.menu.getActiveSelectable()) {
+ this.select($selectable) && $e.preventDefault();
+ }
+ },
+ _onTabKeyed: function onTabKeyed(type, $e) {
+ var $selectable;
+ if ($selectable = this.menu.getActiveSelectable()) {
+ this.select($selectable) && $e.preventDefault();
+ } else if ($selectable = this.menu.getTopSelectable()) {
+ this.autocomplete($selectable) && $e.preventDefault();
+ }
+ },
+ _onEscKeyed: function onEscKeyed() {
+ this.close();
+ },
+ _onUpKeyed: function onUpKeyed() {
+ this.moveCursor(-1);
+ },
+ _onDownKeyed: function onDownKeyed() {
+ this.moveCursor(+1);
+ },
+ _onLeftKeyed: function onLeftKeyed() {
+ if (this.dir === "rtl" && this.input.isCursorAtEnd()) {
+ this.autocomplete(this.menu.getTopSelectable());
+ }
+ },
+ _onRightKeyed: function onRightKeyed() {
+ if (this.dir === "ltr" && this.input.isCursorAtEnd()) {
+ this.autocomplete(this.menu.getTopSelectable());
+ }
+ },
+ _onQueryChanged: function onQueryChanged(e, query) {
+ this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty();
+ },
+ _onWhitespaceChanged: function onWhitespaceChanged() {
+ this._updateHint();
+ },
+ _onLangDirChanged: function onLangDirChanged(e, dir) {
+ if (this.dir !== dir) {
+ this.dir = dir;
+ this.menu.setLanguageDirection(dir);
+ }
+ },
+ _openIfActive: function openIfActive() {
+ this.isActive() && this.open();
+ },
+ _minLengthMet: function minLengthMet(query) {
+ query = _.isString(query) ? query : this.input.getQuery() || "";
+ return query.length >= this.minLength;
+ },
+ _updateHint: function updateHint() {
+ var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match;
+ $selectable = this.menu.getTopSelectable();
+ data = this.menu.getSelectableData($selectable);
+ val = this.input.getInputValue();
+ if (data && !_.isBlankString(val) && !this.input.hasOverflow()) {
+ query = Input.normalizeQuery(val);
+ escapedQuery = _.escapeRegExChars(query);
+ frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i");
+ match = frontMatchRegEx.exec(data.val);
+ match && this.input.setHint(val + match[1]);
+ } else {
+ this.input.clearHint();
+ }
+ },
+ isEnabled: function isEnabled() {
+ return this.enabled;
+ },
+ enable: function enable() {
+ this.enabled = true;
+ },
+ disable: function disable() {
+ this.enabled = false;
+ },
+ isActive: function isActive() {
+ return this.active;
+ },
+ activate: function activate() {
+ if (this.isActive()) {
+ return true;
+ } else if (!this.isEnabled() || this.eventBus.before("active")) {
+ return false;
+ } else {
+ this.active = true;
+ this.eventBus.trigger("active");
+ return true;
+ }
+ },
+ deactivate: function deactivate() {
+ if (!this.isActive()) {
+ return true;
+ } else if (this.eventBus.before("idle")) {
+ return false;
+ } else {
+ this.active = false;
+ this.close();
+ this.eventBus.trigger("idle");
+ return true;
+ }
+ },
+ isOpen: function isOpen() {
+ return this.menu.isOpen();
+ },
+ open: function open() {
+ if (!this.isOpen() && !this.eventBus.before("open")) {
+ this.menu.open();
+ this._updateHint();
+ this.eventBus.trigger("open");
+ }
+ return this.isOpen();
+ },
+ close: function close() {
+ if (this.isOpen() && !this.eventBus.before("close")) {
+ this.menu.close();
+ this.input.clearHint();
+ this.input.resetInputValue();
+ this.eventBus.trigger("close");
+ }
+ return !this.isOpen();
+ },
+ setVal: function setVal(val) {
+ this.input.setQuery(_.toStr(val));
+ },
+ getVal: function getVal() {
+ return this.input.getQuery();
+ },
+ select: function select($selectable) {
+ var data = this.menu.getSelectableData($selectable);
+ if (data && !this.eventBus.before("select", data.obj)) {
+ this.input.setQuery(data.val, true);
+ this.eventBus.trigger("select", data.obj);
+ this.close();
+ return true;
+ }
+ return false;
+ },
+ autocomplete: function autocomplete($selectable) {
+ var query, data, isValid;
+ query = this.input.getQuery();
+ data = this.menu.getSelectableData($selectable);
+ isValid = data && query !== data.val;
+ if (isValid && !this.eventBus.before("autocomplete", data.obj)) {
+ this.input.setQuery(data.val);
+ this.eventBus.trigger("autocomplete", data.obj);
+ return true;
+ }
+ return false;
+ },
+ moveCursor: function moveCursor(delta) {
+ var query, $candidate, data, payload, cancelMove;
+ query = this.input.getQuery();
+ $candidate = this.menu.selectableRelativeToCursor(delta);
+ data = this.menu.getSelectableData($candidate);
+ payload = data ? data.obj : null;
+ cancelMove = this._minLengthMet() && this.menu.update(query);
+ if (!cancelMove && !this.eventBus.before("cursorchange", payload)) {
+ this.menu.setCursor($candidate);
+ if (data) {
+ this.input.setInputValue(data.val);
+ } else {
+ this.input.resetInputValue();
+ this._updateHint();
+ }
+ this.eventBus.trigger("cursorchange", payload);
+ return true;
+ }
+ return false;
+ },
+ destroy: function destroy() {
+ this.input.destroy();
+ this.menu.destroy();
+ }
+ });
+ return Typeahead;
+ function c(ctx) {
+ var methods = [].slice.call(arguments, 1);
+ return function() {
+ var args = [].slice.call(arguments);
+ _.each(methods, function(method) {
+ return ctx[method].apply(ctx, args);
+ });
+ };
+ }
+ }();
+ (function() {
+ "use strict";
+ var old, keys, methods;
+ old = $.fn.typeahead;
+ keys = {
+ www: "tt-www",
+ attrs: "tt-attrs",
+ typeahead: "tt-typeahead"
+ };
+ methods = {
+ initialize: function initialize(o, datasets) {
+ var www;
+ datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);
+ o = o || {};
+ www = WWW(o.classNames);
+ return this.each(attach);
+ function attach() {
+ var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor;
+ _.each(datasets, function(d) {
+ d.highlight = !!o.highlight;
+ });
+ $input = $(this);
+ $wrapper = $(www.html.wrapper);
+ $hint = $elOrNull(o.hint);
+ $menu = $elOrNull(o.menu);
+ defaultHint = o.hint !== false && !$hint;
+ defaultMenu = o.menu !== false && !$menu;
+ defaultHint && ($hint = buildHintFromInput($input, www));
+ defaultMenu && ($menu = $(www.html.menu).css(www.css.menu));
+ $hint && $hint.val("");
+ $input = prepInput($input, www);
+ if (defaultHint || defaultMenu) {
+ $wrapper.css(www.css.wrapper);
+ $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint);
+ $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null);
+ }
+ MenuConstructor = defaultMenu ? DefaultMenu : Menu;
+ eventBus = new EventBus({
+ el: $input
+ });
+ input = new Input({
+ hint: $hint,
+ input: $input
+ }, www);
+ menu = new MenuConstructor({
+ node: $menu,
+ datasets: datasets
+ }, www);
+ typeahead = new Typeahead({
+ input: input,
+ menu: menu,
+ eventBus: eventBus,
+ minLength: o.minLength
+ }, www);
+ $input.data(keys.www, www);
+ $input.data(keys.typeahead, typeahead);
+ }
+ },
+ isEnabled: function isEnabled() {
+ var enabled;
+ ttEach(this.first(), function(t) {
+ enabled = t.isEnabled();
+ });
+ return enabled;
+ },
+ enable: function enable() {
+ ttEach(this, function(t) {
+ t.enable();
+ });
+ return this;
+ },
+ disable: function disable() {
+ ttEach(this, function(t) {
+ t.disable();
+ });
+ return this;
+ },
+ isActive: function isActive() {
+ var active;
+ ttEach(this.first(), function(t) {
+ active = t.isActive();
+ });
+ return active;
+ },
+ activate: function activate() {
+ ttEach(this, function(t) {
+ t.activate();
+ });
+ return this;
+ },
+ deactivate: function deactivate() {
+ ttEach(this, function(t) {
+ t.deactivate();
+ });
+ return this;
+ },
+ isOpen: function isOpen() {
+ var open;
+ ttEach(this.first(), function(t) {
+ open = t.isOpen();
+ });
+ return open;
+ },
+ open: function open() {
+ ttEach(this, function(t) {
+ t.open();
+ });
+ return this;
+ },
+ close: function close() {
+ ttEach(this, function(t) {
+ t.close();
+ });
+ return this;
+ },
+ select: function select(el) {
+ var success = false, $el = $(el);
+ ttEach(this.first(), function(t) {
+ success = t.select($el);
+ });
+ return success;
+ },
+ autocomplete: function autocomplete(el) {
+ var success = false, $el = $(el);
+ ttEach(this.first(), function(t) {
+ success = t.autocomplete($el);
+ });
+ return success;
+ },
+ moveCursor: function moveCursoe(delta) {
+ var success = false;
+ ttEach(this.first(), function(t) {
+ success = t.moveCursor(delta);
+ });
+ return success;
+ },
+ val: function val(newVal) {
+ var query;
+ if (!arguments.length) {
+ ttEach(this.first(), function(t) {
+ query = t.getVal();
+ });
+ return query;
+ } else {
+ ttEach(this, function(t) {
+ t.setVal(newVal);
+ });
+ return this;
+ }
+ },
+ destroy: function destroy() {
+ ttEach(this, function(typeahead, $input) {
+ revert($input);
+ typeahead.destroy();
+ });
+ return this;
+ }
+ };
+ $.fn.typeahead = function(method) {
+ if (methods[method]) {
+ return methods[method].apply(this, [].slice.call(arguments, 1));
+ } else {
+ return methods.initialize.apply(this, arguments);
+ }
+ };
+ $.fn.typeahead.noConflict = function noConflict() {
+ $.fn.typeahead = old;
+ return this;
+ };
+ function ttEach($els, fn) {
+ $els.each(function() {
+ var $input = $(this), typeahead;
+ (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input);
+ });
+ }
+ function buildHintFromInput($input, www) {
+ return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({
+ autocomplete: "off",
+ spellcheck: "false",
+ tabindex: -1
+ });
+ }
+ function prepInput($input, www) {
+ $input.data(keys.attrs, {
+ dir: $input.attr("dir"),
+ autocomplete: $input.attr("autocomplete"),
+ spellcheck: $input.attr("spellcheck"),
+ style: $input.attr("style")
+ });
+ $input.addClass(www.classes.input).attr({
+ autocomplete: "off",
+ spellcheck: false
+ });
+ try {
+ !$input.attr("dir") && $input.attr("dir", "auto");
+ } catch (e) {}
+ return $input;
+ }
+ function getBackgroundStyles($el) {
+ return {
+ backgroundAttachment: $el.css("background-attachment"),
+ backgroundClip: $el.css("background-clip"),
+ backgroundColor: $el.css("background-color"),
+ backgroundImage: $el.css("background-image"),
+ backgroundOrigin: $el.css("background-origin"),
+ backgroundPosition: $el.css("background-position"),
+ backgroundRepeat: $el.css("background-repeat"),
+ backgroundSize: $el.css("background-size")
+ };
+ }
+ function revert($input) {
+ var www, $wrapper;
+ www = $input.data(keys.www);
+ $wrapper = $input.parent().filter(www.selectors.wrapper);
+ _.each($input.data(keys.attrs), function(val, key) {
+ _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
+ });
+ $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input);
+ if ($wrapper.length) {
+ $input.detach().insertAfter($wrapper);
+ $wrapper.remove();
+ }
+ }
+ function $elOrNull(obj) {
+ var isValid, $el;
+ isValid = _.isJQuery(obj) || _.isElement(obj);
+ $el = isValid ? $(obj).first() : [];
+ return $el.length ? $el : null;
+ }
+ })();
+});
+;/*})'"*/
+;/*})'"*/
+
+Drupal.wysiwyg = Drupal.wysiwyg || { 'instances': {}, 'excludeIdSelectors': { 'tokens': ['[id^="token-"]'] } };
+
+Drupal.wysiwyg.editor = Drupal.wysiwyg.editor || { 'init': {}, 'update': {}, 'attach': {}, 'detach': {}, 'instance': {} };
+
+Drupal.wysiwyg.plugins = Drupal.wysiwyg.plugins || {};
+
+(function ($) {
+ // Determine support for queryCommandEnabled().
+ // An exception should be thrown for non-existing commands.
+ // Safari and Chrome (WebKit based) return -1 instead.
+ try {
+ document.queryCommandEnabled('__wysiwygTestCommand');
+ $.support.queryCommandEnabled = false;
+ }
+ catch (error) {
+ $.support.queryCommandEnabled = true;
+ }
+})(jQuery);
+
+;/*})'"*/
+;/*})'"*/
+/*!
+Chosen, a Select Box Enhancer for jQuery and Prototype
+by Patrick Filler for Harvest, http://getharvest.com
+
+Version 1.8.7
+Full source at https://github.com/harvesthq/chosen
+Copyright (c) 2011-2018 Harvest http://getharvest.com
+
+MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
+This file is generated by `grunt build`, do not edit it by hand.
+*/
+
+(function() {
+ var $, AbstractChosen, Chosen, SelectParser,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+ SelectParser = (function() {
+ function SelectParser() {
+ this.options_index = 0;
+ this.parsed = [];
+ }
+
+ SelectParser.prototype.add_node = function(child) {
+ if (child.nodeName.toUpperCase() === "OPTGROUP") {
+ return this.add_group(child);
+ } else {
+ return this.add_option(child);
+ }
+ };
+
+ SelectParser.prototype.add_group = function(group) {
+ var group_position, i, len, option, ref, results1;
+ group_position = this.parsed.length;
+ this.parsed.push({
+ array_index: group_position,
+ group: true,
+ label: group.label,
+ title: group.title ? group.title : void 0,
+ children: 0,
+ disabled: group.disabled,
+ classes: group.className
+ });
+ ref = group.childNodes;
+ results1 = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ option = ref[i];
+ results1.push(this.add_option(option, group_position, group.disabled));
+ }
+ return results1;
+ };
+
+ SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
+ if (option.nodeName.toUpperCase() === "OPTION") {
+ if (option.text !== "") {
+ if (group_position != null) {
+ this.parsed[group_position].children += 1;
+ }
+ this.parsed.push({
+ array_index: this.parsed.length,
+ options_index: this.options_index,
+ value: option.value,
+ text: option.text,
+ html: option.innerHTML,
+ title: option.title ? option.title : void 0,
+ selected: option.selected,
+ disabled: group_disabled === true ? group_disabled : option.disabled,
+ group_array_index: group_position,
+ group_label: group_position != null ? this.parsed[group_position].label : null,
+ classes: option.className,
+ style: option.style.cssText
+ });
+ } else {
+ this.parsed.push({
+ array_index: this.parsed.length,
+ options_index: this.options_index,
+ empty: true
+ });
+ }
+ return this.options_index += 1;
+ }
+ };
+
+ return SelectParser;
+
+ })();
+
+ SelectParser.select_to_array = function(select) {
+ var child, i, len, parser, ref;
+ parser = new SelectParser();
+ ref = select.childNodes;
+ for (i = 0, len = ref.length; i < len; i++) {
+ child = ref[i];
+ parser.add_node(child);
+ }
+ return parser.parsed;
+ };
+
+ AbstractChosen = (function() {
+ function AbstractChosen(form_field, options1) {
+ this.form_field = form_field;
+ this.options = options1 != null ? options1 : {};
+ this.label_click_handler = bind(this.label_click_handler, this);
+ if (!AbstractChosen.browser_is_supported()) {
+ return;
+ }
+ this.is_multiple = this.form_field.multiple;
+ this.set_default_text();
+ this.set_default_values();
+ this.setup();
+ this.set_up_html();
+ this.register_observers();
+ this.on_ready();
+ }
+
+ AbstractChosen.prototype.set_default_values = function() {
+ this.click_test_action = (function(_this) {
+ return function(evt) {
+ return _this.test_active_click(evt);
+ };
+ })(this);
+ this.activate_action = (function(_this) {
+ return function(evt) {
+ return _this.activate_field(evt);
+ };
+ })(this);
+ this.active_field = false;
+ this.mouse_on_container = false;
+ this.results_showing = false;
+ this.result_highlighted = null;
+ this.is_rtl = this.options.rtl || /\bchosen-rtl\b/.test(this.form_field.className);
+ this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
+ this.disable_search_threshold = this.options.disable_search_threshold || 0;
+ this.disable_search = this.options.disable_search || false;
+ this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
+ this.group_search = this.options.group_search != null ? this.options.group_search : true;
+ this.search_contains = this.options.search_contains || false;
+ this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
+ this.max_selected_options = this.options.max_selected_options || Infinity;
+ this.inherit_select_classes = this.options.inherit_select_classes || false;
+ this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
+ this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
+ this.include_group_label_in_selected = this.options.include_group_label_in_selected || false;
+ this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY;
+ this.case_sensitive_search = this.options.case_sensitive_search || false;
+ return this.hide_results_on_select = this.options.hide_results_on_select != null ? this.options.hide_results_on_select : false;
+ };
+
+ AbstractChosen.prototype.set_default_text = function() {
+ if (this.form_field.getAttribute("data-placeholder")) {
+ this.default_text = this.form_field.getAttribute("data-placeholder");
+ } else if (this.is_multiple) {
+ this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
+ if(this.form_field.id=='edit-field-tag-und') this.default_text = this.options.placeholder_text_multiple_group;
+ if(this.form_field.id=='edit-field-tags-und') this.default_text = this.options.placeholder_text_multiple_tags;
+ } else {
+ this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
+ }
+ this.default_text = this.escape_html(this.default_text);
+ return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
+ };
+
+ AbstractChosen.prototype.choice_label = function(item) {
+ if (this.include_group_label_in_selected && (item.group_label != null)) {
+ return "
" + (this.escape_html(item.group_label)) + "" + item.html;
+ } else {
+ return item.html;
+ }
+ };
+
+ AbstractChosen.prototype.mouse_enter = function() {
+ return this.mouse_on_container = true;
+ };
+
+ AbstractChosen.prototype.mouse_leave = function() {
+ return this.mouse_on_container = false;
+ };
+
+ AbstractChosen.prototype.input_focus = function(evt) {
+ if (this.is_multiple) {
+ if (!this.active_field) {
+ return setTimeout(((function(_this) {
+ return function() {
+ return _this.container_mousedown();
+ };
+ })(this)), 50);
+ }
+ } else {
+ if (!this.active_field) {
+ return this.activate_field();
+ }
+ }
+ };
+
+ AbstractChosen.prototype.input_blur = function(evt) {
+ if (!this.mouse_on_container) {
+ this.active_field = false;
+ return setTimeout(((function(_this) {
+ return function() {
+ return _this.blur_test();
+ };
+ })(this)), 100);
+ }
+ };
+
+ AbstractChosen.prototype.label_click_handler = function(evt) {
+ if (this.is_multiple) {
+ return this.container_mousedown(evt);
+ } else {
+ return this.activate_field();
+ }
+ };
+
+ AbstractChosen.prototype.results_option_build = function(options) {
+ var content, data, data_content, i, len, ref, shown_results;
+ content = '';
+ shown_results = 0;
+ ref = this.results_data;
+ for (i = 0, len = ref.length; i < len; i++) {
+ data = ref[i];
+ data_content = '';
+ if (data.group) {
+ data_content = this.result_add_group(data);
+ } else {
+ data_content = this.result_add_option(data);
+ }
+ if (data_content !== '') {
+ shown_results++;
+ content += data_content;
+ }
+ if (options != null ? options.first : void 0) {
+ if (data.selected && this.is_multiple) {
+ this.choice_build(data);
+ } else if (data.selected && !this.is_multiple) {
+ this.single_set_selected_text(this.choice_label(data));
+ }
+ }
+ if (shown_results >= this.max_shown_results) {
+ break;
+ }
+ }
+ return content;
+ };
+
+ AbstractChosen.prototype.result_add_option = function(option) {
+ var classes, option_el;
+ if (!option.search_match) {
+ return '';
+ }
+ if (!this.include_option_in_results(option)) {
+ return '';
+ }
+ classes = [];
+ if (!option.disabled && !(option.selected && this.is_multiple)) {
+ classes.push("active-result");
+ }
+ if (option.disabled && !(option.selected && this.is_multiple)) {
+ classes.push("disabled-result");
+ }
+ if (option.selected) {
+ classes.push("result-selected");
+ }
+ if (option.group_array_index != null) {
+ classes.push("group-option");
+ }
+ if (option.classes !== "") {
+ classes.push(option.classes);
+ }
+ option_el = document.createElement("li");
+ option_el.className = classes.join(" ");
+ if (option.style) {
+ option_el.style.cssText = option.style;
+ }
+ option_el.setAttribute("data-option-array-index", option.array_index);
+ option_el.innerHTML = option.highlighted_html || option.html;
+ if (option.title) {
+ option_el.title = option.title;
+ }
+ return this.outerHTML(option_el);
+ };
+
+ AbstractChosen.prototype.result_add_group = function(group) {
+ var classes, group_el;
+ if (!(group.search_match || group.group_match)) {
+ return '';
+ }
+ if (!(group.active_options > 0)) {
+ return '';
+ }
+ classes = [];
+ classes.push("group-result");
+ if (group.classes) {
+ classes.push(group.classes);
+ }
+ group_el = document.createElement("li");
+ group_el.className = classes.join(" ");
+ group_el.innerHTML = group.highlighted_html || this.escape_html(group.label);
+ if (group.title) {
+ group_el.title = group.title;
+ }
+ return this.outerHTML(group_el);
+ };
+
+ AbstractChosen.prototype.results_update_field = function() {
+ this.set_default_text();
+ if (!this.is_multiple) {
+ this.results_reset_cleanup();
+ }
+ this.result_clear_highlight();
+ this.results_build();
+ if (this.results_showing) {
+ return this.winnow_results();
+ }
+ };
+
+ AbstractChosen.prototype.reset_single_select_options = function() {
+ var i, len, ref, result, results1;
+ ref = this.results_data;
+ results1 = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ result = ref[i];
+ if (result.selected) {
+ results1.push(result.selected = false);
+ } else {
+ results1.push(void 0);
+ }
+ }
+ return results1;
+ };
+
+ AbstractChosen.prototype.results_toggle = function() {
+ if (this.results_showing) {
+ return this.results_hide();
+ } else {
+ return this.results_show();
+ }
+ };
+
+ AbstractChosen.prototype.results_search = function(evt) {
+ if (this.results_showing) {
+ return this.winnow_results();
+ } else {
+ return this.results_show();
+ }
+ };
+
+ AbstractChosen.prototype.winnow_results = function(options) {
+ var escapedQuery, fix, i, len, option, prefix, query, ref, regex, results, results_group, search_match, startpos, suffix, text;
+ this.no_results_clear();
+ results = 0;
+ query = this.get_search_text();
+ escapedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ regex = this.get_search_regex(escapedQuery);
+ ref = this.results_data;
+ for (i = 0, len = ref.length; i < len; i++) {
+ option = ref[i];
+ option.search_match = false;
+ results_group = null;
+ search_match = null;
+ option.highlighted_html = '';
+ if (this.include_option_in_results(option)) {
+ if (option.group) {
+ option.group_match = false;
+ option.active_options = 0;
+ }
+ if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
+ results_group = this.results_data[option.group_array_index];
+ if (results_group.active_options === 0 && results_group.search_match) {
+ results += 1;
+ }
+ results_group.active_options += 1;
+ }
+ text = option.group ? option.label : option.text;
+ if (!(option.group && !this.group_search)) {
+ search_match = this.search_string_match(text, regex);
+ option.search_match = search_match != null;
+ if (option.search_match && !option.group) {
+ results += 1;
+ }
+ if (option.search_match) {
+ if (query.length) {
+ startpos = search_match.index;
+ prefix = text.slice(0, startpos);
+ fix = text.slice(startpos, startpos + query.length);
+ suffix = text.slice(startpos + query.length);
+ option.highlighted_html = (this.escape_html(prefix)) + "
" + (this.escape_html(fix)) + "" + (this.escape_html(suffix));
+ }
+ if (results_group != null) {
+ results_group.group_match = true;
+ }
+ } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
+ option.search_match = true;
+ }
+ }
+ }
+ }
+ this.result_clear_highlight();
+ if (results < 1 && query.length) {
+ this.update_results_content("");
+ return this.no_results(query);
+ } else {
+ this.update_results_content(this.results_option_build());
+ if (!(options != null ? options.skip_highlight : void 0)) {
+ return this.winnow_results_set_highlight();
+ }
+ }
+ };
+
+ AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
+ var regex_flag, regex_string;
+ regex_string = this.search_contains ? escaped_search_string : "(^|\\s|\\b)" + escaped_search_string + "[^\\s]*";
+ if (!(this.enable_split_word_search || this.search_contains)) {
+ regex_string = "^" + regex_string;
+ }
+ regex_flag = this.case_sensitive_search ? "" : "i";
+ return new RegExp(regex_string, regex_flag);
+ };
+
+ AbstractChosen.prototype.search_string_match = function(search_string, regex) {
+ var match;
+ match = regex.exec(search_string);
+ if (!this.search_contains && (match != null ? match[1] : void 0)) {
+ match.index += 1;
+ }
+ return match;
+ };
+
+ AbstractChosen.prototype.choices_count = function() {
+ var i, len, option, ref;
+ if (this.selected_option_count != null) {
+ return this.selected_option_count;
+ }
+ this.selected_option_count = 0;
+ ref = this.form_field.options;
+ for (i = 0, len = ref.length; i < len; i++) {
+ option = ref[i];
+ if (option.selected) {
+ this.selected_option_count += 1;
+ }
+ }
+ return this.selected_option_count;
+ };
+
+ AbstractChosen.prototype.choices_click = function(evt) {
+ evt.preventDefault();
+ this.activate_field();
+ if (!(this.results_showing || this.is_disabled)) {
+ return this.results_show();
+ }
+ };
+
+ AbstractChosen.prototype.keydown_checker = function(evt) {
+ var ref, stroke;
+ stroke = (ref = evt.which) != null ? ref : evt.keyCode;
+ this.search_field_scale();
+ if (stroke !== 8 && this.pending_backstroke) {
+ this.clear_backstroke();
+ }
+ switch (stroke) {
+ case 8:
+ this.backstroke_length = this.get_search_field_value().length;
+ break;
+ case 9:
+ if (this.results_showing && !this.is_multiple) {
+ this.result_select(evt);
+ }
+ this.mouse_on_container = false;
+ break;
+ case 13:
+ if (this.results_showing) {
+ evt.preventDefault();
+ }
+ break;
+ case 27:
+ if (this.results_showing) {
+ evt.preventDefault();
+ }
+ break;
+ case 32:
+ if (this.disable_search) {
+ evt.preventDefault();
+ }
+ break;
+ case 38:
+ evt.preventDefault();
+ this.keyup_arrow();
+ break;
+ case 40:
+ evt.preventDefault();
+ this.keydown_arrow();
+ break;
+ }
+ };
+
+ AbstractChosen.prototype.keyup_checker = function(evt) {
+ var ref, stroke;
+ stroke = (ref = evt.which) != null ? ref : evt.keyCode;
+ this.search_field_scale();
+ switch (stroke) {
+ case 8:
+ if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
+ this.keydown_backstroke();
+ } else if (!this.pending_backstroke) {
+ this.result_clear_highlight();
+ this.results_search();
+ }
+ break;
+ case 13:
+ evt.preventDefault();
+ if (this.results_showing) {
+ this.result_select(evt);
+ }
+ break;
+ case 27:
+ if (this.results_showing) {
+ this.results_hide();
+ }
+ break;
+ case 9:
+ case 16:
+ case 17:
+ case 18:
+ case 38:
+ case 40:
+ case 91:
+ break;
+ default:
+ this.results_search();
+ break;
+ }
+ };
+
+ AbstractChosen.prototype.clipboard_event_checker = function(evt) {
+ if (this.is_disabled) {
+ return;
+ }
+ return setTimeout(((function(_this) {
+ return function() {
+ return _this.results_search();
+ };
+ })(this)), 50);
+ };
+
+ AbstractChosen.prototype.container_width = function() {
+ if (this.options.width != null) {
+ return this.options.width;
+ } else {
+ return this.form_field.offsetWidth + "px";
+ }
+ };
+
+ AbstractChosen.prototype.include_option_in_results = function(option) {
+ if (this.is_multiple && (!this.display_selected_options && option.selected)) {
+ return false;
+ }
+ if (!this.display_disabled_options && option.disabled) {
+ return false;
+ }
+ if (option.empty) {
+ return false;
+ }
+ return true;
+ };
+
+ AbstractChosen.prototype.search_results_touchstart = function(evt) {
+ this.touch_started = true;
+ return this.search_results_mouseover(evt);
+ };
+
+ AbstractChosen.prototype.search_results_touchmove = function(evt) {
+ this.touch_started = false;
+ return this.search_results_mouseout(evt);
+ };
+
+ AbstractChosen.prototype.search_results_touchend = function(evt) {
+ if (this.touch_started) {
+ return this.search_results_mouseup(evt);
+ }
+ };
+
+ AbstractChosen.prototype.outerHTML = function(element) {
+ var tmp;
+ if (element.outerHTML) {
+ return element.outerHTML;
+ }
+ tmp = document.createElement("div");
+ tmp.appendChild(element);
+ return tmp.innerHTML;
+ };
+
+ AbstractChosen.prototype.get_single_html = function() {
+ return "
\n " + this.default_text + "\n
\n\n
";
+ };
+
+ AbstractChosen.prototype.get_multi_html = function() {
+ return "
\n
";
+ };
+
+ AbstractChosen.prototype.get_no_results_html = function(terms) {
+ return "
\n " + this.results_none_found + " " + (this.escape_html(terms)) + "\n";
+ };
+
+ AbstractChosen.browser_is_supported = function() {
+ if ("Microsoft Internet Explorer" === window.navigator.appName) {
+ return document.documentMode >= 8;
+ }
+ return true;
+ if (/iP(od|hone)/i.test(window.navigator.userAgent) || /IEMobile/i.test(window.navigator.userAgent) || /Windows Phone/i.test(window.navigator.userAgent) || /BlackBerry/i.test(window.navigator.userAgent) || /BB10/i.test(window.navigator.userAgent) || /Android.*Mobile/i.test(window.navigator.userAgent)) {
+ return false;
+ }
+ return true;
+ };
+
+ AbstractChosen.default_multiple_text = "Select Some Options";
+
+ AbstractChosen.default_single_text = "Select an Option";
+
+ AbstractChosen.default_no_result_text = "No results match";
+
+ return AbstractChosen;
+
+ })();
+
+ $ = jQuery;
+
+ $.fn.extend({
+ chosen: function(options) {
+ if (!AbstractChosen.browser_is_supported()) {
+ return this;
+ }
+ return this.each(function(input_field) {
+ var $this, chosen;
+ $this = $(this);
+ chosen = $this.data('chosen');
+ if (options === 'destroy') {
+ if (chosen instanceof Chosen) {
+ chosen.destroy();
+ }
+ return;
+ }
+ if (!(chosen instanceof Chosen)) {
+ $this.data('chosen', new Chosen(this, options));
+ }
+ });
+ }
+ });
+
+ Chosen = (function(superClass) {
+ extend(Chosen, superClass);
+
+ function Chosen() {
+ return Chosen.__super__.constructor.apply(this, arguments);
+ }
+
+ Chosen.prototype.setup = function() {
+ this.form_field_jq = $(this.form_field);
+ return this.current_selectedIndex = this.form_field.selectedIndex;
+ };
+
+ Chosen.prototype.set_up_html = function() {
+ var container_classes, container_props;
+ container_classes = ["chosen-container"];
+ container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
+ if (this.inherit_select_classes && this.form_field.className) {
+ container_classes.push(this.form_field.className);
+ }
+ if (this.is_rtl) {
+ container_classes.push("chosen-rtl");
+ }
+ container_props = {
+ 'class': container_classes.join(' '),
+ 'title': this.form_field.title
+ };
+ if (this.form_field.id.length) {
+ container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
+ }
+ this.container = $("
", container_props);
+ this.container.width(this.container_width());
+ if (this.is_multiple) {
+ this.container.html(this.get_multi_html());
+ } else {
+ this.container.html(this.get_single_html());
+ }
+ this.form_field_jq.hide().after(this.container);
+ this.dropdown = this.container.find('div.chosen-drop').first();
+ this.search_field = this.container.find('input').first();
+ this.search_results = this.container.find('ul.chosen-results').first();
+ this.search_field_scale();
+ this.search_no_results = this.container.find('li.no-results').first();
+ if (this.is_multiple) {
+ this.search_choices = this.container.find('ul.chosen-choices').first();
+ this.search_container = this.container.find('li.search-field').first();
+ } else {
+ this.search_container = this.container.find('div.chosen-search').first();
+ this.selected_item = this.container.find('.chosen-single').first();
+ }
+ this.results_build();
+ this.set_tab_index();
+ return this.set_label_behavior();
+ };
+
+ Chosen.prototype.on_ready = function() {
+ return this.form_field_jq.trigger("chosen:ready", {
+ chosen: this
+ });
+ };
+
+ Chosen.prototype.register_observers = function() {
+ this.container.on('touchstart.chosen', (function(_this) {
+ return function(evt) {
+ _this.container_mousedown(evt);
+ };
+ })(this));
+ this.container.on('touchend.chosen', (function(_this) {
+ return function(evt) {
+ _this.container_mouseup(evt);
+ };
+ })(this));
+ this.container.on('mousedown.chosen', (function(_this) {
+ return function(evt) {
+ _this.container_mousedown(evt);
+ };
+ })(this));
+ this.container.on('mouseup.chosen', (function(_this) {
+ return function(evt) {
+ _this.container_mouseup(evt);
+ };
+ })(this));
+ this.container.on('mouseenter.chosen', (function(_this) {
+ return function(evt) {
+ _this.mouse_enter(evt);
+ };
+ })(this));
+ this.container.on('mouseleave.chosen', (function(_this) {
+ return function(evt) {
+ _this.mouse_leave(evt);
+ };
+ })(this));
+ this.search_results.on('mouseup.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_mouseup(evt);
+ };
+ })(this));
+ this.search_results.on('mouseover.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_mouseover(evt);
+ };
+ })(this));
+ this.search_results.on('mouseout.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_mouseout(evt);
+ };
+ })(this));
+ this.search_results.on('mousewheel.chosen DOMMouseScroll.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_mousewheel(evt);
+ };
+ })(this));
+ this.search_results.on('touchstart.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_touchstart(evt);
+ };
+ })(this));
+ this.search_results.on('touchmove.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_touchmove(evt);
+ };
+ })(this));
+ this.search_results.on('touchend.chosen', (function(_this) {
+ return function(evt) {
+ _this.search_results_touchend(evt);
+ };
+ })(this));
+ this.form_field_jq.on("chosen:updated.chosen", (function(_this) {
+ return function(evt) {
+ _this.results_update_field(evt);
+ };
+ })(this));
+ this.form_field_jq.on("chosen:activate.chosen", (function(_this) {
+ return function(evt) {
+ _this.activate_field(evt);
+ };
+ })(this));
+ this.form_field_jq.on("chosen:open.chosen", (function(_this) {
+ return function(evt) {
+ _this.container_mousedown(evt);
+ };
+ })(this));
+ this.form_field_jq.on("chosen:close.chosen", (function(_this) {
+ return function(evt) {
+ _this.close_field(evt);
+ };
+ })(this));
+ this.search_field.on('blur.chosen', (function(_this) {
+ return function(evt) {
+ _this.input_blur(evt);
+ };
+ })(this));
+ this.search_field.on('keyup.chosen', (function(_this) {
+ return function(evt) {
+ _this.keyup_checker(evt);
+ };
+ })(this));
+ this.search_field.on('keydown.chosen', (function(_this) {
+ return function(evt) {
+ _this.keydown_checker(evt);
+ };
+ })(this));
+ this.search_field.on('focus.chosen', (function(_this) {
+ return function(evt) {
+ _this.input_focus(evt);
+ };
+ })(this));
+ this.search_field.on('cut.chosen', (function(_this) {
+ return function(evt) {
+ _this.clipboard_event_checker(evt);
+ };
+ })(this));
+ this.search_field.on('paste.chosen', (function(_this) {
+ return function(evt) {
+ _this.clipboard_event_checker(evt);
+ };
+ })(this));
+ if (this.is_multiple) {
+ return this.search_choices.on('click.chosen', (function(_this) {
+ return function(evt) {
+ _this.choices_click(evt);
+ };
+ })(this));
+ } else {
+ return this.container.on('click.chosen', function(evt) {
+ evt.preventDefault();
+ });
+ }
+ };
+
+ Chosen.prototype.destroy = function() {
+ $(this.container[0].ownerDocument).off('click.chosen', this.click_test_action);
+ if (this.form_field_label.length > 0) {
+ this.form_field_label.off('click.chosen');
+ }
+ if (this.search_field[0].tabIndex) {
+ this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
+ }
+ this.container.remove();
+ this.form_field_jq.removeData('chosen');
+ return this.form_field_jq.show();
+ };
+
+ Chosen.prototype.search_field_disabled = function() {
+ this.is_disabled = this.form_field.disabled || this.form_field_jq.parents('fieldset').is(':disabled');
+ this.container.toggleClass('chosen-disabled', this.is_disabled);
+ this.search_field[0].disabled = this.is_disabled;
+ if (!this.is_multiple) {
+ this.selected_item.off('focus.chosen', this.activate_field);
+ }
+ if (this.is_disabled) {
+ return this.close_field();
+ } else if (!this.is_multiple) {
+ return this.selected_item.on('focus.chosen', this.activate_field);
+ }
+ };
+
+ Chosen.prototype.container_mousedown = function(evt) {
+ var ref;
+ if (this.is_disabled) {
+ return;
+ }
+ if (evt && ((ref = evt.type) === 'mousedown' || ref === 'touchstart') && !this.results_showing) {
+ evt.preventDefault();
+ }
+ if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
+ if (!this.active_field) {
+ if (this.is_multiple) {
+ this.search_field.val("");
+ }
+ $(this.container[0].ownerDocument).on('click.chosen', this.click_test_action);
+ this.results_show();
+ } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
+ evt.preventDefault();
+ this.results_toggle();
+ }
+ return this.activate_field();
+ }
+ };
+
+ Chosen.prototype.container_mouseup = function(evt) {
+ if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
+ return this.results_reset(evt);
+ }
+ };
+
+ Chosen.prototype.search_results_mousewheel = function(evt) {
+ var delta;
+ if (evt.originalEvent) {
+ delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
+ }
+ if (delta != null) {
+ evt.preventDefault();
+ if (evt.type === 'DOMMouseScroll') {
+ delta = delta * 40;
+ }
+ return this.search_results.scrollTop(delta + this.search_results.scrollTop());
+ }
+ };
+
+ Chosen.prototype.blur_test = function(evt) {
+ if (!this.active_field && this.container.hasClass("chosen-container-active")) {
+ return this.close_field();
+ }
+ };
+
+ Chosen.prototype.close_field = function() {
+ $(this.container[0].ownerDocument).off("click.chosen", this.click_test_action);
+ this.active_field = false;
+ this.results_hide();
+ this.container.removeClass("chosen-container-active");
+ this.clear_backstroke();
+ this.show_search_field_default();
+ this.search_field_scale();
+ return this.search_field.blur();
+ };
+
+ Chosen.prototype.activate_field = function() {
+ if (this.is_disabled) {
+ return;
+ }
+ this.container.addClass("chosen-container-active");
+ this.active_field = true;
+ this.search_field.val(this.search_field.val());
+ return this.search_field.focus();
+ };
+
+ Chosen.prototype.test_active_click = function(evt) {
+ var active_container;
+ active_container = $(evt.target).closest('.chosen-container');
+ if (active_container.length && this.container[0] === active_container[0]) {
+ return this.active_field = true;
+ } else {
+ return this.close_field();
+ }
+ };
+
+ Chosen.prototype.results_build = function() {
+ this.parsing = true;
+ this.selected_option_count = null;
+ this.results_data = SelectParser.select_to_array(this.form_field);
+ if (this.is_multiple) {
+ this.search_choices.find("li.search-choice").remove();
+ } else {
+ this.single_set_selected_text();
+ if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
+ this.search_field[0].readOnly = true;
+ this.container.addClass("chosen-container-single-nosearch");
+ } else {
+ this.search_field[0].readOnly = false;
+ this.container.removeClass("chosen-container-single-nosearch");
+ }
+ }
+ this.update_results_content(this.results_option_build({
+ first: true
+ }));
+ this.search_field_disabled();
+ this.show_search_field_default();
+ this.search_field_scale();
+ return this.parsing = false;
+ };
+
+ Chosen.prototype.result_do_highlight = function(el) {
+ var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
+ if (el.length) {
+ this.result_clear_highlight();
+ this.result_highlight = el;
+ this.result_highlight.addClass("highlighted");
+ maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
+ visible_top = this.search_results.scrollTop();
+ visible_bottom = maxHeight + visible_top;
+ high_top = this.result_highlight.position().top + this.search_results.scrollTop();
+ high_bottom = high_top + this.result_highlight.outerHeight();
+ if (high_bottom >= visible_bottom) {
+ return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
+ } else if (high_top < visible_top) {
+ return this.search_results.scrollTop(high_top);
+ }
+ }
+ };
+
+ Chosen.prototype.result_clear_highlight = function() {
+ if (this.result_highlight) {
+ this.result_highlight.removeClass("highlighted");
+ }
+ return this.result_highlight = null;
+ };
+
+ Chosen.prototype.results_show = function() {
+ if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
+ this.form_field_jq.trigger("chosen:maxselected", {
+ chosen: this
+ });
+ return false;
+ }
+ this.container.addClass("chosen-with-drop");
+ this.results_showing = true;
+ this.search_field.focus();
+ this.search_field.val(this.get_search_field_value());
+ this.winnow_results();
+ return this.form_field_jq.trigger("chosen:showing_dropdown", {
+ chosen: this
+ });
+ };
+
+ Chosen.prototype.update_results_content = function(content) {
+ return this.search_results.html(content);
+ };
+
+ Chosen.prototype.results_hide = function() {
+ if (this.results_showing) {
+ this.result_clear_highlight();
+ this.container.removeClass("chosen-with-drop");
+ this.form_field_jq.trigger("chosen:hiding_dropdown", {
+ chosen: this
+ });
+ }
+ return this.results_showing = false;
+ };
+
+ Chosen.prototype.set_tab_index = function(el) {
+ var ti;
+ if (this.form_field.tabIndex) {
+ ti = this.form_field.tabIndex;
+ this.form_field.tabIndex = -1;
+ return this.search_field[0].tabIndex = ti;
+ }
+ };
+
+ Chosen.prototype.set_label_behavior = function() {
+ this.form_field_label = this.form_field_jq.parents("label");
+ if (!this.form_field_label.length && this.form_field.id.length) {
+ this.form_field_label = $("label[for='" + this.form_field.id + "']");
+ }
+ if (this.form_field_label.length > 0) {
+ return this.form_field_label.on('click.chosen', this.label_click_handler);
+ }
+ };
+
+ Chosen.prototype.show_search_field_default = function() {
+ if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
+ this.search_field.val(this.default_text);
+ return this.search_field.addClass("default");
+ } else {
+ this.search_field.val("");
+ return this.search_field.removeClass("default");
+ }
+ };
+
+ Chosen.prototype.search_results_mouseup = function(evt) {
+ var target;
+ target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
+ if (target.length) {
+ this.result_highlight = target;
+ this.result_select(evt);
+ return this.search_field.focus();
+ }
+ };
+
+ Chosen.prototype.search_results_mouseover = function(evt) {
+ var target;
+ target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
+ if (target) {
+ return this.result_do_highlight(target);
+ }
+ };
+
+ Chosen.prototype.search_results_mouseout = function(evt) {
+ if ($(evt.target).hasClass("active-result") || $(evt.target).parents('.active-result').first()) {
+ return this.result_clear_highlight();
+ }
+ };
+
+ Chosen.prototype.choice_build = function(item) {
+ var choice, close_link;
+ choice = $('
', {
+ "class": "search-choice"
+ }).html("
" + (this.choice_label(item)) + "");
+ if (item.disabled) {
+ choice.addClass('search-choice-disabled');
+ } else {
+ close_link = $('
', {
+ "class": 'search-choice-close',
+ 'data-option-array-index': item.array_index
+ });
+ close_link.on('click.chosen', (function(_this) {
+ return function(evt) {
+ return _this.choice_destroy_link_click(evt);
+ };
+ })(this));
+
+ close_link.on('touchstart', (function(_this) {
+ return function(evt) {
+ return _this.choice_destroy_link_click(evt);
+ };
+ })(this));
+
+ choice.append(close_link);
+ }
+ return this.search_container.before(choice);
+ };
+
+ Chosen.prototype.choice_destroy_link_click = function(evt) {
+ evt.preventDefault();
+ evt.stopPropagation();
+ if (!this.is_disabled) {
+ return this.choice_destroy($(evt.target));
+ }
+ };
+
+ Chosen.prototype.choice_destroy = function(link) {
+ if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
+ if (this.active_field) {
+ this.search_field.focus();
+ } else {
+ this.show_search_field_default();
+ }
+ if (this.is_multiple && this.choices_count() > 0 && this.get_search_field_value().length < 1) {
+ this.results_hide();
+ }
+ link.parents('li').first().remove();
+ return this.search_field_scale();
+ }
+ };
+
+ Chosen.prototype.results_reset = function() {
+ this.reset_single_select_options();
+ this.form_field.options[0].selected = true;
+ this.single_set_selected_text();
+ this.show_search_field_default();
+ this.results_reset_cleanup();
+ this.trigger_form_field_change();
+ if (this.active_field) {
+ return this.results_hide();
+ }
+ };
+
+ Chosen.prototype.results_reset_cleanup = function() {
+ this.current_selectedIndex = this.form_field.selectedIndex;
+ return this.selected_item.find("abbr").remove();
+ };
+
+ Chosen.prototype.result_select = function(evt) {
+ var high, item;
+ if (this.result_highlight) {
+ high = this.result_highlight;
+ this.result_clear_highlight();
+ if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
+ this.form_field_jq.trigger("chosen:maxselected", {
+ chosen: this
+ });
+ return false;
+ }
+ if (this.is_multiple) {
+ high.removeClass("active-result");
+ } else {
+ this.reset_single_select_options();
+ }
+ high.addClass("result-selected");
+ item = this.results_data[high[0].getAttribute("data-option-array-index")];
+ item.selected = true;
+ this.form_field.options[item.options_index].selected = true;
+ this.selected_option_count = null;
+ if (this.is_multiple) {
+ this.choice_build(item);
+ } else {
+ this.single_set_selected_text(this.choice_label(item));
+ }
+ if (this.is_multiple && (!this.hide_results_on_select || (evt.metaKey || evt.ctrlKey))) {
+ if (evt.metaKey || evt.ctrlKey) {
+ this.winnow_results({
+ skip_highlight: true
+ });
+ } else {
+ this.search_field.val("");
+ this.winnow_results();
+ }
+ } else {
+ this.results_hide();
+ this.show_search_field_default();
+ }
+ if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
+ this.trigger_form_field_change({
+ selected: this.form_field.options[item.options_index].value
+ });
+ }
+ this.current_selectedIndex = this.form_field.selectedIndex;
+ evt.preventDefault();
+ return this.search_field_scale();
+ }
+ };
+
+ Chosen.prototype.single_set_selected_text = function(text) {
+ if (text == null) {
+ text = this.default_text;
+ }
+ if (text === this.default_text) {
+ this.selected_item.addClass("chosen-default");
+ } else {
+ this.single_deselect_control_build();
+ this.selected_item.removeClass("chosen-default");
+ }
+ return this.selected_item.find("span").html(text);
+ };
+
+ Chosen.prototype.result_deselect = function(pos) {
+ var result_data;
+ result_data = this.results_data[pos];
+ if (!this.form_field.options[result_data.options_index].disabled) {
+ result_data.selected = false;
+ this.form_field.options[result_data.options_index].selected = false;
+ this.selected_option_count = null;
+ this.result_clear_highlight();
+ if (this.results_showing) {
+ this.winnow_results();
+ }
+ this.trigger_form_field_change({
+ deselected: this.form_field.options[result_data.options_index].value
+ });
+ this.search_field_scale();
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ Chosen.prototype.single_deselect_control_build = function() {
+ if (!this.allow_single_deselect) {
+ return;
+ }
+ if (!this.selected_item.find("abbr").length) {
+ this.selected_item.find("span").first().after("
");
+ }
+ return this.selected_item.addClass("chosen-single-with-deselect");
+ };
+
+ Chosen.prototype.get_search_field_value = function() {
+ return this.search_field.val();
+ };
+
+ Chosen.prototype.get_search_text = function() {
+ return $.trim(this.get_search_field_value());
+ };
+
+ Chosen.prototype.escape_html = function(text) {
+ return $('
').text(text).html();
+ };
+
+ Chosen.prototype.winnow_results_set_highlight = function() {
+ var do_high, selected_results;
+ selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
+ do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
+ if (do_high != null) {
+ return this.result_do_highlight(do_high);
+ }
+ };
+
+ Chosen.prototype.no_results = function(terms) {
+ var no_results_html;
+ no_results_html = this.get_no_results_html(terms);
+ this.search_results.append(no_results_html);
+ return this.form_field_jq.trigger("chosen:no_results", {
+ chosen: this
+ });
+ };
+
+ Chosen.prototype.no_results_clear = function() {
+ return this.search_results.find(".no-results").remove();
+ };
+
+ Chosen.prototype.keydown_arrow = function() {
+ var next_sib;
+ if (this.results_showing && this.result_highlight) {
+ next_sib = this.result_highlight.nextAll("li.active-result").first();
+ if (next_sib) {
+ return this.result_do_highlight(next_sib);
+ }
+ } else {
+ return this.results_show();
+ }
+ };
+
+ Chosen.prototype.keyup_arrow = function() {
+ var prev_sibs;
+ if (!this.results_showing && !this.is_multiple) {
+ return this.results_show();
+ } else if (this.result_highlight) {
+ prev_sibs = this.result_highlight.prevAll("li.active-result");
+ if (prev_sibs.length) {
+ return this.result_do_highlight(prev_sibs.first());
+ } else {
+ if (this.choices_count() > 0) {
+ this.results_hide();
+ }
+ return this.result_clear_highlight();
+ }
+ }
+ };
+
+ Chosen.prototype.keydown_backstroke = function() {
+ var next_available_destroy;
+ if (this.pending_backstroke) {
+ this.choice_destroy(this.pending_backstroke.find("a").first());
+ return this.clear_backstroke();
+ } else {
+ next_available_destroy = this.search_container.siblings("li.search-choice").last();
+ if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
+ this.pending_backstroke = next_available_destroy;
+ if (this.single_backstroke_delete) {
+ return this.keydown_backstroke();
+ } else {
+ return this.pending_backstroke.addClass("search-choice-focus");
+ }
+ }
+ }
+ };
+
+ Chosen.prototype.clear_backstroke = function() {
+ if (this.pending_backstroke) {
+ this.pending_backstroke.removeClass("search-choice-focus");
+ }
+ return this.pending_backstroke = null;
+ };
+
+ Chosen.prototype.search_field_scale = function() {
+ var div, i, len, style, style_block, styles, width;
+ if (!this.is_multiple) {
+ return;
+ }
+ style_block = {
+ position: 'absolute',
+ left: '-1000px',
+ top: '-1000px',
+ display: 'none',
+ whiteSpace: 'pre'
+ };
+ styles = ['fontSize', 'fontStyle', 'fontWeight', 'fontFamily', 'lineHeight', 'textTransform', 'letterSpacing'];
+ for (i = 0, len = styles.length; i < len; i++) {
+ style = styles[i];
+ style_block[style] = this.search_field.css(style);
+ }
+ div = $('
').css(style_block);
+ div.text(this.get_search_field_value());
+ $('body').append(div);
+ width = div.width() + 25;
+ div.remove();
+ if (this.container.is(':visible')) {
+ width = Math.min(this.container.outerWidth() - 10, width);
+ }
+ return this.search_field.width(width);
+ };
+
+ Chosen.prototype.trigger_form_field_change = function(extra) {
+ this.form_field_jq.trigger("input", extra);
+ return this.form_field_jq.trigger("change", extra);
+ };
+
+ return Chosen;
+
+ })(AbstractChosen);
+
+}).call(this);
+
+;/*})'"*/
+;/*})'"*/
+/**
+ * @license MIT
+ */
+(function(window, document, undefined) {'use strict';
+ // ie10+
+ var ie10plus = window.navigator.msPointerEnabled;
+ /**
+ * Flow.js is a library providing multiple simultaneous, stable and
+ * resumable uploads via the HTML5 File API.
+ * @param [opts]
+ * @param {number} [opts.chunkSize]
+ * @param {bool} [opts.forceChunkSize]
+ * @param {number} [opts.simultaneousUploads]
+ * @param {bool} [opts.singleFile]
+ * @param {string} [opts.fileParameterName]
+ * @param {number} [opts.progressCallbacksInterval]
+ * @param {number} [opts.speedSmoothingFactor]
+ * @param {Object|Function} [opts.query]
+ * @param {Object|Function} [opts.headers]
+ * @param {bool} [opts.withCredentials]
+ * @param {Function} [opts.preprocess]
+ * @param {string} [opts.method]
+ * @param {string|Function} [opts.testMethod]
+ * @param {string|Function} [opts.uploadMethod]
+ * @param {bool} [opts.prioritizeFirstAndLastChunk]
+ * @param {bool} [opts.allowDuplicateUploads]
+ * @param {string|Function} [opts.target]
+ * @param {number} [opts.maxChunkRetries]
+ * @param {number} [opts.chunkRetryInterval]
+ * @param {Array.
} [opts.permanentErrors]
+ * @param {Array.} [opts.successStatuses]
+ * @param {Function} [opts.initFileFn]
+ * @param {Function} [opts.readFileFn]
+ * @param {Function} [opts.generateUniqueIdentifier]
+ * @constructor
+ */
+ function Flow(opts) {
+ /**
+ * Supported by browser?
+ * @type {boolean}
+ */
+ this.support = (
+ typeof File !== 'undefined' &&
+ typeof Blob !== 'undefined' &&
+ typeof FileList !== 'undefined' &&
+ (
+ !!Blob.prototype.slice || !!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice ||
+ false
+ ) // slicing files support
+ );
+
+ if (!this.support) {
+ return ;
+ }
+
+ /**
+ * Check if directory upload is supported
+ * @type {boolean}
+ */
+ this.supportDirectory = /Chrome/.test(window.navigator.userAgent);
+
+ /**
+ * List of FlowFile objects
+ * @type {Array.}
+ */
+ this.files = [];
+
+ /**
+ * Default options for flow.js
+ * @type {Object}
+ */
+ this.defaults = {
+ chunkSize: 1024 * 1024,
+ forceChunkSize: false,
+ simultaneousUploads: 3,
+ singleFile: false,
+ fileParameterName: 'file',
+ progressCallbacksInterval: 500,
+ speedSmoothingFactor: 0.1,
+ query: {},
+ headers: {},
+ withCredentials: false,
+ preprocess: null,
+ method: 'multipart',
+ testMethod: 'GET',
+ uploadMethod: 'POST',
+ prioritizeFirstAndLastChunk: false,
+ allowDuplicateUploads: false,
+ target: '/',
+ testChunks: true,
+ generateUniqueIdentifier: null,
+ maxChunkRetries: 0,
+ chunkRetryInterval: null,
+ permanentErrors: [404, 413, 415, 500, 501],
+ successStatuses: [200, 201, 202],
+ onDropStopPropagation: false,
+ initFileFn: null,
+ readFileFn: webAPIFileRead
+ };
+
+ /**
+ * Current options
+ * @type {Object}
+ */
+ this.opts = {};
+
+ /**
+ * List of events:
+ * key stands for event name
+ * value array list of callbacks
+ * @type {}
+ */
+ this.events = {};
+
+ var $ = this;
+
+ /**
+ * On drop event
+ * @function
+ * @param {MouseEvent} event
+ */
+ this.onDrop = function (event) {
+ if ($.opts.onDropStopPropagation) {
+ event.stopPropagation();
+ }
+ event.preventDefault();
+ var dataTransfer = event.dataTransfer;
+ if (dataTransfer.items && dataTransfer.items[0] &&
+ dataTransfer.items[0].webkitGetAsEntry) {
+ $.webkitReadDataTransfer(event);
+ } else {
+ $.addFiles(dataTransfer.files, event);
+ }
+ };
+
+ /**
+ * Prevent default
+ * @function
+ * @param {MouseEvent} event
+ */
+ this.preventEvent = function (event) {
+ event.preventDefault();
+ };
+
+
+ /**
+ * Current options
+ * @type {Object}
+ */
+ this.opts = Flow.extend({}, this.defaults, opts || {});
+
+ }
+
+ Flow.prototype = {
+ /**
+ * Set a callback for an event, possible events:
+ * fileSuccess(file), fileProgress(file), fileAdded(file, event),
+ * fileRemoved(file), fileRetry(file), fileError(file, message),
+ * complete(), progress(), error(message, file), pause()
+ * @function
+ * @param {string} event
+ * @param {Function} callback
+ */
+ on: function (event, callback) {
+ event = event.toLowerCase();
+ if (!this.events.hasOwnProperty(event)) {
+ this.events[event] = [];
+ }
+ this.events[event].push(callback);
+ },
+
+ /**
+ * Remove event callback
+ * @function
+ * @param {string} [event] removes all events if not specified
+ * @param {Function} [fn] removes all callbacks of event if not specified
+ */
+ off: function (event, fn) {
+ if (event !== undefined) {
+ event = event.toLowerCase();
+ if (fn !== undefined) {
+ if (this.events.hasOwnProperty(event)) {
+ arrayRemove(this.events[event], fn);
+ }
+ } else {
+ delete this.events[event];
+ }
+ } else {
+ this.events = {};
+ }
+ },
+
+ /**
+ * Fire an event
+ * @function
+ * @param {string} event event name
+ * @param {...} args arguments of a callback
+ * @return {bool} value is false if at least one of the event handlers which handled this event
+ * returned false. Otherwise it returns true.
+ */
+ fire: function (event, args) {
+ // `arguments` is an object, not array, in FF, so:
+ args = Array.prototype.slice.call(arguments);
+ event = event.toLowerCase();
+ var preventDefault = false;
+ if (this.events.hasOwnProperty(event)) {
+ each(this.events[event], function (callback) {
+ preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault;
+ }, this);
+ }
+ if (event != 'catchall') {
+ args.unshift('catchAll');
+ preventDefault = this.fire.apply(this, args) === false || preventDefault;
+ }
+ return !preventDefault;
+ },
+
+ /**
+ * Read webkit dataTransfer object
+ * @param event
+ */
+ webkitReadDataTransfer: function (event) {
+ var $ = this;
+ var queue = event.dataTransfer.items.length;
+ var files = [];
+ each(event.dataTransfer.items, function (item) {
+ var entry = item.webkitGetAsEntry();
+ if (!entry) {
+ decrement();
+ return ;
+ }
+ if (entry.isFile) {
+ // due to a bug in Chrome's File System API impl - #149735
+ fileReadSuccess(item.getAsFile(), entry.fullPath);
+ } else {
+ readDirectory(entry.createReader());
+ }
+ });
+ function readDirectory(reader) {
+ reader.readEntries(function (entries) {
+ if (entries.length) {
+ queue += entries.length;
+ each(entries, function(entry) {
+ if (entry.isFile) {
+ var fullPath = entry.fullPath;
+ entry.file(function (file) {
+ fileReadSuccess(file, fullPath);
+ }, readError);
+ } else if (entry.isDirectory) {
+ readDirectory(entry.createReader());
+ }
+ });
+ readDirectory(reader);
+ } else {
+ decrement();
+ }
+ }, readError);
+ }
+ function fileReadSuccess(file, fullPath) {
+ // relative path should not start with "/"
+ file.relativePath = fullPath.substring(1);
+ files.push(file);
+ decrement();
+ }
+ function readError(fileError) {
+ throw fileError;
+ }
+ function decrement() {
+ if (--queue == 0) {
+ $.addFiles(files, event);
+ }
+ }
+ },
+
+ /**
+ * Generate unique identifier for a file
+ * @function
+ * @param {FlowFile} file
+ * @returns {string}
+ */
+ generateUniqueIdentifier: function (file) {
+ var custom = this.opts.generateUniqueIdentifier;
+ if (typeof custom === 'function') {
+ return custom(file);
+ }
+ // Some confusion in different versions of Firefox
+ var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name;
+ return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '');
+ },
+
+ /**
+ * Upload next chunk from the queue
+ * @function
+ * @returns {boolean}
+ * @private
+ */
+ uploadNextChunk: function (preventEvents) {
+ // In some cases (such as videos) it's really handy to upload the first
+ // and last chunk of a file quickly; this let's the server check the file's
+ // metadata and determine if there's even a point in continuing.
+ var found = false;
+ if (this.opts.prioritizeFirstAndLastChunk) {
+ each(this.files, function (file) {
+ if (!file.paused && file.chunks.length &&
+ file.chunks[0].status() === 'pending') {
+ file.chunks[0].send();
+ found = true;
+ return false;
+ }
+ if (!file.paused && file.chunks.length > 1 &&
+ file.chunks[file.chunks.length - 1].status() === 'pending') {
+ file.chunks[file.chunks.length - 1].send();
+ found = true;
+ return false;
+ }
+ });
+ if (found) {
+ return found;
+ }
+ }
+
+ // Now, simply look for the next, best thing to upload
+ each(this.files, function (file) {
+ if (!file.paused) {
+ each(file.chunks, function (chunk) {
+ if (chunk.status() === 'pending') {
+ chunk.send();
+ found = true;
+ return false;
+ }
+ });
+ }
+ if (found) {
+ return false;
+ }
+ });
+ if (found) {
+ return true;
+ }
+
+ // The are no more outstanding chunks to upload, check is everything is done
+ var outstanding = false;
+ each(this.files, function (file) {
+ if (!file.isComplete()) {
+ outstanding = true;
+ return false;
+ }
+ });
+ if (!outstanding && !preventEvents) {
+ // All chunks have been uploaded, complete
+ async(function () {
+ this.fire('complete');
+ }, this);
+ }
+ return false;
+ },
+
+
+ /**
+ * Assign a browse action to one or more DOM nodes.
+ * @function
+ * @param {Element|Array.} domNodes
+ * @param {boolean} isDirectory Pass in true to allow directories to
+ * @param {boolean} singleFile prevent multi file upload
+ * @param {Object} attributes set custom attributes:
+ * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
+ * eg: accept: 'image/*'
+ * be selected (Chrome only).
+ */
+ assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
+ if (domNodes instanceof Element) {
+ domNodes = [domNodes];
+ }
+
+ each(domNodes, function (domNode) {
+ var input;
+ if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
+ input = domNode;
+ } else {
+ input = document.createElement('input');
+ input.setAttribute('type', 'file');
+ // display:none - not working in opera 12
+ extend(input.style, {
+ visibility: 'hidden',
+ position: 'absolute',
+ width: '1px',
+ height: '1px'
+ });
+ // for opera 12 browser, input must be assigned to a document
+ domNode.appendChild(input);
+ // https://developer.mozilla.org/en/using_files_from_web_applications)
+ // event listener is executed two times
+ // first one - original mouse click event
+ // second - input.click(), input is inside domNode
+ domNode.addEventListener('click', function() {
+ input.click();
+ }, false);
+ }
+ if (!this.opts.singleFile && !singleFile) {
+ input.setAttribute('multiple', 'multiple');
+ }
+ if (isDirectory) {
+ input.setAttribute('webkitdirectory', 'webkitdirectory');
+ }
+ each(attributes, function (value, key) {
+ input.setAttribute(key, value);
+ });
+ // When new files are added, simply append them to the overall list
+ var $ = this;
+ input.addEventListener('change', function (e) {
+ if (e.target.value) {
+ $.addFiles(e.target.files, e);
+ e.target.value = '';
+ }
+ }, false);
+ }, this);
+ },
+
+ /**
+ * Assign one or more DOM nodes as a drop target.
+ * @function
+ * @param {Element|Array.} domNodes
+ */
+ assignDrop: function (domNodes) {
+ if (typeof domNodes.length === 'undefined') {
+ domNodes = [domNodes];
+ }
+ each(domNodes, function (domNode) {
+ domNode.addEventListener('dragover', this.preventEvent, false);
+ domNode.addEventListener('dragenter', this.preventEvent, false);
+ domNode.addEventListener('drop', this.onDrop, false);
+ }, this);
+ },
+
+ /**
+ * Un-assign drop event from DOM nodes
+ * @function
+ * @param domNodes
+ */
+ unAssignDrop: function (domNodes) {
+ if (typeof domNodes.length === 'undefined') {
+ domNodes = [domNodes];
+ }
+ each(domNodes, function (domNode) {
+ domNode.removeEventListener('dragover', this.preventEvent);
+ domNode.removeEventListener('dragenter', this.preventEvent);
+ domNode.removeEventListener('drop', this.onDrop);
+ }, this);
+ },
+
+ /**
+ * Returns a boolean indicating whether or not the instance is currently
+ * uploading anything.
+ * @function
+ * @returns {boolean}
+ */
+ isUploading: function () {
+ var uploading = false;
+ each(this.files, function (file) {
+ if (file.isUploading()) {
+ uploading = true;
+ return false;
+ }
+ });
+ return uploading;
+ },
+
+ /**
+ * should upload next chunk
+ * @function
+ * @returns {boolean|number}
+ */
+ _shouldUploadNext: function () {
+ var num = 0;
+ var should = true;
+ var simultaneousUploads = this.opts.simultaneousUploads;
+ each(this.files, function (file) {
+ each(file.chunks, function(chunk) {
+ if (chunk.status() === 'uploading') {
+ num++;
+ if (num >= simultaneousUploads) {
+ should = false;
+ return false;
+ }
+ }
+ });
+ });
+ // if should is true then return uploading chunks's length
+ return should && num;
+ },
+
+ /**
+ * Start or resume uploading.
+ * @function
+ */
+ upload: function () {
+ // Make sure we don't start too many uploads at once
+ var ret = this._shouldUploadNext();
+ if (ret === false) {
+ return;
+ }
+ // Kick off the queue
+ this.fire('uploadStart');
+ var started = false;
+ for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
+ started = this.uploadNextChunk(true) || started;
+ }
+ if (!started) {
+ async(function () {
+ this.fire('complete');
+ }, this);
+ }
+ },
+
+ /**
+ * Resume uploading.
+ * @function
+ */
+ resume: function () {
+ each(this.files, function (file) {
+ file.resume();
+ });
+ },
+
+ /**
+ * Pause uploading.
+ * @function
+ */
+ pause: function () {
+ each(this.files, function (file) {
+ file.pause();
+ });
+ },
+
+ /**
+ * Cancel upload of all FlowFile objects and remove them from the list.
+ * @function
+ */
+ cancel: function () {
+ for (var i = this.files.length - 1; i >= 0; i--) {
+ this.files[i].cancel();
+ }
+ },
+
+ /**
+ * Returns a number between 0 and 1 indicating the current upload progress
+ * of all files.
+ * @function
+ * @returns {number}
+ */
+ progress: function () {
+ var totalDone = 0;
+ var totalSize = 0;
+ // Resume all chunks currently being uploaded
+ each(this.files, function (file) {
+ totalDone += file.progress() * file.size;
+ totalSize += file.size;
+ });
+ return totalSize > 0 ? totalDone / totalSize : 0;
+ },
+
+ /**
+ * Add a HTML5 File object to the list of files.
+ * @function
+ * @param {File} file
+ * @param {Event} [event] event is optional
+ */
+ addFile: function (file, event) {
+ this.addFiles([file], event);
+ },
+
+ /**
+ * Add a HTML5 File object to the list of files.
+ * @function
+ * @param {FileList|Array} fileList
+ * @param {Event} [event] event is optional
+ */
+ addFiles: function (fileList, event) {
+ var files = [];
+ each(fileList, function (file) {
+ // https://github.com/flowjs/flow.js/issues/55
+ if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.')) &&
+ (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(this.generateUniqueIdentifier(file)))) {
+ var f = new FlowFile(this, file);
+ if (this.fire('fileAdded', f, event)) {
+ files.push(f);
+ }
+ }
+ }, this);
+ if (this.fire('filesAdded', files, event)) {
+ each(files, function (file) {
+ if (this.opts.singleFile && this.files.length > 0) {
+ this.removeFile(this.files[0]);
+ }
+ this.files.push(file);
+ }, this);
+ this.fire('filesSubmitted', files, event);
+ }
+ },
+
+
+ /**
+ * Cancel upload of a specific FlowFile object from the list.
+ * @function
+ * @param {FlowFile} file
+ */
+ removeFile: function (file) {
+ for (var i = this.files.length - 1; i >= 0; i--) {
+ if (this.files[i] === file) {
+ this.files.splice(i, 1);
+ file.abort();
+ this.fire('fileRemoved', file);
+ }
+ }
+ },
+
+ /**
+ * Look up a FlowFile object by its unique identifier.
+ * @function
+ * @param {string} uniqueIdentifier
+ * @returns {boolean|FlowFile} false if file was not found
+ */
+ getFromUniqueIdentifier: function (uniqueIdentifier) {
+ var ret = false;
+ each(this.files, function (file) {
+ if (file.uniqueIdentifier === uniqueIdentifier) {
+ ret = file;
+ }
+ });
+ return ret;
+ },
+
+ /**
+ * Returns the total size of all files in bytes.
+ * @function
+ * @returns {number}
+ */
+ getSize: function () {
+ var totalSize = 0;
+ each(this.files, function (file) {
+ totalSize += file.size;
+ });
+ return totalSize;
+ },
+
+ /**
+ * Returns the total size uploaded of all files in bytes.
+ * @function
+ * @returns {number}
+ */
+ sizeUploaded: function () {
+ var size = 0;
+ each(this.files, function (file) {
+ size += file.sizeUploaded();
+ });
+ return size;
+ },
+
+ /**
+ * Returns remaining time to upload all files in seconds. Accuracy is based on average speed.
+ * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
+ * @function
+ * @returns {number}
+ */
+ timeRemaining: function () {
+ var sizeDelta = 0;
+ var averageSpeed = 0;
+ each(this.files, function (file) {
+ if (!file.paused && !file.error) {
+ sizeDelta += file.size - file.sizeUploaded();
+ averageSpeed += file.averageSpeed;
+ }
+ });
+ if (sizeDelta && !averageSpeed) {
+ return Number.POSITIVE_INFINITY;
+ }
+ if (!sizeDelta && !averageSpeed) {
+ return 0;
+ }
+ return Math.floor(sizeDelta / averageSpeed);
+ }
+ };
+
+
+
+
+
+
+ /**
+ * FlowFile class
+ * @name FlowFile
+ * @param {Flow} flowObj
+ * @param {File} file
+ * @constructor
+ */
+ function FlowFile(flowObj, file) {
+
+ /**
+ * Reference to parent Flow instance
+ * @type {Flow}
+ */
+ this.flowObj = flowObj;
+
+ /**
+ * Used to store the bytes read
+ * @type {Blob|string}
+ */
+ this.bytes = null;
+
+ /**
+ * Reference to file
+ * @type {File}
+ */
+ this.file = file;
+
+ /**
+ * File name. Some confusion in different versions of Firefox
+ * @type {string}
+ */
+ this.name = file.fileName || file.name;
+
+ /**
+ * File size
+ * @type {number}
+ */
+ this.size = file.size;
+
+ /**
+ * Relative file path
+ * @type {string}
+ */
+ this.relativePath = file.relativePath || file.webkitRelativePath || this.name;
+
+ /**
+ * File unique identifier
+ * @type {string}
+ */
+ this.uniqueIdentifier = flowObj.generateUniqueIdentifier(file);
+
+ /**
+ * List of chunks
+ * @type {Array.}
+ */
+ this.chunks = [];
+
+ /**
+ * Indicated if file is paused
+ * @type {boolean}
+ */
+ this.paused = false;
+
+ /**
+ * Indicated if file has encountered an error
+ * @type {boolean}
+ */
+ this.error = false;
+
+ /**
+ * Average upload speed
+ * @type {number}
+ */
+ this.averageSpeed = 0;
+
+ /**
+ * Current upload speed
+ * @type {number}
+ */
+ this.currentSpeed = 0;
+
+ /**
+ * Date then progress was called last time
+ * @type {number}
+ * @private
+ */
+ this._lastProgressCallback = Date.now();
+
+ /**
+ * Previously uploaded file size
+ * @type {number}
+ * @private
+ */
+ this._prevUploadedSize = 0;
+
+ /**
+ * Holds previous progress
+ * @type {number}
+ * @private
+ */
+ this._prevProgress = 0;
+
+ this.bootstrap();
+ }
+
+ FlowFile.prototype = {
+ /**
+ * Update speed parameters
+ * @link http://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately
+ * @function
+ */
+ measureSpeed: function () {
+ var timeSpan = Date.now() - this._lastProgressCallback;
+ if (!timeSpan) {
+ return ;
+ }
+ var smoothingFactor = this.flowObj.opts.speedSmoothingFactor;
+ var uploaded = this.sizeUploaded();
+ // Prevent negative upload speed after file upload resume
+ this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0);
+ this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed;
+ this._prevUploadedSize = uploaded;
+ },
+
+ /**
+ * For internal usage only.
+ * Callback when something happens within the chunk.
+ * @function
+ * @param {FlowChunk} chunk
+ * @param {string} event can be 'progress', 'success', 'error' or 'retry'
+ * @param {string} [message]
+ */
+ chunkEvent: function (chunk, event, message) {
+ switch (event) {
+ case 'progress':
+ if (Date.now() - this._lastProgressCallback <
+ this.flowObj.opts.progressCallbacksInterval) {
+ break;
+ }
+ this.measureSpeed();
+ this.flowObj.fire('fileProgress', this, chunk);
+ this.flowObj.fire('progress');
+ this._lastProgressCallback = Date.now();
+ break;
+ case 'error':
+ this.error = true;
+ this.abort(true);
+ this.flowObj.fire('fileError', this, message, chunk);
+ this.flowObj.fire('error', message, this, chunk);
+ break;
+ case 'success':
+ if (this.error) {
+ return;
+ }
+ this.measureSpeed();
+ this.flowObj.fire('fileProgress', this, chunk);
+ this.flowObj.fire('progress');
+ this._lastProgressCallback = Date.now();
+ if (this.isComplete()) {
+ this.currentSpeed = 0;
+ this.averageSpeed = 0;
+ this.flowObj.fire('fileSuccess', this, message, chunk);
+ }
+ break;
+ case 'retry':
+ this.flowObj.fire('fileRetry', this, chunk);
+ break;
+ }
+ },
+
+ /**
+ * Pause file upload
+ * @function
+ */
+ pause: function() {
+ this.paused = true;
+ this.abort();
+ },
+
+ /**
+ * Resume file upload
+ * @function
+ */
+ resume: function() {
+ this.paused = false;
+ this.flowObj.upload();
+ },
+
+ /**
+ * Abort current upload
+ * @function
+ */
+ abort: function (reset) {
+ this.currentSpeed = 0;
+ this.averageSpeed = 0;
+ var chunks = this.chunks;
+ if (reset) {
+ this.chunks = [];
+ }
+ each(chunks, function (c) {
+ if (c.status() === 'uploading') {
+ c.abort();
+ this.flowObj.uploadNextChunk();
+ }
+ }, this);
+ },
+
+ /**
+ * Cancel current upload and remove from a list
+ * @function
+ */
+ cancel: function () {
+ this.flowObj.removeFile(this);
+ },
+
+ /**
+ * Retry aborted file upload
+ * @function
+ */
+ retry: function () {
+ this.bootstrap();
+ this.flowObj.upload();
+ },
+
+ /**
+ * Clear current chunks and slice file again
+ * @function
+ */
+ bootstrap: function () {
+ if (typeof this.flowObj.opts.initFileFn === "function") {
+ this.flowObj.opts.initFileFn(this);
+ }
+
+ this.abort(true);
+ this.error = false;
+ // Rebuild stack of chunks from file
+ this._prevProgress = 0;
+ var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor;
+ var chunks = Math.max(
+ round(this.size / this.flowObj.opts.chunkSize), 1
+ );
+ for (var offset = 0; offset < chunks; offset++) {
+ this.chunks.push(
+ new FlowChunk(this.flowObj, this, offset)
+ );
+ }
+ },
+
+ /**
+ * Get current upload progress status
+ * @function
+ * @returns {number} from 0 to 1
+ */
+ progress: function () {
+ if (this.error) {
+ return 1;
+ }
+ if (this.chunks.length === 1) {
+ this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress());
+ return this._prevProgress;
+ }
+ // Sum up progress across everything
+ var bytesLoaded = 0;
+ each(this.chunks, function (c) {
+ // get chunk progress relative to entire file
+ bytesLoaded += c.progress() * (c.endByte - c.startByte);
+ });
+ var percent = bytesLoaded / this.size;
+ // We don't want to lose percentages when an upload is paused
+ this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent);
+ return this._prevProgress;
+ },
+
+ /**
+ * Indicates if file is being uploaded at the moment
+ * @function
+ * @returns {boolean}
+ */
+ isUploading: function () {
+ var uploading = false;
+ each(this.chunks, function (chunk) {
+ if (chunk.status() === 'uploading') {
+ uploading = true;
+ return false;
+ }
+ });
+ return uploading;
+ },
+
+ /**
+ * Indicates if file is has finished uploading and received a response
+ * @function
+ * @returns {boolean}
+ */
+ isComplete: function () {
+ var outstanding = false;
+ each(this.chunks, function (chunk) {
+ var status = chunk.status();
+ if (status === 'pending' || status === 'uploading' || status === 'reading' || chunk.preprocessState === 1 || chunk.readState === 1) {
+ outstanding = true;
+ return false;
+ }
+ });
+ return !outstanding;
+ },
+
+ /**
+ * Count total size uploaded
+ * @function
+ * @returns {number}
+ */
+ sizeUploaded: function () {
+ var size = 0;
+ each(this.chunks, function (chunk) {
+ size += chunk.sizeUploaded();
+ });
+ return size;
+ },
+
+ /**
+ * Returns remaining time to finish upload file in seconds. Accuracy is based on average speed.
+ * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
+ * @function
+ * @returns {number}
+ */
+ timeRemaining: function () {
+ if (this.paused || this.error) {
+ return 0;
+ }
+ var delta = this.size - this.sizeUploaded();
+ if (delta && !this.averageSpeed) {
+ return Number.POSITIVE_INFINITY;
+ }
+ if (!delta && !this.averageSpeed) {
+ return 0;
+ }
+ return Math.floor(delta / this.averageSpeed);
+ },
+
+ /**
+ * Get file type
+ * @function
+ * @returns {string}
+ */
+ getType: function () {
+ return this.file.type && this.file.type.split('/')[1];
+ },
+
+ /**
+ * Get file extension
+ * @function
+ * @returns {string}
+ */
+ getExtension: function () {
+ return this.name.substr((~-this.name.lastIndexOf(".") >>> 0) + 2).toLowerCase();
+ }
+ };
+
+ /**
+ * Default read function using the webAPI
+ *
+ * @function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk)
+ *
+ */
+ function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk) {
+ var function_name = 'slice';
+
+ if (fileObj.file.slice)
+ function_name = 'slice';
+ else if (fileObj.file.mozSlice)
+ function_name = 'mozSlice';
+ else if (fileObj.file.webkitSlice)
+ function_name = 'webkitSlice';
+
+ chunk.readFinished(fileObj.file[function_name](startByte, endByte, fileType));
+ }
+
+
+ /**
+ * Class for storing a single chunk
+ * @name FlowChunk
+ * @param {Flow} flowObj
+ * @param {FlowFile} fileObj
+ * @param {number} offset
+ * @constructor
+ */
+ function FlowChunk(flowObj, fileObj, offset) {
+
+ /**
+ * Reference to parent flow object
+ * @type {Flow}
+ */
+ this.flowObj = flowObj;
+
+ /**
+ * Reference to parent FlowFile object
+ * @type {FlowFile}
+ */
+ this.fileObj = fileObj;
+
+ /**
+ * File offset
+ * @type {number}
+ */
+ this.offset = offset;
+
+ /**
+ * Indicates if chunk existence was checked on the server
+ * @type {boolean}
+ */
+ this.tested = false;
+
+ /**
+ * Number of retries performed
+ * @type {number}
+ */
+ this.retries = 0;
+
+ /**
+ * Pending retry
+ * @type {boolean}
+ */
+ this.pendingRetry = false;
+
+ /**
+ * Preprocess state
+ * @type {number} 0 = unprocessed, 1 = processing, 2 = finished
+ */
+ this.preprocessState = 0;
+
+ /**
+ * Read state
+ * @type {number} 0 = not read, 1 = reading, 2 = finished
+ */
+ this.readState = 0;
+
+
+ /**
+ * Bytes transferred from total request size
+ * @type {number}
+ */
+ this.loaded = 0;
+
+ /**
+ * Total request size
+ * @type {number}
+ */
+ this.total = 0;
+
+ /**
+ * Size of a chunk
+ * @type {number}
+ */
+ this.chunkSize = this.flowObj.opts.chunkSize;
+
+ /**
+ * Chunk start byte in a file
+ * @type {number}
+ */
+ this.startByte = this.offset * this.chunkSize;
+
+ /**
+ * Compute the endbyte in a file
+ *
+ */
+ this.computeEndByte = function() {
+ var endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize);
+ if (this.fileObj.size - endByte < this.chunkSize && !this.flowObj.opts.forceChunkSize) {
+ // The last chunk will be bigger than the chunk size,
+ // but less than 2 * this.chunkSize
+ endByte = this.fileObj.size;
+ }
+ return endByte;
+ }
+
+ /**
+ * Chunk end byte in a file
+ * @type {number}
+ */
+ this.endByte = this.computeEndByte();
+
+ /**
+ * XMLHttpRequest
+ * @type {XMLHttpRequest}
+ */
+ this.xhr = null;
+
+ var $ = this;
+
+ /**
+ * Send chunk event
+ * @param event
+ * @param {...} args arguments of a callback
+ */
+ this.event = function (event, args) {
+ args = Array.prototype.slice.call(arguments);
+ args.unshift($);
+ $.fileObj.chunkEvent.apply($.fileObj, args);
+ };
+ /**
+ * Catch progress event
+ * @param {ProgressEvent} event
+ */
+ this.progressHandler = function(event) {
+ if (event.lengthComputable) {
+ $.loaded = event.loaded ;
+ $.total = event.total;
+ }
+ $.event('progress', event);
+ };
+
+ /**
+ * Catch test event
+ * @param {Event} event
+ */
+ this.testHandler = function(event) {
+ var status = $.status(true);
+ if (status === 'error') {
+ $.event(status, $.message());
+ $.flowObj.uploadNextChunk();
+ } else if (status === 'success') {
+ $.tested = true;
+ $.event(status, $.message());
+ $.flowObj.uploadNextChunk();
+ } else if (!$.fileObj.paused) {
+ // Error might be caused by file pause method
+ // Chunks does not exist on the server side
+ $.tested = true;
+ $.send();
+ }
+ };
+
+ /**
+ * Upload has stopped
+ * @param {Event} event
+ */
+ this.doneHandler = function(event) {
+ var status = $.status();
+ if (status === 'success' || status === 'error') {
+ delete this.data;
+ $.event(status, $.message());
+ $.flowObj.uploadNextChunk();
+ } else {
+ $.event('retry', $.message());
+ $.pendingRetry = true;
+ $.abort();
+ $.retries++;
+ var retryInterval = $.flowObj.opts.chunkRetryInterval;
+ if (retryInterval !== null) {
+ setTimeout(function () {
+ $.send();
+ }, retryInterval);
+ } else {
+ $.send();
+ }
+ }
+ };
+ }
+
+ FlowChunk.prototype = {
+ /**
+ * Get params for a request
+ * @function
+ */
+ getParams: function () {
+ return {
+ flowChunkNumber: this.offset + 1,
+ flowChunkSize: this.flowObj.opts.chunkSize,
+ flowCurrentChunkSize: this.endByte - this.startByte,
+ flowTotalSize: this.fileObj.size,
+ flowIdentifier: this.fileObj.uniqueIdentifier,
+ flowFilename: this.fileObj.name,
+ flowRelativePath: this.fileObj.relativePath,
+ flowTotalChunks: this.fileObj.chunks.length
+ };
+ },
+
+ /**
+ * Get target option with query params
+ * @function
+ * @param params
+ * @returns {string}
+ */
+ getTarget: function(target, params){
+ if(target.indexOf('?') < 0) {
+ target += '?';
+ } else {
+ target += '&';
+ }
+ return target + params.join('&');
+ },
+
+ /**
+ * Makes a GET request without any data to see if the chunk has already
+ * been uploaded in a previous session
+ * @function
+ */
+ test: function () {
+ // Set up request and listen for event
+ this.xhr = new XMLHttpRequest();
+ this.xhr.addEventListener("load", this.testHandler, false);
+ this.xhr.addEventListener("error", this.testHandler, false);
+ var testMethod = evalOpts(this.flowObj.opts.testMethod, this.fileObj, this);
+ var data = this.prepareXhrRequest(testMethod, true);
+ this.xhr.send(data);
+ },
+
+ /**
+ * Finish preprocess state
+ * @function
+ */
+ preprocessFinished: function () {
+ // Re-compute the endByte after the preprocess function to allow an
+ // implementer of preprocess to set the fileObj size
+ this.endByte = this.computeEndByte();
+
+ this.preprocessState = 2;
+ this.send();
+ },
+
+ /**
+ * Finish read state
+ * @function
+ */
+ readFinished: function (bytes) {
+ this.readState = 2;
+ this.bytes = bytes;
+ this.send();
+ },
+
+
+ /**
+ * Uploads the actual data in a POST call
+ * @function
+ */
+ send: function () {
+ var preprocess = this.flowObj.opts.preprocess;
+ var read = this.flowObj.opts.readFileFn;
+ if (typeof preprocess === 'function') {
+ switch (this.preprocessState) {
+ case 0:
+ this.preprocessState = 1;
+ preprocess(this);
+ return;
+ case 1:
+ return;
+ }
+ }
+ switch (this.readState) {
+ case 0:
+ this.readState = 1;
+ read(this.fileObj, this.startByte, this.endByte, this.fileObj.file.type, this);
+ return;
+ case 1:
+ return;
+ }
+ if (this.flowObj.opts.testChunks && !this.tested) {
+ this.test();
+ return;
+ }
+
+ this.loaded = 0;
+ this.total = 0;
+ this.pendingRetry = false;
+
+ // Set up request and listen for event
+ this.xhr = new XMLHttpRequest();
+ this.xhr.upload.addEventListener('progress', this.progressHandler, false);
+ this.xhr.addEventListener("load", this.doneHandler, false);
+ this.xhr.addEventListener("error", this.doneHandler, false);
+
+ var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this);
+ var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, this.bytes);
+ this.xhr.send(data);
+ },
+
+ /**
+ * Abort current xhr request
+ * @function
+ */
+ abort: function () {
+ // Abort and reset
+ var xhr = this.xhr;
+ this.xhr = null;
+ if (xhr) {
+ xhr.abort();
+ }
+ },
+
+ /**
+ * Retrieve current chunk upload status
+ * @function
+ * @returns {string} 'pending', 'uploading', 'success', 'error'
+ */
+ status: function (isTest) {
+ if (this.readState === 1) {
+ return 'reading';
+ } else if (this.pendingRetry || this.preprocessState === 1) {
+ // if pending retry then that's effectively the same as actively uploading,
+ // there might just be a slight delay before the retry starts
+ return 'uploading';
+ } else if (!this.xhr) {
+ return 'pending';
+ } else if (this.xhr.readyState < 4) {
+ // Status is really 'OPENED', 'HEADERS_RECEIVED'
+ // or 'LOADING' - meaning that stuff is happening
+ return 'uploading';
+ } else {
+ if (this.flowObj.opts.successStatuses.indexOf(this.xhr.status) > -1) {
+ // HTTP 200, perfect
+ // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
+ return 'success';
+ } else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
+ !isTest && this.retries >= this.flowObj.opts.maxChunkRetries) {
+ // HTTP 413/415/500/501, permanent error
+ return 'error';
+ } else {
+ // this should never happen, but we'll reset and queue a retry
+ // a likely case for this would be 503 service unavailable
+ this.abort();
+ return 'pending';
+ }
+ }
+ },
+
+ /**
+ * Get response from xhr request
+ * @function
+ * @returns {String}
+ */
+ message: function () {
+ return this.xhr ? this.xhr.responseText : '';
+ },
+
+ /**
+ * Get upload progress
+ * @function
+ * @returns {number}
+ */
+ progress: function () {
+ if (this.pendingRetry) {
+ return 0;
+ }
+ var s = this.status();
+ if (s === 'success' || s === 'error') {
+ return 1;
+ } else if (s === 'pending') {
+ return 0;
+ } else {
+ return this.total > 0 ? this.loaded / this.total : 0;
+ }
+ },
+
+ /**
+ * Count total size uploaded
+ * @function
+ * @returns {number}
+ */
+ sizeUploaded: function () {
+ var size = this.endByte - this.startByte;
+ // can't return only chunk.loaded value, because it is bigger than chunk size
+ if (this.status() !== 'success') {
+ size = this.progress() * size;
+ }
+ return size;
+ },
+
+ /**
+ * Prepare Xhr request. Set query, headers and data
+ * @param {string} method GET or POST
+ * @param {bool} isTest is this a test request
+ * @param {string} [paramsMethod] octet or form
+ * @param {Blob} [blob] to send
+ * @returns {FormData|Blob|Null} data to send
+ */
+ prepareXhrRequest: function(method, isTest, paramsMethod, blob) {
+ // Add data from the query options
+ var query = evalOpts(this.flowObj.opts.query, this.fileObj, this, isTest);
+ query = extend(query, this.getParams());
+
+ var target = evalOpts(this.flowObj.opts.target, this.fileObj, this, isTest);
+ var data = null;
+ if (method === 'GET' || paramsMethod === 'octet') {
+ // Add data from the query options
+ var params = [];
+ each(query, function (v, k) {
+ params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
+ });
+ target = this.getTarget(target, params);
+ data = blob || null;
+ } else {
+ // Add data from the query options
+ data = new FormData();
+ each(query, function (v, k) {
+ data.append(k, v);
+ });
+ data.append(this.flowObj.opts.fileParameterName, blob, this.fileObj.file.name);
+ }
+
+ this.xhr.open(method, target, true);
+ this.xhr.withCredentials = this.flowObj.opts.withCredentials;
+
+ // Add data from header options
+ each(evalOpts(this.flowObj.opts.headers, this.fileObj, this, isTest), function (v, k) {
+ this.xhr.setRequestHeader(k, v);
+ }, this);
+
+ return data;
+ }
+ };
+
+ /**
+ * Remove value from array
+ * @param array
+ * @param value
+ */
+ function arrayRemove(array, value) {
+ var index = array.indexOf(value);
+ if (index > -1) {
+ array.splice(index, 1);
+ }
+ }
+
+ /**
+ * If option is a function, evaluate it with given params
+ * @param {*} data
+ * @param {...} args arguments of a callback
+ * @returns {*}
+ */
+ function evalOpts(data, args) {
+ if (typeof data === "function") {
+ // `arguments` is an object, not array, in FF, so:
+ args = Array.prototype.slice.call(arguments);
+ data = data.apply(null, args.slice(1));
+ }
+ return data;
+ }
+ Flow.evalOpts = evalOpts;
+
+ /**
+ * Execute function asynchronously
+ * @param fn
+ * @param context
+ */
+ function async(fn, context) {
+ setTimeout(fn.bind(context), 0);
+ }
+
+ /**
+ * Extends the destination object `dst` by copying all of the properties from
+ * the `src` object(s) to `dst`. You can specify multiple `src` objects.
+ * @function
+ * @param {Object} dst Destination object.
+ * @param {...Object} src Source object(s).
+ * @returns {Object} Reference to `dst`.
+ */
+ function extend(dst, src) {
+ each(arguments, function(obj) {
+ if (obj !== dst) {
+ each(obj, function(value, key){
+ dst[key] = value;
+ });
+ }
+ });
+ return dst;
+ }
+ Flow.extend = extend;
+
+ /**
+ * Iterate each element of an object
+ * @function
+ * @param {Array|Object} obj object or an array to iterate
+ * @param {Function} callback first argument is a value and second is a key.
+ * @param {Object=} context Object to become context (`this`) for the iterator function.
+ */
+ function each(obj, callback, context) {
+ if (!obj) {
+ return ;
+ }
+ var key;
+ // Is Array?
+ // Array.isArray won't work, not only arrays can be iterated by index https://github.com/flowjs/ng-flow/issues/236#
+ if (typeof(obj.length) !== 'undefined') {
+ for (key = 0; key < obj.length; key++) {
+ if (callback.call(context, obj[key], key) === false) {
+ return ;
+ }
+ }
+ } else {
+ for (key in obj) {
+ if (obj.hasOwnProperty(key) && callback.call(context, obj[key], key) === false) {
+ return ;
+ }
+ }
+ }
+ }
+ Flow.each = each;
+
+ /**
+ * FlowFile constructor
+ * @type {FlowFile}
+ */
+ Flow.FlowFile = FlowFile;
+
+ /**
+ * FlowFile constructor
+ * @type {FlowChunk}
+ */
+ Flow.FlowChunk = FlowChunk;
+
+ /**
+ * Library version
+ * @type {string}
+ */
+ Flow.version = '2.11.2';
+
+ if ( typeof module === "object" && module && typeof module.exports === "object" ) {
+ // Expose Flow as module.exports in loaders that implement the Node
+ // module pattern (including browserify). Do not create the global, since
+ // the user will be storing it themselves locally, and globals are frowned
+ // upon in the Node module world.
+ module.exports = Flow;
+ } else {
+ // Otherwise expose Flow to the global object as usual
+ window.Flow = Flow;
+
+ // Register as a named AMD module, since Flow can be concatenated with other
+ // files that may use define, but not via a proper concatenation script that
+ // understands anonymous AMD modules. A named AMD is safest and most robust
+ // way to register. Lowercase flow is used because AMD module names are
+ // derived from file names, and Flow is normally delivered in a lowercase
+ // file name. Do this after creating the global so that if an AMD module wants
+ // to call noConflict to hide this version of Flow, it will work.
+ if ( typeof define === "function" && define.amd ) {
+ define( "flow", [], function () { return Flow; } );
+ }
+ }
+})(window, document);
+
+;/*})'"*/
+;/*})'"*/
+(function ($) {
+ Drupal.ocupload = Drupal.ocupload || {};
+
+ /**
+ * Create and configure Flow.js object.
+ */
+ Drupal.ocupload.createFlow = function () {
+ // Create Flow.js instance
+ var flow = new Flow({
+ target: Drupal.settings.basePath + 'ocupload/upload',
+ testChunks: false,
+ chunkSize: 5*1024*1024,
+ simultaneousUploads: 1
+ });
+
+ if (!flow.support) {
+ return flow;
+ }
+
+ flow.on('fileAdded', Drupal.ocupload.onFileAdded);
+ flow.on('filesSubmitted', Drupal.ocupload.onFilesSubmitted);
+ flow.on('fileProgress', Drupal.ocupload.onFileProgress);
+ flow.on('fileSuccess', Drupal.ocupload.onFileSuccess);
+ flow.on('error', Drupal.ocupload.onError);
+ flow.on('complete', Drupal.ocupload.onComplete);
+
+ return flow;
+ };
+
+ /**
+ * Return true if response in JSON format.
+ */
+ Drupal.ocupload.checkResponse = function (response) {
+ return $.trim(response).substring(0, 1) == '{';
+ };
+
+ /**
+ * Return target textarea.
+ */
+ Drupal.ocupload.findTextarea = function(element) {
+ var $parent = $(element).parent();
+ var $textarea = $parent.find('textarea:first');
+ return ($textarea.length == 0) ? Drupal.ocupload.findTextarea($parent) : $textarea;
+ };
+
+ /**
+ * File added handler.
+ */
+ Drupal.ocupload.onFileAdded = function (file, event) {
+ if ($.inArray(file.getExtension(), Drupal.settings.ocupload.allowedExt) == -1) {
+ alert(Drupal.t('You can not upload files of type .@file_ext', {'@file_ext':file.getExtension()}));
+ return false;
+ }
+ };
+
+ /**
+ * Files selected handler.
+ */
+ Drupal.ocupload.onFilesSubmitted = function (files, event) {
+ var flow = this;
+ var $textarea = Drupal.ocupload.findTextarea(event.target);
+ var $queue = $('#upload-queue');
+
+ if ($queue.length == 0) {
+ $queue = $('').appendTo('body');
+ }
+
+ $.each(files, function (index, file) {
+ $queue.prepend('' + file.name + '
');
+ });
+
+ flow.opts.query.fieldName = $textarea.attr('name');
+ flow.opts.query.formId = $textarea.closest('form').find('input[name="form_id"]').val();
+ };
+
+ /**
+ * File upload progress handler.
+ */
+ Drupal.ocupload.onFileProgress = function (file, chunk) {
+ var $fileQueue = $('#queue-' + file.uniqueIdentifier);
+ $fileQueue.css({
+ 'background': 'url(' + Drupal.settings.basePath + 'misc/progress.gif) repeat-x 0 center',
+ 'color': 'white'
+ });
+ };
+
+ /**
+ * File uploaded handler.
+ */
+ Drupal.ocupload.onFileSuccess = function (file, response, chunk) {
+ var $fileQueue = $('#queue-' + file.uniqueIdentifier);
+ $fileQueue.hide('fast', function () {
+ $fileQueue.remove();
+ });
+
+ if (!Drupal.ocupload.checkResponse(response)) {
+ alert(Drupal.t('Server response came not in JSON format: @response', {'@response':response}));
+ }
+ };
+
+ /**
+ * Upload error handler.
+ */
+ Drupal.ocupload.onError = function (message, file, chunk) {
+ alert(Drupal.t('Upload error: @message', {'@message': message}))
+ };
+
+ /**
+ * Files uploaded handler.
+ */
+ Drupal.ocupload.onComplete = function () {
+ var flow = this;
+ flow.cancel();
+ };
+})(jQuery);
+
+// Translate string because plugin.js not visible in locale_js_alter()
+// Drupal.t('Upload file');
+// Drupal.t('Your browser not support HTML5 File API');
+
+;/*})'"*/
+;/*})'"*/
+(function ($) {
+ Drupal.behaviors.ocuploadTextarea = {
+ attach: function (context, settings) {
+ if (!Drupal.settings.ocupload || !Drupal.settings.ocupload.allowedExt) {
+ return;
+ }
+
+ $('textarea.ocupload-drop', context).once('ocupload-drop').each(function () {
+ var textarea = this;
+
+ // Lazy create and configure Flow.js object
+ if (!Drupal.ocupload.textareaPlugin.flow) {
+ Drupal.ocupload.textareaPlugin.createFlow();
+ }
+
+ // Process textarea
+ if (Drupal.ocupload.textareaPlugin.flow.support) {
+ Drupal.ocupload.textareaPlugin.flow.assignDrop(textarea);
+
+ // Hack for IE. IE loses textarea selection on drag start.
+ if (Drupal.ocupload.textareaPlugin.isIE) {
+ $(textarea).bind('blur', Drupal.ocupload.textareaPlugin.saveSelection);
+ }
+ }
+ });
+ }
+ };
+
+ Drupal.ocupload = Drupal.ocupload || {};
+ Drupal.ocupload.textareaPlugin = Drupal.ocupload.textareaPlugin || {};
+ Drupal.ocupload.textareaPlugin.isIE = document.documentMode ? true : false;
+
+ /**
+ * Create and configure Flow.js object.
+ */
+ Drupal.ocupload.textareaPlugin.createFlow = function () {
+ Drupal.ocupload.textareaPlugin.flow = Drupal.ocupload.createFlow();
+
+ if (!Drupal.ocupload.textareaPlugin.flow.support) {
+ return false;
+ }
+
+ Drupal.ocupload.textareaPlugin.flow.on('filesSubmitted', Drupal.ocupload.textareaPlugin.onFilesSubmitted);
+ Drupal.ocupload.textareaPlugin.flow.on('fileSuccess', Drupal.ocupload.textareaPlugin.onFileSuccess);
+ Drupal.ocupload.textareaPlugin.flow.on('complete', Drupal.ocupload.textareaPlugin.onComplete);
+
+ return true;
+ };
+
+ /**
+ * Get selected text in textarea.
+ */
+ Drupal.ocupload.textareaPlugin.getSelectedText = function (element) {
+ if (element instanceof jQuery) {
+ element = element[0];
+ }
+ return element.value.substring(element.selectionStart, element.selectionEnd);
+ };
+
+ /**
+ * Save selection info in element data attribute.
+ */
+ Drupal.ocupload.textareaPlugin.saveSelection = function (event) {
+ var textarea = this;
+
+ $(textarea).data('ocuploadSelection', {
+ selectedText: Drupal.ocupload.textareaPlugin.getSelectedText(textarea),
+ selectionStart: textarea.selectionStart,
+ selectionEnd: textarea.selectionEnd,
+ });
+ };
+
+ /**
+ * Files selected handler.
+ */
+ Drupal.ocupload.textareaPlugin.onFilesSubmitted = function (files, event) {
+ var $textarea = $(event.target).closest('.form-item').find('textarea');
+ var selectedText = Drupal.ocupload.textareaPlugin.getSelectedText($textarea);
+
+ // Hack for IE. Restore selection from data
+ if (Drupal.ocupload.textareaPlugin.isIE) {
+ selectedText = $textarea.data('ocuploadSelection').selectedText;
+ }
+
+ Drupal.ocupload.textareaPlugin.flow.opts.query.selectedText = selectedText;
+ Drupal.ocupload.textareaPlugin.flow.upload();
+
+ $textarea[0].disabled = true;
+
+ // Save textarea id in global var, because event 'complete' not contains this information
+ Drupal.ocupload.textareaPlugin.activeTextareaId = $textarea.attr('id');
+ };
+
+ /**
+ * File uploaded handler.
+ */
+ Drupal.ocupload.textareaPlugin.onFileSuccess = function (file, response, chunk) {
+ if (!Drupal.ocupload.checkResponse(response)) {
+ return;
+ }
+
+ response = $.parseJSON(response);
+
+ if (response.status) {
+ var $textarea = $('#' + Drupal.ocupload.textareaPlugin.activeTextareaId);
+ var textarea = $textarea[0];
+ var selectionStart = textarea.selectionStart;
+ var selectionEnd = textarea.selectionEnd;
+ var insertedText = response.data;
+
+ // Hack for IE
+ if (Drupal.ocupload.textareaPlugin.isIE) {
+ var selection = $textarea.data('ocuploadSelection');
+ selectionStart = selection.selectionStart;
+ selectionEnd = selection.selectionEnd;
+
+ textarea.disabled = false;
+ textarea.focus();
+ }
+
+ if (selectionStart == selectionEnd) {
+ insertedText += "\n";
+ }
+
+ textarea.value = textarea.value.substring(0, selectionStart)
+ + insertedText
+ + textarea.value.substring(selectionEnd, textarea.value.length);
+
+ var cursorPosition = selectionStart + insertedText.length;
+ textarea.selectionStart = cursorPosition;
+ textarea.selectionEnd = cursorPosition;
+
+ // Hack for IE
+ if (Drupal.ocupload.textareaPlugin.isIE) {
+ textarea.disabled = true;
+ $textarea.data('ocuploadSelection', {
+ selectionStart: cursorPosition,
+ selectionEnd: cursorPosition,
+ })
+ }
+ }
+ else {
+ alert(response.data);
+ }
+ };
+
+ /**
+ * Files uploaded handler.
+ */
+ Drupal.ocupload.textareaPlugin.onComplete = function () {
+ var $textarea = $('#' + Drupal.ocupload.textareaPlugin.activeTextareaId);
+ $textarea[0].disabled = false;
+ $textarea.focus();
+ };
+})(jQuery);
+
+;/*})'"*/
+;/*})'"*/
+(function ($) {
+
+/**
+ * Retrieves the summary for the first element.
+ */
+$.fn.drupalGetSummary = function () {
+ var callback = this.data('summaryCallback');
+ return (this[0] && callback) ? $.trim(callback(this[0])) : '';
+};
+
+/**
+ * Sets the summary for all matched elements.
+ *
+ * @param callback
+ * Either a function that will be called each time the summary is
+ * retrieved or a string (which is returned each time).
+ */
+$.fn.drupalSetSummary = function (callback) {
+ var self = this;
+
+ // To facilitate things, the callback should always be a function. If it's
+ // not, we wrap it into an anonymous function which just returns the value.
+ if (typeof callback != 'function') {
+ var val = callback;
+ callback = function () { return val; };
+ }
+
+ return this
+ .data('summaryCallback', callback)
+ // To prevent duplicate events, the handlers are first removed and then
+ // (re-)added.
+ .unbind('formUpdated.summary')
+ .bind('formUpdated.summary', function () {
+ self.trigger('summaryUpdated');
+ })
+ // The actual summaryUpdated handler doesn't fire when the callback is
+ // changed, so we have to do this manually.
+ .trigger('summaryUpdated');
+};
+
+/**
+ * Sends a 'formUpdated' event each time a form element is modified.
+ */
+Drupal.behaviors.formUpdated = {
+ attach: function (context) {
+ // These events are namespaced so that we can remove them later.
+ var events = 'change.formUpdated click.formUpdated blur.formUpdated keyup.formUpdated';
+ $(context)
+ // Since context could be an input element itself, it's added back to
+ // the jQuery object and filtered again.
+ .find(':input').andSelf().filter(':input')
+ // To prevent duplicate events, the handlers are first removed and then
+ // (re-)added.
+ .unbind(events).bind(events, function () {
+ $(this).trigger('formUpdated');
+ });
+ }
+};
+
+/**
+ * Prepopulate form fields with information from the visitor cookie.
+ */
+Drupal.behaviors.fillUserInfoFromCookie = {
+ attach: function (context, settings) {
+ $('form.user-info-from-cookie').once('user-info-from-cookie', function () {
+ var formContext = this;
+ $.each(['name', 'mail', 'homepage'], function () {
+ var $element = $('[name=' + this + ']', formContext);
+ var cookie = $.cookie('Drupal.visitor.' + this);
+ if ($element.length && cookie) {
+ $element.val(cookie);
+ }
+ });
+ });
+ }
+};
+
+})(jQuery);
+
+;/*})'"*/
+;/*})'"*/
+(function ($) {
+
+/**
+ * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML).
+ *
+ * Ajax is a method of making a request via JavaScript while viewing an HTML
+ * page. The request returns an array of commands encoded in JSON, which is
+ * then executed to make any changes that are necessary to the page.
+ *
+ * Drupal uses this file to enhance form elements with #ajax['path'] and
+ * #ajax['wrapper'] properties. If set, this file will automatically be included
+ * to provide Ajax capabilities.
+ */
+
+Drupal.ajax = Drupal.ajax || {};
+
+Drupal.settings.urlIsAjaxTrusted = Drupal.settings.urlIsAjaxTrusted || {};
+
+/**
+ * Attaches the Ajax behavior to each Ajax form element.
+ */
+Drupal.behaviors.AJAX = {
+ attach: function (context, settings) {
+ // Load all Ajax behaviors specified in the settings.
+ for (var base in settings.ajax) {
+ if (!$('#' + base + '.ajax-processed').length) {
+ var element_settings = settings.ajax[base];
+
+ if (typeof element_settings.selector == 'undefined') {
+ element_settings.selector = '#' + base;
+ }
+ $(element_settings.selector).each(function () {
+ element_settings.element = this;
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+
+ $('#' + base).addClass('ajax-processed');
+ }
+ }
+
+ // Bind Ajax behaviors to all items showing the class.
+ $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () {
+ var element_settings = {};
+ // Clicked links look better with the throbber than the progress bar.
+ element_settings.progress = { 'type': 'throbber' };
+
+ // For anchor tags, these will go to the target of the anchor rather
+ // than the usual location.
+ if ($(this).attr('href')) {
+ element_settings.url = $(this).attr('href');
+ element_settings.event = 'click';
+ }
+ var base = $(this).attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+
+ // This class means to submit the form to the action using Ajax.
+ $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () {
+ var element_settings = {};
+
+ // Ajax submits specified in this manner automatically submit to the
+ // normal form action.
+ element_settings.url = $(this.form).attr('action');
+ // Form submit button clicks need to tell the form what was clicked so
+ // it gets passed in the POST request.
+ element_settings.setClick = true;
+ // Form buttons use the 'click' event rather than mousedown.
+ element_settings.event = 'click';
+ // Clicked form buttons look better with the throbber than the progress bar.
+ element_settings.progress = { 'type': 'throbber' };
+
+ var base = $(this).attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+ }
+};
+
+/**
+ * Ajax object.
+ *
+ * All Ajax objects on a page are accessible through the global Drupal.ajax
+ * object and are keyed by the submit button's ID. You can access them from
+ * your module's JavaScript file to override properties or functions.
+ *
+ * For example, if your Ajax enabled button has the ID 'edit-submit', you can
+ * redefine the function that is called to insert the new content like this
+ * (inside a Drupal.behaviors attach block):
+ * @code
+ * Drupal.behaviors.myCustomAJAXStuff = {
+ * attach: function (context, settings) {
+ * Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) {
+ * new_content = $(response.data);
+ * $('#my-wrapper').append(new_content);
+ * alert('New content was appended to #my-wrapper');
+ * }
+ * }
+ * };
+ * @endcode
+ */
+Drupal.ajax = function (base, element, element_settings) {
+ var defaults = {
+ url: 'system/ajax',
+ event: 'mousedown',
+ keypress: true,
+ selector: '#' + base,
+ effect: 'none',
+ speed: 'none',
+ method: 'replaceWith',
+ progress: {
+ type: 'throbber',
+ message: Drupal.t('Please wait...')
+ },
+ submit: {
+ 'js': true
+ }
+ };
+
+ $.extend(this, defaults, element_settings);
+
+ this.element = element;
+ this.element_settings = element_settings;
+
+ // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
+ // the server detect when it needs to degrade gracefully.
+ // There are five scenarios to check for:
+ // 1. /nojs/
+ // 2. /nojs$ - The end of a URL string.
+ // 3. /nojs? - Followed by a query (with clean URLs enabled).
+ // E.g.: path/nojs?destination=foobar
+ // 4. /nojs& - Followed by a query (without clean URLs enabled).
+ // E.g.: ?q=path/nojs&destination=foobar
+ // 5. /nojs# - Followed by a fragment.
+ // E.g.: path/nojs#myfragment
+ this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1');
+ // If the 'nojs' version of the URL is trusted, also trust the 'ajax' version.
+ if (Drupal.settings.urlIsAjaxTrusted[element_settings.url]) {
+ Drupal.settings.urlIsAjaxTrusted[this.url] = true;
+ }
+
+ this.wrapper = '#' + element_settings.wrapper;
+
+ // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
+ // bind Ajax to links as well.
+ if (this.element.form) {
+ this.form = $(this.element.form);
+ }
+
+ // Set the options for the ajaxSubmit function.
+ // The 'this' variable will not persist inside of the options object.
+ var ajax = this;
+ ajax.options = {
+ url: Drupal.sanitizeAjaxUrl(ajax.url),
+ data: ajax.submit,
+ beforeSerialize: function (element_settings, options) {
+ return ajax.beforeSerialize(element_settings, options);
+ },
+ beforeSubmit: function (form_values, element_settings, options) {
+ ajax.ajaxing = true;
+ return ajax.beforeSubmit(form_values, element_settings, options);
+ },
+ beforeSend: function (xmlhttprequest, options) {
+ ajax.ajaxing = true;
+ return ajax.beforeSend(xmlhttprequest, options);
+ },
+ success: function (response, status, xmlhttprequest) {
+ // Sanity check for browser support (object expected).
+ // When using iFrame uploads, responses must be returned as a string.
+ if (typeof response == 'string') {
+ response = $.parseJSON(response);
+ }
+
+ // Prior to invoking the response's commands, verify that they can be
+ // trusted by checking for a response header. See
+ // ajax_set_verification_header() for details.
+ // - Empty responses are harmless so can bypass verification. This avoids
+ // an alert message for server-generated no-op responses that skip Ajax
+ // rendering.
+ // - Ajax objects with trusted URLs (e.g., ones defined server-side via
+ // #ajax) can bypass header verification. This is especially useful for
+ // Ajax with multipart forms. Because IFRAME transport is used, the
+ // response headers cannot be accessed for verification.
+ if (response !== null && !Drupal.settings.urlIsAjaxTrusted[ajax.url]) {
+ if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') {
+ var customMessage = Drupal.t("The response failed verification so will not be processed.");
+ return ajax.error(xmlhttprequest, ajax.url, customMessage);
+ }
+ }
+
+ return ajax.success(response, status);
+ },
+ complete: function (xmlhttprequest, status) {
+ ajax.ajaxing = false;
+ if (status == 'error' || status == 'parsererror') {
+ return ajax.error(xmlhttprequest, ajax.url);
+ }
+ },
+ dataType: 'json',
+ jsonp: false,
+ type: 'POST'
+ };
+
+ // For multipart forms (e.g., file uploads), jQuery Form targets the form
+ // submission to an iframe instead of using an XHR object. The initial "src"
+ // of the iframe, prior to the form submission, is set to options.iframeSrc.
+ // "about:blank" is the semantically correct, standards-compliant, way to
+ // initialize a blank iframe; however, some old IE versions (possibly only 6)
+ // incorrectly report a mixed content warning when iframes with an
+ // "about:blank" src are added to a parent document with an https:// origin.
+ // jQuery Form works around this by defaulting to "javascript:false" instead,
+ // but that breaks on Chrome 83, so here we force the semantically correct
+ // behavior for all browsers except old IE.
+ // @see https://www.drupal.org/project/drupal/issues/3143016
+ // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68
+ // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874
+ // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts
+ // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
+ if (navigator.userAgent.indexOf("MSIE") === -1) {
+ ajax.options.iframeSrc = 'about:blank';
+ }
+
+ // Bind the ajaxSubmit function to the element event.
+ $(ajax.element).bind(element_settings.event, function (event) {
+ if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) {
+ throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url}));
+ }
+ return ajax.eventResponse(this, event);
+ });
+
+ // If necessary, enable keyboard submission so that Ajax behaviors
+ // can be triggered through keyboard input as well as e.g. a mousedown
+ // action.
+ if (element_settings.keypress) {
+ $(ajax.element).keypress(function (event) {
+ return ajax.keypressResponse(this, event);
+ });
+ }
+
+ // If necessary, prevent the browser default action of an additional event.
+ // For example, prevent the browser default action of a click, even if the
+ // AJAX behavior binds to mousedown.
+ if (element_settings.prevent) {
+ $(ajax.element).bind(element_settings.prevent, false);
+ }
+};
+
+/**
+ * Handle a key press.
+ *
+ * The Ajax object will, if instructed, bind to a key press response. This
+ * will test to see if the key press is valid to trigger this event and
+ * if it is, trigger it for us and prevent other keypresses from triggering.
+ * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
+ * and 32. RETURN is often used to submit a form when in a textfield, and
+ * SPACE is often used to activate an element without submitting.
+ */
+Drupal.ajax.prototype.keypressResponse = function (element, event) {
+ // Create a synonym for this to reduce code confusion.
+ var ajax = this;
+
+ // Detect enter key and space bar and allow the standard response for them,
+ // except for form elements of type 'text' and 'textarea', where the
+ // spacebar activation causes inappropriate activation if #ajax['keypress'] is
+ // TRUE. On a text-type widget a space should always be a space.
+ if (event.which == 13 || (event.which == 32 && element.type != 'text' && element.type != 'textarea')) {
+ $(ajax.element_settings.element).trigger(ajax.element_settings.event);
+ return false;
+ }
+};
+
+/**
+ * Handle an event that triggers an Ajax response.
+ *
+ * When an event that triggers an Ajax response happens, this method will
+ * perform the actual Ajax call. It is bound to the event using
+ * bind() in the constructor, and it uses the options specified on the
+ * ajax object.
+ */
+Drupal.ajax.prototype.eventResponse = function (element, event) {
+ // Create a synonym for this to reduce code confusion.
+ var ajax = this;
+
+ // Do not perform another ajax command if one is already in progress.
+ if (ajax.ajaxing) {
+ return false;
+ }
+
+ try {
+ if (ajax.form) {
+ // If setClick is set, we must set this to ensure that the button's
+ // value is passed.
+ if (ajax.setClick) {
+ // Mark the clicked button. 'form.clk' is a special variable for
+ // ajaxSubmit that tells the system which element got clicked to
+ // trigger the submit. Without it there would be no 'op' or
+ // equivalent.
+ element.form.clk = element;
+ }
+
+ ajax.form.ajaxSubmit(ajax.options);
+ }
+ else {
+ ajax.beforeSerialize(ajax.element, ajax.options);
+ $.ajax(ajax.options);
+ }
+ }
+ catch (e) {
+ // Unset the ajax.ajaxing flag here because it won't be unset during
+ // the complete response.
+ ajax.ajaxing = false;
+ alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message);
+ }
+
+ // For radio/checkbox, allow the default event. On IE, this means letting
+ // it actually check the box.
+ if (typeof element.type != 'undefined' && (element.type == 'checkbox' || element.type == 'radio')) {
+ return true;
+ }
+ else {
+ return false;
+ }
+
+};
+
+/**
+ * Handler for the form serialization.
+ *
+ * Runs before the beforeSend() handler (see below), and unlike that one, runs
+ * before field data is collected.
+ */
+Drupal.ajax.prototype.beforeSerialize = function (element, options) {
+ // Allow detaching behaviors to update field values before collecting them.
+ // This is only needed when field values are added to the POST data, so only
+ // when there is a form such that this.form.ajaxSubmit() is used instead of
+ // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
+ // isn't called, but don't rely on that: explicitly check this.form.
+ if (this.form) {
+ var settings = this.settings || Drupal.settings;
+ Drupal.detachBehaviors(this.form, settings, 'serialize');
+ }
+
+ // Prevent duplicate HTML ids in the returned markup.
+ // @see drupal_html_id()
+ options.data['ajax_html_ids[]'] = [];
+ $('[id]').each(function () {
+ options.data['ajax_html_ids[]'].push(this.id);
+ });
+
+ // Allow Drupal to return new JavaScript and CSS files to load without
+ // returning the ones already loaded.
+ // @see ajax_base_page_theme()
+ // @see drupal_get_css()
+ // @see drupal_get_js()
+ options.data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme;
+ options.data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token;
+ for (var key in Drupal.settings.ajaxPageState.css) {
+ options.data['ajax_page_state[css][' + key + ']'] = 1;
+ }
+ for (var key in Drupal.settings.ajaxPageState.js) {
+ options.data['ajax_page_state[js][' + key + ']'] = 1;
+ }
+};
+
+/**
+ * Modify form values prior to form submission.
+ */
+Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
+ // This function is left empty to make it simple to override for modules
+ // that wish to add functionality here.
+};
+
+/**
+ * Prepare the Ajax request before it is sent.
+ */
+Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
+ // For forms without file inputs, the jQuery Form plugin serializes the form
+ // values, and then calls jQuery's $.ajax() function, which invokes this
+ // handler. In this circumstance, options.extraData is never used. For forms
+ // with file inputs, the jQuery Form plugin uses the browser's normal form
+ // submission mechanism, but captures the response in a hidden IFRAME. In this
+ // circumstance, it calls this handler first, and then appends hidden fields
+ // to the form to submit the values in options.extraData. There is no simple
+ // way to know which submission mechanism will be used, so we add to extraData
+ // regardless, and allow it to be ignored in the former case.
+ if (this.form) {
+ options.extraData = options.extraData || {};
+
+ // Let the server know when the IFRAME submission mechanism is used. The
+ // server can use this information to wrap the JSON response in a TEXTAREA,
+ // as per http://jquery.malsup.com/form/#file-upload.
+ options.extraData.ajax_iframe_upload = '1';
+
+ // The triggering element is about to be disabled (see below), but if it
+ // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that
+ // value is included in the submission. As per above, submissions that use
+ // $.ajax() are already serialized prior to the element being disabled, so
+ // this is only needed for IFRAME submissions.
+ var v = $.fieldValue(this.element);
+ if (v !== null) {
+ options.extraData[this.element.name] = Drupal.checkPlain(v);
+ }
+ }
+
+ // Disable the element that received the change to prevent user interface
+ // interaction while the Ajax request is in progress. ajax.ajaxing prevents
+ // the element from triggering a new request, but does not prevent the user
+ // from changing its value.
+ $(this.element).addClass('progress-disabled').attr('disabled', true);
+
+ // Insert progressbar or throbber.
+ if (this.progress.type == 'bar') {
+ var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop);
+ if (this.progress.message) {
+ progressBar.setProgress(-1, this.progress.message);
+ }
+ if (this.progress.url) {
+ progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
+ }
+ this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
+ this.progress.object = progressBar;
+ $(this.element).after(this.progress.element);
+ }
+ else if (this.progress.type == 'throbber') {
+ this.progress.element = $('');
+ if (this.progress.message) {
+ $('.throbber', this.progress.element).after('' + this.progress.message + '
');
+ }
+ $(this.element).after(this.progress.element);
+ }
+};
+
+/**
+ * Handler for the form redirection completion.
+ */
+Drupal.ajax.prototype.success = function (response, status) {
+ // Remove the progress element.
+ if (this.progress.element) {
+ $(this.progress.element).remove();
+ }
+ if (this.progress.object) {
+ this.progress.object.stopMonitoring();
+ }
+ $(this.element).removeClass('progress-disabled').removeAttr('disabled');
+
+ Drupal.freezeHeight();
+
+ for (var i in response) {
+ if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) {
+ this.commands[response[i]['command']](this, response[i], status);
+ }
+ }
+
+ // Reattach behaviors, if they were detached in beforeSerialize(). The
+ // attachBehaviors() called on the new content from processing the response
+ // commands is not sufficient, because behaviors from the entire form need
+ // to be reattached.
+ if (this.form) {
+ var settings = this.settings || Drupal.settings;
+ Drupal.attachBehaviors(this.form, settings);
+ }
+
+ Drupal.unfreezeHeight();
+
+ // Remove any response-specific settings so they don't get used on the next
+ // call by mistake.
+ this.settings = null;
+};
+
+/**
+ * Build an effect object which tells us how to apply the effect when adding new HTML.
+ */
+Drupal.ajax.prototype.getEffect = function (response) {
+ var type = response.effect || this.effect;
+ var speed = response.speed || this.speed;
+
+ var effect = {};
+ if (type == 'none') {
+ effect.showEffect = 'show';
+ effect.hideEffect = 'hide';
+ effect.showSpeed = '';
+ }
+ else if (type == 'fade') {
+ effect.showEffect = 'fadeIn';
+ effect.hideEffect = 'fadeOut';
+ effect.showSpeed = speed;
+ }
+ else {
+ effect.showEffect = type + 'Toggle';
+ effect.hideEffect = type + 'Toggle';
+ effect.showSpeed = speed;
+ }
+
+ return effect;
+};
+
+/**
+ * Handler for the form redirection error.
+ */
+Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) {
+ Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage));
+ // Remove the progress element.
+ if (this.progress.element) {
+ $(this.progress.element).remove();
+ }
+ if (this.progress.object) {
+ this.progress.object.stopMonitoring();
+ }
+ // Undo hide.
+ $(this.wrapper).show();
+ // Re-enable the element.
+ $(this.element).removeClass('progress-disabled').removeAttr('disabled');
+ // Reattach behaviors, if they were detached in beforeSerialize().
+ if (this.form) {
+ var settings = this.settings || Drupal.settings;
+ Drupal.attachBehaviors(this.form, settings);
+ }
+};
+
+/**
+ * Provide a series of commands that the server can request the client perform.
+ */
+Drupal.ajax.prototype.commands = {
+ /**
+ * Command to insert new content into the DOM.
+ */
+ insert: function (ajax, response, status) {
+ // Get information from the response. If it is not there, default to
+ // our presets.
+ var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
+ var method = response.method || ajax.method;
+ var effect = ajax.getEffect(response);
+
+ // We don't know what response.data contains: it might be a string of text
+ // without HTML, so don't rely on jQuery correctly iterpreting
+ // $(response.data) as new HTML rather than a CSS selector. Also, if
+ // response.data contains top-level text nodes, they get lost with either
+ // $(response.data) or $('').replaceWith(response.data).
+ var new_content_wrapped = $('').html(response.data);
+ var new_content = new_content_wrapped.contents();
+
+ // For legacy reasons, the effects processing code assumes that new_content
+ // consists of a single top-level element. Also, it has not been
+ // sufficiently tested whether attachBehaviors() can be successfully called
+ // with a context object that includes top-level text nodes. However, to
+ // give developers full control of the HTML appearing in the page, and to
+ // enable Ajax content to be inserted in places where DIV elements are not
+ // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new
+ // content satisfies the requirement of a single top-level element, and
+ // only use the container DIV created above when it doesn't. For more
+ // information, please see http://drupal.org/node/736066.
+ if (new_content.length != 1 || new_content.get(0).nodeType != 1) {
+ new_content = new_content_wrapped;
+ }
+
+ // If removing content from the wrapper, detach behaviors first.
+ switch (method) {
+ case 'html':
+ case 'replaceWith':
+ case 'replaceAll':
+ case 'empty':
+ case 'remove':
+ var settings = response.settings || ajax.settings || Drupal.settings;
+ Drupal.detachBehaviors(wrapper, settings);
+ }
+
+ // Add the new content to the page.
+ wrapper[method](new_content);
+
+ // Immediately hide the new content if we're using any effects.
+ if (effect.showEffect != 'show') {
+ new_content.hide();
+ }
+
+ // Determine which effect to use and what content will receive the
+ // effect, then show the new content.
+ if ($('.ajax-new-content', new_content).length > 0) {
+ $('.ajax-new-content', new_content).hide();
+ new_content.show();
+ $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed);
+ }
+ else if (effect.showEffect != 'show') {
+ new_content[effect.showEffect](effect.showSpeed);
+ }
+
+ // Attach all JavaScript behaviors to the new content, if it was successfully
+ // added to the page, this if statement allows #ajax['wrapper'] to be
+ // optional.
+ if (new_content.parents('html').length > 0) {
+ // Apply any settings from the returned JSON if available.
+ var settings = response.settings || ajax.settings || Drupal.settings;
+ Drupal.attachBehaviors(new_content, settings);
+ }
+ },
+
+ /**
+ * Command to remove a chunk from the page.
+ */
+ remove: function (ajax, response, status) {
+ var settings = response.settings || ajax.settings || Drupal.settings;
+ Drupal.detachBehaviors($(response.selector), settings);
+ $(response.selector).remove();
+ },
+
+ /**
+ * Command to mark a chunk changed.
+ */
+ changed: function (ajax, response, status) {
+ if (!$(response.selector).hasClass('ajax-changed')) {
+ $(response.selector).addClass('ajax-changed');
+ if (response.asterisk) {
+ $(response.selector).find(response.asterisk).append(' * ');
+ }
+ }
+ },
+
+ /**
+ * Command to provide an alert.
+ */
+ alert: function (ajax, response, status) {
+ alert(response.text, response.title);
+ },
+
+ /**
+ * Command to provide the jQuery css() function.
+ */
+ css: function (ajax, response, status) {
+ $(response.selector).css(response.argument);
+ },
+
+ /**
+ * Command to set the settings that will be used for other commands in this response.
+ */
+ settings: function (ajax, response, status) {
+ if (response.merge) {
+ $.extend(true, Drupal.settings, response.settings);
+ }
+ else {
+ ajax.settings = response.settings;
+ }
+ },
+
+ /**
+ * Command to attach data using jQuery's data API.
+ */
+ data: function (ajax, response, status) {
+ $(response.selector).data(response.name, response.value);
+ },
+
+ /**
+ * Command to apply a jQuery method.
+ */
+ invoke: function (ajax, response, status) {
+ var $element = $(response.selector);
+ $element[response.method].apply($element, response.arguments);
+ },
+
+ /**
+ * Command to restripe a table.
+ */
+ restripe: function (ajax, response, status) {
+ // :even and :odd are reversed because jQuery counts from 0 and
+ // we count from 1, so we're out of sync.
+ // Match immediate children of the parent element to allow nesting.
+ $('> tbody > tr:visible, > tr:visible', $(response.selector))
+ .removeClass('odd even')
+ .filter(':even').addClass('odd').end()
+ .filter(':odd').addClass('even');
+ },
+
+ /**
+ * Command to add css.
+ *
+ * Uses the proprietary addImport method if available as browsers which
+ * support that method ignore @import statements in dynamically added
+ * stylesheets.
+ */
+ add_css: function (ajax, response, status) {
+ // Add the styles in the normal way.
+ $('head').prepend(response.data);
+ // Add imports in the styles using the addImport method if available.
+ var match, importMatch = /^@import url\("(.*)"\);$/igm;
+ if (document.styleSheets[0].addImport && importMatch.test(response.data)) {
+ importMatch.lastIndex = 0;
+ while (match = importMatch.exec(response.data)) {
+ document.styleSheets[0].addImport(match[1]);
+ }
+ }
+ },
+
+ /**
+ * Command to update a form's build ID.
+ */
+ updateBuildId: function(ajax, response, status) {
+ $('input[name="form_build_id"][value="' + response['old'] + '"]').val(response['new']);
+ }
+};
+
+})(jQuery);
+
+;/*})'"*/
+;/*})'"*/
+(function (D) {
+ var beforeSerialize = D.ajax.prototype.beforeSerialize;
+ D.ajax.prototype.beforeSerialize = function (element, options) {
+ beforeSerialize.call(this, element, options);
+ options.data['ajax_page_state[jquery_version]'] = D.settings.ajaxPageState.jquery_version;
+ }
+})(Drupal);
+
+;/*})'"*/
+;/*})'"*/