From ac701696b34866e4ea4b032c38066bcf01e33a1f Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 26 Feb 2018 11:42:57 +0100 Subject: [PATCH] FEATURE: replaces tag-chooser/tag-group-chooser with select-kit component These component were also the last using select2. As a consequence select2 is removed from Discourse in this commit. --- .../discourse/components/tag-chooser.js.es6 | 142 - .../components/tag-group-chooser.js.es6 | 86 - .../controllers/preferences/categories.js.es6 | 4 +- .../controllers/preferences/tags.js.es6 | 7 +- .../discourse/templates/bulk-tag.hbs | 2 +- .../components/edit-category-tags.hbs | 8 +- .../templates/components/queued-post.hbs | 2 +- .../components/search-advanced-options.hbs | 7 +- .../discourse/templates/preferences/tags.hbs | 32 +- .../discourse/templates/tag-groups-show.hbs | 11 +- .../javascripts/discourse/templates/topic.hbs | 2 +- .../components/mini-tag-chooser.js.es6 | 138 +- .../select-kit/components/multi-select.js.es6 | 47 +- .../multi-select/selected-name.js.es6 | 28 +- .../select-kit/components/select-kit.js.es6 | 46 +- .../select-kit/select-kit-filter.js.es6 | 11 +- .../components/single-select.js.es6 | 22 +- .../select-kit/components/tag-chooser.js.es6 | 107 + .../components/tag-group-chooser.js.es6 | 79 + .../javascripts/select-kit/mixins/tags.js.es6 | 52 + .../templates/components/multi-select.hbs | 50 + .../multi-select/multi-select-header.hbs | 7 +- .../multi-select/selected-category.hbs | 4 - .../components/multi-select/selected-name.hbs | 10 - .../select-kit/select-kit-collection.hbs | 2 +- .../select-kit/select-kit-filter.hbs | 2 +- .../{select-kit.hbs => single-select.hbs} | 8 +- app/assets/javascripts/vendor.js | 1 - app/assets/javascripts/wizard-vendor.js | 1 - app/assets/stylesheets/common.scss | 1 - .../stylesheets/common/admin/admin_base.scss | 20 +- .../stylesheets/common/admin/customize.scss | 2 +- .../stylesheets/common/base/compose.scss | 4 - app/assets/stylesheets/common/base/modal.scss | 4 + .../stylesheets/common/base/search.scss | 14 +- .../stylesheets/common/base/tagging.scss | 49 +- app/assets/stylesheets/common/base/topic.scss | 10 +- app/assets/stylesheets/common/base/user.scss | 19 +- .../common/select-kit/legacy-combo-box.scss | 88 - .../common/select-kit/mini-tag-chooser.scss | 3 +- .../common/select-kit/multi-select.scss | 86 +- .../common/select-kit/select-kit.scss | 1 + .../common/select-kit/tag-chooser.scss | 14 + app/assets/stylesheets/desktop/modal.scss | 8 - .../stylesheets/desktop/queued-posts.scss | 9 + app/assets/stylesheets/desktop/topic.scss | 5 +- app/assets/stylesheets/desktop/user.scss | 8 +- app/assets/stylesheets/mobile/topic.scss | 5 - app/assets/stylesheets/vendor/select2.scss | 575 --- app/assets/stylesheets/wizard.scss | 5 - .../finish_installation/register.html.erb | 6 - .../acceptance/search-full-test.js.es6 | 24 - vendor/assets/javascripts/select2.js | 3729 ----------------- 53 files changed, 637 insertions(+), 4970 deletions(-) delete mode 100644 app/assets/javascripts/discourse/components/tag-chooser.js.es6 delete mode 100644 app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 create mode 100644 app/assets/javascripts/select-kit/components/tag-chooser.js.es6 create mode 100644 app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 create mode 100644 app/assets/javascripts/select-kit/mixins/tags.js.es6 create mode 100644 app/assets/javascripts/select-kit/templates/components/multi-select.hbs rename app/assets/javascripts/select-kit/templates/components/{select-kit.hbs => single-select.hbs} (86%) delete mode 100644 app/assets/stylesheets/common/select-kit/legacy-combo-box.scss create mode 100644 app/assets/stylesheets/common/select-kit/tag-chooser.scss delete mode 100644 app/assets/stylesheets/vendor/select2.scss delete mode 100644 vendor/assets/javascripts/select2.js diff --git a/app/assets/javascripts/discourse/components/tag-chooser.js.es6 b/app/assets/javascripts/discourse/components/tag-chooser.js.es6 deleted file mode 100644 index 9f962596e44..00000000000 --- a/app/assets/javascripts/discourse/components/tag-chooser.js.es6 +++ /dev/null @@ -1,142 +0,0 @@ -import renderTag from 'discourse/lib/render-tag'; - -function formatTag(t) { - return renderTag(t.id, {count: t.count, noHref: true}); -} - -export default Ember.TextField.extend({ - classNameBindings: [':tag-chooser'], - attributeBindings: ['tabIndex', 'placeholderKey', 'categoryId'], - - init() { - this._super(); - const tags = this.get('tags') || []; - this.set('value', tags.join(", ")); - - if (this.get('allowCreate') !== false) { - this.set('allowCreate', this.site.get('can_create_tag')); - } - - this.set('termMatchesForbidden', false); - }, - - _valueChanged: function() { - const tags = this.get('value').split(',').map(v => v.trim()).reject(v => v.length === 0).uniq(); - this.set('tags', tags); - }.observes('value'), - - _tagsChanged: function() { - const $tagChooser = this.$(), - val = this.get('value'); - - if ($tagChooser && val !== this.get('tags')) { - if (this.get('tags')) { - const data = this.get('tags').map((t) => {return {id: t, text: t};}); - $tagChooser.select2('data', data); - } else { - $tagChooser.select2('data', []); - } - } - }.observes('tags'), - - didInsertElement() { - this._super(); - - const self = this; - const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); - - let limit = this.siteSettings.max_tags_per_topic; - - if (this.get('unlimitedTagCount')) { - limit = null; - } else if (this.get('limit')) { - limit = parseInt(this.get('limit')); - } - - this.$().select2({ - tags: true, - placeholder: this.get('placeholder') === "" ? "" : I18n.t(this.get('placeholderKey') || 'tagging.choose_for_topic'), - maximumInputLength: this.siteSettings.max_tag_length, - maximumSelectionSize: limit, - width: this.get('width') || 'resolve', - initSelection(element, callback) { - const data = []; - - function splitVal(string, separator) { - var val, i, l; - if (string === null || string.length < 1) return []; - val = string.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); - return val; - } - - $(splitVal(element.val(), ",")).each(function () { - data.push({ - id: this, - text: this - }); - }); - - callback(data); - }, - createSearchChoice(term, data) { - term = term.replace(filterRegexp, '').trim().toLowerCase(); - - // No empty terms, make sure the user has permission to create the tag - if (!term.length || !self.get('allowCreate') || self.get('termMatchesForbidden')) return; - - if ($(data).filter(function() { - return this.text.localeCompare(term) === 0; - }).length === 0) { - return { id: term, text: term }; - } - }, - createSearchChoicePosition(list, item) { - // Search term goes on the bottom - list.push(item); - }, - formatSelection(data) { - return data ? renderTag(this.text(data), {noHref: true}) : undefined; - }, - formatSelectionCssClass() { - return "discourse-tag-select2"; - }, - formatResult: formatTag, - multiple: true, - ajax: { - quietMillis: 200, - cache: true, - url: Discourse.getURL("/tags/filter/search"), - dataType: 'json', - data: function (term) { - const selectedTags = self.get('tags'); - const d = { - q: term, - limit: self.siteSettings.max_tag_search_results, - categoryId: self.get('categoryId') - }; - if (selectedTags) { - d.selected_tags = selectedTags.slice(0,100); - } - if (!self.get('everyTag')) { - d.filterForInput = true; - } - return d; - }, - results: function (data) { - if (self.siteSettings.tags_sort_alphabetically) { - data.results = data.results.sort(function(a,b) { return a.id > b.id; }); - } - self.set('termMatchesForbidden', data.forbidden ? true : false); - return data; - } - }, - }); - }, - - willDestroyElement() { - this._super(); - this.$().select2('destroy'); - } - -}); diff --git a/app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 b/app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 deleted file mode 100644 index efe35db5bb5..00000000000 --- a/app/assets/javascripts/discourse/components/tag-group-chooser.js.es6 +++ /dev/null @@ -1,86 +0,0 @@ -function renderTagGroup(tag) { - return "" + Handlebars.Utils.escapeExpression(tag.text ? tag.text : tag) + ""; -}; - -export default Ember.TextField.extend({ - classNameBindings: [':tag-chooser'], - attributeBindings: ['tabIndex', 'placeholderKey', 'categoryId'], - - _initValue: function() { - const names = this.get('tagGroups') || []; - this.set('value', names.join(",")); - }.on('init'), - - _valueChanged: function() { - const names = this.get('value').split(',').map(v => v.trim()).reject(v => v.length === 0).uniq(); - if ( this.get('tagGroups').join(',') !== this.get('value') ) { - this.set('tagGroups', names); - } - }.observes('value'), - - _tagGroupsChanged: function() { - const $chooser = this.$(), - val = this.get('value'); - - if ($chooser && val !== this.get('tagGroups')) { - if (this.get('tagGroups')) { - const data = this.get('tagGroups').map((t) => {return {id: t, text: t};}); - $chooser.select2('data', data); - } else { - $chooser.select2('data', []); - } - } - }.observes('tagGroups'), - - _initializeChooser: function() { - const self = this; - - this.$().select2({ - tags: true, - placeholder: this.get('placeholderKey') ? I18n.t(this.get('placeholderKey')) : null, - initSelection(element, callback) { - const data = []; - - function splitVal(string, separator) { - var val, i, l; - if (string === null || string.length < 1) return []; - val = string.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); - return val; - } - - $(splitVal(element.val(), ",")).each(function () { - data.push({ id: this, text: this }); - }); - - callback(data); - }, - formatSelection: function (data) { - return data ? renderTagGroup(this.text(data)) : undefined; - }, - formatSelectionCssClass: function(){ - return "discourse-tag-select2"; - }, - formatResult: renderTagGroup, - multiple: true, - ajax: { - quietMillis: 200, - cache: true, - url: Discourse.getURL("/tag_groups/filter/search"), - dataType: 'json', - data: function (term) { - return { q: term, limit: self.siteSettings.max_tag_search_results }; - }, - results: function (data) { - data.results = data.results.sort(function(a,b) { return a.text > b.text; }); - return data; - } - }, - }); - }.on('didInsertElement'), - - _destroyChooser: function() { - this.$().select2('destroy'); - }.on('willDestroyElement') - -}); diff --git a/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 index f394acd3e1e..6e27ea04e07 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/categories.js.es6 @@ -1,6 +1,6 @@ import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { popupAjaxError } from 'discourse/lib/ajax-error'; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import computed from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend(PreferencesTabController, { saveAttrNames: [ @@ -12,7 +12,7 @@ export default Ember.Controller.extend(PreferencesTabController, { @computed("model.watchedCategories", "model.watchedFirstPostCategories", "model.trackedCategories", "model.mutedCategories") selectedCategories(watched, watchedFirst, tracked, muted) { - return [].concat(watched, watchedFirst, tracked, muted); + return [].concat(watched, watchedFirst, tracked, muted).filter(t => t); }, canSave: function() { diff --git a/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 index 6264d42b2de..b7e0ab97cf4 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences/tags.js.es6 @@ -1,8 +1,8 @@ import PreferencesTabController from "discourse/mixins/preferences-tab-controller"; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import computed from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend(PreferencesTabController, { - saveAttrNames: [ 'muted_tags', 'tracked_tags', @@ -10,6 +10,11 @@ export default Ember.Controller.extend(PreferencesTabController, { 'watching_first_post_tags' ], + @computed("model.watched_tags", "model.watching_first_post_tags", "model.tracked_tags", "model.muted_tags") + selectedTags(watched, watchedFirst, tracked, muted) { + return [].concat(watched, watchedFirst, tracked, muted).filter(t => t); + }, + actions: { save() { this.set('saved', false); diff --git a/app/assets/javascripts/discourse/templates/bulk-tag.hbs b/app/assets/javascripts/discourse/templates/bulk-tag.hbs index 4ed48ddc2b6..3a54edf758d 100644 --- a/app/assets/javascripts/discourse/templates/bulk-tag.hbs +++ b/app/assets/javascripts/discourse/templates/bulk-tag.hbs @@ -1,5 +1,5 @@

{{i18n (concat "topics.bulk." title)}}

-

{{tag-chooser tags=tags categoryId=categoryId}}

+

{{tag-chooser filterPlaceholder=null tags=tags categoryId=categoryId}}

{{d-button action=action disabled=emptyTags label=(concat "topics.bulk." label)}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs index df6544d770b..c8ea0536360 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs @@ -1,7 +1,11 @@

{{i18n 'category.tags_allowed_tags'}}

- {{tag-chooser placeholderKey="category.tags_placeholder" tags=category.allowed_tags everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + filterPlaceholder="category.tags_placeholder" + tags=category.allowed_tags + everyTag=true + unlimitedTagCount=true}}

{{i18n 'category.tags_allowed_tag_groups'}}

- {{tag-group-chooser placeholderKey="category.tag_groups_placeholder" tagGroups=category.allowed_tag_groups}} + {{tag-group-chooser tagGroups=category.allowed_tag_groups}}
diff --git a/app/assets/javascripts/discourse/templates/components/queued-post.hbs b/app/assets/javascripts/discourse/templates/components/queued-post.hbs index de2709ca4ab..99162937d14 100644 --- a/app/assets/javascripts/discourse/templates/components/queued-post.hbs +++ b/app/assets/javascripts/discourse/templates/components/queued-post.hbs @@ -52,7 +52,7 @@ {{/each}} {{else if editTags}} - {{tag-chooser tags=buffered.tags categoryId=buffered.category_id width='100%'}} + {{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs index f53a0e0c50b..3ce9b3d84f9 100644 --- a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs +++ b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs @@ -40,7 +40,12 @@
- {{tag-chooser tags=searchedTerms.tags blacklist=searchedTerms.tags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true" width="70%"}} + {{tag-chooser + tags=searchedTerms.tags + allowCreate=false + filterPlaceholder=null + everyTag=true + unlimitedTagCount=true}}
diff --git a/app/assets/javascripts/discourse/templates/preferences/tags.hbs b/app/assets/javascripts/discourse/templates/preferences/tags.hbs index 1c24147a2f6..af170058de8 100644 --- a/app/assets/javascripts/discourse/templates/preferences/tags.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/tags.hbs @@ -5,25 +5,49 @@
- {{tag-chooser tags=model.watched_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.watched_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.watched_tags_instructions'}}
- {{tag-chooser tags=model.tracked_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.tracked_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.tracked_tags_instructions'}}
- {{tag-chooser tags=model.watching_first_post_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.watching_first_post_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.watched_first_post_tags_instructions'}}
- {{tag-chooser tags=model.muted_tags blacklist=selectedTags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.muted_tags + blacklist=selectedTags + filterPlaceholder=null + allowCreate=false + everyTag=true + unlimitedTagCount=true}}
{{i18n 'user.muted_tags_instructions'}}
diff --git a/app/assets/javascripts/discourse/templates/tag-groups-show.hbs b/app/assets/javascripts/discourse/templates/tag-groups-show.hbs index 26fde8830f4..dec3a0e3e9a 100644 --- a/app/assets/javascripts/discourse/templates/tag-groups-show.hbs +++ b/app/assets/javascripts/discourse/templates/tag-groups-show.hbs @@ -3,12 +3,19 @@

- {{tag-chooser tags=model.tag_names everyTag="true" unlimitedTagCount="true"}} + {{tag-chooser + tags=model.tag_names + everyTag=true + unlimitedTagCount=true}}
- {{tag-chooser tags=model.parent_tag_name everyTag="true" limit="1" placeholderKey="tagging.groups.parent_tag_placeholder"}} + {{tag-chooser + tags=model.parent_tag_name + everyTag=true + limit=1 + filterPlaceholder="tagging.groups.parent_tag_placeholder"}} {{i18n 'tagging.groups.parent_tag_description'}}
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 167ffcf138b..810679b3741 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -22,7 +22,7 @@ {{/if}} {{#if canEditTags}} - {{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} + {{mini-tag-chooser tags=buffered.tags categoryId=buffered.category_id}} {{/if}} {{plugin-outlet name="edit-topic" args=(hash model=model buffered=buffered)}} diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 index d28f7a1caae..1a9d4028901 100644 --- a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 @@ -1,22 +1,22 @@ import ComboBox from "select-kit/components/combo-box"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from 'discourse/lib/ajax-error'; +import Tags from "select-kit/mixins/tags"; import { default as computed } from "ember-addons/ember-computed-decorators"; import renderTag from "discourse/lib/render-tag"; -const { get, isEmpty, isPresent, run, makeArray } = Ember; +const { get, isEmpty, run, makeArray } = Ember; -export default ComboBox.extend({ +export default ComboBox.extend(Tags, { allowContentReplacement: true, pluginApiIdentifiers: ["mini-tag-chooser"], + attributeBindings: ["categoryId"], classNames: ["mini-tag-chooser"], classNameBindings: ["noTags"], verticalOffset: 3, filterable: true, - noTags: Ember.computed.empty("computedTags"), + noTags: Ember.computed.empty("selectedTags"), allowAny: true, - maximumSelectionSize: Ember.computed.alias("siteSettings.max_tags_per_topic"), caretUpIcon: Ember.computed.alias("caretIcon"), caretDownIcon: Ember.computed.alias("caretIcon"), + isAsync: true, init() { this._super(); @@ -30,17 +30,8 @@ export default ComboBox.extend({ noHref: true }); }); - }, - @computed("limitReached", "maximumSelectionSize") - maxContentRow(limitReached, count) { - if (limitReached) { - return I18n.t("select_kit.max_content_reached", { count }); - } - }, - - mutateAttributes() { - this.set("value", null); + this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); }, @computed("limitReached") @@ -48,9 +39,9 @@ export default ComboBox.extend({ return limitReached ? null : "plus"; }, - @computed("computedTags.[]", "maximumSelectionSize") - limitReached(computedTags, maximumSelectionSize) { - if (computedTags.length >= maximumSelectionSize) { + @computed("selectedTags.[]", "limit") + limitReached(selectedTags, limit) { + if (selectedTags.length >= limit) { return true; } @@ -58,33 +49,10 @@ export default ComboBox.extend({ }, @computed("tags") - computedTags(tags) { + selectedTags(tags) { return makeArray(tags); }, - validateCreate(term) { - if (this.get("limitReached") || !this.site.get("can_create_tag")) { - return false; - } - - const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); - term = term.replace(filterRegexp, "").trim().toLowerCase(); - - if (!term.length || this.get("termMatchesForbidden")) { - return false; - } - - if (this.get("siteSettings.max_tag_length") < term.length) { - return false; - } - - return true; - }, - - validateSelect() { - return this.get("computedTags").length < this.get("siteSettings.max_tags_per_topic"); - }, - filterComputedContent(computedContent) { return computedContent; }, @@ -92,7 +60,7 @@ export default ComboBox.extend({ didRender() { this._super(); - this.$().on("click.mini-tag-chooser", ".selected-tag", (event) => { + $(".select-kit-body").on("click.mini-tag-chooser", ".selected-tag", (event) => { event.stopImmediatePropagation(); this.send("removeTag", $(event.target).attr("data-value")); }); @@ -102,9 +70,6 @@ export default ComboBox.extend({ this._super(); $(".select-kit-body").off("click.mini-tag-chooser"); - - const searchDebounce = this.get("searchDebounce"); - if (isPresent(searchDebounce)) { run.cancel(searchDebounce); } }, didPressEscape(event) { @@ -167,9 +132,9 @@ export default ComboBox.extend({ computeHeaderContent() { let content = this.baseHeaderComputedContent(); - const joinedTags = this.get("computedTags").join(", "); + const joinedTags = this.get("selectedTags").join(", "); - if (isEmpty(this.get("computedTags"))) { + if (isEmpty(this.get("selectedTags"))) { content.label = I18n.t("tagging.choose_for_topic"); } else { content.label = joinedTags; @@ -182,80 +147,61 @@ export default ComboBox.extend({ actions: { removeTag(tag) { - let tags = this.get("computedTags"); + let tags = this.get("selectedTags"); delete tags[tags.indexOf(tag)]; this.set("tags", tags.filter(t => t)); - this.set("content", []); - this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); }, onExpand() { - if (isEmpty(this.get("content"))) { - this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + if (isEmpty(this.get("collectionComputedContent"))) { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); } }, onFilter(filter) { filter = isEmpty(filter) ? null : filter; - this.set("searchDebounce", run.debounce(this, this._searchTags, filter, 250)); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); }, onSelect(tag) { - if (isEmpty(this.get("computedTags"))) { + if (isEmpty(this.get("selectedTags"))) { this.set("tags", makeArray(tag)); } else { - this.set("tags", this.get("computedTags").concat(tag)); + this.set("tags", this.get("selectedTags").concat(tag)); } - this.set("content", []); - this.set("searchDebounce", run.debounce(this, this._searchTags, this.get("filter"), 250)); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); + + this.autoHighlight(); } }, - _searchTags(query) { - this.startLoading(); - - const self = this; - const selectedTags = makeArray(this.get("computedTags")).filter(t => t); - const sortTags = this.siteSettings.tags_sort_alphabetically; + prepareSearch(query) { const data = { q: query, - limit: this.siteSettings.max_tag_search_results, + limit: this.get("siteSettings.max_tag_search_results"), categoryId: this.get("categoryId") }; + if (this.get("selectedTags")) data.selected_tags = this.get("selectedTags").slice(0, 100); + if (!this.get("everyTag")) data.filterForInput = true; - if (selectedTags) { - data.selected_tags = selectedTags.slice(0, 100); + this.searchTags("/tags/filter/search", data, this._transformJson); + }, + + _transformJson(context, json) { + let results = json.results; + + context.set("termMatchesForbidden", json.forbidden ? true : false); + + if (context.get("siteSettings.tags_sort_alphabetically")) { + results = results.sort((a, b) => a.id > b.id); } - ajax(Discourse.getURL("/tags/filter/search"), { - quietMillis: 200, - cache: true, - dataType: "json", - data, - }).then(json => { - let results = json.results; + results = results.filter(r => !context.get("selectedTags").includes(r.id)); - self.set("termMatchesForbidden", json.forbidden ? true : false); - - if (sortTags) { - results = results.sort((a, b) => a.id > b.id); - } - - const content = results.map((result) => { - return { - id: result.text, - name: result.text, - count: result.count - }; - }).filter(c => !selectedTags.includes(c.id)); - - self.set("content", content); - self.stopLoading(); - this.autoHighlight(); - }).catch(error => { - self.stopLoading(); - popupAjaxError(error); - }); + return results.map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); } }); diff --git a/app/assets/javascripts/select-kit/components/multi-select.js.es6 b/app/assets/javascripts/select-kit/components/multi-select.js.es6 index e3e0ad9a132..b74b371c59c 100644 --- a/app/assets/javascripts/select-kit/components/multi-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select.js.es6 @@ -8,14 +8,15 @@ import { export default SelectKitComponent.extend({ pluginApiIdentifiers: ["multi-select"], + layoutName: "select-kit/templates/components/multi-select", classNames: "multi-select", headerComponent: "multi-select/multi-select-header", - filterComponent: null, headerText: "select_kit.default_header_text", allowAny: true, allowInitialValueMutation: false, autoFilterable: true, selectedNameComponent: "multi-select/selected-name", + filterIcon: null, init() { this._super(); @@ -38,16 +39,22 @@ export default SelectKitComponent.extend({ _compute() { Ember.run.scheduleOnce("afterRender", () => { this.willComputeAttributes(); - let content = this.willComputeContent(this.get("content") || []); + let content = this.get("content") || []; + let asyncContent = this.get("asyncContent") || []; + content = this.willComputeContent(content); + asyncContent = this.willComputeAsyncContent(asyncContent); let values = this._beforeWillComputeValues(this.get("values")); content = this.computeContent(content); + asyncContent = this.computeAsyncContent(asyncContent); content = this._beforeDidComputeContent(content); + asyncContent = this._beforeDidComputeAsyncContent(asyncContent); values = this.willComputeValues(values); values = this.computeValues(values); values = this._beforeDidComputeValues(values); this._setHeaderComputedContent(); this._setCollectionHeaderComputedContent(); this.didComputeContent(content); + this.didComputeAsyncContent(asyncContent); this.didComputeValues(values); this.didComputeAttributes(); }); @@ -102,6 +109,19 @@ export default SelectKitComponent.extend({ }); }, + @computed("computedAsyncContent.[]", "computedValues.[]") + filteredAsyncComputedContent(computedAsyncContent, computedValues) { + computedAsyncContent = computedAsyncContent.filter(c => { + return !computedValues.includes(get(c, "value")); + }); + + if (this.get("limitMatches")) { + return computedAsyncContent.slice(0, this.get("limitMatches")); + } + + return computedAsyncContent; + }, + @computed("computedContent.[]", "computedValues.[]", "filter") filteredComputedContent(computedContent, computedValues, filter) { computedContent = computedContent.filter(c => { @@ -133,6 +153,16 @@ export default SelectKitComponent.extend({ }; }, + @computed("limit", "computedValues.[]") + limitReached(limit, computedValues) { + if (!limit) return false; + return computedValues.length >= limit; + }, + + validateSelect() { + return this._super() && !this.get("limitReached"); + }, + didPressBackspace(event) { this.expand(event); this.keyDown(event); @@ -178,16 +208,16 @@ export default SelectKitComponent.extend({ if ($lastSelectedValue.length === 0) { return; } if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) { - $lastSelectedValue.click(); + $lastSelectedValue.trigger("backspace"); return false; } if ($filterInput.val() === "") { if ($filterInput.is(":focus")) { - if ($lastSelectedValue.length > 0) { $lastSelectedValue.click(); } + if ($lastSelectedValue.length > 0) { $lastSelectedValue.trigger("backspace"); } } else { if ($lastSelectedValue.length > 0) { - $lastSelectedValue.click(); + $lastSelectedValue.trigger("backspace"); } else { $filterInput.focus(); } @@ -217,14 +247,14 @@ export default SelectKitComponent.extend({ if (!this.get("renderedBodyOnce")) return; if (!isNone(this.get("highlightedValue"))) return; - if (isEmpty(this.get("filteredComputedContent"))) { + if (isEmpty(this.get("collectionComputedContent"))) { if (this.get("createRowComputedContent")) { this.send("highlight", this.get("createRowComputedContent")); } else if (this.get("noneRowComputedContent") && this.get("hasSelection")) { this.send("highlight", this.get("noneRowComputedContent")); } } else { - this.send("highlight", this.get("filteredComputedContent.firstObject")); + this.send("highlight", this.get("collectionComputedContent.firstObject")); } }); }, @@ -247,9 +277,10 @@ export default SelectKitComponent.extend({ this.set("highlightedValue", null); }, - didDeselect() { + didDeselect(rowComputedContentItems) { this.focusFilterOrHeader(); this.autoHighlight(); + this._boundaryActionHandler("onDeselect", rowComputedContentItems); }, actions: { diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 index a6b672d50ed..eff2b9716d4 100644 --- a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 +++ b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 @@ -28,6 +28,20 @@ export default Ember.Component.extend({ return null; }, + didInsertElement() { + this._super(); + + $(this.element).on("backspace.selected-name", () => { + this._handleBackspace(); + }); + }, + + willDestroyElement() { + this._super(); + + $(this.element).off("backspace.selected-name"); + }, + label: Ember.computed.or("computedContent.label", "title", "name"), name: Ember.computed.alias("computedContent.name"), @@ -39,8 +53,18 @@ export default Ember.Component.extend({ }), click() { - if (this.get("isLocked") === true) { return false; } - this.toggleProperty("isHighlighted"); + if (this.get("isLocked") === true) return false; + this.sendAction("deselect", [this.get("computedContent")]); return false; + }, + + _handleBackspace() { + if (this.get("isLocked") === true) return false; + + if (this.get("isHighlighted")) { + this.sendAction("deselect", [this.get("computedContent")]); + } else { + this.set("isHighlighted", true); + } } }); diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6 index b8bdf5c6674..b8418805085 100644 --- a/app/assets/javascripts/select-kit/components/select-kit.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6 @@ -32,6 +32,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi isFocused: false, isHidden: false, isLoading: false, + isAsync: false, renderedBodyOnce: false, renderedFilterOnce: false, tabindex: 0, @@ -69,8 +70,6 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi allowContentReplacement: false, collectionHeader: null, allowAutoSelectFirst: true, - maximumSelectionSize: null, - maxContentRow: null, init() { this._super(); @@ -91,11 +90,16 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi if (this.get("allowContentReplacement")) { this.addObserver(`content.[]`, this, this._compute); } + + if (this.get("isAsync")) { + this.addObserver(`asyncContent.[]`, this, this._compute); + } }, willDestroyElement() { this.removeObserver(`content.@each.${this.get("nameProperty")}`, this, this._compute); this.removeObserver(`content.[]`, this, this._compute); + this.removeObserver(`asyncContent.[]`, this, this._compute); }, willComputeAttributes() {}, @@ -114,6 +118,17 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi }, didComputeContent() {}, + willComputeAsyncContent(content) { return content; }, + computeAsyncContent(content) { return content; }, + _beforeDidComputeAsyncContent(content) { + content = applyContentPluginApiCallbacks(this.get("pluginApiIdentifiers"), content, this); + this.setProperties({ + computedAsyncContent: content.map(c => this.computeAsyncContentItem(c)) + }); + return content; + }, + didComputeAsyncContent() {}, + computeHeaderContent() { return this.baseHeaderComputedContent(); }, @@ -122,6 +137,15 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return this.baseComputedContentItem(contentItem, options); }, + computeAsyncContentItem(contentItem, options) { + return this.computeContentItem(contentItem, options); + }, + + @computed("isAsync", "filteredAsyncComputedContent", "filteredComputedContent") + collectionComputedContent(isAsync, filteredAsyncComputedContent, filteredComputedContent) { + return isAsync ? filteredAsyncComputedContent : filteredComputedContent; + }, + validateCreate() { return true; }, validateSelect() { return true; }, @@ -155,13 +179,20 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return false; }, - @computed("filter", "filteredComputedContent.[]") - noContentRow(filter, filteredComputedContent) { - if (filter.length > 0 && filteredComputedContent.length === 0) { + @computed("filter", "collectionComputedContent.[]") + noContentRow(filter, collectionComputedContent) { + if (filter.length > 0 && collectionComputedContent.length === 0) { return I18n.t("select_kit.no_content"); } }, + @computed("limitReached", "limit") + maxContentRow(limitReached, limit) { + if (limitReached) { + return I18n.t("select_kit.max_content_reached", { count: limit }); + } + }, + @computed("filter", "filterable", "autoFilterable", "renderedFilterOnce") shouldFilter(filter, filterable, autoFilterable, renderedFilterOnce) { if (renderedFilterOnce && filterable) return true; @@ -170,8 +201,9 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi return false; }, - @computed("filter", "computedContent") - shouldDisplayCreateRow(filter, computedContent) { + @computed("filter", "computedContent", "limitReached") + shouldDisplayCreateRow(filter, computedContent, limitReached) { + if (limitReached) return false; if (computedContent.map(c => c.value).includes(filter)) return false; if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true; return false; diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 index fbfed55bc3d..60e7ec98199 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-filter.js.es6 @@ -1,6 +1,15 @@ +import computed from "ember-addons/ember-computed-decorators"; +const { isEmpty } = Ember; + export default Ember.Component.extend({ layoutName: "select-kit/templates/components/select-kit/select-kit-filter", classNames: ["select-kit-filter"], classNameBindings: ["isFocused", "isHidden"], - isHidden: Ember.computed.not("shouldDisplayFilter") + isHidden: Ember.computed.not("shouldDisplayFilter"), + + @computed("placeholder", "hasSelection") + computedPlaceholder(placeholder, hasSelection) { + if (hasSelection) return ""; + return isEmpty(placeholder) ? "" : I18n.t(placeholder); + } }); diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6 index d442a9e2ae7..e859effe406 100644 --- a/app/assets/javascripts/select-kit/components/single-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/single-select.js.es6 @@ -4,6 +4,7 @@ const { get, isNone, isEmpty, isPresent, run } = Ember; export default SelectKitComponent.extend({ pluginApiIdentifiers: ["single-select"], + layoutName: "select-kit/templates/components/single-select", classNames: "single-select", computedValue: null, value: null, @@ -13,14 +14,20 @@ export default SelectKitComponent.extend({ _compute() { run.scheduleOnce("afterRender", () => { this.willComputeAttributes(); - let content = this.willComputeContent(this.get("content") || []); + let content = this.get("content") || []; + let asyncContent = this.get("asyncContent") || []; + content = this.willComputeContent(content); + asyncContent = this.willComputeAsyncContent(asyncContent); let value = this._beforeWillComputeValue(this.get("value")); content = this.computeContent(content); + asyncContent = this.computeAsyncContent(asyncContent); content = this._beforeDidComputeContent(content); + asyncContent = this._beforeDidComputeAsyncContent(asyncContent); value = this.willComputeValue(value); value = this.computeValue(value); value = this._beforeDidComputeValue(value); this.didComputeContent(content); + this.didComputeAsyncContent(asyncContent); this.didComputeValue(value); this.didComputeAttributes(); @@ -86,6 +93,19 @@ export default SelectKitComponent.extend({ }; }, + @computed("computedAsyncContent.[]", "computedValue") + filteredAsyncComputedContent(computedAsyncContent, computedValue) { + computedAsyncContent = computedAsyncContent.filter(c => { + return computedValue !== get(c, "value"); + }); + + if (this.get("limitMatches")) { + return computedAsyncContent.slice(0, this.get("limitMatches")); + } + + return computedAsyncContent; + }, + @computed("computedContent.[]", "computedValue", "filter", "shouldFilter") filteredComputedContent(computedContent, computedValue, filter, shouldFilter) { if (shouldFilter) { diff --git a/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 new file mode 100644 index 00000000000..c79e88bab02 --- /dev/null +++ b/app/assets/javascripts/select-kit/components/tag-chooser.js.es6 @@ -0,0 +1,107 @@ +import MultiSelectComponent from "select-kit/components/multi-select"; +import Tags from "select-kit/mixins/tags"; +import renderTag from "discourse/lib/render-tag"; +import computed from "ember-addons/ember-computed-decorators"; +const { get, isEmpty, run, makeArray } = Ember; + +export default MultiSelectComponent.extend(Tags, { + pluginApiIdentifiers: ["tag-chooser"], + classNames: "tag-chooser", + isAsync: true, + filterable: true, + filterPlaceholder: "tagging.choose_for_topic", + limit: null, + attributeBindings: ["categoryId"], + allowAny: Ember.computed.alias("allowCreate"), + + init() { + this._super(); + + if (this.get("allowCreate") !== false) { + this.set("allowCreate", this.get("siteSettings.can_create_tag")); + } + + this.set("termMatchesForbidden", false); + + this.set("templateForRow", (rowComponent) => { + const tag = rowComponent.get("computedContent"); + return renderTag(get(tag, "value"), { + count: get(tag, "originalContent.count"), + noHref: true + }); + }); + + if (!this.get("unlimitedTagCount")) { + this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); + } + }, + + mutateValues(values) { + this.set("tags", values.filter(v => v)); + }, + + @computed("tags") + values(tags) { + return makeArray(tags); + }, + + @computed("tags") + content(tags) { + return makeArray(tags); + }, + + actions: { + onFilter(filter) { + this.expand(); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); + }, + + onExpand() { + if (isEmpty(this.get("collectionComputedContent"))) { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + } + }, + + onDeselect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + }, + + onSelect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); + } + }, + + prepareSearch(query) { + const selectedTags = makeArray(this.get("values")).filter(t => t); + + const data = { + q: query, + limit: this.get("siteSettings.max_tag_search_results"), + categoryId: this.get("categoryId") + }; + if (selectedTags) data.selected_tags = selectedTags.slice(0, 100); + if (!this.get("everyTag")) data.filterForInput = true; + + this.searchTags("/tags/filter/search", data, this._transformJson); + }, + + _transformJson(context, json) { + let results = json.results; + + context.set("termMatchesForbidden", json.forbidden ? true : false); + + if (context.get("blacklist")) { + results = results.filter(result => { + return !context.get("blacklist").includes(result.id); + }); + } + + if (context.get("siteSettings.tags_sort_alphabetically")) { + results = results.sort((a, b) => a.id > b.id); + } + + return results.map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); + } +}); diff --git a/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 b/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 new file mode 100644 index 00000000000..2b16037f72c --- /dev/null +++ b/app/assets/javascripts/select-kit/components/tag-group-chooser.js.es6 @@ -0,0 +1,79 @@ +import MultiSelectComponent from "select-kit/components/multi-select"; +import Tags from "select-kit/mixins/tags"; +import renderTag from "discourse/lib/render-tag"; +import computed from "ember-addons/ember-computed-decorators"; +const { get, isEmpty, run, makeArray } = Ember; + +export default MultiSelectComponent.extend(Tags, { + pluginApiIdentifiers: ["tag-group-chooser"], + classNames: ["tag-group-chooser", "tag-chooser"], + isAsync: true, + filterable: true, + filterPlaceholder: "category.tag_groups_placeholder", + limit: null, + allowAny: false, + + init() { + this._super(); + + this.set("templateForRow", (rowComponent) => { + const tag = rowComponent.get("computedContent"); + return renderTag(get(tag, "value"), { + count: get(tag, "originalContent.count"), + noHref: true + }); + }); + }, + + mutateValues(values) { + this.set("tagGroups", values.filter(v => v)); + }, + + @computed("tagGroups") + values(tagGroups) { + return makeArray(tagGroups); + }, + + @computed("tagGroups") + content(tagGroups) { + return makeArray(tagGroups); + }, + + actions: { + onFilter(filter) { + this.expand(); + this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); + }, + + onExpand() { + if (isEmpty(this.get("collectionComputedContent"))) { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + } + }, + + onDeselect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); + }, + + onSelect() { + this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); + } + }, + + prepareSearch(query) { + const data = { + q: query, + limit: this.get("siteSettings.max_tag_search_results") + }; + + this.searchTags("/tags/filter/search", data, this._transformJson); + }, + + _transformJson(context, json) { + let results = json.results.sort((a, b) => a.id > b.id); + + return results.map(result => { + return { id: result.text, name: result.text, count: result.count }; + }); + }, +}); diff --git a/app/assets/javascripts/select-kit/mixins/tags.js.es6 b/app/assets/javascripts/select-kit/mixins/tags.js.es6 new file mode 100644 index 00000000000..8d2b2da3b65 --- /dev/null +++ b/app/assets/javascripts/select-kit/mixins/tags.js.es6 @@ -0,0 +1,52 @@ +const { run } = Ember; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default Ember.Mixin.create({ + willDestroyElement() { + this._super(); + + const searchDebounce = this.get("searchDebounce"); + if (searchDebounce) run.cancel(searchDebounce); + }, + + searchTags(url, data, callback) { + const self = this; + + this.startLoading(); + + return ajax(Discourse.getURL(url), { + quietMillis: 200, + cache: true, + dataType: "json", + data + }).then(json => { + self.set("asyncContent", callback(self, json)); + }).catch(error => { + popupAjaxError(error); + }) + .finally(() => { + self.stopLoading(); + self.autoHighlight(); + }); + }, + + validateCreate(term) { + if (this.get("limitReached") || !this.site.get("can_create_tag")) { + return false; + } + + const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); + term = term.replace(filterRegexp, "").trim().toLowerCase(); + + if (!term.length || this.get("termMatchesForbidden")) { + return false; + } + + if (this.get("siteSettings.max_tag_length") < term.length) { + return false; + } + + return true; + }, +}); diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select.hbs new file mode 100644 index 00000000000..072a4bff866 --- /dev/null +++ b/app/assets/javascripts/select-kit/templates/components/multi-select.hbs @@ -0,0 +1,50 @@ +{{#component headerComponent + tabindex=tabindex + isFocused=isFocused + isExpanded=isExpanded + computedContent=headerComputedContent + deselect=(action "deselect") + toggle=(action "toggle") + clearSelection=(action "clearSelection") + options=headerComponentOptions +}} + {{component filterComponent + icon=filterIcon + placeholder=filterPlaceholder + filter=filter + hasSelection=hasSelection + isLoading=isLoading + shouldDisplayFilter=shouldDisplayFilter + isFocused=isFocused + filterComputedContent=(action "filterComputedContent") + }} +{{/component}} + +
+ {{#if renderedBodyOnce}} + {{component collectionComponent + collectionHeaderComputedContent=collectionHeaderComputedContent + hasSelection=hasSelection + noneRowComputedContent=noneRowComputedContent + createRowComputedContent=createRowComputedContent + collectionComputedContent=collectionComputedContent + rowComponent=rowComponent + noneRowComponent=noneRowComponent + createRowComponent=createRowComponent + templateForRow=templateForRow + templateForNoneRow=templateForNoneRow + templateForCreateRow=templateForCreateRow + clearSelection=(action "clearSelection") + select=(action "select") + highlight=(action "highlight") + create=(action "create") + highlightedValue=highlightedValue + computedValue=computedValue + rowComponentOptions=rowComponentOptions + noContentRow=noContentRow + maxContentRow=maxContentRow + }} + {{/if}} +
+ +
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs index eb4aba3f06d..71173acb30d 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs @@ -5,11 +5,6 @@ computedContent=selectedComputedContent}} {{/each}} - {{component "select-kit/select-kit-filter" - filterComputedContent=filterComputedContent - shouldDisplayFilter=shouldDisplayFilter - isFocused=isFocused - filter=filter - }} + {{yield}}
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs index 6e2b1b362fd..09b5adfa859 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs @@ -1,7 +1,3 @@
- - {{d-icon "times"}} - - {{badge}}
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs index e4167b47b4d..ffa6a8f3b91 100644 --- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs +++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs @@ -1,16 +1,6 @@ {{#if headerContent}}
{{headerContent}}
{{/if}}
- {{#if isLocked}} - - {{d-icon "lock"}} - - {{else}} - - {{d-icon "times"}} - - {{/if}} - {{{label}}} diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs index 35d46789dd3..12f5d053800 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs @@ -40,7 +40,7 @@ {{noContentRow}} {{else}} - {{#each filteredComputedContent as |computedContent|}} + {{#each collectionComputedContent as |computedContent|}} {{component rowComponent computedContent=computedContent highlightedValue=highlightedValue diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs index e7d1e96a7ef..dfe505f5b1f 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs +++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-filter.hbs @@ -1,7 +1,7 @@ {{input tabindex=-1 class="filter-input" - placeholder=placeholder + placeholder=computedPlaceholder key-up=filterComputedContent autocomplete="off" autocorrect="off" diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs b/app/assets/javascripts/select-kit/templates/components/single-select.hbs similarity index 86% rename from app/assets/javascripts/select-kit/templates/components/select-kit.hbs rename to app/assets/javascripts/select-kit/templates/components/single-select.hbs index cd92fb83122..8f676036031 100644 --- a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs +++ b/app/assets/javascripts/select-kit/templates/components/single-select.hbs @@ -1,13 +1,10 @@ {{component headerComponent tabindex=tabindex - shouldDisplayFilter=shouldDisplayFilter isFocused=isFocused isExpanded=isExpanded computedContent=headerComputedContent deselect=(action "deselect") toggle=(action "toggle") - isLoading=isLoading - filterComputedContent=(action "filterComputedContent") clearSelection=(action "clearSelection") options=headerComponentOptions }} @@ -17,8 +14,9 @@ filter=filter isLoading=isLoading icon=filterIcon + hasSelection=hasSelection shouldDisplayFilter=shouldDisplayFilter - placeholder=(i18n filterPlaceholder) + placeholder=filterPlaceholder isFocused=isFocused filterComputedContent=(action "filterComputedContent") }} @@ -29,7 +27,7 @@ hasSelection=hasSelection noneRowComputedContent=noneRowComputedContent createRowComputedContent=createRowComputedContent - filteredComputedContent=filteredComputedContent + collectionComputedContent=collectionComputedContent rowComponent=rowComponent noneRowComponent=noneRowComponent createRowComponent=createRowComponent diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 6a7bd345eca..03accd59828 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -14,7 +14,6 @@ //= require bootstrap-dropdown.js //= require bootstrap-modal.js //= require bootstrap-transition.js -//= require select2.js //= require div_resizer //= require caret_position //= require favcount.js diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js index ecb8d2bd7e0..4d07d0ceea5 100644 --- a/app/assets/javascripts/wizard-vendor.js +++ b/app/assets/javascripts/wizard-vendor.js @@ -1,5 +1,4 @@ //= require template_include.js -//= require select2.js //= require jquery.ui.widget.js //= require jquery.fileupload.js //= require sweetalert.js diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 565e4317447..9bf58300568 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -3,7 +3,6 @@ @import "vendor/pikaday"; @import "common/foundation/helpers"; @import "common/foundation/base"; -@import "vendor/select2"; @import "common/foundation/mixins"; @import "common/foundation/variables"; @import "common/select-kit/*"; diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index c9f786c9b1f..1b199179aae 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -270,7 +270,7 @@ $mobile-breakpoint: 700px; @include clearfix; nav { float: left; - margin-left: 12px; + margin-left: 12px; } .nav.nav-pills { li.active { @@ -493,9 +493,6 @@ $mobile-breakpoint: 700px; width: 100% !important; // !important overrides hard-coded mobile width of 68px } } - .select2-container-multi .select2-choices { - border: none; - } } .setting-controls { float: left; @@ -521,13 +518,6 @@ $mobile-breakpoint: 700px; border-radius: 3px; transition: border linear 0.2s, box-shadow linear 0.2s; - li.select2-search-choice { - cursor: pointer; - .select2-search-choice-close { - content: "x" - } - } - li.sortable-placeholder { padding: 3px 5px 3px 18px; margin: 3px 0 3px 5px; @@ -831,14 +821,6 @@ section.details { .controls { margin-top: 10px; } - .select2-container { - width: 100%; - } - .select2-choices { - width: 100%; - border-color: dark-light-choose($primary-low-mid, $secondary-high); - } - .content-list { margin-right: 20px; } diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss index e3204d60f5b..841e4d4d991 100644 --- a/app/assets/stylesheets/common/admin/customize.scss +++ b/app/assets/stylesheets/common/admin/customize.scss @@ -36,7 +36,7 @@ .admin-footer { margin-top: 20px; } - .select2-chosen, .color-schemes li { + .color-schemes li { .fa { margin-right: 6px; color: dark-light-choose($primary-medium, $secondary-medium); diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss index 2504fbd97ac..e050f69ceff 100644 --- a/app/assets/stylesheets/common/base/compose.scss +++ b/app/assets/stylesheets/common/base/compose.scss @@ -229,10 +229,6 @@ margin: 0; flex: 1 1 100%; } - - &.select2-dropdown-open, &.select2-container-active { - border-color: $tertiary; - } } .wmd-controls { diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss index 2195c8e0186..f4c70b4b193 100644 --- a/app/assets/stylesheets/common/base/modal.scss +++ b/app/assets/stylesheets/common/base/modal.scss @@ -88,6 +88,10 @@ .select-kit { width: 220px; + + &.tag-chooser { + width: 100%; + } } } diff --git a/app/assets/stylesheets/common/base/search.scss b/app/assets/stylesheets/common/base/search.scss index cf5b0e4b9a4..46e6bb36f62 100644 --- a/app/assets/stylesheets/common/base/search.scss +++ b/app/assets/stylesheets/common/base/search.scss @@ -92,7 +92,7 @@ } .search-bar { - display: flex; + display: flex; margin-bottom: 10px; max-width: 780px; input { @@ -122,7 +122,7 @@ position: relative; margin: 10px 0 15px; max-width: 780px; - border-bottom: 3px solid $primary-low; + border-bottom: 3px solid $primary-low; width: 100%; .term { font-weight: bold; @@ -130,7 +130,7 @@ .result-count { float: left; - margin-bottom: 4px; + margin-bottom: 4px; span { line-height: $line-height-large; height: 28px; @@ -163,7 +163,7 @@ max-width: 780px; .search-advanced-options { border: 1px solid $primary-low; - padding: 0 20px; + padding: 0 20px; width: 100%; .date-picker-wrapper { vertical-align: top; @@ -183,6 +183,10 @@ font-weight: bold; } + .tag-chooser { + width: 70%; + } + .container { display: flex; flex: 1 1 100%; @@ -206,4 +210,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index 11248496fcc..eb93138ccf7 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -25,8 +25,8 @@ .tag-count { font-size: $font-down-1; - vertical-align: middle; - line-height: $line-height-small; + vertical-align: middle; + line-height: $line-height-small; } } @@ -61,7 +61,7 @@ &.box, &.bullet { } - + &.box + .topic-header-extra, &.bullet + .topic-header-extra, &.bar + .topic-header-extra { @@ -69,25 +69,17 @@ } } -.add-tags .select2 { - margin: 0; -} - $tag-color: $primary-medium; .discourse-tag-count { font-size: $font-down-1; color: $tag-color; line-height: $line-height-small; - vertical-align: middle; -} - -.select2-result-label .discourse-tag { - margin-right: 0; + vertical-align: middle; } .discourse-tag { - max-width: 14em; + max-width: 14em; display: inline-block; white-space: nowrap; overflow: hidden; @@ -131,29 +123,13 @@ $tag-color: $primary-medium; } .d-header .topic-header-extra { - .discourse-tags { + .discourse-tags { display: inline-block; - font-size: $font-down-1; + font-size: $font-down-1; } .topic-featured-link { margin-left: 8px; } } -.select2-container-multi .select2-choices .select2-search-choice.discourse-tag-select2 { - -webkit-box-shadow: none; - box-shadow: none; - border: 0; - border-radius: 0; - background-color: transparent; - .discourse-tag { - padding: 4px; - &.box { - padding: 1px 8px; - margin: 3px 5px; - } - } -} - - .fps-result .add-full-page-tags { display: inline-block; } @@ -213,11 +189,6 @@ header .discourse-tag {color: $tag-color } width: 100%; max-width: 100%; margin: 5px 0; - ul.select2-choices { - max-height: 30px; - padding-left: 10px; - overflow-y: auto; - } } .title-wrapper .tag-chooser { @@ -278,11 +249,7 @@ header .discourse-tag {color: $tag-color } } } .group-tags-list .tag-chooser { - height: 250px !important; - ul.select2-choices { - height: 250px !important; // to fight with select2.scss's important - max-height: none; - } + width: 100%; } .btn {margin-left: 10px;} .saving { diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index e0cfaa11e2c..0a76f198ab6 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -105,15 +105,15 @@ a.badge-category { .private-message-glyph { margin: 5px 5px 0 0; } - .category-chooser, .tag-chooser { + .category-chooser, .mini-tag-chooser { flex: 1 1 49%; margin: 0 0 9px 0; @media all and (max-width: 500px) { - flex: 1 1 100%; + flex: 1 1 100%; } - + } - .tag-chooser { + .mini-tag-chooser { margin-left: 2%; @media all and (max-width: 500px) { margin-left: 0; @@ -204,7 +204,7 @@ a.badge-category { .post-links { margin-top: 1em; padding-top: 1em; - border-top: 1px solid $primary-low; + border-top: 1px solid $primary-low; li:last-of-type { margin-bottom: 1em; } diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 59c235e5be5..4fedecbc544 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -198,9 +198,9 @@ .staff-counters { background: $primary; - color: $secondary; + color: $secondary; display: flex; - padding: 10px; + padding: 10px; > div, > div a { display: flex; align-items: baseline; @@ -209,9 +209,9 @@ span { padding: 1px 6px; border-radius: 10px; - margin-right: 5px; + margin-right: 5px; } - } + } a { color: $secondary; @@ -404,7 +404,7 @@ &.linked-stat { // This makes the entire "box" (the li) clickable instead of a narrow area. padding: 0; a { - padding: 10px 14px; + padding: 10px 14px; width: 100%; height: 100%; display: block; @@ -519,15 +519,6 @@ .tag-notifications .tag-controls { margin-top: 24px; } - - .tags .select2-container-multi { - border: 1px solid $primary-low; - width: 540px; - border-radius: 0; - .select2-choices { - border: none; - } - } } .paginated-topics-list { diff --git a/app/assets/stylesheets/common/select-kit/legacy-combo-box.scss b/app/assets/stylesheets/common/select-kit/legacy-combo-box.scss deleted file mode 100644 index 2b72735d4ca..00000000000 --- a/app/assets/stylesheets/common/select-kit/legacy-combo-box.scss +++ /dev/null @@ -1,88 +0,0 @@ -// DO NOT MODIFY -// TODO: remove when all select2 instances are gone -.select2-results .select2-highlighted { - background: $highlight-medium; - color: $primary; -} - -.select2-drop { - .badge-category { - display: inline-block; - } - .topic-count { - font-size: $font-down-2; - color: $primary; - display: inline-block; - } - .highlighted .topic-count, .select2-highlighted .category-desc { - color: $primary; - } - .category-desc { - color: $primary; - font-size: $font-down-1; - line-height: 16px; - } -} - -.select2-drop { - background: $secondary; - .d-icon { - color: dark-light-choose($primary-medium, $secondary-medium); - } -} - -.select2-search input { - background: image-url("select2.png") no-repeat 100% -22px, $secondary 0 0 -} - -.select2-container { - min-width: 200px; - - &.select2-dropdown-open { - border: 0; - margin-bottom: 2px; - } - &.select2-container-active { - border-color: $tertiary; - } - &.select2-container-disabled .select2-chosen { - color: blend-primary-secondary(50%); - } -} - -.select2-container-multi .select2-choices .select2-search-field input.select2-active { - background: $secondary image-url("select2-spinner.gif") no-repeat 100% !important; -} - -.select2-container-multi .select2-choices { - border: 1px solid $primary-medium; -} - -.select2-container a.select2-choice { - background: $secondary; - border-radius: 3px; - border-color: $secondary; - color: $primary; -} - -.select2-dropdown-open a.select2-choice { - box-shadow: none; - border-radius: 3px 3px 0 0; - border-color: $tertiary; -} -.select2-drop { - color: $primary; -} -.select2-drop-active { - border: 1px solid $tertiary; - border-top: 0; -} - -.select2-container-active { - box-shadow: shadow("focus"); -} - -.select2-results .select2-no-results, .select2-results .select2-searching, .select2-results .select2-selection-limit { - background: $secondary; - color: $primary; -} diff --git a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss index 362e9b187af..27c8f44fc9e 100644 --- a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss +++ b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss @@ -7,7 +7,6 @@ &.is-expanded { .select-kit-header { border: 1px solid $tertiary; - box-shadow: shadow("focus"); } } @@ -59,7 +58,7 @@ } .selected-tag { - background: $primary-very-low; + background: $primary-low; padding: 2px 4px; margin: 2px; border: 0; diff --git a/app/assets/stylesheets/common/select-kit/multi-select.scss b/app/assets/stylesheets/common/select-kit/multi-select.scss index db3734bfb63..91ef4c15395 100644 --- a/app/assets/stylesheets/common/select-kit/multi-select.scss +++ b/app/assets/stylesheets/common/select-kit/multi-select.scss @@ -17,6 +17,7 @@ .select-kit-filter { border: 0; + flex: 1; } .multi-select-header { @@ -79,7 +80,7 @@ justify-content: space-between; flex-wrap: wrap; flex-direction: row; - margin: 2.5px; + margin: 2px; } .filter { @@ -89,6 +90,7 @@ min-width: 50px; padding: 0; outline: none; + flex: 1; .filter-input, .filter-input:focus { border: none; @@ -117,17 +119,36 @@ .color-preview { height: 5px; margin: 0 2px 2px 2px; - border-radius: 5px; display: flex; width: 100%; } } + .selected-category { + .badge-wrapper { + &.bullet { + margin-right: 2.5px; + } + + margin: auto 2.5px; + padding: 2px 4px; + line-height: $line-height-medium; + display: flex; + flex: 1; + align-items: center; + + &:after { + content: '\f00d'; + color: $primary-low-mid; + font-family: 'FontAwesome'; + font-size: $font-down-2; + margin-left: 5px; + } + } + } + .selected-name { color: $primary; - border: 1px solid $primary-medium; - border-radius: 3px; - box-shadow: 0 0 2px $secondary inset, 0 1px 0 rgba(0,0,0,0.05); background-clip: padding-box; -webkit-touch-callout: none; user-select: none; @@ -148,55 +169,32 @@ } .body { - width: 100%; - display: inline-flex; + display: flex; align-items: center; - - .locked-icon, .delete-icon { - justify-content: center; - align-items: center; - display: inline-flex; - height: 21px; - width: 21px; - - .d-icon { - color: $primary-medium; - cursor: pointer; - font-size: 1em; - margin: 0; - - &:hover { - color: $primary; - } - } - } + flex: 1; } .name { - padding: 0 5px; + padding: 2px 4px; line-height: $line-height-medium; + + &:after { + content: '\f00d'; + color: $primary-low-mid; + font-family: 'FontAwesome'; + font-size: $font-down-2; + } + + &:hover { + &:after { + color: $danger; + } + } } &.is-highlighted { box-shadow: 0 0 2px $danger, 0 1px 0 rgba(0,0,0,0.05); } - - .locked-icon, .delete-icon { - justify-content: center; - align-items: center; - width: 21px; - height: 21px; - display: inline-flex; - .d-icon { - color: $primary-medium; - cursor: pointer; - font-size: $font-0; - - &:hover { - color: $primary; - } - } - } } } } diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss index b5c1597ef32..3f61e5e2609 100644 --- a/app/assets/stylesheets/common/select-kit/select-kit.scss +++ b/app/assets/stylesheets/common/select-kit/select-kit.scss @@ -202,6 +202,7 @@ border-radius: inherit; -webkit-overflow-scrolling: touch; margin: 0; + padding: 0; max-height: 200px; .select-kit-collection { diff --git a/app/assets/stylesheets/common/select-kit/tag-chooser.scss b/app/assets/stylesheets/common/select-kit/tag-chooser.scss new file mode 100644 index 00000000000..cc51e23ef50 --- /dev/null +++ b/app/assets/stylesheets/common/select-kit/tag-chooser.scss @@ -0,0 +1,14 @@ +.select-kit { + &.multi-select { + &.tag-chooser { + .select-kit-row { + display: flex; + align-items: center; + + .discourse-tag-count { + margin-left: 5px; + } + } + } + } +} diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss index 2a0fbf8d982..d52c0d48692 100644 --- a/app/assets/stylesheets/desktop/modal.scss +++ b/app/assets/stylesheets/desktop/modal.scss @@ -56,14 +56,6 @@ .category-combobox { width: 430px; - - .select2-drop { - left: -9000px; - width: 428px; - } - .select2-search input { - width: 378px; - } } } diff --git a/app/assets/stylesheets/desktop/queued-posts.scss b/app/assets/stylesheets/desktop/queued-posts.scss index 79cfb1f6303..b4c5ef08883 100644 --- a/app/assets/stylesheets/desktop/queued-posts.scss +++ b/app/assets/stylesheets/desktop/queued-posts.scss @@ -24,6 +24,15 @@ } } + .tag-chooser { + width: 100%; + margin-bottom: .5em; + + .select-kit-collection { + padding: 0; + } + } + .queue-controls { button { float: left; diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index b4d33b09580..db173a36810 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -28,15 +28,12 @@ font-size: $font-up-4; line-height: $line-height-medium; overflow: hidden; - width: 100%; + width: 100%; a {color: $primary;} } .topic-statuses { margin-top: -2px; } - .select2-container { - vertical-align: middle; - } .private-message-glyph { display: none; } .remove-featured-link { float: right; diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 0fb3e102f9f..d393e58994f 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -279,10 +279,12 @@ width: 530px; } + .category-selector, .tag-chooser { + width: 530px; + } + input { - &.category-selector, - &.user-selector, - &.tag-chooser { + &.user-selector { width: 530px; } diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss index 703bba38ceb..48e682e147c 100644 --- a/app/assets/stylesheets/mobile/topic.scss +++ b/app/assets/stylesheets/mobile/topic.scss @@ -181,11 +181,6 @@ sup sup, sub sup, sup sub, sub sub { top: 0; } width: 100%; height: 32px; } - - .select2-container { - box-sizing: border-box; - width: 100% !important; - } .btn-small { padding: 6px 12px; margin: 6px 6px 0 0; diff --git a/app/assets/stylesheets/vendor/select2.scss b/app/assets/stylesheets/vendor/select2.scss deleted file mode 100644 index 0981475b458..00000000000 --- a/app/assets/stylesheets/vendor/select2.scss +++ /dev/null @@ -1,575 +0,0 @@ -/* -Version: @@ver@@ Timestamp: @@timestamp@@ -*/ -.select2-container { - margin: 0; - position: relative; - display: inline-block; - /* inline-block for ie7 */ - zoom: 1; - vertical-align: middle; -} - -.select2-container, -.select2-drop, -.select2-search, -.select2-search input { - /* - Force border-box so that % widths fit the parent - container without overlap because of margin/padding. - More Info : http://www.quirksmode.org/css/box.html - */ - -webkit-box-sizing: border-box; /* webkit */ - -moz-box-sizing: border-box; /* firefox */ - box-sizing: border-box; /* css3 */ -} - -.select2-container .select2-choice { - display: block; - height: 26px; - padding: 0 0 0 8px; - overflow: hidden; - position: relative; - - border: 1px solid #aaa; - white-space: nowrap; - line-height: 26px; - color: #444; - text-decoration: none; - - border-radius: 4px; - - background-clip: padding-box; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - background-color: #fff; -} - -.select2-container.select2-drop-above .select2-choice { - border-bottom-color: #aaa; - - border-radius: 0 0 4px 4px; - -} - -.select2-container.select2-allowclear .select2-choice .select2-chosen { - margin-right: 42px; -} - -.select2-container .select2-choice > .select2-chosen { - margin-right: 26px; - display: block; - overflow: hidden; - - white-space: nowrap; - - text-overflow: ellipsis; - float: none; - width: auto; -} - -.select2-container .select2-choice abbr { - display: none; - width: 12px; - height: 12px; - position: absolute; - right: 24px; - top: 8px; - - font-size: 1px; - text-decoration: none; - - border: 0; - background: asset-url('select2.png') right top no-repeat; - cursor: pointer; - outline: 0; -} - -.select2-container.select2-allowclear .select2-choice abbr { - display: inline-block; -} - -.select2-container .select2-choice abbr:hover { - background-position: right -11px; - cursor: pointer; -} - -.select2-drop-mask { - border: 0; - margin: 0; - padding: 0; - position: fixed; - left: 0; - top: 0; - min-height: 100%; - min-width: 100%; - height: auto; - width: auto; - opacity: 0; - z-index: 9998; - /* styles required for IE to work */ - background-color: #fff; - filter: alpha(opacity=0); -} - -.select2-drop { - width: 100%; - margin-top: -1px; - position: absolute; - z-index: 9999; - top: 100%; - - background: #fff; - color: #000; - border: 1px solid #aaa; - border-top: 0; - - - -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); - box-shadow: 0 4px 5px rgba(0, 0, 0, .15); -} - -.select2-drop.select2-drop-above { - margin-top: 1px; - border-top: 1px solid #aaa; - border-bottom: 0; - - - -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); - box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); -} - -.select2-drop-active { - border: 1px solid #5897fb; - border-top: none; -} - -.select2-drop.select2-drop-above.select2-drop-active { - border-top: 1px solid #5897fb; -} - -.select2-drop-auto-width { - border-top: 1px solid #aaa; - width: auto; -} - -.select2-drop-auto-width .select2-search { - padding-top: 4px; -} - -.select2-container .select2-choice .select2-arrow { - display: inline-block; - width: 18px; - height: 100%; - position: absolute; - right: 0; - top: 0; - - border-radius: 0 4px 4px 0; - - background-clip: padding-box; - -} - -.select2-container .select2-choice .select2-arrow b { - display: block; - width: 100%; - height: 100%; - background: asset-url('select2.png') no-repeat 0 1px; -} - -.select2-search { - display: inline-block; - width: 100%; - min-height: 26px; - margin: 0; - padding-left: 4px; - padding-right: 4px; - - position: relative; - z-index: 10000; - - white-space: nowrap; -} - -//noinspection CssOverwrittenProperties -.select2-search input { - width: 100%; - height: auto !important; - min-height: 26px; - padding: 4px 20px 4px 5px; - margin: 0; - - outline: 0; - font-family: sans-serif; - font-size: 1em; - - border: 1px solid #aaa; - border-radius: 0; - - -webkit-box-shadow: none; - box-shadow: none; - - background: #fff asset-url('select2.png') no-repeat 100% -22px; - background: asset-url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); - background: asset-url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; -} - -.select2-drop.select2-drop-above .select2-search input { - margin-top: 4px; -} - -//noinspection CssOverwrittenProperties -.select2-search input.select2-active { - background: #fff asset-url('select2-spinner.gif') no-repeat 100%; - background: asset-url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); - background: asset-url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); - background: asset-url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; -} - -.select2-container-active .select2-choice, -.select2-container-active .select2-choices { - border: 1px solid #5897fb; - outline: none; - - -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); - box-shadow: 0 0 5px rgba(0, 0, 0, .3); -} - -.select2-dropdown-open .select2-choice { - border-bottom-color: transparent; - -webkit-box-shadow: 0 1px 0 #fff inset; - box-shadow: 0 1px 0 #fff inset; - - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - - background-color: #eee; -} - -.select2-dropdown-open.select2-drop-above .select2-choice, -.select2-dropdown-open.select2-drop-above .select2-choices { - border: 1px solid #5897fb; - border-top-color: transparent; -} - -.select2-dropdown-open .select2-choice .select2-arrow { - background: transparent; - border-left: none; - filter: none; -} -.select2-dropdown-open .select2-choice .select2-arrow b { - background-position: -18px 1px; -} - -.select2-hidden-accessible { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} - -/* results */ -.select2-results { - max-height: 200px; - padding: 0 0 0 4px; - margin: 4px 4px 4px 0; - position: relative; - overflow-x: hidden; - overflow-y: auto; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -.select2-results ul.select2-result-sub { - margin: 0; - padding-left: 0; -} - -.select2-results li { - list-style: none; - display: list-item; - background-image: none; -} - -.select2-results li.select2-result-with-children > .select2-result-label { - font-weight: bold; -} - -.select2-results .select2-result-label { - padding: 3px 7px 4px; - margin: 0; - cursor: pointer; - - min-height: 1em; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.select2-results-dept-1 .select2-result-label { padding-left: 20px } -.select2-results-dept-2 .select2-result-label { padding-left: 40px } -.select2-results-dept-3 .select2-result-label { padding-left: 60px } -.select2-results-dept-4 .select2-result-label { padding-left: 80px } -.select2-results-dept-5 .select2-result-label { padding-left: 100px } -.select2-results-dept-6 .select2-result-label { padding-left: 110px } -.select2-results-dept-7 .select2-result-label { padding-left: 120px } - -.select2-results li em { - background: #feffde; - font-style: normal; -} - -.select2-results .select2-highlighted em { - background: transparent; -} - -.select2-results .select2-highlighted ul { - background: #fff; - color: #000; -} - - -.select2-results .select2-no-results, -.select2-results .select2-searching, -.select2-results .select2-selection-limit { - background: #f4f4f4; - display: list-item; - padding-left: 5px; -} - -/* -disabled look for disabled choices in the results dropdown -*/ -.select2-results .select2-disabled.select2-highlighted { - color: #666; - background: #f4f4f4; - display: list-item; - cursor: default; -} -.select2-results .select2-disabled { - background: #f4f4f4; - display: list-item; - cursor: default; -} - -.select2-results .select2-selected { - display: none; -} - -.select2-more-results.select2-active { - background: #f4f4f4 asset-url('select2-spinner.gif') no-repeat 100%; -} - -.select2-more-results { - background: #f4f4f4; - display: list-item; -} - -/* disabled styles */ - -.select2-container.select2-container-disabled .select2-choice { - background: #f4f4f4 none; - border: 1px solid #ddd; - cursor: default; -} - -.select2-container.select2-container-disabled .select2-choice .select2-arrow { - background: #f4f4f4 none; - border-left: 0; -} - -.select2-container.select2-container-disabled .select2-choice abbr { - display: none; -} - - -/* multiselect */ - -.select2-container-multi .select2-choices { - height: auto !important; - margin: 0; - padding: 0 5px 0 0; - position: relative; - cursor: text; - overflow: hidden; -} - -.select2-locked { - padding: 3px 5px 3px 5px !important; -} - -.select2-container-multi .select2-choices { - min-height: 26px; -} - -.select2-container-multi.select2-container-active .select2-choices { - border: 1px solid #5897fb; - outline: none; - - -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); - box-shadow: 0 0 5px rgba(0, 0, 0, .3); -} -.select2-container-multi .select2-choices li { - float: left; - list-style: none; -} -html[dir="rtl"] .select2-container-multi .select2-choices li -{ - float: right; -} -.select2-container-multi .select2-choices .select2-search-field { - margin: 0; - padding: 0; - white-space: nowrap; -} - -.select2-container-multi .select2-choices .select2-search-field input { - padding-left: 0; - font-family: sans-serif; - font-size: 1em; - color: #666; - outline: 0; - border: 0; - margin-bottom: 0; - -webkit-box-shadow: none; - box-shadow: none; - background: transparent !important; -} - -.select2-container-multi .select2-choices .select2-search-field input.select2-active { - background: #fff asset-url('select2-spinner.gif') no-repeat 100% !important; -} - -.select2-default { - color: #999 !important; -} - -.select2-container-multi .select2-choices .select2-search-choice { - padding: 0 0 0 12px; - margin: 0; - position: relative; - - color: #333; - cursor: default; - border: 1px solid #aaaaaa; - - border-radius: 3px; - - -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); - box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); - - background-clip: padding-box; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - background-color: #e4e4e4; -} -html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice -{ - margin-left: 0; - margin-right: 5px; -} -.select2-container-multi .select2-choices .select2-search-choice .select2-chosen { - cursor: default; -} -.select2-container-multi .select2-choices .select2-search-choice-focus { - background: #d4d4d4; -} - -.select2-search-choice-close { - display: block; - width: 12px; - height: 13px; - position: absolute; - right: 3px; - top: 8px; - - font-size: 1px; - outline: none; - background: asset-url('select2.png') right top no-repeat; -} -html[dir="rtl"] .select2-search-choice-close { - right: auto; - left: 3px; -} - -.select2-container-multi .select2-search-choice-close { - left: 3px; -} - -.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { - background-position: right -11px; -} -.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { - background-position: right -11px; -} - -/* disabled styles */ -.select2-container-multi.select2-container-disabled .select2-choices { - background: #f4f4f4 none; - border: 1px solid #ddd; - cursor: default; -} - -.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { - padding: 3px 5px 3px 5px; - border: 1px solid #ddd; - background: #f4f4f4 none; -} - -.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; - background: none; -} -/* end multiselect */ - - -.select2-result-selectable .select2-match, -.select2-result-unselectable .select2-match { - text-decoration: underline; -} - -.select2-offscreen, .select2-offscreen:focus { - clip: rect(0 0 0 0) !important; - width: 1px !important; - height: 1px !important; - border: 0 !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - position: absolute !important; - outline: 0 !important; - left: 0 !important; - top: 0 !important; -} - -.select2-display-none { - display: none; -} - -.select2-measure-scrollbar { - position: absolute; - top: -10000px; - left: -10000px; - width: 100px; - height: 100px; - overflow: scroll; -} diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index 821c8e86895..0fa4c95008c 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -1,6 +1,5 @@ @import "vendor/normalize"; @import "vendor/font_awesome/font-awesome"; -@import "vendor/select2"; @import "vendor/sweetalert"; @import "common/foundation/colors"; @import "common/foundation/variables"; @@ -63,9 +62,6 @@ body.wizard { .select { width: 400px; } -.select2-results .select2-highlighted { - background: #ff9; -} .wizard-canvas { position: absolute; @@ -484,7 +480,6 @@ body.wizard { .wizard-column { margin: auto !important; } .wizard-step-contents { min-height: auto !important; } .wizard-step-banner { width: 100% !important; margin-bottom: 1em !important; } - .select2-container { width: 100% !important; } .wizard-step-footer { display: block !important; } .wizard-progress { margin-bottom: 10px !important; } .wizard-buttons { text-align: right !important; } diff --git a/app/views/finish_installation/register.html.erb b/app/views/finish_installation/register.html.erb index 8fa60e3e380..cf6457da3d2 100644 --- a/app/views/finish_installation/register.html.erb +++ b/app/views/finish_installation/register.html.erb @@ -51,9 +51,3 @@ <%- else -%>

<%= raw(t 'finish_installation.register.no_emails') %>

<%- end %> - - diff --git a/test/javascripts/acceptance/search-full-test.js.es6 b/test/javascripts/acceptance/search-full-test.js.es6 index dba53a92525..8027f64823b 100644 --- a/test/javascripts/acceptance/search-full-test.js.es6 +++ b/test/javascripts/acceptance/search-full-test.js.es6 @@ -75,30 +75,6 @@ QUnit.test("open advanced search", assert => { andThen(() => assert.ok(visible('.search-advanced .search-advanced-options'), '"search-advanced-options" is visible')); }); -// these tests are screwy with the runloop - -// test("validate population of advanced search", assert => { -// visit("/search"); -// fillIn('.search input.full-page-search', 'test user:admin #bug group:moderators badge:Reader tags:monkey in:likes in:private in:wiki in:bookmarks status:open after:2016-10-05 min_post_count:10'); -// click('.search-advanced-btn'); -// -// andThen(() => { -// assert.ok(exists('.search-advanced-options span:contains("admin")'), 'has "admin" pre-populated'); -// assert.ok(exists('.search-advanced-options .badge-category:contains("bug")'), 'has "bug" pre-populated'); -// //assert.ok(exists('.search-advanced-options span:contains("moderators")'), 'has "moderators" pre-populated'); -// //assert.ok(exists('.search-advanced-options span:contains("Reader")'), 'has "Reader" pre-populated'); -// assert.ok(exists('.search-advanced-options .tag-chooser .tag-monkey'), 'has "monkey" pre-populated'); -// assert.ok(exists('.search-advanced-options .in-likes:checked'), 'has "I liked" pre-populated'); -// assert.ok(exists('.search-advanced-options .in-private:checked'), 'has "are in my messages" pre-populated'); -// assert.ok(exists('.search-advanced-options .in-wiki:checked'), 'has "are wiki" pre-populated'); -// assert.ok(exists('.search-advanced-options .combobox .select2-choice .select2-chosen:contains("I\'ve bookmarked")'), 'has "I\'ve bookmarked" pre-populated'); -// assert.ok(exists('.search-advanced-options .combobox .select2-choice .select2-chosen:contains("are open")'), 'has "are open" pre-populated'); -// assert.ok(exists('.search-advanced-options .combobox .select2-choice .select2-chosen:contains("after")'), 'has "after" pre-populated'); -// assert.equal(find('.search-advanced-options #search-post-date').val(), "2016-10-05", 'has "2016-10-05" pre-populated'); -// assert.equal(find('.search-advanced-options #search-min-post-count').val(), "10", 'has "10" pre-populated'); -// }); -// }); - QUnit.test("escape search term", (assert) => { visit("/search"); fillIn('.search input.full-page-search', '@gmail.com'); diff --git a/vendor/assets/javascripts/select2.js b/vendor/assets/javascripts/select2.js deleted file mode 100644 index 195ccee5bb0..00000000000 --- a/vendor/assets/javascripts/select2.js +++ /dev/null @@ -1,3729 +0,0 @@ -/* -Copyright 2012 Igor Vaynberg - -Version: 3.5.4 Timestamp: Sun Aug 30 13:30:32 EDT 2015 - -This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU -General Public License version 2 (the "GPL License"). You may choose either license to govern your -use of this software only upon the condition that you accept all of the terms of either the Apache -License or the GPL License. - -You may obtain a copy of the Apache License and the GPL License at: - - http://www.apache.org/licenses/LICENSE-2.0 - http://www.gnu.org/licenses/gpl-2.0.html - -Unless required by applicable law or agreed to in writing, software distributed under the -Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for -the specific language governing permissions and limitations under the Apache License and the GPL License. -*/ -(function ($) { - if(typeof $.fn.each2 == "undefined") { - $.extend($.fn, { - /* - * 4-10 times faster .each replacement - * use it carefully, as it overrides jQuery context of element on each iteration - */ - each2 : function (c) { - var j = $([0]), i = -1, l = this.length; - while ( - ++i < l - && (j.context = j[0] = this[i]) - && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object - ); - return this; - } - }); - } -})(jQuery); - -(function ($, undefined) { - "use strict"; - /*global document, window, jQuery, console */ - - if (window.Select2 !== undefined) { - return; - } - - var AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer, - lastMousePosition={x:0,y:0}, $document, scrollBarDimensions, - - KEY = { - TAB: 9, - ENTER: 13, - ESC: 27, - SPACE: 32, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - SHIFT: 16, - CTRL: 17, - ALT: 18, - PAGE_UP: 33, - PAGE_DOWN: 34, - HOME: 36, - END: 35, - BACKSPACE: 8, - DELETE: 46, - isArrow: function (k) { - k = k.which ? k.which : k; - switch (k) { - case KEY.LEFT: - case KEY.RIGHT: - case KEY.UP: - case KEY.DOWN: - return true; - } - return false; - }, - isControl: function (e) { - var k = e.which; - switch (k) { - case KEY.SHIFT: - case KEY.CTRL: - case KEY.ALT: - return true; - } - - if (e.metaKey) return true; - - return false; - }, - isFunctionKey: function (k) { - k = k.which ? k.which : k; - return k >= 112 && k <= 123; - } - }, - MEASURE_SCROLLBAR_TEMPLATE = "
", - - DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"}; - - $document = $(document); - - nextUid=(function() { var counter=1; return function() { return counter++; }; }()); - - - function reinsertElement(element) { - var placeholder = $(document.createTextNode('')); - - element.before(placeholder); - placeholder.before(element); - placeholder.remove(); - } - - function stripDiacritics(str) { - // Used 'uni range + named function' from http://jsperf.com/diacritics/18 - function match(a) { - return DIACRITICS[a] || a; - } - - return str.replace(/[^\u0000-\u007E]/g, match); - } - - function indexOf(value, array) { - var i = 0, l = array.length; - for (; i < l; i = i + 1) { - if (equal(value, array[i])) return i; - } - return -1; - } - - function measureScrollbar () { - var $template = $( MEASURE_SCROLLBAR_TEMPLATE ); - $template.appendTo(document.body); - - var dim = { - width: $template.width() - $template[0].clientWidth, - height: $template.height() - $template[0].clientHeight - }; - $template.remove(); - - return dim; - } - - /** - * Compares equality of a and b - * @param a - * @param b - */ - function equal(a, b) { - if (a === b) return true; - if (a === undefined || b === undefined) return false; - if (a === null || b === null) return false; - // Check whether 'a' or 'b' is a string (primitive or object). - // The concatenation of an empty string (+'') converts its argument to a string's primitive. - if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object - if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object - return false; - } - - /** - * Splits the string into an array of values, transforming each value. An empty array is returned for nulls or empty - * strings - * @param string - * @param separator - */ - function splitVal(string, separator, transform) { - var val, i, l; - if (string === null || string.length < 1) return []; - val = string.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) val[i] = transform(val[i]); - return val; - } - - function getSideBorderPadding(element) { - return element.outerWidth(false) - element.width(); - } - - function installKeyUpChangeEvent(element) { - var key="keyup-change-value"; - element.on("keydown", function () { - if ($.data(element, key) === undefined) { - $.data(element, key, element.val()); - } - }); - element.on("keyup", function () { - var val= $.data(element, key); - if (val !== undefined && element.val() !== val) { - $.removeData(element, key); - element.trigger("keyup-change"); - } - }); - } - - - /** - * filters mouse events so an event is fired only if the mouse moved. - * - * filters out mouse events that occur when mouse is stationary but - * the elements under the pointer are scrolled. - */ - function installFilteredMouseMove(element) { - element.on("mousemove", function (e) { - var lastpos = lastMousePosition; - if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { - $(e.target).trigger("mousemove-filtered", e); - } - }); - } - - /** - * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made - * within the last quietMillis milliseconds. - * - * @param quietMillis number of milliseconds to wait before invoking fn - * @param fn function to be debounced - * @param ctx object to be used as this reference within fn - * @return debounced version of fn - */ - function debounce(quietMillis, fn, ctx) { - ctx = ctx || undefined; - var timeout; - return function () { - var args = arguments; - window.clearTimeout(timeout); - timeout = window.setTimeout(function() { - fn.apply(ctx, args); - }, quietMillis); - }; - } - - function installDebouncedScroll(threshold, element) { - var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); - element.on("scroll", function (e) { - if (indexOf(e.target, element.get()) >= 0) notify(e); - }); - } - - function focus($el) { - if ($el[0] === document.activeElement) return; - - /* set the focus in a 0 timeout - that way the focus is set after the processing - of the current event has finished - which seems like the only reliable way - to set focus */ - window.setTimeout(function() { - var el=$el[0], pos=$el.val().length, range; - - $el.focus(); - - /* make sure el received focus so we do not error out when trying to manipulate the caret. - sometimes modals or others listeners may steal it after its set */ - var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0); - if (isVisible && el === document.activeElement) { - - /* after the focus is set move the caret to the end, necessary when we val() - just before setting focus */ - if(el.setSelectionRange) - { - el.setSelectionRange(pos, pos); - } - else if (el.createTextRange) { - range = el.createTextRange(); - range.collapse(false); - range.select(); - } - } - }, 0); - } - - function getCursorInfo(el) { - el = $(el)[0]; - var offset = 0; - var length = 0; - if ('selectionStart' in el) { - offset = el.selectionStart; - length = el.selectionEnd - offset; - } else if ('selection' in document) { - el.focus(); - var sel = document.selection.createRange(); - length = document.selection.createRange().text.length; - sel.moveStart('character', -el.value.length); - offset = sel.text.length - length; - } - return { offset: offset, length: length }; - } - - function killEvent(event) { - event.preventDefault(); - event.stopPropagation(); - } - function killEventImmediately(event) { - event.preventDefault(); - event.stopImmediatePropagation(); - } - - function measureTextWidth(e) { - if (!sizer){ - var style = e[0].currentStyle || window.getComputedStyle(e[0], null); - sizer = $(document.createElement("div")).css({ - position: "absolute", - left: "-10000px", - top: "-10000px", - display: "none", - fontSize: style.fontSize, - fontFamily: style.fontFamily, - fontStyle: style.fontStyle, - fontWeight: style.fontWeight, - letterSpacing: style.letterSpacing, - textTransform: style.textTransform, - whiteSpace: "nowrap" - }); - sizer.attr("class","select2-sizer"); - $(document.body).append(sizer); - } - sizer.text(e.val()); - return sizer.width(); - } - - function syncCssClasses(dest, src, adapter) { - var classes, replacements = [], adapted; - - classes = $.trim(dest.attr("class")); - - if (classes) { - classes = '' + classes; // for IE which returns object - - $(classes.split(/\s+/)).each2(function() { - if (this.indexOf("select2-") === 0) { - replacements.push(this); - } - }); - } - - classes = $.trim(src.attr("class")); - - if (classes) { - classes = '' + classes; // for IE which returns object - - $(classes.split(/\s+/)).each2(function() { - if (this.indexOf("select2-") !== 0) { - adapted = adapter(this); - - if (adapted) { - replacements.push(adapted); - } - } - }); - } - - dest.attr("class", replacements.join(" ")); - } - - - function markMatch(text, term, markup, escapeMarkup) { - var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())), - tl=term.length; - - if (match<0) { - markup.push(escapeMarkup(text)); - return; - } - - markup.push(escapeMarkup(text.substring(0, match))); - markup.push(""); - markup.push(escapeMarkup(text.substring(match, match + tl))); - markup.push(""); - markup.push(escapeMarkup(text.substring(match + tl, text.length))); - } - - function defaultEscapeMarkup(markup) { - var replace_map = { - '\\': '\', - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - "/": '/' - }; - - return String(markup).replace(/[&<>"'\/\\]/g, function (match) { - return replace_map[match]; - }); - } - - /** - * Produces an ajax-based query function - * - * @param options object containing configuration parameters - * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax - * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax - * @param options.url url for the data - * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url. - * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified - * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often - * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2. - * The expected format is an object containing the following keys: - * results array of objects that will be used as choices - * more (optional) boolean indicating whether there are more results available - * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true} - */ - function ajax(options) { - var timeout, // current scheduled but not yet executed request - handler = null, - quietMillis = options.quietMillis || 100, - ajaxUrl = options.url, - self = this; - - return function (query) { - window.clearTimeout(timeout); - timeout = window.setTimeout(function () { - var data = options.data, // ajax data function - url = ajaxUrl, // ajax url string or function - transport = options.transport || $.fn.select2.ajaxDefaults.transport, - // deprecated - to be removed in 4.0 - use params instead - deprecated = { - type: options.type || 'GET', // set type of request (GET or POST) - cache: options.cache || false, - jsonpCallback: options.jsonpCallback||undefined, - dataType: options.dataType||"json" - }, - params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated); - - data = data ? data.call(self, query.term, query.page, query.context) : null; - url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url; - - if (handler && typeof handler.abort === "function") { handler.abort(); } - - if (options.params) { - if ($.isFunction(options.params)) { - $.extend(params, options.params.call(self)); - } else { - $.extend(params, options.params); - } - } - - $.extend(params, { - url: url, - dataType: options.dataType, - data: data, - success: function (data) { - // TODO - replace query.page with query so users have access to term, page, etc. - // added query as third paramter to keep backwards compatibility - var results = options.results(data, query.page, query); - query.callback(results); - }, - error: function(jqXHR, textStatus, errorThrown){ - var results = { - hasError: true, - jqXHR: jqXHR, - textStatus: textStatus, - errorThrown: errorThrown - }; - - query.callback(results); - } - }); - handler = transport.call(self, params); - }, quietMillis); - }; - } - - /** - * Produces a query function that works with a local array - * - * @param options object containing configuration parameters. The options parameter can either be an array or an - * object. - * - * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys. - * - * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain - * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text' - * key can either be a String in which case it is expected that each element in the 'data' array has a key with the - * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract - * the text. - */ - function local(options) { - var data = options, // data elements - dataText, - tmp, - text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search - - if ($.isArray(data)) { - tmp = data; - data = { results: tmp }; - } - - if ($.isFunction(data) === false) { - tmp = data; - data = function() { return tmp; }; - } - - var dataItem = data(); - if (dataItem.text) { - text = dataItem.text; - // if text is not a function we assume it to be a key name - if (!$.isFunction(text)) { - dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available - text = function (item) { return item[dataText]; }; - } - } - - return function (query) { - var t = query.term, filtered = { results: [] }, process; - if (t === "") { - query.callback(data()); - return; - } - - process = function(datum, collection) { - var group, attr; - datum = datum[0]; - if (datum.children) { - group = {}; - for (attr in datum) { - if (datum.hasOwnProperty(attr)) group[attr]=datum[attr]; - } - group.children=[]; - $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); }); - if (group.children.length || query.matcher(t, text(group), datum)) { - collection.push(group); - } - } else { - if (query.matcher(t, text(datum), datum)) { - collection.push(datum); - } - } - }; - - $(data().results).each2(function(i, datum) { process(datum, filtered.results); }); - query.callback(filtered); - }; - } - - // TODO javadoc - function tags(data) { - var isFunc = $.isFunction(data); - return function (query) { - var t = query.term, filtered = {results: []}; - var result = isFunc ? data(query) : data; - if ($.isArray(result)) { - $(result).each(function () { - var isObject = this.text !== undefined, - text = isObject ? this.text : this; - if (t === "" || query.matcher(t, text)) { - filtered.results.push(isObject ? this : {id: this, text: this}); - } - }); - query.callback(filtered); - } - }; - } - - /** - * Checks if the formatter function should be used. - * - * Throws an error if it is not a function. Returns true if it should be used, - * false if no formatting should be performed. - * - * @param formatter - */ - function checkFormatter(formatter, formatterName) { - if ($.isFunction(formatter)) return true; - if (!formatter) return false; - if (typeof(formatter) === 'string') return true; - throw new Error(formatterName +" must be a string, function, or falsy value"); - } - - /** - * Returns a given value - * If given a function, returns its output - * - * @param val string|function - * @param context value of "this" to be passed to function - * @returns {*} - */ - function evaluate(val, context) { - if ($.isFunction(val)) { - var args = Array.prototype.slice.call(arguments, 2); - return val.apply(context, args); - } - return val; - } - - function countResults(results) { - var count = 0; - $.each(results, function(i, item) { - if (item.children) { - count += countResults(item.children); - } else { - count++; - } - }); - return count; - } - - /** - * Default tokenizer. This function uses breaks the input on substring match of any string from the - * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those - * two options have to be defined in order for the tokenizer to work. - * - * @param input text user has typed so far or pasted into the search field - * @param selection currently selected choices - * @param selectCallback function(choice) callback tho add the choice to selection - * @param opts select2's opts - * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value - */ - function defaultTokenizer(input, selection, selectCallback, opts) { - var original = input, // store the original so we can compare and know if we need to tell the search to update its text - dupe = false, // check for whether a token we extracted represents a duplicate selected choice - token, // token - index, // position at which the separator was found - i, l, // looping variables - separator; // the matched separator - - if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined; - - while (true) { - index = -1; - - for (i = 0, l = opts.tokenSeparators.length; i < l; i++) { - separator = opts.tokenSeparators[i]; - index = input.indexOf(separator); - if (index >= 0) break; - } - - if (index < 0) break; // did not find any token separator in the input string, bail - - token = input.substring(0, index); - input = input.substring(index + separator.length); - - if (token.length > 0) { - token = opts.createSearchChoice.call(this, token, selection); - if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { - dupe = false; - for (i = 0, l = selection.length; i < l; i++) { - if (equal(opts.id(token), opts.id(selection[i]))) { - dupe = true; break; - } - } - - if (!dupe) selectCallback(token); - } - } - } - - if (original!==input) return input; - } - - function cleanupJQueryElements() { - var self = this; - - $.each(arguments, function (i, element) { - self[element].remove(); - self[element] = null; - }); - } - - /** - * Creates a new class - * - * @param superClass - * @param methods - */ - function clazz(SuperClass, methods) { - var constructor = function () {}; - constructor.prototype = new SuperClass; - constructor.prototype.constructor = constructor; - constructor.prototype.parent = SuperClass.prototype; - constructor.prototype = $.extend(constructor.prototype, methods); - return constructor; - } - - AbstractSelect2 = clazz(Object, { - - // abstract - bind: function (func) { - var self = this; - return function () { - func.apply(self, arguments); - }; - }, - - // abstract - init: function (opts) { - var results, search, resultsSelector = ".select2-results"; - - // prepare options - this.opts = opts = this.prepareOpts(opts); - - this.id=opts.id; - - // destroy if called on an existing component - if (opts.element.data("select2") !== undefined && - opts.element.data("select2") !== null) { - opts.element.data("select2").destroy(); - } - - this.container = this.createContainer(); - - this.liveRegion = $('.select2-hidden-accessible'); - if (this.liveRegion.length == 0) { - this.liveRegion = $("", { - role: "status", - "aria-live": "polite" - }) - .addClass("select2-hidden-accessible") - .appendTo(document.body); - } - - this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()); - this.containerEventName= this.containerId - .replace(/([.])/g, '_') - .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); - this.container.attr("id", this.containerId); - - this.container.attr("title", opts.element.attr("title")); - - this.body = $(document.body); - - syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); - - this.container.attr("style", opts.element.attr("style")); - this.container.css(evaluate(opts.containerCss, this.opts.element)); - this.container.addClass(evaluate(opts.containerCssClass, this.opts.element)); - - this.elementTabIndex = this.opts.element.attr("tabindex"); - - // swap container for the element - this.opts.element - .data("select2", this) - .attr("tabindex", "-1") - .before(this.container) - .on("click.select2", killEvent); // do not leak click events - - this.container.data("select2", this); - - this.dropdown = this.container.find(".select2-drop"); - - syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass); - - this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element)); - this.dropdown.data("select2", this); - this.dropdown.on("click", killEvent); - - this.results = results = this.container.find(resultsSelector); - this.search = search = this.container.find("input.select2-input"); - - this.queryCount = 0; - this.resultsPage = 0; - this.context = null; - - // initialize the container - this.initContainer(); - - this.container.on("click", killEvent); - - installFilteredMouseMove(this.results); - - this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent)); - this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) { - this._touchEvent = true; - this.highlightUnderEvent(event); - })); - this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved)); - this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved)); - - // Waiting for a click event on touch devices to select option and hide dropdown - // otherwise click will be triggered on an underlying element - this.dropdown.on('click', this.bind(function (event) { - if (this._touchEvent) { - this._touchEvent = false; - this.selectHighlighted(); - } - })); - - installDebouncedScroll(80, this.results); - this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded)); - - // do not propagate change event from the search field out of the component - $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();}); - $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();}); - - // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel - if ($.fn.mousewheel) { - results.mousewheel(function (e, delta, deltaX, deltaY) { - var top = results.scrollTop(); - if (deltaY > 0 && top - deltaY <= 0) { - results.scrollTop(0); - killEvent(e); - } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { - results.scrollTop(results.get(0).scrollHeight - results.height()); - killEvent(e); - } - }); - } - - installKeyUpChangeEvent(search); - search.on("keyup-change input paste", this.bind(this.updateResults)); - search.on("focus", function () { search.addClass("select2-focused"); }); - search.on("blur", function () { search.removeClass("select2-focused");}); - - this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) { - if ($(e.target).closest(".select2-result-selectable").length > 0) { - this.highlightUnderEvent(e); - this.selectHighlighted(e); - } - })); - - // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening - // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's - // dom it will trigger the popup close, which is not what we want - // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal. - this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); }); - - this.lastSearchTerm = undefined; - - if ($.isFunction(this.opts.initSelection)) { - // initialize selection based on the current value of the source element - this.initSelection(); - - // if the user has provided a function that can set selection based on the value of the source element - // we monitor the change event on the element and trigger it, allowing for two way synchronization - this.monitorSource(); - } - - if (opts.maximumInputLength !== null) { - this.search.attr("maxlength", opts.maximumInputLength); - } - - var disabled = opts.element.prop("disabled"); - if (disabled === undefined) disabled = false; - this.enable(!disabled); - - var readonly = opts.element.prop("readonly"); - if (readonly === undefined) readonly = false; - this.readonly(readonly); - - // Calculate size of scrollbar - scrollBarDimensions = scrollBarDimensions || measureScrollbar(); - - this.autofocus = opts.element.prop("autofocus"); - opts.element.prop("autofocus", false); - if (this.autofocus) this.focus(); - - this.search.attr("placeholder", opts.searchInputPlaceholder); - }, - - // abstract - destroy: function () { - var element=this.opts.element, select2 = element.data("select2"), self = this; - - this.close(); - - if (element.length && element[0].detachEvent && self._sync) { - element.each(function () { - if (self._sync) { - this.detachEvent("onpropertychange", self._sync); - } - }); - } - if (this.propertyObserver) { - this.propertyObserver.disconnect(); - this.propertyObserver = null; - } - this._sync = null; - - if (select2 !== undefined) { - select2.container.remove(); - select2.liveRegion.remove(); - select2.dropdown.remove(); - element.removeData("select2") - .off(".select2"); - if (!element.is("input[type='hidden']")) { - element - .show() - .prop("autofocus", this.autofocus || false); - if (this.elementTabIndex) { - element.attr({tabindex: this.elementTabIndex}); - } else { - element.removeAttr("tabindex"); - } - element.show(); - } else { - element.css("display", ""); - } - } - - cleanupJQueryElements.call(this, - "container", - "liveRegion", - "dropdown", - "results", - "search" - ); - }, - - // abstract - optionToData: function(element) { - if (element.is("option")) { - return { - id:element.prop("value"), - text:element.text(), - element: element.get(), - css: element.attr("class"), - disabled: element.prop("disabled"), - locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true) - }; - } else if (element.is("optgroup")) { - return { - text:element.attr("label"), - children:[], - element: element.get(), - css: element.attr("class") - }; - } - }, - - // abstract - prepareOpts: function (opts) { - var element, select, idKey, ajaxUrl, self = this; - - element = opts.element; - - if (element.get(0).tagName.toLowerCase() === "select") { - this.select = select = opts.element; - } - - if (select) { - // these options are not allowed when attached to a select because they are picked up off the element itself - $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { - if (this in opts) { - throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a ", - "
", - " ", - "
    ", - "
", - "
"].join("")); - return container; - }, - - // single - enableInterface: function() { - if (this.parent.enableInterface.apply(this, arguments)) { - this.focusser.prop("disabled", !this.isInterfaceEnabled()); - } - }, - - // single - opening: function () { - var el, range, len; - - if (this.opts.minimumResultsForSearch >= 0) { - this.showSearch(true); - } - - this.parent.opening.apply(this, arguments); - - if (this.showSearchInput !== false) { - // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range - // all other browsers handle this just fine - - this.search.val(this.focusser.val()); - } - if (this.opts.shouldFocusInput(this)) { - this.search.focus(); - // move the cursor to the end after focussing, otherwise it will be at the beginning and - // new text will appear *before* focusser.val() - el = this.search.get(0); - if (el.createTextRange) { - range = el.createTextRange(); - range.collapse(false); - range.select(); - } else if (el.setSelectionRange) { - len = this.search.val().length; - el.setSelectionRange(len, len); - } - } - - this.prefillNextSearchTerm(); - - this.focusser.prop("disabled", true).val(""); - this.updateResults(true); - this.opts.element.trigger($.Event("select2-open")); - }, - - // single - close: function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - - this.focusser.prop("disabled", false); - - if (this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - }, - - // single - focus: function () { - if (this.opened()) { - this.close(); - } else { - this.focusser.prop("disabled", false); - if (this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - } - }, - - // single - isFocused: function () { - return this.container.hasClass("select2-container-active"); - }, - - // single - cancel: function () { - this.parent.cancel.apply(this, arguments); - this.focusser.prop("disabled", false); - - if (this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - }, - - // single - destroy: function() { - $("label[for='" + this.focusser.attr('id') + "']") - .attr('for', this.opts.element.attr("id")); - this.parent.destroy.apply(this, arguments); - - cleanupJQueryElements.call(this, - "selection", - "focusser" - ); - }, - - // single - initContainer: function () { - - var selection, - container = this.container, - dropdown = this.dropdown, - idSuffix = nextUid(), - elementLabel; - - if (this.opts.minimumResultsForSearch < 0) { - this.showSearch(false); - } else { - this.showSearch(true); - } - - this.selection = selection = container.find(".select2-choice"); - - this.focusser = container.find(".select2-focusser"); - - // add aria associations - selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix); - this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix); - this.results.attr("id", "select2-results-"+idSuffix); - this.search.attr("aria-owns", "select2-results-"+idSuffix); - - // rewrite labels from original element to focusser - this.focusser.attr("id", "s2id_autogen"+idSuffix); - - elementLabel = $("label[for='" + this.opts.element.attr("id") + "']"); - this.opts.element.on('focus.select2', this.bind(function () { this.focus(); })); - - this.focusser.prev() - .text(elementLabel.text()) - .attr('for', this.focusser.attr('id')); - - // Ensure the original element retains an accessible name - var originalTitle = this.opts.element.attr("title"); - this.opts.element.attr("title", (originalTitle || elementLabel.text())); - - this.focusser.attr("tabindex", this.elementTabIndex); - - // write label for search field using the label from the focusser element - this.search.attr("id", this.focusser.attr('id') + '_search'); - - this.search.prev() - .text($("label[for='" + this.focusser.attr('id') + "']").text()) - .attr('for', this.search.attr('id')); - - this.search.on("keydown", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - // filter 229 keyCodes (input method editor is processing key input) - if (229 == e.keyCode) return; - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - return; - } - - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.ENTER: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.TAB: - this.selectHighlighted({noFocus: true}); - return; - case KEY.ESC: - this.cancel(e); - killEvent(e); - return; - } - })); - - this.search.on("blur", this.bind(function(e) { - // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown. - // without this the search field loses focus which is annoying - if (document.activeElement === this.body.get(0)) { - window.setTimeout(this.bind(function() { - if (this.opened() && this.results && this.results.length > 1) { - this.search.focus(); - } - }), 0); - } - })); - - this.focusser.on("keydown", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { - return; - } - - if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { - killEvent(e); - return; - } - - if (e.which == KEY.DOWN || e.which == KEY.UP - || (e.which == KEY.ENTER && this.opts.openOnEnter)) { - - if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return; - - this.open(); - killEvent(e); - return; - } - - if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) { - if (this.opts.allowClear) { - this.clear(); - } - killEvent(e); - return; - } - })); - - - installKeyUpChangeEvent(this.focusser); - this.focusser.on("keyup-change input", this.bind(function(e) { - if (this.opts.minimumResultsForSearch >= 0) { - e.stopPropagation(); - if (this.opened()) return; - this.open(); - } - })); - - selection.on("mousedown touchstart", "abbr", this.bind(function (e) { - if (!this.isInterfaceEnabled()) { - return; - } - - this.clear(); - killEventImmediately(e); - this.close(); - - if (this.selection) { - this.selection.focus(); - } - })); - - selection.on("mousedown touchstart", this.bind(function (e) { - // Prevent IE from generating a click event on the body - reinsertElement(selection); - - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - - if (this.opened()) { - this.close(); - } else if (this.isInterfaceEnabled()) { - this.open(); - } - - killEvent(e); - })); - - dropdown.on("mousedown touchstart", this.bind(function() { - if (this.opts.shouldFocusInput(this)) { - this.search.focus(); - } - })); - - selection.on("focus", this.bind(function(e) { - killEvent(e); - })); - - this.focusser.on("focus", this.bind(function(){ - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.container.addClass("select2-container-active"); - })).on("blur", this.bind(function() { - if (!this.opened()) { - this.container.removeClass("select2-container-active"); - this.opts.element.trigger($.Event("select2-blur")); - } - })); - this.search.on("focus", this.bind(function(){ - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.container.addClass("select2-container-active"); - })); - - this.initContainerWidth(); - this.opts.element.hide(); - this.setPlaceholder(); - - }, - - // single - clear: function(triggerChange) { - var data=this.selection.data("select2-data"); - if (data) { // guard against queued quick consecutive clicks - var evt = $.Event("select2-clearing"); - this.opts.element.trigger(evt); - if (evt.isDefaultPrevented()) { - return; - } - var placeholderOption = this.getPlaceholderOption(); - this.opts.element.val(placeholderOption ? placeholderOption.val() : ""); - this.selection.find(".select2-chosen").empty(); - this.selection.removeData("select2-data"); - this.setPlaceholder(); - - if (triggerChange !== false){ - this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); - this.triggerChange({removed:data}); - } - } - }, - - /** - * Sets selection based on source element's value - */ - // single - initSelection: function () { - var selected; - if (this.isPlaceholderOptionSelected()) { - this.updateSelection(null); - this.close(); - this.setPlaceholder(); - } else { - var self = this; - this.opts.initSelection.call(null, this.opts.element, function(selected){ - if (selected !== undefined && selected !== null) { - self.updateSelection(selected); - self.close(); - self.setPlaceholder(); - self.lastSearchTerm = self.search.val(); - } - }); - } - }, - - isPlaceholderOptionSelected: function() { - var placeholderOption; - if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered - return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected")) - || (this.opts.element.val() === "") - || (this.opts.element.val() === undefined) - || (this.opts.element.val() === null); - }, - - // single - prepareOpts: function () { - var opts = this.parent.prepareOpts.apply(this, arguments), - self=this; - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install the selection initializer - opts.initSelection = function (element, callback) { - var selected = element.find("option").filter(function() { return this.selected && !this.disabled }); - // a single select box always has a value, no need to null check 'selected' - callback(self.optionToData(selected)); - }; - } else if ("data" in opts) { - // install default initSelection when applied to hidden input and data is local - opts.initSelection = opts.initSelection || function (element, callback) { - var id = element.val(); - //search in data by id, storing the actual matching item - var match = null; - opts.query({ - matcher: function(term, text, el){ - var is_match = equal(id, opts.id(el)); - if (is_match) { - match = el; - } - return is_match; - }, - callback: !$.isFunction(callback) ? $.noop : function() { - callback(match); - } - }); - }; - } - - return opts; - }, - - // single - getPlaceholder: function() { - // if a placeholder is specified on a single select without a valid placeholder option ignore it - if (this.select) { - if (this.getPlaceholderOption() === undefined) { - return undefined; - } - } - - return this.parent.getPlaceholder.apply(this, arguments); - }, - - // single - setPlaceholder: function () { - var placeholder = this.getPlaceholder(); - - if (this.isPlaceholderOptionSelected() && placeholder !== undefined) { - - // check for a placeholder option if attached to a select - if (this.select && this.getPlaceholderOption() === undefined) return; - - this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder)); - - this.selection.addClass("select2-default"); - - this.container.removeClass("select2-allowclear"); - } - }, - - // single - postprocessResults: function (data, initial, noHighlightUpdate) { - var selected = 0, self = this, showSearchInput = true; - - // find the selected element in the result list - - this.findHighlightableChoices().each2(function (i, elm) { - if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { - selected = i; - return false; - } - }); - - // and highlight it - if (noHighlightUpdate !== false) { - if (initial === true && selected >= 0) { - this.highlight(selected); - } else { - this.highlight(0); - } - } - - // hide the search box if this is the first we got the results and there are enough of them for search - - if (initial === true) { - var min = this.opts.minimumResultsForSearch; - if (min >= 0) { - this.showSearch(countResults(data.results) >= min); - } - } - }, - - // single - showSearch: function(showSearchInput) { - if (this.showSearchInput === showSearchInput) return; - - this.showSearchInput = showSearchInput; - - this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput); - this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput); - //add "select2-with-searchbox" to the container if search box is shown - $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput); - }, - - // single - onSelect: function (data, options) { - - if (!this.triggerSelect(data)) { return; } - - var old = this.opts.element.val(), - oldData = this.data(); - - this.opts.element.val(this.id(data)); - this.updateSelection(data); - - this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data }); - - this.lastSearchTerm = this.search.val(); - this.close(); - - if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - - if (!equal(old, this.id(data))) { - this.triggerChange({ added: data, removed: oldData }); - } - }, - - // single - updateSelection: function (data) { - - var container=this.selection.find(".select2-chosen"), formatted, cssClass; - - this.selection.data("select2-data", data); - - container.empty(); - if (data !== null) { - formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup); - } - if (formatted !== undefined) { - container.append(formatted); - } - cssClass=this.opts.formatSelectionCssClass(data, container); - if (cssClass !== undefined) { - container.addClass(cssClass); - } - - this.selection.removeClass("select2-default"); - - if (this.opts.allowClear && this.getPlaceholder() !== undefined) { - this.container.addClass("select2-allowclear"); - } - }, - - // single - val: function () { - var val, - triggerChange = false, - data = null, - self = this, - oldData = this.data(); - - if (arguments.length === 0) { - return this.opts.element.val(); - } - - val = arguments[0]; - - if (arguments.length > 1) { - triggerChange = arguments[1]; - - if (this.opts.debug && console && console.warn) { - console.warn( - 'Select2: The second option to `select2("val")` is not supported in Select2 4.0.0. ' + - 'The `change` event will always be triggered in 4.0.0.' - ); - } - } - - if (this.select) { - if (this.opts.debug && console && console.warn) { - console.warn( - 'Select2: Setting the value on a ", - " ", - "", - "
", - "
    ", - "
", - "
"].join("")); - return container; - }, - - // multi - prepareOpts: function () { - var opts = this.parent.prepareOpts.apply(this, arguments), - self=this; - - // TODO validate placeholder is a string if specified - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install the selection initializer - opts.initSelection = function (element, callback) { - - var data = []; - - element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) { - data.push(self.optionToData(elm)); - }); - callback(data); - }; - } else if ("data" in opts) { - // install default initSelection when applied to hidden input and data is local - opts.initSelection = opts.initSelection || function (element, callback) { - var ids = splitVal(element.val(), opts.separator, opts.transformVal); - //search in data by array of ids, storing matching items in a list - var matches = []; - opts.query({ - matcher: function(term, text, el){ - var is_match = $.grep(ids, function(id) { - return equal(id, opts.id(el)); - }).length; - if (is_match) { - matches.push(el); - } - return is_match; - }, - callback: !$.isFunction(callback) ? $.noop : function() { - // reorder matches based on the order they appear in the ids array because right now - // they are in the order in which they appear in data array - var ordered = []; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - if (equal(id, opts.id(match))) { - ordered.push(match); - matches.splice(j, 1); - break; - } - } - } - callback(ordered); - } - }); - }; - } - - return opts; - }, - - // multi - selectChoice: function (choice) { - - var selected = this.container.find(".select2-search-choice-focus"); - if (selected.length && choice && choice[0] == selected[0]) { - - } else { - if (selected.length) { - this.opts.element.trigger("choice-deselected", selected); - } - selected.removeClass("select2-search-choice-focus"); - if (choice && choice.length) { - this.close(); - choice.addClass("select2-search-choice-focus"); - this.opts.element.trigger("choice-selected", choice); - } - } - }, - - // multi - destroy: function() { - $("label[for='" + this.search.attr('id') + "']") - .attr('for', this.opts.element.attr("id")); - this.parent.destroy.apply(this, arguments); - - cleanupJQueryElements.call(this, - "searchContainer", - "selection" - ); - }, - - // multi - initContainer: function () { - - var selector = ".select2-choices", selection; - - this.searchContainer = this.container.find(".select2-search-field"); - this.selection = selection = this.container.find(selector); - - var _this = this; - this.selection.on("click", ".select2-container:not(.select2-container-disabled) .select2-search-choice:not(.select2-locked)", function (e) { - _this.search[0].focus(); - _this.selectChoice($(this)); - }); - - // rewrite labels from original element to focusser - this.search.attr("id", "s2id_autogen"+nextUid()); - - this.search.prev() - .text($("label[for='" + this.opts.element.attr("id") + "']").text()) - .attr('for', this.search.attr('id')); - this.opts.element.on('focus.select2', this.bind(function () { this.focus(); })); - - this.search.on("input paste", this.bind(function() { - if (this.search.attr('placeholder') && this.search.val().length == 0) return; - if (!this.isInterfaceEnabled()) return; - if (!this.opened()) { - this.open(); - } - })); - - this.search.attr("tabindex", this.elementTabIndex); - - this.keydowns = 0; - this.search.on("keydown", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - ++this.keydowns; - var selected = selection.find(".select2-search-choice-focus"); - var prev = selected.prev(".select2-search-choice:not(.select2-locked)"); - var next = selected.next(".select2-search-choice:not(.select2-locked)"); - var pos = getCursorInfo(this.search); - - if (selected.length && - (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) { - var selectedChoice = selected; - if (e.which == KEY.LEFT && prev.length) { - selectedChoice = prev; - } - else if (e.which == KEY.RIGHT) { - selectedChoice = next.length ? next : null; - } - else if (e.which === KEY.BACKSPACE) { - if (this.unselect(selected.first())) { - this.search.width(10); - selectedChoice = prev.length ? prev : next; - } - } else if (e.which == KEY.DELETE) { - if (this.unselect(selected.first())) { - this.search.width(10); - selectedChoice = next.length ? next : null; - } - } else if (e.which == KEY.ENTER) { - selectedChoice = null; - } - - this.selectChoice(selectedChoice); - killEvent(e); - if (!selectedChoice || !selectedChoice.length) { - this.open(); - } - return; - } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1) - || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) { - - this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last()); - killEvent(e); - return; - } else { - this.selectChoice(null); - } - - if (this.opened()) { - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.ENTER: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.TAB: - this.selectHighlighted({noFocus:true}); - this.close(); - return; - case KEY.ESC: - this.cancel(e); - killEvent(e); - return; - } - } - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) - || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { - return; - } - - if (e.which === KEY.ENTER) { - if (this.opts.openOnEnter === false) { - return; - } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { - return; - } - } - - this.open(); - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - } - - if (e.which === KEY.ENTER) { - // prevent form from being submitted - killEvent(e); - } - - })); - - this.search.on("keyup", this.bind(function (e) { - this.keydowns = 0; - this.resizeSearch(); - }) - ); - - this.search.on("blur", this.bind(function(e) { - this.container.removeClass("select2-container-active"); - this.search.removeClass("select2-focused"); - this.selectChoice(null); - if (!this.opened()) this.clearSearch(); - e.stopImmediatePropagation(); - this.opts.element.trigger($.Event("select2-blur")); - })); - - this.container.on("click", selector, this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - if ($(e.target).closest(".select2-search-choice").length > 0) { - // clicked inside a select2 search choice, do not open - return; - } - this.selectChoice(null); - this.clearPlaceholder(); - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.open(); - this.focusSearch(); - e.preventDefault(); - })); - - this.container.on("focus", selector, this.bind(function () { - if (!this.isInterfaceEnabled()) return; - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.container.addClass("select2-container-active"); - this.dropdown.addClass("select2-drop-active"); - this.clearPlaceholder(); - })); - - this.initContainerWidth(); - this.opts.element.hide(); - - // set the placeholder if necessary - this.clearSearch(); - }, - - // multi - enableInterface: function() { - if (this.parent.enableInterface.apply(this, arguments)) { - this.search.prop("disabled", !this.isInterfaceEnabled()); - } - }, - - // multi - initSelection: function () { - var data; - if (this.opts.element.val() === "" && this.opts.element.text() === "") { - this.updateSelection([]); - this.close(); - // set the placeholder if necessary - this.clearSearch(); - } - if (this.select || this.opts.element.val() !== "") { - var self = this; - this.opts.initSelection.call(null, this.opts.element, function(data){ - if (data !== undefined && data !== null) { - self.updateSelection(data); - self.close(); - // set the placeholder if necessary - self.clearSearch(); - } - }); - } - }, - - // multi - clearSearch: function () { - var placeholder = this.getPlaceholder(), - maxWidth = this.getMaxSearchWidth(); - - if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { - this.search.val(placeholder).addClass("select2-default"); - // stretch the search box to full width of the container so as much of the placeholder is visible as possible - // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944 - this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width")); - } else { - this.search.val("").width(10); - } - }, - - // multi - clearPlaceholder: function () { - if (this.search.hasClass("select2-default")) { - this.search.val("").removeClass("select2-default"); - } - }, - - // multi - opening: function () { - this.clearPlaceholder(); // should be done before super so placeholder is not used to search - this.resizeSearch(); - - this.parent.opening.apply(this, arguments); - - this.focusSearch(); - - this.prefillNextSearchTerm(); - this.updateResults(true); - - if (this.opts.shouldFocusInput(this)) { - this.search.focus(); - } - this.opts.element.trigger($.Event("select2-open")); - }, - - // multi - close: function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - }, - - // multi - focus: function () { - this.close(); - this.search.focus(); - }, - - // multi - isFocused: function () { - return this.search.hasClass("select2-focused"); - }, - - // multi - updateSelection: function (data) { - var ids = {}, filtered = [], self = this; - - // filter out duplicates - $(data).each(function () { - if (!(self.id(this) in ids)) { - ids[self.id(this)] = 0; - filtered.push(this); - } - }); - - this.selection.find(".select2-search-choice").remove(); - this.addSelectedChoice(filtered); - self.postprocessResults(); - }, - - // multi - tokenize: function() { - var input = this.search.val(); - input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts); - if (input != null && input != undefined) { - this.search.val(input); - if (input.length > 0) { - this.open(); - } - } - - }, - - // multi - onSelect: function (data, options) { - - if (!this.triggerSelect(data) || data.text === "") { return; } - - this.addSelectedChoice(data); - - this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data }); - - // keep track of the search's value before it gets cleared - this.lastSearchTerm = this.search.val(); - - this.clearSearch(); - this.updateResults(); - - if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true); - - if (this.opts.closeOnSelect) { - this.close(); - this.search.width(10); - } else { - if (this.countSelectableResults()>0) { - this.search.width(10); - this.resizeSearch(); - if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) { - // if we reached max selection size repaint the results so choices - // are replaced with the max selection reached message - this.updateResults(true); - } else { - // initializes search's value with nextSearchTerm and update search result - if (this.prefillNextSearchTerm()) { - this.updateResults(); - } - } - this.positionDropdown(); - } else { - // if nothing left to select close - this.close(); - this.search.width(10); - } - } - - // since its not possible to select an element that has already been - // added we do not need to check if this is a new element before firing change - this.triggerChange({ added: data }); - - if (!options || !options.noFocus) - this.focusSearch(); - }, - - // multi - cancel: function () { - this.close(); - this.focusSearch(); - }, - - addSelectedChoice: function (data) { - var val = this.getVal(), self = this; - $(data).each(function () { - val.push(self.createChoice(this)); - }); - this.setVal(val); - }, - - createChoice: function (data) { - var enableChoice = !data.locked, - enabledItem = $( - "
  • " + - "
    " + - " " + - "
  • "), - disabledItem = $( - "
  • " + - "
    " + - "
  • "); - var choice = enableChoice ? enabledItem : disabledItem, - id = this.id(data), - formatted, - cssClass; - - formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup); - if (formatted != undefined) { - choice.find("div").replaceWith($("
    ").html(formatted)); - } - cssClass=this.opts.formatSelectionCssClass(data, choice.find("div")); - if (cssClass != undefined) { - choice.addClass(cssClass); - } - - if(enableChoice){ - choice.find(".select2-search-choice-close") - .on("mousedown", killEvent) - .on("click dblclick", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - this.unselect($(e.target)); - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - killEvent(e); - this.close(); - this.focusSearch(); - })).on("focus", this.bind(function () { - if (!this.isInterfaceEnabled()) return; - this.container.addClass("select2-container-active"); - this.dropdown.addClass("select2-drop-active"); - })); - } - - choice.data("select2-data", data); - choice.insertBefore(this.searchContainer); - - return id; - }, - - // multi - unselect: function (selected) { - var val = this.getVal(), - data, - index; - selected = selected.closest(".select2-search-choice"); - - if (selected.length === 0) { - throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; - } - - data = selected.data("select2-data"); - - if (!data) { - // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued - // and invoked on an element already removed - return; - } - - var evt = $.Event("select2-removing"); - evt.val = this.id(data); - evt.choice = data; - this.opts.element.trigger(evt); - - if (evt.isDefaultPrevented()) { - return false; - } - - while((index = indexOf(this.id(data), val)) >= 0) { - val.splice(index, 1); - this.setVal(val); - if (this.select) this.postprocessResults(); - } - - selected.remove(); - - this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); - this.triggerChange({ removed: data }); - - return true; - }, - - // multi - postprocessResults: function (data, initial, noHighlightUpdate) { - var val = this.getVal(), - choices = this.results.find(".select2-result"), - compound = this.results.find(".select2-result-with-children"), - self = this; - - choices.each2(function (i, choice) { - var id = self.id(choice.data("select2-data")); - if (indexOf(id, val) >= 0) { - choice.addClass("select2-selected"); - // mark all children of the selected parent as selected - choice.find(".select2-result-selectable").addClass("select2-selected"); - } - }); - - compound.each2(function(i, choice) { - // hide an optgroup if it doesn't have any selectable children - if (!choice.is('.select2-result-selectable') - && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) { - choice.addClass("select2-selected"); - } - }); - - if (this.highlight() == -1 && noHighlightUpdate !== false && this.opts.closeOnSelect === true){ - self.highlight(0); - } - - //If all results are chosen render formatNoMatches - if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){ - if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) { - if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) { - this.results.append("
  • " + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "
  • "); - } - } - } - - }, - - // multi - getMaxSearchWidth: function() { - return this.selection.width() - getSideBorderPadding(this.search); - }, - - // multi - resizeSearch: function () { - var minimumWidth, left, maxWidth, containerLeft, searchWidth, - sideBorderPadding = getSideBorderPadding(this.search); - - minimumWidth = measureTextWidth(this.search) + 10; - - left = this.search.offset().left; - - maxWidth = this.selection.width(); - containerLeft = this.selection.offset().left; - - searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; - - if (searchWidth < minimumWidth) { - searchWidth = maxWidth - sideBorderPadding; - } - - if (searchWidth < 40) { - searchWidth = maxWidth - sideBorderPadding; - } - - if (searchWidth <= 0) { - searchWidth = minimumWidth; - } - - this.search.width(Math.floor(searchWidth)); - }, - - // multi - getVal: function () { - var val; - if (this.select) { - val = this.select.val(); - return val === null ? [] : val; - } else { - val = this.opts.element.val(); - return splitVal(val, this.opts.separator, this.opts.transformVal); - } - }, - - // multi - setVal: function (val) { - if (this.select) { - this.select.val(val); - } else { - var unique = [], valMap = {}; - // filter out duplicates - $(val).each(function () { - if (!(this in valMap)) { - unique.push(this); - valMap[this] = 0; - } - }); - this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); - } - }, - - // multi - buildChangeDetails: function (old, current) { - var current = current.slice(0), - old = old.slice(0); - - // remove intersection from each array - for (var i = 0; i < current.length; i++) { - for (var j = 0; j < old.length; j++) { - if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) { - current.splice(i, 1); - i--; - old.splice(j, 1); - break; - } - } - } - - return {added: current, removed: old}; - }, - - - // multi - val: function (val, triggerChange) { - var oldData, self=this; - - if (arguments.length === 0) { - return this.getVal(); - } - - oldData=this.data(); - if (!oldData.length) oldData=[]; - - // val is an id. !val is true for [undefined,null,'',0] - 0 is legal - if (!val && val !== 0) { - this.opts.element.val(""); - this.updateSelection([]); - this.clearSearch(); - if (triggerChange) { - this.triggerChange({added: this.data(), removed: oldData}); - } - return; - } - - // val is a list of ids - this.setVal(val); - - if (this.select) { - this.opts.initSelection(this.select, this.bind(this.updateSelection)); - if (triggerChange) { - this.triggerChange(this.buildChangeDetails(oldData, this.data())); - } - } else { - if (this.opts.initSelection === undefined) { - throw new Error("val() cannot be called if initSelection() is not defined"); - } - - this.opts.initSelection(this.opts.element, function(data){ - var ids=$.map(data, self.id); - self.setVal(ids); - self.updateSelection(data); - self.clearSearch(); - if (triggerChange) { - self.triggerChange(self.buildChangeDetails(oldData, self.data())); - } - }); - } - this.clearSearch(); - }, - - // multi - onSortStart: function() { - if (this.select) { - throw new Error("Sorting of elements is not supported when attached to instead."); - } - - // collapse search field into 0 width so its container can be collapsed as well - this.search.width(0); - // hide the container - this.searchContainer.hide(); - }, - - // multi - onSortEnd:function() { - - var val=[], self=this; - - // show search and move it to the end of the list - this.searchContainer.show(); - // make sure the search container is the last item in the list - this.searchContainer.appendTo(this.searchContainer.parent()); - // since we collapsed the width in dragStarted, we resize it here - this.resizeSearch(); - - // update selection - this.selection.find(".select2-search-choice").each(function() { - val.push(self.opts.id($(this).data("select2-data"))); - }); - this.setVal(val); - this.triggerChange(); - }, - - // multi - data: function(values, triggerChange) { - var self=this, ids, old; - if (arguments.length === 0) { - return this.selection - .children(".select2-search-choice") - .map(function() { return $(this).data("select2-data"); }) - .get(); - } else { - old = this.data(); - if (!values) { values = []; } - ids = $.map(values, function(e) { return self.opts.id(e); }); - this.setVal(ids); - this.updateSelection(values); - this.clearSearch(); - if (triggerChange) { - this.triggerChange(this.buildChangeDetails(old, this.data())); - } - } - } - }); - - $.fn.select2 = function () { - - var args = Array.prototype.slice.call(arguments, 0), - opts, - select2, - method, value, multiple, - allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"], - valueMethods = ["opened", "isFocused", "container", "dropdown"], - propertyMethods = ["val", "data"], - methodsMap = { search: "externalSearch" }; - - this.each(function () { - if (args.length === 0 || typeof(args[0]) === "object") { - opts = args.length === 0 ? {} : $.extend({}, args[0]); - opts.element = $(this); - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - multiple = opts.element.prop("multiple"); - } else { - multiple = opts.multiple || false; - if ("tags" in opts) {opts.multiple = multiple = true;} - } - - select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single(); - select2.init(opts); - } else if (typeof(args[0]) === "string") { - - if (indexOf(args[0], allowedMethods) < 0) { - throw "Unknown method: " + args[0]; - } - - value = undefined; - select2 = $(this).data("select2"); - if (select2 === undefined) return; - - method=args[0]; - - if (method === "container") { - value = select2.container; - } else if (method === "dropdown") { - value = select2.dropdown; - } else { - if (methodsMap[method]) method = methodsMap[method]; - - value = select2[method].apply(select2, args.slice(1)); - } - if (indexOf(args[0], valueMethods) >= 0 - || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) { - return false; // abort the iteration, ready to return first matched value - } - } else { - throw "Invalid arguments to select2 plugin: " + args; - } - }); - return (value === undefined) ? this : value; - }; - - // plugin defaults, accessible to users - $.fn.select2.defaults = { - debug: false, - width: "copy", - loadMorePadding: 0, - closeOnSelect: true, - openOnEnter: true, - containerCss: {}, - dropdownCss: {}, - containerCssClass: "", - dropdownCssClass: "", - formatResult: function(result, container, query, escapeMarkup) { - var markup=[]; - markMatch(this.text(result), query.term, markup, escapeMarkup); - return markup.join(""); - }, - transformVal: function(val) { - return $.trim(val); - }, - formatSelection: function (data, container, escapeMarkup) { - return data ? escapeMarkup(this.text(data)) : undefined; - }, - sortResults: function (results, container, query) { - return results; - }, - formatResultCssClass: function(data) {return data.css;}, - formatSelectionCssClass: function(data, container) {return undefined;}, - minimumResultsForSearch: 0, - minimumInputLength: 0, - maximumInputLength: null, - maximumSelectionSize: 0, - id: function (e) { return e == undefined ? null : e.id; }, - text: function (e) { - if (e && this.data && this.data.text) { - if ($.isFunction(this.data.text)) { - return this.data.text(e); - } else { - return e[this.data.text]; - } - } else { - return e.text; - } - }, - matcher: function(term, text) { - return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0; - }, - separator: ",", - tokenSeparators: [], - tokenizer: defaultTokenizer, - escapeMarkup: defaultEscapeMarkup, - blurOnChange: false, - selectOnBlur: false, - adaptContainerCssClass: function(c) { return c; }, - adaptDropdownCssClass: function(c) { return null; }, - nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; }, - searchInputPlaceholder: '', - createSearchChoicePosition: 'top', - shouldFocusInput: function (instance) { - // Attempt to detect touch devices - var supportsTouchEvents = (('ontouchstart' in window) || - (navigator.msMaxTouchPoints > 0)); - - // Only devices which support touch events should be special cased - if (!supportsTouchEvents) { - return true; - } - - // Never focus the input if search is disabled - if (instance.opts.minimumResultsForSearch < 0) { - return false; - } - - return true; - } - }; - - $.fn.select2.locales = []; - - $.fn.select2.locales['en'] = { - formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; }, - formatNoMatches: function () { return "No matches found"; }, - formatAjaxError: function (jqXHR, textStatus, errorThrown) { return "Loading failed"; }, - formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1 ? "" : "s"); }, - formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); }, - formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, - formatLoadMore: function (pageNumber) { return "Loading more results…"; }, - formatSearching: function () { return "Searching…"; } - }; - - $.extend($.fn.select2.defaults, $.fn.select2.locales['en']); - - $.fn.select2.ajaxDefaults = { - transport: $.ajax, - params: { - type: "GET", - cache: false, - dataType: "json" - } - }; - - // exports - window.Select2 = { - query: { - ajax: ajax, - local: local, - tags: tags - }, util: { - debounce: debounce, - markMatch: markMatch, - escapeMarkup: defaultEscapeMarkup, - stripDiacritics: stripDiacritics - }, "class": { - "abstract": AbstractSelect2, - "single": SingleSelect2, - "multi": MultiSelect2 - } - }; - -}(jQuery));