From eb884f992897274bf529371c982c9c33a3869266 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Sun, 1 Jun 2014 16:36:26 +0200 Subject: [PATCH] Switch Admin Settings Lists to Select2.js - and use jquery.sortable to allow sorting - support for autocompletion --- .../components/list_setting_component.js | 90 ++++++------------- .../components/list_setting_item_component.js | 44 --------- .../site_settings/setting_list.js.handlebars | 2 +- .../components/list-setting.js.handlebars | 9 +- .../stylesheets/common/admin/admin_base.scss | 37 ++++---- 5 files changed, 46 insertions(+), 136 deletions(-) delete mode 100644 app/assets/javascripts/admin/components/list_setting_item_component.js diff --git a/app/assets/javascripts/admin/components/list_setting_component.js b/app/assets/javascripts/admin/components/list_setting_component.js index 9f39d46ce6f..4676e0905a5 100644 --- a/app/assets/javascripts/admin/components/list_setting_component.js +++ b/app/assets/javascripts/admin/components/list_setting_component.js @@ -2,81 +2,41 @@ Provide a nice GUI for a pipe-delimited list in the site settings. @param settingValue is a reference to SiteSetting.value. + @param choices is a reference to SiteSetting.choices @class Discourse.ListSettingComponent @extends Ember.Component @namespace Discourse @module Discourse **/ + Discourse.ListSettingComponent = Ember.Component.extend({ - layoutName: 'components/list-setting', + tagName: 'div', - init: function() { - this._super(); - this.on("focusOut", this.uncacheValue); - this.set('children', []); + didInsertElement: function(){ + this.$("input").select2({ + multiple: false, + separator: "|", + tokenSeparators: [",", " ", "|"], + tags : this.get("choices") || [], + width: 'off' + }).on("change", function(obj) { + this.set("settingValue", obj.val.join("|")); + this.refreshSortables(); + }.bind(this)); + + this.refreshSortables(); }, - canAddNew: true, + refreshOnReset: function() { + this.$("input").select2("val", this.get("settingValue").split("|")); + }.observes("settingValue"), - readValues: function() { - return this.get('settingValue').split('|'); - }.property('settingValue'), - - /** - Transfer the debounced value into the settingValue parameter. - - This will cause a redraw of the child textboxes. - - @param newFocus {Number|undefined} Which list index to focus on next, or undefined to not refocus - **/ - uncacheValue: function(newFocus) { - var oldValue = this.get('settingValue'), - newValue = this.get('settingValueCached'), - self = this; - - if (newValue !== undefined && newValue !== oldValue) { - this.set('settingValue', newValue); - } - - if (newFocus !== undefined && newFocus > 0) { - Em.run.schedule('afterRender', function() { - var children = self.get('children'); - if (newFocus < children.length) { - $(children[newFocus].get('element')).focus(); - } else if (newFocus === children.length) { - $(self.get('element')).children().children('.list-add-value').focus(); - } - }); - } - }, - - setItemValue: function(index, item) { - var values = this.get('readValues'); - values[index] = item; - - // Remove blank items - values = values.filter(function(s) { return s !== ''; }); - this.setProperties({ - settingValueCached: values.join('|'), - canAddNew: true - }); - }, - - actions: { - addNewItem: function() { - var newValue = this.get('settingValue') + '|'; - this.setProperties({ - settingValue: newValue, - settingValueCached: newValue, - canAddNew: false - }); - - var self = this; - Em.run.schedule('afterRender', function() { - var children = self.get('children'); - $(children[children.length - 1].get('element')).focus(); - }); - } + refreshSortables: function() { + this.$("ul.select2-choices").sortable().on('sortupdate', function() { + this.$("input").select2("onSortEnd"); + }.bind(this)); } }); + + diff --git a/app/assets/javascripts/admin/components/list_setting_item_component.js b/app/assets/javascripts/admin/components/list_setting_item_component.js deleted file mode 100644 index fac5f339b87..00000000000 --- a/app/assets/javascripts/admin/components/list_setting_item_component.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - One item in a ListSetting. - - @param parent is the ListSettingComponent. - - @class Discourse.ListSettingItemComponent - @extends Ember.Component, Ember.TextSupport - @namespace Discourse - @module Discourse - **/ -Discourse.ListSettingItemComponent = Ember.Component.extend(Ember.TextSupport, { - classNames: ['ember-text-field'], - tagName: "input", - attributeBindings: ['type', 'value', 'size', 'pattern'], - - _initialize: function() { - // _parentView is the #each - // parent is the ListSettingComponent - this.setProperties({ - value: this.get('_parentView.content'), - index: this.get('_parentView.contentIndex') - }); - this.get('parent').get('children')[this.get('index')] = this; - }.on('init'), - - markTab: function(e) { - var keyCode = e.keyCode || e.which; - - if (keyCode === 9) { - this.set('nextIndex', this.get('index') + (e.shiftKey ? -1 : 1)); - } - }.on('keyDown'), - - reloadList: function() { - var nextIndex = this.get('nextIndex'); - this.set('nextIndex', undefined); // one use only - this.get('parent').uncacheValue(nextIndex); - }.on('focusOut'), - - _elementValueDidChange: function() { - this._super(); - this.get('parent').setItemValue(this.get('index'), this.get('value')); - } -}); diff --git a/app/assets/javascripts/admin/templates/site_settings/setting_list.js.handlebars b/app/assets/javascripts/admin/templates/site_settings/setting_list.js.handlebars index 29bb00c7078..6fadce5e2c2 100644 --- a/app/assets/javascripts/admin/templates/site_settings/setting_list.js.handlebars +++ b/app/assets/javascripts/admin/templates/site_settings/setting_list.js.handlebars @@ -2,7 +2,7 @@

{{unbound setting}}

- {{list-setting settingValue=value}} + {{list-setting settingValue=value choices=choices}}
{{unbound description}}
{{#if dirty}} diff --git a/app/assets/javascripts/discourse/templates/components/list-setting.js.handlebars b/app/assets/javascripts/discourse/templates/components/list-setting.js.handlebars index ddf8dcabe39..830982fc474 100644 --- a/app/assets/javascripts/discourse/templates/components/list-setting.js.handlebars +++ b/app/assets/javascripts/discourse/templates/components/list-setting.js.handlebars @@ -1,8 +1,3 @@ -
- {{#each readValues}} - {{list-setting-item parent=view action=update classNames="list-input-item"}} - {{/each}} - {{#if canAddNew}} - - {{/if}} +
+
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 1f279b02877..58b98903953 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -205,30 +205,29 @@ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; transition: border linear 0.2s, box-shadow linear 0.2s; - .list-input-item { - width: 90px; - margin: 2px 1px; - background-color: $secondary; - border: 1px solid scale-color-diff(); - border-radius: 3px; - box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3); - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; - - &:focus { - border-color: $tertiary; - outline: 0; - box-shadow: inset 0 1px 1px rgba(51, 51, 51, 0.3), 0 0 8px $tertiary; + li.select2-search-choice { + cursor: pointer; + .select2-search-choice-close { + content: "x" } } - .btn.list-add-value { - margin: 0px 3px; - padding: 4px 10px; - color: $tertiary; + li.sortable-placeholder { + padding: 3px 5px 3px 18px; + margin: 3px 0px 3px 5px; + position: relative; + line-height: 13px; + cursor: default; + border: 1px dashed #AAA; + border-radius: 3px; + background-clip: padding-box; + -moz-user-select: none; + background-color: none; + width: 3em; + height: 1em; } - } + } .desc { padding-top: 3px;