Switch Admin Settings Lists to Select2.js

- and use jquery.sortable to allow sorting
 - support for autocompletion
This commit is contained in:
Benjamin Kampmann 2014-06-01 16:36:26 +02:00
parent 68600f1dce
commit eb884f9928
5 changed files with 46 additions and 136 deletions

View File

@ -2,81 +2,41 @@
Provide a nice GUI for a pipe-delimited list in the site settings. Provide a nice GUI for a pipe-delimited list in the site settings.
@param settingValue is a reference to SiteSetting.value. @param settingValue is a reference to SiteSetting.value.
@param choices is a reference to SiteSetting.choices
@class Discourse.ListSettingComponent @class Discourse.ListSettingComponent
@extends Ember.Component @extends Ember.Component
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.ListSettingComponent = Ember.Component.extend({ Discourse.ListSettingComponent = Ember.Component.extend({
layoutName: 'components/list-setting', tagName: 'div',
init: function() { didInsertElement: function(){
this._super(); this.$("input").select2({
this.on("focusOut", this.uncacheValue); multiple: false,
this.set('children', []); 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() { refreshSortables: function() {
return this.get('settingValue').split('|'); this.$("ul.select2-choices").sortable().on('sortupdate', function() {
}.property('settingValue'), this.$("input").select2("onSortEnd");
}.bind(this));
/**
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();
});
}
} }
}); });

View File

@ -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'));
}
});

View File

@ -2,7 +2,7 @@
<h3>{{unbound setting}}</h3> <h3>{{unbound setting}}</h3>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{list-setting settingValue=value}} {{list-setting settingValue=value choices=choices}}
<div class='desc'>{{unbound description}}</div> <div class='desc'>{{unbound description}}</div>
</div> </div>
{{#if dirty}} {{#if dirty}}

View File

@ -1,8 +1,3 @@
<div class="ac-wrap clearfix input-setting-list"> <div class="input-setting-list">
{{#each readValues}} <input type="text" value="{{unbound settingValue}}">
{{list-setting-item parent=view action=update classNames="list-input-item"}}
{{/each}}
{{#if canAddNew}}
<button class="btn no-text list-add-value" {{action addNewItem}}><i class="fa fa-plus"></i></button>
{{/if}}
</div> </div>

View File

@ -205,30 +205,29 @@
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s; -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s; transition: border linear 0.2s, box-shadow linear 0.2s;
.list-input-item { li.select2-search-choice {
width: 90px; cursor: pointer;
margin: 2px 1px; .select2-search-choice-close {
background-color: $secondary; content: "x"
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;
} }
} }
.btn.list-add-value { li.sortable-placeholder {
margin: 0px 3px; padding: 3px 5px 3px 18px;
padding: 4px 10px; margin: 3px 0px 3px 5px;
color: $tertiary; 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 { .desc {
padding-top: 3px; padding-top: 3px;