diff --git a/.eslintrc b/.eslintrc index 71e21b4381a..6f7c9f0ecdd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -43,11 +43,11 @@ "asyncRender":true, "selectDropdown":true, "selectBox":true, - "expandSelectBox":true, - "collapseSelectBox":true, - "selectBoxSelectRow":true, - "selectBoxSelectNoneRow":true, - "selectBoxFillInFilter":true, + "expandSelectBoxKit":true, + "collapseSelectBoxKit":true, + "selectBoxKitSelectRow":true, + "selectBoxKitSelectNoneRow":true, + "selectBoxKitFillInFilter":true, "asyncTestDiscourse":true, "fixture":true, "find":true, diff --git a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 b/app/assets/javascripts/admin/components/admin-group-selector.js.es6 deleted file mode 100644 index fd487640d0e..00000000000 --- a/app/assets/javascripts/admin/components/admin-group-selector.js.es6 +++ /dev/null @@ -1,43 +0,0 @@ -export default Ember.Component.extend({ - tagName: 'div', - - _init: function(){ - this.$("input").select2({ - multiple: true, - width: '100%', - query: function(opts) { - opts.callback({ - results: this.get("available").filter(function(o) { - return -1 !== o.name.toLowerCase().indexOf(opts.term.toLowerCase()); - }).map(this._format) - }); - }.bind(this) - }).on("change", function(evt) { - if (evt.added){ - this.triggerAction({ - action: "groupAdded", - actionContext: this.get("available").findBy("id", evt.added.id) - }); - } else if (evt.removed) { - this.triggerAction({ - action:"groupRemoved", - actionContext: evt.removed.id - }); - } - }.bind(this)); - - this._refreshOnReset(); - }.on("didInsertElement"), - - _format(item) { - return { - "text": item.name, - "id": item.id, - "locked": item.automatic - }; - }, - - _refreshOnReset: function() { - this.$("input").select2("data", this.get("selected").map(this._format)); - }.observes("selected") -}); diff --git a/app/assets/javascripts/admin/components/list-setting.js.es6 b/app/assets/javascripts/admin/components/list-setting.js.es6 deleted file mode 100644 index 9a1d865133d..00000000000 --- a/app/assets/javascripts/admin/components/list-setting.js.es6 +++ /dev/null @@ -1,52 +0,0 @@ -/** - 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 -**/ -export default Ember.Component.extend({ - - _select2FormatSelection: function(selectedObject, jqueryWrapper, htmlEscaper) { - var text = selectedObject.text; - if (text.length <= 6) { - jqueryWrapper.closest('li.select2-search-choice').css({"border-bottom": '7px solid #'+text}); - } - return htmlEscaper(text); - }, - - _initializeSelect2: function(){ - var options = { - multiple: false, - separator: "|", - tokenSeparators: ["|"], - tags : this.get("choices") || [], - width: 'off', - dropdownCss: this.get("choices") ? {} : {display: 'none'}, - selectOnBlur: this.get("choices") ? false : true - }; - - var settingName = this.get('settingName'); - if (typeof settingName === 'string' && settingName.indexOf('colors') > -1) { - options.formatSelection = this._select2FormatSelection; - } - - var self = this; - this.$("input").select2(options).on("change", function(obj) { - self.set("settingValue", obj.val.join("|")); - self.refreshSortables(); - }); - - this.refreshSortables(); - }.on('didInsertElement'), - - refreshOnReset: function() { - this.$("input").select2("val", this.get("settingValue").split("|")); - }.observes("settingValue"), - - refreshSortables: function() { - var self = this; - this.$("ul.select2-choices").sortable().on('sortupdate', function() { - self.$("input").select2("onSortEnd"); - }); - } -}); diff --git a/app/assets/javascripts/discourse/templates/components/admin-group-selector.hbs b/app/assets/javascripts/discourse/templates/components/admin-group-selector.hbs deleted file mode 100644 index 4856de5179a..00000000000 --- a/app/assets/javascripts/discourse/templates/components/admin-group-selector.hbs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/assets/javascripts/discourse/templates/components/list-setting.hbs b/app/assets/javascripts/discourse/templates/components/list-setting.hbs deleted file mode 100644 index 830982fc474..00000000000 --- a/app/assets/javascripts/discourse/templates/components/list-setting.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/app/assets/javascripts/select-box-kit/components/admin-group-selector.js.es6 b/app/assets/javascripts/select-box-kit/components/admin-group-selector.js.es6 new file mode 100644 index 00000000000..08248e72bda --- /dev/null +++ b/app/assets/javascripts/select-box-kit/components/admin-group-selector.js.es6 @@ -0,0 +1,45 @@ +import MultiComboBoxComponent from "select-box-kit/components/multi-combo-box"; + +export default MultiComboBoxComponent.extend({ + classNames: "admin-group-selector", + selected: null, + available: null, + allowAny: false, + + didReceiveAttrs() { + this._super(); + + this.set("value", this.get("selected").map(s => this._valueForContent(s))); + this.set("content", this.get("available")); + }, + + formatRowContent(content) { + let formatedContent = this._super(content); + formatedContent.locked = content.automatic; + return formatedContent; + }, + + didUpdateAttrs() { + this._super(); + + this.set("highlightedValue", null); + Ember.run.schedule("afterRender", () => { + this.autoHighlightFunction(); + }); + }, + + selectValuesFunction(values) { + values.forEach(value => { + this.triggerAction({ + action: "groupAdded", + actionContext: this.get("content").findBy("id", parseInt(value, 10)) + }); + }); + }, + + deselectValuesFunction(values) { + values.forEach(value => { + this.triggerAction({ action: "groupRemoved", actionContext: value }); + }); + } +}); diff --git a/app/assets/javascripts/select-box-kit/components/categories-admin-dropdown.js.es6 b/app/assets/javascripts/select-box-kit/components/categories-admin-dropdown.js.es6 index 61ffa1c2253..187a710b880 100644 --- a/app/assets/javascripts/select-box-kit/components/categories-admin-dropdown.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/categories-admin-dropdown.js.es6 @@ -8,10 +8,10 @@ export default DropdownSelectBoxComponent.extend({ @on("didReceiveAttrs") _setComponentOptions() { - this.set("headerComponentOptions", Ember.Object.create({ + this.get("headerComponentOptions").setProperties({ shouldDisplaySelectedName: false, icon: `${iconHTML('bars')}${iconHTML('caret-down')}`.htmlSafe(), - })); + }); }, @computed @@ -38,11 +38,8 @@ export default DropdownSelectBoxComponent.extend({ return items; }, - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); - this.get(value)(); - this.set("value", null); - } + selectValueFunction(value) { + this.get(value)(); + this.set("value", null); } }); diff --git a/app/assets/javascripts/select-box-kit/components/category-chooser.js.es6 b/app/assets/javascripts/select-box-kit/components/category-chooser.js.es6 index ee6e9b158ab..668f168b38c 100644 --- a/app/assets/javascripts/select-box-kit/components/category-chooser.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/category-chooser.js.es6 @@ -12,24 +12,24 @@ export default ComboBoxComponent.extend({ castInteger: true, allowUncategorized: null, - filterFunction(computedContent) { - const _matchFunction = (filter, text) => { - return text.toLowerCase().indexOf(filter) > -1; - }; + filteredContentFunction(computedContent, computedValue, filter) { + if (isEmpty(filter)) { return computedContent; } - return (selectBox) => { - const filter = selectBox.get("filter").toLowerCase(); - return _.filter(computedContent, c => { - const category = Category.findById(get(c, "value")); - const text = get(c, "name"); - if (category && category.get("parentCategory")) { - const categoryName = category.get("parentCategory.name"); - return _matchFunction(filter, text) || _matchFunction(filter, categoryName); - } else { - return _matchFunction(filter, text); - } - }); + const _matchFunction = (f, text) => { + return text.toLowerCase().indexOf(f) > -1; }; + const lowerFilter = filter.toLowerCase(); + + return computedContent.filter(c => { + const category = Category.findById(get(c, "value")); + const text = get(c, "name"); + if (category && category.get("parentCategory")) { + const categoryName = category.get("parentCategory.name"); + return _matchFunction(lowerFilter, text) || _matchFunction(lowerFilter, categoryName); + } else { + return _matchFunction(lowerFilter, text); + } + }); }, @computed("rootNone", "rootNoneLabel") diff --git a/app/assets/javascripts/select-box-kit/components/category-notifications-button.js.es6 b/app/assets/javascripts/select-box-kit/components/category-notifications-button.js.es6 index d022c3c64cd..05908840069 100644 --- a/app/assets/javascripts/select-box-kit/components/category-notifications-button.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/category-notifications-button.js.es6 @@ -7,11 +7,7 @@ export default NotificationOptionsComponent.extend({ value: Ember.computed.alias("category.notification_level"), headerComponent: "category-notifications-button/category-notifications-button-header", - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); - this.get("category").setNotification(value); - this.blur(); - } + selectValueFunction(value) { + this.get("category").setNotification(value); } }); diff --git a/app/assets/javascripts/select-box-kit/components/combo-box.js.es6 b/app/assets/javascripts/select-box-kit/components/combo-box.js.es6 index f5ff7d03058..26779a38949 100644 --- a/app/assets/javascripts/select-box-kit/components/combo-box.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/combo-box.js.es6 @@ -12,10 +12,10 @@ export default SelectBoxKitComponent.extend({ @on("didReceiveAttrs") _setComboBoxOptions() { - this.set("headerComponentOptions", Ember.Object.create({ + this.get("headerComponentOptions").setProperties({ caretUpIcon: this.get("caretUpIcon"), caretDownIcon: this.get("caretDownIcon"), clearable: this.get("clearable"), - })); + }); } }); diff --git a/app/assets/javascripts/select-box-kit/components/dropdown-select-box.js.es6 b/app/assets/javascripts/select-box-kit/components/dropdown-select-box.js.es6 index 8e2789aa1db..252ff2dd646 100644 --- a/app/assets/javascripts/select-box-kit/components/dropdown-select-box.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/dropdown-select-box.js.es6 @@ -9,16 +9,11 @@ export default SelectBoxKitComponent.extend({ headerComponent: "dropdown-select-box/dropdown-select-box-header", rowComponent: "dropdown-select-box/dropdown-select-box-row", - clickOutside() { - this.close(); - }, + clickOutside() { this.close(); }, - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); - this.set("value", value); + didSelectValue() { + this._super(); - this.blur(); - } + this.blur(); } }); diff --git a/app/assets/javascripts/select-box-kit/components/group-notifications-button.js.es6 b/app/assets/javascripts/select-box-kit/components/group-notifications-button.js.es6 index 81477d2e476..e30b4ec62fc 100644 --- a/app/assets/javascripts/select-box-kit/components/group-notifications-button.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/group-notifications-button.js.es6 @@ -5,10 +5,7 @@ export default NotificationOptionsComponent.extend({ value: Ember.computed.alias("group.group_user.notification_level"), i18nPrefix: "groups.notifications", - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); - this.get("group").setNotification(value, this.get("user.id")); - } + selectValueFunction(value) { + this.get("group").setNotification(value, this.get("user.id")); } }); diff --git a/app/assets/javascripts/select-box-kit/components/list-setting.js.es6 b/app/assets/javascripts/select-box-kit/components/list-setting.js.es6 new file mode 100644 index 00000000000..94fa2fa66f2 --- /dev/null +++ b/app/assets/javascripts/select-box-kit/components/list-setting.js.es6 @@ -0,0 +1,55 @@ +import MultiComboBoxComponent from "select-box-kit/components/multi-combo-box"; +import { observes } from 'ember-addons/ember-computed-decorators'; + +export default MultiComboBoxComponent.extend({ + classNames: "list-setting", + tokenSeparator: "|", + settingValue: "", + choices: null, + filterable: true, + + init() { + const valuesFromString = this.get("settingValue").split(this.get("tokenSeparator")); + this.set("value", valuesFromString.reject(v => Ember.isEmpty(v))); + + if (Ember.isNone(this.get("choices"))) { + this.set("content", valuesFromString); + } else { + this.set("content", this.get("choices")); + } + + if (!Ember.isNone(this.get("settingName"))) { + this.set("nameProperty", this.get("settingName")); + } + + if (Ember.isEmpty(this.get("content"))) { + this.set("rowComponent", null); + this.set("noContentLabel", null); + } + + this._super(); + + if (this.get("nameProperty").indexOf("color") > -1) { + this.set("headerComponentOptions", Ember.Object.create({ + selectedNameComponent: "multi-combo-box/selected-color" + })); + } + }, + + @observes("value.[]") + setSettingValue() { + this.set("settingValue", this.get("value").join(this.get("tokenSeparator"))); + }, + + @observes("content.[]") + setChoices() { this.set("choices", this.get("content")); }, + + _handleTabOnKeyDown(event) { + if (this.$highlightedRow().length === 1) { + this._super(event); + } else { + this.close(); + return false; + } + } +}); diff --git a/app/assets/javascripts/select-box-kit/components/multi-combo-box.js.es6 b/app/assets/javascripts/select-box-kit/components/multi-combo-box.js.es6 index 6ce70b4e8e2..bf08d7da1f1 100644 --- a/app/assets/javascripts/select-box-kit/components/multi-combo-box.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/multi-combo-box.js.es6 @@ -1,20 +1,32 @@ -// Experimental import SelectBoxKitComponent from "select-box-kit/components/select-box-kit"; import computed from "ember-addons/ember-computed-decorators"; -const { get, isNone } = Ember; +const { get, isNone, isEmpty } = Ember; export default SelectBoxKitComponent.extend({ - classNames: "multi-combobox", + classNames: "multi-combo-box", headerComponent: "multi-combo-box/multi-combo-box-header", filterComponent: null, headerText: "select_box.default_header_text", - value: [], allowAny: true, + allowValueMutation: false, + autoSelectFirst: false, + autoFilterable: true, + selectedNameComponent: "multi-combo-box/selected-name", + + init() { + this._super(); + + if (isNone(this.get("value"))) { this.set("value", []); } + + this.set("headerComponentOptions", Ember.Object.create({ + selectedNameComponent: this.get("selectedNameComponent") + })); + }, @computed("filter") templateForCreateRow() { return (rowComponent) => { - return `Create: ${rowComponent.get("content.name")}`; + return I18n.t("select_box.create", { content: rowComponent.get("content.name")}); }; }, @@ -22,78 +34,207 @@ export default SelectBoxKitComponent.extend({ const keyCode = event.keyCode || event.which; const $filterInput = this.$filterInput(); - if (keyCode === 8) { - let $lastSelectedValue = $(this.$(".choices .selected-name").last()); + if (this.get("isFocused") === true && this.get("isExpanded") === false && keyCode === this.keys.BACKSPACE) { + this.expand(); + return; + } - if ($lastSelectedValue.is(":focus") || $(document.activeElement).is($lastSelectedValue)) { - this.send("onDeselect", $lastSelectedValue.data("value")); + // select all choices + if (event.metaKey === true && keyCode === 65 && isEmpty(this.get("filter"))) { + this.$(".choices .selected-name:not(.is-locked)").addClass("is-highlighted"); + return; + } + + // clear selection when multiple + if (Ember.isEmpty(this.get("filter")) && this.$(".selected-name.is-highlighted").length >= 1 && keyCode === this.keys.BACKSPACE) { + const highlightedValues = []; + $.each(this.$(".selected-name.is-highlighted"), (i, el) => { + highlightedValues.push($(el).attr("data-value")); + }); + + this.send("onDeselect", highlightedValues); + return; + } + + // try to remove last item from the list + if (Ember.isEmpty(this.get("filter")) && keyCode === this.keys.BACKSPACE) { + let $lastSelectedValue = $(this.$(".choices .selected-name:not(.is-locked)").last()); + + if ($lastSelectedValue.length === 0) { return; } + + if ($lastSelectedValue.hasClass("is-highlighted") || $(document.activeElement).is($lastSelectedValue)) { + this.send("onDeselect", this.get("selectedContent.lastObject.value")); $filterInput.focus(); return; } + if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) { + $lastSelectedValue.click(); + return false; + } + if ($filterInput.val() === "") { if ($filterInput.is(":focus")) { - if ($lastSelectedValue.length > 0) { - $lastSelectedValue.focus(); - } + if ($lastSelectedValue.length > 0) { $lastSelectedValue.click(); } } else { if ($lastSelectedValue.length > 0) { - $lastSelectedValue.focus(); + $lastSelectedValue.click(); } else { $filterInput.focus(); } } } - } else { - $filterInput.focus(); - this._super(event); - } - }, - - @computed("none") - computedNone(none) { - if (!isNone(none)) { - this.set("none", { name: I18n.t(none), value: "" }); } }, @computed("value.[]") - computedValue(value) { - return value.map(v => this._castInteger(v)); - }, + computedValue(value) { return value.map(v => this._castInteger(v)); }, - @computed("computedValue.[]", "computedContent.[]") - selectedContent(computedValue, computedContent) { + @computed("value.[]", "computedContent.[]") + selectedContent(value, computedContent) { const contents = []; - computedValue.forEach(cv => { - contents.push(computedContent.findBy("value", cv)); + value.forEach(v => { + const content = computedContent.findBy("value", v); + if (!isNone(content)) { contents.push(content); } }); return contents; }, - filterFunction(content) { - return (selectBox, computedValue) => { - const filter = selectBox.get("filter").toLowerCase(); - return _.filter(content, c => { - return !computedValue.includes(get(c, "value")) && - get(c, "name").toLowerCase().indexOf(filter) > -1; - }); - }; + filteredContentFunction(computedContent, computedValue, filter) { + computedContent = computedContent.filter(c => { + return !computedValue.includes(get(c, "value")); + }); + + if (isEmpty(filter)) { return computedContent; } + + const lowerFilter = filter.toLowerCase(); + return computedContent.filter(c => { + return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1; + }); + }, + + willCreateContent() { + this.set("highlightedValue", null); + }, + + didCreateContent() { + this.clearFilter(); + this.autoHighlightFunction(); + }, + + createContentFunction(input) { + if (!this.get("content").includes(input)) { + this.get("content").pushObject(input); + this.get("value").pushObject(input); + } + }, + + deselectValuesFunction(values) { + const contents = this._computeRemovableContentsForValues(values); + this.get("value").removeObjects(values); + this.get("content").removeObjects(contents); + }, + + highlightValueFunction(value) { + this.set("highlightedValue", value); + }, + + selectValuesFunction(values) { + this.get("value").pushObjects(values); + }, + + willSelectValues() { + this.expand(); + this.set("highlightedValue", null); + }, + + didSelectValues() { + this.focus(); + this.clearFilter(); + this.autoHighlightFunction(); + }, + + willDeselectValues() { + this.set("highlightedValue", null); + }, + + didDeselectValues() { + this.autoHighlightFunction(); + }, + + willHighlightValue() {}, + + didHighlightValue() {}, + + autoHighlightFunction() { + Ember.run.schedule("afterRender", () => { + if (this.get("isExpanded") === false) { return; } + if (this.get("renderedBodyOnce") === false) { return; } + if (!isNone(this.get("highlightedValue"))) { return; } + + if (isEmpty(this.get("filteredContent"))) { + if (!isEmpty(this.get("filter"))) { + this.send("onHighlight", this.get("filter")); + } else if (this.get("none") && !isEmpty(this.get("selectedContent"))) { + this.send("onHighlight", this.noneValue); + } + } else { + this.send("onHighlight", this.get("filteredContent.firstObject.value")); + } + }); }, actions: { onClearSelection() { - this.send("onSelect", []); + const values = this.get("selectedContent").map(c => get(c, "value")); + this.send("onDeselect", values); }, - onSelect(value) { - this.setProperties({ filter: "", highlightedValue: null }); - this.get("value").pushObject(value); + onHighlight(value) { + value = this._originalValueForValue(value); + this.willHighlightValue(value); + this.set("highlightedValue", value); + this.highlightValueFunction(value); + this.didHighlightValue(value); }, - onDeselect(value) { - this.defaultOnDeselect(value); - this.get("value").removeObject(value); + onCreateContent(input) { + this.willCreateContent(input); + this.createContentFunction(input); + this.didCreateContent(input); + }, + + onSelect(values) { + values = Ember.makeArray(values).map(v => this._originalValueForValue(v)); + this.willSelectValues(values); + this.selectValuesFunction(values); + this.didSelectValues(values); + }, + + onDeselect(values) { + values = Ember.makeArray(this._computeRemovableValues(values)); + this.willDeselectValues(values); + this.deselectValuesFunction(values); + this.didSelectValues(values); } + }, + + _computeRemovableContentsForValues(values) { + const removableContents = []; + values.forEach(v => { + if (!this.get("_initialValues").includes(v)) { + const content = this._contentForValue(v); + if (!isNone(content)) { removableContents.push(content); } + } + }); + return removableContents; + }, + + _computeRemovableValues(values) { + return Ember.makeArray(values) + .map(v => this._originalValueForValue(v)) + .filter(v => { + return get(this._computedContentForValue(v), "locked") !== true; + }); } }); diff --git a/app/assets/javascripts/select-box-kit/components/multi-combo-box/multi-combo-box-header.js.es6 b/app/assets/javascripts/select-box-kit/components/multi-combo-box/multi-combo-box-header.js.es6 index 0b5f302e7b1..10e2d299ceb 100644 --- a/app/assets/javascripts/select-box-kit/components/multi-combo-box/multi-combo-box-header.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/multi-combo-box/multi-combo-box-header.js.es6 @@ -4,37 +4,27 @@ import SelectBoxKitHeaderComponent from "select-box-kit/components/select-box-ki export default SelectBoxKitHeaderComponent.extend({ attributeBindings: ["names:data-name"], - classNames: "multi-combobox-header", + classNames: "multi-combo-box-header", layoutName: "select-box-kit/templates/components/multi-combo-box/multi-combo-box-header", - - @computed("filter", "selectedContent.[]", "isFocused", "selectBoxIsExpanded") - shouldDisplayFilterPlaceholder(filter, selectedContent, isFocused) { - if (Ember.isEmpty(selectedContent)) { - if (filter.length > 0) { return false; } - if (isFocused === true) { return false; } - return true; - } - - return false; - }, + selectedNameComponent: Ember.computed.alias("options.selectedNameComponent"), @on("didRender") _positionFilter() { - this.$(".filter").width(0); + if (this.get("shouldDisplayFilter") === false) { return; } + + const $filter = this.$(".filter"); + $filter.width(0); const leftHeaderOffset = this.$().offset().left; - const leftFilterOffset = this.$(".filter").offset().left; + const leftFilterOffset = $filter.offset().left; const offset = leftFilterOffset - leftHeaderOffset; const width = this.$().outerWidth(false); const availableSpace = width - offset; - - // TODO: avoid magic number 8 - // TODO: make sure the filter doesn’t end up being very small - this.$(".filter").width(availableSpace - 8); + const $choices = $filter.parent(".choices"); + const parentRightPadding = parseInt($choices.css("padding-right") , 10); + $filter.width(availableSpace - parentRightPadding * 4); }, @computed("selectedContent.[]") - names(selectedContent) { - return selectedContent.map(sc => sc.name).join(","); - } + names(selectedContent) { return selectedContent.map(sc => sc.name).join(","); } }); diff --git a/app/assets/javascripts/select-box-kit/components/multi-combo-box/selected-color.js.es6 b/app/assets/javascripts/select-box-kit/components/multi-combo-box/selected-color.js.es6 new file mode 100644 index 00000000000..240c3570d9e --- /dev/null +++ b/app/assets/javascripts/select-box-kit/components/multi-combo-box/selected-color.js.es6 @@ -0,0 +1,8 @@ +import SelectedNameComponent from "select-box-kit/components/multi-combo-box/selected-name"; + +export default SelectedNameComponent.extend({ + didRender() { + const name = this.get("content.name"); + this.$().css("border-bottom", Handlebars.Utils.escapeExpression(`7px solid #${name}`)); + } +}); diff --git a/app/assets/javascripts/select-box-kit/components/multi-combo-box/selected-name.js.es6 b/app/assets/javascripts/select-box-kit/components/multi-combo-box/selected-name.js.es6 new file mode 100644 index 00000000000..07c3ad0d64c --- /dev/null +++ b/app/assets/javascripts/select-box-kit/components/multi-combo-box/selected-name.js.es6 @@ -0,0 +1,19 @@ +export default Ember.Component.extend({ + attributeBindings: ["tabindex","content.name:data-name", "content.value:data-value"], + classNames: "selected-name", + classNameBindings: ["isHighlighted", "isLocked"], + layoutName: "select-box-kit/templates/components/multi-combo-box/selected-name", + tagName: "li", + tabindex: -1, + + isLocked: Ember.computed("content.locked", function() { + return this.getWithDefault("content.locked", false); + }), + + click() { + if (this.get("isLocked") === true) { return false; } + + this.toggleProperty("isHighlighted"); + return false; + } +}); diff --git a/app/assets/javascripts/select-box-kit/components/notifications-button.js.es6 b/app/assets/javascripts/select-box-kit/components/notifications-button.js.es6 index 88a1e71e982..d0515535001 100644 --- a/app/assets/javascripts/select-box-kit/components/notifications-button.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/notifications-button.js.es6 @@ -22,15 +22,15 @@ export default DropdownSelectBoxComponent.extend({ @on("didReceiveAttrs", "didUpdateAttrs") _setComponentOptions() { - this.set("headerComponentOptions", Ember.Object.create({ + this.get("headerComponentOptions").setProperties({ i18nPrefix: this.get("i18nPrefix"), showFullTitle: this.get("showFullTitle"), - })); + }); - this.set("rowComponentOptions", Ember.Object.create({ + this.get("rowComponentOptions").setProperties({ i18nPrefix: this.get("i18nPrefix"), i18nPostfix: this.get("i18nPostfix") - })); + }); }, @computed("computedValue") diff --git a/app/assets/javascripts/select-box-kit/components/notifications-button/notifications-button-header.js.es6 b/app/assets/javascripts/select-box-kit/components/notifications-button/notifications-button-header.js.es6 index 96b15534928..268e8d7b311 100644 --- a/app/assets/javascripts/select-box-kit/components/notifications-button/notifications-button-header.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/notifications-button/notifications-button-header.js.es6 @@ -11,7 +11,7 @@ export default DropdownSelectBoxHeaderComponent.extend({ @computed("_selectedDetails.icon", "_selectedDetails.key") icon(icon, key) { - return iconHTML(icon, {class: key}).htmlSafe(); + return iconHTML(icon, { class: key }).htmlSafe(); }, @computed("_selectedDetails.key", "i18nPrefix") @@ -20,5 +20,7 @@ export default DropdownSelectBoxHeaderComponent.extend({ }, @computed("selectedContent.firstObject.value") - _selectedDetails(value) { return buttonDetails(value); } + _selectedDetails(value) { + return buttonDetails(value); + } }); diff --git a/app/assets/javascripts/select-box-kit/components/pinned-options.js.es6 b/app/assets/javascripts/select-box-kit/components/pinned-options.js.es6 index c1be2e6ab38..3c96f2ee228 100644 --- a/app/assets/javascripts/select-box-kit/components/pinned-options.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/pinned-options.js.es6 @@ -48,17 +48,13 @@ export default DropdownSelectBoxComponent.extend({ ]; }, - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); + selectValueFunction(value) { + const topic = this.get("topic"); - const topic = this.get("topic"); - - if (value === "unpinned") { - topic.clearPin(); - } else { - topic.rePin(); - } + if (value === "unpinned") { + topic.clearPin(); + } else { + topic.rePin(); } } }); diff --git a/app/assets/javascripts/select-box-kit/components/select-box-kit.js.es6 b/app/assets/javascripts/select-box-kit/components/select-box-kit.js.es6 index 486b39bd701..ffb118eba88 100644 --- a/app/assets/javascripts/select-box-kit/components/select-box-kit.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/select-box-kit.js.es6 @@ -1,5 +1,5 @@ const { get, isNone, isEmpty, isPresent } = Ember; -import { on, observes } from "ember-addons/ember-computed-decorators"; +import { on } from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators"; import UtilsMixin from "select-box-kit/mixins/utils"; import DomHelpersMixin from "select-box-kit/mixins/dom-helpers"; @@ -22,7 +22,8 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin isExpanded: false, isFocused: false, isHidden: false, - renderBody: false, + renderedBodyOnce: false, + renderedFilterOnce: false, tabindex: 0, scrollableParentSelector: ".modal-body", value: null, @@ -37,10 +38,12 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin filterPlaceholder: "select_box.filter_placeholder", filterIcon: "search", rowComponent: "select-box-kit/select-box-kit-row", + rowComponentOptions: null, noneRowComponent: "select-box-kit/select-box-kit-none-row", createRowComponent: "select-box-kit/select-box-kit-create-row", filterComponent: "select-box-kit/select-box-kit-filter", headerComponent: "select-box-kit/select-box-kit-header", + headerComponentOptions: null, collectionComponent: "select-box-kit/select-box-kit-collection", collectionHeight: 200, verticalOffset: 0, @@ -50,126 +53,92 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin allowAny: false, allowValueMutation: true, autoSelectFirst: true, + content: null, + _initialValues: null, init() { this._super(); + this.noneValue = "__none__"; + this._previousScrollParentOverflow = "auto"; + this._previousCSSContext = {}; + this.set("headerComponentOptions", Ember.Object.create()); + this.set("rowComponentOptions", Ember.Object.create()); + if ($(window).outerWidth(false) <= 420) { this.setProperties({ filterable: false, autoFilterable: false }); } - this._previousScrollParentOverflow = "auto"; - this._previousCSSContext = {}; + if (isNone(this.get("content"))) { this.set("content", []); } + this.set("value", this._castInteger(this.get("value"))); + + this.setInitialValues(); }, - click(event) { - event.stopPropagation(); + setInitialValues() { + this.set("_initialValues", this.getWithDefault("content", []).map((c) => { + return this._valueForContent(c); + })); }, - close() { - this.setProperties({ isExpanded: false, isFocused: false }); + @computed("computedContent.[]", "computedValue.[]", "filter") + filteredContent(computedContent, computedValue, filter) { + return this.filteredContentFunction(computedContent, computedValue, filter); }, - focus() { - Ember.run.schedule("afterRender", () => this.$offscreenInput().select() ); + filteredContentFunction(computedContent, computedValue, filter) { + if (isEmpty(filter)) { return computedContent; } + + const lowerFilter = filter.toLowerCase(); + return computedContent.filter(c => { + return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1; + }); }, - blur() { - Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() ); - }, + formatRowContent(content) { + let originalContent; - clickOutside(event) { - if ($(event.target).parents(".select-box-kit").length === 1) { - this.close(); - return; - } - - if (this.get("isExpanded") === true) { - this.set("isExpanded", false); - this.focus(); + if (typeof content === "string" || typeof content === "number") { + originalContent = {}; + originalContent[this.get("valueAttribute")] = content; + originalContent[this.get("nameProperty")] = content; } else { - this.close(); - } - }, - - createFunction(input) { - return (selectedBox) => { - const formatedContent = selectedBox.formatContent(input); - formatedContent.meta.generated = true; - return formatedContent; - }; - }, - - filterFunction(content) { - return selectBox => { - const filter = selectBox.get("filter").toLowerCase(); - return _.filter(content, c => { - return get(c, "name").toLowerCase().indexOf(filter) > -1; - }); - }; - }, - - nameForContent(content) { - if (isNone(content)) { - return null; + originalContent = content; } - if (typeof content === "object") { - return get(content, this.get("nameProperty")); - } - - return content; - }, - - valueForContent(content) { - switch (typeof content) { - case "string": - case "number": - return this._castInteger(content); - default: - return this._castInteger(get(content, this.get("valueAttribute"))); - } - }, - - formatContent(content) { return { - value: this.valueForContent(content), - name: this.nameForContent(content), - originalContent: content, - meta: { generated: false } + value: this._castInteger(this._valueForContent(content)), + name: this._nameForContent(content), + locked: false, + originalContent }; }, formatContents(contents) { - return contents.map(content => this.formatContent(content)); + return contents.map(content => this.formatRowContent(content)); }, - @computed("filter", "filterable", "autoFilterable") - computedFilterable(filter, filterable, autoFilterable) { - if (filterable === true) { - return true; - } - - if (filter.length > 0 && autoFilterable === true) { - return true; - } - + @computed("filter", "filterable", "autoFilterable", "renderedFilterOnce") + shouldDisplayFilter(filter, filterable, autoFilterable, renderedFilterOnce) { + if (renderedFilterOnce === true || filterable === true) { return true; } + if (filter.length > 0 && autoFilterable === true) { return true; } return false; }, - @computed("computedFilterable", "filter", "allowAny") - shouldDisplayCreateRow(computedFilterable, filter, allow) { - return computedFilterable === true && filter.length > 0 && allow === true; + @computed("filter") + shouldDisplayCreateRow(filter) { + if (this.get("allowAny") === true && filter.length > 0) { return true; } + return false; }, - @computed("filter", "allowAny") - createRowContent(filter, allow) { - if (allow === true) { + @computed("filter", "shouldDisplayCreateRow") + createRowContent(filter, shouldDisplayCreateRow) { + if (shouldDisplayCreateRow === true && !this.get("value").includes(filter)) { return Ember.Object.create({ value: filter, name: filter }); } }, - @computed("content.[]") + @computed("content.[]", "value.[]") computedContent(content) { this._mutateValue(); return this.formatContents(content || []); @@ -178,10 +147,10 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin @computed("value", "none", "computedContent.firstObject.value") computedValue(value, none, firstContentValue) { if (isNone(value) && isNone(none) && this.get("autoSelectFirst") === true) { - return this._castInteger(firstContentValue); + return firstContentValue; } - return this._castInteger(value); + return value; }, @computed @@ -195,90 +164,51 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin @computed("none") computedNone(none) { - if (isNone(none)) { - return null; - } + if (isNone(none)) { return null; } switch (typeof none) { case "string": - return Ember.Object.create({ name: I18n.t(none), value: "" }); + return Ember.Object.create({ name: I18n.t(none), value: this.noneValue }); default: - return this.formatContent(none); + return this.formatRowContent(none); } }, @computed("computedValue", "computedContent.[]") selectedContent(computedValue, computedContent) { - if (isNone(computedValue)) { - return []; - } - - return [ computedContent.findBy("value", this._castInteger(computedValue)) ]; - }, - - @on("didRender") - _configureSelectBoxDOM() { - if (this.get("isExpanded") === true) { - Ember.run.schedule("afterRender", () => { - this.$collection().css("max-height", this.get("collectionHeight")); - this._applyDirection(); - this._positionWrapper(); - }); - } - }, - - @on("willDestroyElement") - _cleanHandlers() { - $(window).off("resize.select-box-kit"); - this._removeFixedPosition(); + if (isNone(computedValue)) { return []; } + return [ computedContent.findBy("value", computedValue) ]; }, @on("didInsertElement") _setupResizeListener() { - $(window).on("resize.select-box-kit", () => this.set("isExpanded", false) ); + $(window).on("resize.select-box-kit", () => this.collapse() ); }, - @observes("filter", "filteredContent.[]", "shouldDisplayCreateRow") - _setHighlightedValue() { - const filteredContent = this.get("filteredContent"); - const display = this.get("shouldDisplayCreateRow"); - const none = this.get("computedNone"); - if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) { - this.set("highlightedValue", get(filteredContent, "firstObject.value")); - return; - } + autoHighlightFunction() { + Ember.run.schedule("afterRender", () => { + if (!isNone(this.get("highlightedValue"))) { return; } - if (display === true && isEmpty(filteredContent)) { - this.set("highlightedValue", this.get("filter")); - } - else if (!isEmpty(filteredContent)) { - this.set("highlightedValue", get(filteredContent, "firstObject.value")); - } - else if (isEmpty(filteredContent) && isPresent(none) && display === false) { - this.set("highlightedValue", get(none, "value")); - } - }, + const filteredContent = this.get("filteredContent"); + const display = this.get("shouldDisplayCreateRow"); + const none = this.get("computedNone"); - @observes("isExpanded") - _isExpandedChanged() { - if (this.get("isExpanded") === true) { - this._applyFixedPosition(); + if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) { + this.send("onHighlight", get(filteredContent, "firstObject.value")); + return; + } - this.setProperties({ - highlightedValue: this.get("computedValue"), - renderBody: true, - isFocused: true - }); - } else { - this._removeFixedPosition(); - } - }, - - @computed("filter", "computedFilterable", "computedContent.[]", "computedValue.[]") - filteredContent(filter, computedFilterable, computedContent, computedValue) { - if (computedFilterable === false) { return computedContent; } - return this.filterFunction(computedContent)(this, computedValue); + if (display === true && isEmpty(filteredContent)) { + this.send("onHighlight", this.get("filter")); + } + else if (!isEmpty(filteredContent)) { + this.send("onHighlight", get(filteredContent, "firstObject.value")); + } + else if (isEmpty(filteredContent) && isPresent(none) && display === false) { + this.send("onHighlight", get(none, "value")); + } + }); }, @computed("scrollableParentSelector") @@ -286,191 +216,100 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin return this.$().parents(scrollableParentSelector).first(); }, + willFilterContent() { + this.expand(); + this.set("highlightedValue", null); + }, + didFilterContent() { + this.set("renderedFilterOnce", true); + this.autoHighlightFunction(); + }, + + willCreateContent() { }, + createContentFunction(input) { + this.get("content").pushObject(input); + this.send("onSelect", input); + }, + didCreateContent() { + this.clearFilter(); + this.autoHighlightFunction(); + }, + + willHighlightValue() {}, + highlightValueFunction(value) { + this.set("highlightedValue", value); + }, + didHighlightValue() {}, + + willSelectValue() { + this.clearFilter(); + this.set("highlightedValue", null); + }, + selectValueFunction(value) { + this.set("value", value); + }, + didSelectValue() { + this.collapse(); + this.focus(); + }, + + willDeselectValue() { + this.set("highlightedValue", null); + }, + unsetValueFunction() { + this.set("value", null); + }, + didDeselectValue() { + this.focus(); + }, + actions: { onToggle() { - this.toggleProperty("isExpanded"); - - if (this.get("isExpanded") === true) { this.focus(); } - }, - - onCreateContent(input) { - const content = this.createFunction(input)(this); - this.get("computedContent").pushObject(content); - this.send("onSelect", content.value); - }, - - onFilterChange(filter) { - this.set("filter", filter); - }, - - onHighlight(value) { - this.set("highlightedValue", value); + this.get("isExpanded") === true ? this.collapse() : this.expand(); }, onClearSelection() { - this.send("onSelect", null); + this.send("onDeselect", this.get("value")); + }, + + onHighlight(value) { + value = this._originalValueForValue(value); + this.willHighlightValue(value); + this.set("highlightedValue", value); + this.highlightValueFunction(value); + this.didHighlightValue(value); + }, + + onCreateContent(input) { + this.willCreateContent(input); + this.createContentFunction(input); + this.didCreateContent(input); }, onSelect(value) { - value = this.defaultOnSelect(value); - this.set("value", value); + if (value === "") { value = null; } + this.willSelectValue(value); + this.selectValueFunction(value); + this.didSelectValue(value); }, - onDeselect() { - this.defaultOnDeselect(); - this.set("value", null); - } + onDeselect(value) { + value = this._originalValueForValue(value); + this.willDeselectValue(value); + this.unsetValueFunction(value); + this.didSelectValue(value); + }, + + onFilterChange(_filter) { + this.willFilterContent(_filter); + this.set("filter", _filter); + this.didFilterContent(_filter); + }, }, - defaultOnSelect(value) { - if (value === "") { value = null; } - - this.setProperties({ - highlightedValue: null, - isExpanded: false, - filter: "" - }); - - this.focus(); - - return value; - }, - - defaultOnDeselect(value) { - const content = this.get("computedContent").findBy("value", value); - if (!isNone(content) && get(content, "meta.generated") === true) { - this.get("computedContent").removeObject(content); - } - }, - - _applyDirection() { - let options = { left: "auto", bottom: "auto", top: "auto" }; - - const dHeader = $(".d-header")[0]; - const dHeaderBounds = dHeader ? dHeader.getBoundingClientRect() : {top: 0, height: 0}; - const dHeaderHeight = dHeaderBounds.top + dHeaderBounds.height; - const headerHeight = this.$header().outerHeight(false); - const headerWidth = this.$header().outerWidth(false); - const bodyHeight = this.$body().outerHeight(false); - const windowWidth = $(window).width(); - const windowHeight = $(window).height(); - const boundingRect = this.get("element").getBoundingClientRect(); - const offsetTop = boundingRect.top; - const offsetBottom = boundingRect.bottom; - - if (this.get("fullWidthOnMobile") && windowWidth <= 420) { - const margin = 10; - const relativeLeft = this.$().offset().left - $(window).scrollLeft(); - options.left = margin - relativeLeft; - options.width = windowWidth - margin * 2; - options.maxWidth = options.minWidth = "unset"; - } else { - const bodyWidth = this.$body().outerWidth(false); - - if ($("html").css("direction") === "rtl") { - const horizontalSpacing = boundingRect.right; - const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0; - if (hasHorizontalSpace) { - this.setProperties({ isLeftAligned: true, isRightAligned: false }); - options.left = bodyWidth + this.get("horizontalOffset"); - } else { - this.setProperties({ isLeftAligned: false, isRightAligned: true }); - options.right = - (bodyWidth - headerWidth + this.get("horizontalOffset")); - } - } else { - const horizontalSpacing = boundingRect.left; - const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0); - if (hasHorizontalSpace) { - this.setProperties({ isLeftAligned: true, isRightAligned: false }); - options.left = this.get("horizontalOffset"); - } else { - this.setProperties({ isLeftAligned: false, isRightAligned: true }); - options.right = this.get("horizontalOffset"); - } - } - } - - const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight; - const hasBelowSpace = windowHeight - offsetBottom - componentHeight > 0; - const hasAboveSpace = offsetTop - componentHeight - dHeaderHeight > 0; - if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) { - this.setProperties({ isBelow: true, isAbove: false }); - options.top = headerHeight + this.get("verticalOffset"); - } else { - this.setProperties({ isBelow: false, isAbove: true }); - options.bottom = headerHeight + this.get("verticalOffset"); - } - - this.$body().css(options); - }, - - _applyFixedPosition() { - const width = this.$().outerWidth(false); - const height = this.$header().outerHeight(false); - - if (this.get("scrollableParent").length === 0) { return; } - - const $placeholder = $(`
`); - - this._previousScrollParentOverflow = this.get("scrollableParent").css("overflow"); - this.get("scrollableParent").css({ overflow: "hidden" }); - - this._previousCSSContext = { - minWidth: this.$().css("min-width"), - maxWidth: this.$().css("max-width") - }; - - const componentStyles = { - position: "fixed", - "margin-top": -this.get("scrollableParent").scrollTop(), - width, - minWidth: "unset", - maxWidth: "unset" - }; - - if ($("html").css("direction") === "rtl") { - componentStyles.marginRight = -width; - } else { - componentStyles.marginLeft = -width; - } - - $placeholder.css({ display: "inline-block", width, height, "vertical-align": "middle" }); - - this.$().before($placeholder).css(componentStyles); - }, - - _removeFixedPosition() { - if (this.get("scrollableParent").length === 0) { - return; - } - - $(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove(); - - const css = _.extend( - this._previousCSSContext, - { - top: "auto", - left: "auto", - "margin-left": "auto", - "margin-right": "auto", - "margin-top": "auto", - position: "relative" - } - ); - this.$().css(css); - - this.get("scrollableParent").css({ - overflow: this._previousScrollParentOverflow - }); - }, - - _positionWrapper() { - const headerHeight = this.$header().outerHeight(false); - - this.$(".select-box-kit-wrapper").css({ - width: this.$().width(), - height: headerHeight + this.$body().outerHeight(false) - }); + clearFilter() { + this.$filterInput().val(""); + this.setProperties({ filter: "" }); }, @on("didReceiveAttrs") diff --git a/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-filter.js.es6 b/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-filter.js.es6 index 9717b59f78a..aafaa5e1363 100644 --- a/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-filter.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-filter.js.es6 @@ -2,5 +2,5 @@ export default Ember.Component.extend({ layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-filter", classNames: "select-box-kit-filter", classNameBindings: ["isFocused", "isHidden"], - isHidden: Ember.computed.not("filterable"), + isHidden: Ember.computed.not("shouldDisplayFilter") }); diff --git a/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-row.js.es6 b/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-row.js.es6 index efdb299a564..0ef116bba9a 100644 --- a/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-row.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/select-box-kit/select-box-kit-row.js.es6 @@ -8,12 +8,15 @@ export default Ember.Component.extend(UtilsMixin, { layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-row", classNames: "select-box-kit-row", tagName: "li", + tabIndex: -1, attributeBindings: [ + "tabIndex", "title", "content.value:data-value", "content.name:data-name" ], classNameBindings: ["isHighlighted", "isSelected"], + clicked: false, title: Ember.computed.alias("content.name"), @@ -23,17 +26,15 @@ export default Ember.Component.extend(UtilsMixin, { @on("didReceiveAttrs") _setSelectionState() { const contentValue = this.get("content.value"); + this.set("isSelected", this.get("value") === contentValue); - this.set("isHighlighted", this._castInteger(this.get("highlightedValue")) === this._castInteger(contentValue)); + this.set("isHighlighted", this.get("highlightedValue") === contentValue); }, @on("willDestroyElement") _clearDebounce() { const hoverDebounce = this.get("hoverDebounce"); - - if (isPresent(hoverDebounce)) { - run.cancel(hoverDebounce); - } + if (isPresent(hoverDebounce)) { run.cancel(hoverDebounce); } }, @computed("content.originalContent.icon", "content.originalContent.iconClass") @@ -50,7 +51,14 @@ export default Ember.Component.extend(UtilsMixin, { }, click() { - this.sendAction("onSelect", this.get("content.value")); + this._sendOnSelectAction(); + }, + + _sendOnSelectAction() { + if (this.get("clicked") === false) { + this.set("clicked", true); + this.sendAction("onSelect", this.get("content.value")); + } }, _sendOnHighlightAction() { diff --git a/app/assets/javascripts/select-box-kit/components/tag-notifications-button.js.es6 b/app/assets/javascripts/select-box-kit/components/tag-notifications-button.js.es6 index 2ab3ed375ba..bc2d95ac4d0 100644 --- a/app/assets/javascripts/select-box-kit/components/tag-notifications-button.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/tag-notifications-button.js.es6 @@ -6,11 +6,7 @@ export default NotificationOptionsComponent.extend({ showFullTitle: false, headerComponent: "tag-notifications-button/tag-notifications-button-header", - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); - this.sendAction("action", value); - this.blur(); - } + selectValueFunction(value) { + this.sendAction("action", value); } }); diff --git a/app/assets/javascripts/select-box-kit/components/topic-footer-mobile-dropdown.js.es6 b/app/assets/javascripts/select-box-kit/components/topic-footer-mobile-dropdown.js.es6 index 2fb8f3e3172..2222fbc8221 100644 --- a/app/assets/javascripts/select-box-kit/components/topic-footer-mobile-dropdown.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/topic-footer-mobile-dropdown.js.es6 @@ -39,38 +39,34 @@ export default ComboBoxComponent.extend({ return content; }, - actions: { - onSelect(value) { - value = this.defaultOnSelect(value); + selectValueFunction(value) { + const topic = this.get("topic"); - const topic = this.get("topic"); + // In case it"s not a valid topic + if (!topic.get("id")) { + return; + } - // In case it"s not a valid topic - if (!topic.get("id")) { - return; - } + this.set("value", value); - this.set("value", value); + const refresh = () => this.send("onDeselect", value); - const refresh = () => this.set("value", null); - - switch(value) { - case "invite": - this.attrs.showInvite(); - refresh(); - break; - case "bookmark": - topic.toggleBookmark().then(() => refresh() ); - break; - case "share": - this.appEvents.trigger("share:url", topic.get("shareUrl"), $("#topic-footer-buttons")); - refresh(); - break; - case "flag": - this.attrs.showFlagTopic(); - refresh(); - break; - } + switch(value) { + case "invite": + this.attrs.showInvite(); + refresh(); + break; + case "bookmark": + topic.toggleBookmark().then(() => refresh() ); + break; + case "share": + this.appEvents.trigger("share:url", topic.get("shareUrl"), $("#topic-footer-buttons")); + refresh(); + break; + case "flag": + this.attrs.showFlagTopic(); + refresh(); + break; } } }); diff --git a/app/assets/javascripts/select-box-kit/components/topic-notifications-options.js.es6 b/app/assets/javascripts/select-box-kit/components/topic-notifications-options.js.es6 index d1da27684a0..34f63f4ea5a 100644 --- a/app/assets/javascripts/select-box-kit/components/topic-notifications-options.js.es6 +++ b/app/assets/javascripts/select-box-kit/components/topic-notifications-options.js.es6 @@ -24,17 +24,11 @@ export default NotificationOptionsComponent.extend({ this.appEvents.off("topic-notifications-button:changed"); }, - actions: { - onSelect(value) { - if (value !== this.get("computedValue")) { - this.get("topic.details").updateNotifications(value); - } - - this.set("value", value); - - this.defaultOnSelect(value); - - this.blur(); + selectValueFunction(value) { + if (value !== this.get("value")) { + this.get("topic.details").updateNotifications(value); } + + this.set("value", value); } }); diff --git a/app/assets/javascripts/select-box-kit/mixins/dom-helpers.js.es6 b/app/assets/javascripts/select-box-kit/mixins/dom-helpers.js.es6 index d8caacb501e..ed70bcc59e8 100644 --- a/app/assets/javascripts/select-box-kit/mixins/dom-helpers.js.es6 +++ b/app/assets/javascripts/select-box-kit/mixins/dom-helpers.js.es6 @@ -1,3 +1,5 @@ +import { on } from "ember-addons/ember-computed-decorators"; + export default Ember.Mixin.create({ init() { this._super(); @@ -8,46 +10,237 @@ export default Ember.Mixin.create({ this.collectionSelector = ".select-box-kit-collection"; this.headerSelector = ".select-box-kit-header"; this.bodySelector = ".select-box-kit-body"; + this.wrapperSelector = ".select-box-kit-wrapper"; }, - $findRowByValue(value) { - return this.$(`${this.rowSelector}[data-value='${value}']`); + $findRowByValue(value) { return this.$(`${this.rowSelector}[data-value='${value}']`); }, + + $header() { return this.$(this.headerSelector); }, + + $body() { return this.$(this.bodySelector); }, + + $collection() { return this.$(this.collectionSelector); }, + + $rows(withHidden) { + + if (withHidden === true) { + return this.$(`${this.rowSelector}:not(.no-content)`); + } else { + return this.$(`${this.rowSelector}:not(.no-content):not(.is-hidden)`); + } }, - $header() { - return this.$(this.headerSelector); + $highlightedRow() { return this.$rows().filter(".is-highlighted"); }, + + $selectedRow() { return this.$rows().filter(".is-selected"); }, + + $offscreenInput() { return this.$(this.offscreenInputSelector); }, + + $filterInput() { return this.$(this.filterInputSelector); }, + + @on("didRender") + _ajustPosition() { + $(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove(); + this.$collection().css("max-height", this.get("collectionHeight")); + this._applyFixedPosition(); + this._applyDirection(); + this._positionWrapper(); }, - $body() { - return this.$(this.bodySelector); + @on("willDestroyElement") + _clearState() { + $(window).off("resize.select-box-kit"); + $(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove(); }, - $collection() { - return this.$(this.collectionSelector); + // make sure we don’t propagate a click outside component + // to avoid closing a modal containing the component for example + click(event) { this._killEvent(event); }, + + // use to collapse and remove focus + close() { + this.collapse(); + this.setProperties({ isFocused: false }); }, - $rows() { - return this.$(this.rowSelector); + // force the component in a known default state + focus() { + Ember.run.schedule("afterRender", () => this.$offscreenInput().focus() ); }, - $highlightedRow() { - return this.$rows().filter(".is-highlighted"); + expand() { + if (this.get("isExpanded") === true) { return; } + this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true }); + this.focus(); + this.autoHighlightFunction(); }, - $selectedRow() { - return this.$rows().filter(".is-selected"); + collapse() { + this.set("isExpanded", false); + Ember.run.schedule("afterRender", () => this._removeFixedPosition() ); }, - $offscreenInput() { - return this.$(this.offscreenInputSelector); + // make sure we close/unfocus the component when clicked outside + clickOutside(event) { + if ($(event.target).parents(".select-box-kit").length === 1) { + this.close(); + return false; + } + + this.unfocus(); + return; }, - $filterInput() { - return this.$(this.filterInputSelector); + // lose focus of the component in two steps + // first collapase and keep focus and then remove focus + unfocus() { + this.set("highlightedValue", null); + + if (this.get("isExpanded") === true) { + this.collapse(); + this.focus(); + } else { + this.close(); + } + }, + + blur() { + Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() ); }, _killEvent(event) { event.preventDefault(); event.stopPropagation(); - } + }, + + _applyDirection() { + let options = { left: "auto", bottom: "auto", top: "auto" }; + + const dHeader = $(".d-header")[0]; + const dHeaderBounds = dHeader ? dHeader.getBoundingClientRect() : {top: 0, height: 0}; + const dHeaderHeight = dHeaderBounds.top + dHeaderBounds.height; + const headerHeight = this.$header().outerHeight(false); + const headerWidth = this.$header().outerWidth(false); + const bodyHeight = this.$body().outerHeight(false); + const windowWidth = $(window).width(); + const windowHeight = $(window).height(); + const boundingRect = this.get("element").getBoundingClientRect(); + const offsetTop = boundingRect.top; + const offsetBottom = boundingRect.bottom; + + if (this.get("fullWidthOnMobile") && windowWidth <= 420) { + const margin = 10; + const relativeLeft = this.$().offset().left - $(window).scrollLeft(); + options.left = margin - relativeLeft; + options.width = windowWidth - margin * 2; + options.maxWidth = options.minWidth = "unset"; + } else { + const bodyWidth = this.$body().outerWidth(false); + + if ($("html").css("direction") === "rtl") { + const horizontalSpacing = boundingRect.right; + const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0; + if (hasHorizontalSpace) { + this.setProperties({ isLeftAligned: true, isRightAligned: false }); + options.left = bodyWidth + this.get("horizontalOffset"); + } else { + this.setProperties({ isLeftAligned: false, isRightAligned: true }); + options.right = - (bodyWidth - headerWidth + this.get("horizontalOffset")); + } + } else { + const horizontalSpacing = boundingRect.left; + const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0); + if (hasHorizontalSpace) { + this.setProperties({ isLeftAligned: true, isRightAligned: false }); + options.left = this.get("horizontalOffset"); + } else { + this.setProperties({ isLeftAligned: false, isRightAligned: true }); + options.right = this.get("horizontalOffset"); + } + } + } + + const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight; + const hasBelowSpace = windowHeight - offsetBottom - componentHeight > 0; + const hasAboveSpace = offsetTop - componentHeight - dHeaderHeight > 0; + if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) { + this.setProperties({ isBelow: true, isAbove: false }); + options.top = headerHeight + this.get("verticalOffset"); + } else { + this.setProperties({ isBelow: false, isAbove: true }); + options.bottom = headerHeight + this.get("verticalOffset"); + } + + this.$body().css(options); + }, + + _applyFixedPosition() { + if (this.get("scrollableParent").length === 0) { return; } + + const width = this.$().outerWidth(false); + const height = this.$().outerHeight(false); + const $placeholder = $(`
`); + + this._previousScrollParentOverflow = this.get("scrollableParent").css("overflow"); + this.get("scrollableParent").css({ overflow: "hidden" }); + + this._previousCSSContext = { + minWidth: this.$().css("min-width"), + maxWidth: this.$().css("max-width") + }; + + const componentStyles = { + position: "fixed", + "margin-top": -this.get("scrollableParent").scrollTop(), + width, + minWidth: "unset", + maxWidth: "unset" + }; + + if ($("html").css("direction") === "rtl") { + componentStyles.marginRight = -width; + } else { + componentStyles.marginLeft = -width; + } + + $placeholder.css({ display: "inline-block", width, height, "vertical-align": "middle" }); + + this.$().before($placeholder).css(componentStyles); + }, + + _removeFixedPosition() { + $(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove(); + + if (this.get("scrollableParent").length === 0) { + return; + } + + if (!this.element || this.isDestroying || this.isDestroyed) { return; } + + const css = jQuery.extend( + this._previousCSSContext, + { + top: "auto", + left: "auto", + "margin-left": "auto", + "margin-right": "auto", + "margin-top": "auto", + position: "relative" + } + ); + this.$().css(css); + + this.get("scrollableParent").css({ + overflow: this._previousScrollParentOverflow + }); + }, + + _positionWrapper() { + const headerHeight = this.$header().outerHeight(false); + + this.$(this.wrapperSelector).css({ + width: this.$().outerWidth(false), + height: headerHeight + this.$body().outerHeight(false) + }); + }, }); diff --git a/app/assets/javascripts/select-box-kit/mixins/keyboard.js.es6 b/app/assets/javascripts/select-box-kit/mixins/keyboard.js.es6 index 0de89dbb2e7..e7973f5b0f8 100644 --- a/app/assets/javascripts/select-box-kit/mixins/keyboard.js.es6 +++ b/app/assets/javascripts/select-box-kit/mixins/keyboard.js.es6 @@ -1,5 +1,3 @@ -const { isEmpty } = Ember; - export default Ember.Mixin.create({ init() { this._super(); @@ -35,9 +33,13 @@ export default Ember.Mixin.create({ .off("focus.select-box-kit") .off("focusin.select-box-kit") .off("blur.select-box-kit") + .off("keypress.select-box-kit") .off("keydown.select-box-kit"); - this.$filterInput().off("keydown.select-box-kit"); + this.$filterInput() + .off("change.select-box-kit") + .off("keypress.select-box-kit") + .off("keydown.select-box-kit"); }, didInsertElement() { @@ -70,58 +72,67 @@ export default Ember.Mixin.create({ .on("keydown.select-box-kit", (event) => { const keyCode = event.keyCode || event.which; + if (keyCode === this.keys.TAB) { this._handleTabOnKeyDown(event); } + if (keyCode === this.keys.ESC) { this._handleEscOnKeyDown(event); } + if (keyCode === this.keys.UP || keyCode === this.keys.DOWN) { + this._handleArrowKey(keyCode, event); + } + if (keyCode === this.keys.BACKSPACE) { + this.expand(); + + if (this.$filterInput().is(":visible")) { + this.$filterInput().focus().trigger(event).trigger("change"); + } + + return event; + } + + return true; + }) + .on("keypress.select-box-kit", (event) => { + const keyCode = event.keyCode || event.which; + switch (keyCode) { - case this.keys.UP: - case this.keys.DOWN: - if (this.get("isExpanded") === false) { - this.set("isExpanded", true); - } - - Ember.run.schedule("actions", () => { - this._handleArrowKey(keyCode); - }); - - this._killEvent(event); - - return; case this.keys.ENTER: if (this.get("isExpanded") === false) { - this.set("isExpanded", true); - } else { - this.send("onSelect", this.$highlightedRow().data("value")); + this.expand(); + } else if (this.$highlightedRow().length === 1) { + this.$highlightedRow().click(); } - - this._killEvent(event); - - return; - case this.keys.TAB: - if (this.get("isExpanded") === false) { - return true; - } else { - this.send("onSelect", this.$highlightedRow().data("value")); - return; - } - case this.keys.ESC: - this.close(); - this._killEvent(event); - return; + return false; case this.keys.BACKSPACE: - this._killEvent(event); - return; + return event; } if (this._isSpecialKey(keyCode) === false && event.metaKey === false) { - this.setProperties({ - isExpanded: true, - filter: String.fromCharCode(keyCode) - }); + this.expand(); - Ember.run.schedule("afterRender", () => this.$filterInput().focus() ); + if (this.get("filterable") === true || this.get("autoFilterable")) { + this.set("renderedFilterOnce", true); + } + + Ember.run.schedule("afterRender", () => { + this.$filterInput() + .focus() + .val(this.$filterInput().val() + String.fromCharCode(keyCode)); + }); } }); this.$filterInput() - .on(`keydown.select-box-kit`, (event) => { + .on("change.select-box-kit", (event) => { + this.send("onFilterChange", $(event.target).val()); + }) + .on("keydown.select-box-kit", (event) => { + const keyCode = event.keyCode || event.which; + + if (keyCode === this.keys.TAB) { this._handleTabOnKeyDown(event); } + if (keyCode === this.keys.ESC) { this._handleEscOnKeyDown(event); } + if (keyCode === this.keys.UP || keyCode === this.keys.DOWN) { + this._handleArrowKey(keyCode, event); + } + }) + .on("keypress.select-box-kit", (event) => { const keyCode = event.keyCode || event.which; if ([ @@ -133,67 +144,75 @@ export default Ember.Mixin.create({ return true; } + if (keyCode === this.keys.TAB && this.get("isExpanded") === false) { + return true; + } + if (this._isSpecialKey(keyCode) === true) { this.$offscreenInput().focus().trigger(event); + return false; } return true; }); }, - _handleArrowKey(keyCode) { - if (isEmpty(this.get("filteredContent"))) { + _handleEscOnKeyDown(event) { + this.unfocus(); + this._killEvent(event); + }, + + _handleTabOnKeyDown(event) { + if (this.get("isExpanded") === false) { + this.unfocus(); + return true; + } else if (this.$highlightedRow().length === 1) { + this._killEvent(event); + this.$highlightedRow().click(); + this.focus(); + } else { + this.unfocus(); + return true; + } + return false; + }, + + _handleArrowKey(keyCode, event) { + if (this.get("isExpanded") === false) { this.expand(); } + this._killEvent(event); + const $rows = this.$rows(); + + if ($rows.length <= 0) { return; } + if ($rows.length === 1) { + this._rowSelection($rows, 0); return; } - Ember.run.schedule("afterRender", () => { - switch (keyCode) { - case 38: - Ember.run.throttle(this, this._handleUpArrow, 32); - break; - default: - Ember.run.throttle(this, this._handleDownArrow, 32); - } - }); + const direction = keyCode === 38 ? -1 : 1; + + Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32); }, - _moveHighlight(direction) { - const $rows = this.$rows(); + _moveHighlight(direction, $rows) { const currentIndex = $rows.index(this.$highlightedRow()); + let nextIndex = currentIndex + direction; - let nextIndex = 0; - - if (currentIndex < 0) { + if (nextIndex < 0) { + nextIndex = $rows.length - 1; + } else if (nextIndex >= $rows.length) { nextIndex = 0; - } else if (currentIndex + direction < $rows.length) { - nextIndex = currentIndex + direction; } this._rowSelection($rows, nextIndex); }, - _handleDownArrow() { this._moveHighlight(1); }, - - _handleUpArrow() { this._moveHighlight(-1); }, - _rowSelection($rows, nextIndex) { - const highlightableValue = $rows.eq(nextIndex).data("value"); + const highlightableValue = $rows.eq(nextIndex).attr("data-value"); const $highlightableRow = this.$findRowByValue(highlightableValue); - this.send("onHighlight", highlightableValue); Ember.run.schedule("afterRender", () => { - const $collection = this.$collection(); - const currentOffset = $collection.offset().top + - $collection.outerHeight(false); - const nextBottom = $highlightableRow.offset().top + - $highlightableRow.outerHeight(false); - const nextOffset = $collection.scrollTop() + nextBottom - currentOffset; - - if (nextIndex === 0) { - $collection.scrollTop(0); - } else if (nextBottom > currentOffset) { - $collection.scrollTop(nextOffset); - } + $highlightableRow.trigger("mouseover").focus(); + this.focus(); }); }, diff --git a/app/assets/javascripts/select-box-kit/mixins/utils.js.es6 b/app/assets/javascripts/select-box-kit/mixins/utils.js.es6 index 05d896a832b..3ece47cc3bf 100644 --- a/app/assets/javascripts/select-box-kit/mixins/utils.js.es6 +++ b/app/assets/javascripts/select-box-kit/mixins/utils.js.es6 @@ -1,9 +1,57 @@ +const { get, isNone } = Ember; + export default Ember.Mixin.create({ + _nameForContent(content) { + if (isNone(content)) { + return null; + } + + if (typeof content === "object") { + return get(content, this.get("nameProperty")); + } + + return content; + }, + _castInteger(value) { if (this.get("castInteger") === true && Ember.isPresent(value)) { return parseInt(value, 10); } - return Ember.isNone(value) ? value : value.toString(); - } + return value; + }, + + _valueForContent(content) { + switch (typeof content) { + case "string": + case "number": + return content; + default: + return get(content, this.get("valueAttribute")); + } + }, + + _contentForValue(value) { + return this.get("content").find(c => { + if (this._valueForContent(c) === value) { return true; } + }); + }, + + _computedContentForValue(value) { + const searchedValue = value.toString(); + return this.get("computedContent").find(c => { + if (c.value.toString() === searchedValue) { return true; } + }); + }, + + _originalValueForValue(value) { + if (isNone(value)) { return null; } + if (value === this.noneValue) { return this.noneValue; } + + const computedContent = this._computedContentForValue(value); + + if (isNone(computedContent)) { return value; } + + return get(computedContent.originalContent, this.get("valueAttribute")); + }, }); diff --git a/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/multi-combo-box-header.hbs b/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/multi-combo-box-header.hbs index 13cf6c3f6d5..6245b4e8c7b 100644 --- a/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/multi-combo-box-header.hbs +++ b/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/multi-combo-box-header.hbs @@ -1,30 +1,13 @@ diff --git a/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/selected-name.hbs b/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/selected-name.hbs new file mode 100644 index 00000000000..54ef362ed31 --- /dev/null +++ b/app/assets/javascripts/select-box-kit/templates/components/multi-combo-box/selected-name.hbs @@ -0,0 +1,9 @@ + + {{#unless isLocked}} + + {{d-icon "times"}} + + {{/unless}} + + {{content.name}} + diff --git a/app/assets/javascripts/select-box-kit/templates/components/select-box-kit.hbs b/app/assets/javascripts/select-box-kit/templates/components/select-box-kit.hbs index 6575daf8843..9c9e7188ad7 100644 --- a/app/assets/javascripts/select-box-kit/templates/components/select-box-kit.hbs +++ b/app/assets/javascripts/select-box-kit/templates/components/select-box-kit.hbs @@ -19,6 +19,7 @@ onToggle=(action "onToggle") onFilterChange=(action "onFilterChange") onClearSelection=(action "onClearSelection") + shouldDisplayFilter=shouldDisplayFilter options=headerComponentOptions }} @@ -26,16 +27,14 @@ {{component filterComponent onFilterChange=(action "onFilterChange") icon=filterIcon - filter=filter - filterable=computedFilterable + shouldDisplayFilter=shouldDisplayFilter isFocused=isFocused placeholder=(i18n filterPlaceholder) - tabindex=tabindex + filter=filter }} - {{#if renderBody}} + {{#if renderedBodyOnce}} {{component collectionComponent - shouldDisplayCreateRow=shouldDisplayCreateRow none=computedNone createRowContent=createRowContent selectedContent=selectedContent @@ -43,13 +42,9 @@ rowComponent=rowComponent noneRowComponent=noneRowComponent createRowComponent=createRowComponent - iconForRow=iconForRow templateForRow=templateForRow templateForNoneRow=templateForNoneRow templateForCreateRow=templateForCreateRow - shouldHighlightRow=shouldHighlightRow - shouldSelectRow=shouldSelectRow - titleForRow=titleForRow onClearSelection=(action "onClearSelection") onSelect=(action "onSelect") onHighlight=(action "onHighlight") diff --git a/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-collection.hbs b/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-collection.hbs index 73f80e2608a..bf7311049cb 100644 --- a/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-collection.hbs +++ b/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-collection.hbs @@ -3,8 +3,6 @@ {{component noneRowComponent content=none templateForRow=templateForNoneRow - titleForRow=titleForRow - iconForRow=iconForRow highlightedValue=highlightedValue onClearSelection=onClearSelection onHighlight=onHighlight @@ -14,12 +12,11 @@ {{/if}} {{/if}} -{{#if shouldDisplayCreateRow}} +{{#if createRowContent}} {{component createRowComponent content=createRowContent templateForRow=templateForCreateRow titleForRow=titleForRow - iconForRow=iconForRow highlightedValue=highlightedValue onHighlight=onHighlight onCreateContent=onCreateContent @@ -33,7 +30,6 @@ content=content templateForRow=templateForRow titleForRow=titleForRow - iconForRow=iconForRow highlightedValue=highlightedValue onSelect=onSelect onHighlight=onHighlight diff --git a/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-filter.hbs b/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-filter.hbs index 74d12880749..dc50d225040 100644 --- a/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-filter.hbs +++ b/app/assets/javascripts/select-box-kit/templates/components/select-box-kit/select-box-kit-filter.hbs @@ -1,8 +1,8 @@ {{input - tabindex=tabindex + tabindex=-1 class="select-box-kit-filter-input" placeholder=placeholder - key-down=onFilterChange + key-up=onFilterChange autocomplete="off" autocorrect="off" autocapitalize="off" diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 2f418ddad3f..eb8eb427d85 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -215,9 +215,8 @@ $mobile-breakpoint: 700px; .select-box-kit { width: 350px; } - - .select-box-kit-header { - height: 28px; + .select-box-kit.multi-combo-box { + width: 500px; } } @@ -620,6 +619,10 @@ section.details { text-align: left; margin-left: 0; } + + .select-box-kit { + width: inherit; + } } .long-value { width: 800px; @@ -701,7 +704,7 @@ section.details { .ace_editor { pointer-events:none; - + .ace_cursor { visibility: hidden; } diff --git a/app/assets/stylesheets/common/select-box-kit/dropdown-select-box.scss b/app/assets/stylesheets/common/select-box-kit/dropdown-select-box.scss index 363af0d7958..670a31e657c 100644 --- a/app/assets/stylesheets/common/select-box-kit/dropdown-select-box.scss +++ b/app/assets/stylesheets/common/select-box-kit/dropdown-select-box.scss @@ -89,14 +89,6 @@ white-space: normal; } } - - &.is-highlighted { - background: $tertiary-low; - } - - &:hover { - background: $highlight-medium; - } } .select-box-kit-collection { diff --git a/app/assets/stylesheets/common/select-box-kit/list-setting.scss b/app/assets/stylesheets/common/select-box-kit/list-setting.scss new file mode 100644 index 00000000000..eea89e6cb5a --- /dev/null +++ b/app/assets/stylesheets/common/select-box-kit/list-setting.scss @@ -0,0 +1,13 @@ +.select-box-kit { + &.multi-combo-box { + &.list-setting { + .select-box-kit-row.create { + .square { + width: 12px; + height: 12px; + margin-left: 5px; + } + } + } + } +} diff --git a/app/assets/stylesheets/common/select-box-kit/multi-combo-box.scss b/app/assets/stylesheets/common/select-box-kit/multi-combo-box.scss new file mode 100644 index 00000000000..501afde4e6e --- /dev/null +++ b/app/assets/stylesheets/common/select-box-kit/multi-combo-box.scss @@ -0,0 +1,147 @@ +.select-box-kit { + &.multi-combo-box { + width: 300px; + background: $secondary; + border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + border-radius: 0; + + .select-box-kit-body { + width: 100%; + } + + .select-box-kit-row { + margin: 5px; + min-height: 1px; + padding: 5px; + border-radius: 0; + } + + .select-box-kit-filter { + border: 0; + } + + .multi-combo-box-header { + background: $secondary; + border: 0; + border-bottom: 1px solid transparent; + + &.is-focused { + box-shadow: $tertiary 0px 0px 6px 0px; + border-radius: 0; + } + } + + &.is-disabled { + .multi-combo-box-header { + background: #e9e9e9; + border-color: #ddd; + } + } + + &.is-highlighted { + .multi-combo-box-header { + border-radius: 0; + border-bottom: 1px solid transparent; + box-shadow: $tertiary 0px 0px 6px 0px; + } + } + + &.is-expanded { + .select-box-kit-wrapper { + display: block; + border: 1px solid $tertiary; + box-shadow: $tertiary 0px 0px 6px 0px; + border-radius: 0; + } + + .multi-combo-box-header { + border-bottom: 1px solid $primary-low; + border-radius: 0; + box-shadow: none; + } + + .select-box-kit-body { + border-radius: 0; + } + } + + .choices { + list-style: none; + margin: 0; + padding: 5px; + flex: 1; + min-height: 36px; + box-sizing: border-box; + + li { + display: inline-flex; + box-sizing: border-box; + padding: 0 5px; + margin-bottom: 4px; + border: 1px solid transparent; + } + + .filter { + align-items: center; + justify-content: flex-start; + white-space: nowrap; + min-width: 50px; + padding: 0; + + .select-box-kit-filter-input, .select-box-kit-filter-input:focus { + border: none; + background: none; + display: inline-block; + width: 100%; + outline: none; + min-width: auto; + padding: 0; + margin: 0; + outline: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + border-radius: 0; + } + } + + .selected-name { + align-items: center; + justify-content: flex-start; + color: $primary; + cursor: default; + 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; + background-color: $primary-low; + cursor: pointer; + outline: none; + padding: 0; + line-height: normal; + + .name { + padding: 0 5px; + line-height: 22px + } + + &.is-highlighted { + border-color: $danger; + } + + .d-icon { + margin-right: 5px; + color: $primary-medium; + cursor: pointer; + font-size: 12px; + + &:hover { + color: $primary; + } + } + } + } + } +} diff --git a/app/assets/stylesheets/common/select-box-kit/multi-combobox.scss b/app/assets/stylesheets/common/select-box-kit/multi-combobox.scss deleted file mode 100644 index 87235fa8f3a..00000000000 --- a/app/assets/stylesheets/common/select-box-kit/multi-combobox.scss +++ /dev/null @@ -1,131 +0,0 @@ -.multi-combobox { - width: 300px; - background: $secondary; - border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); - - .select-box-kit-body { - width: 100%; - } - - .select-box-kit-row { - margin: 5px; - min-height: 1px; - padding: 5px; - } - - .select-box-kit-filter { - border-top: 1px solid $primary-low; - } - - .select-box-kit-header { - background: $secondary; - border-bottom: 1px solid transparent; - - &.is-focused { - border-bottom: 1px solid $primary-low; - box-shadow: $tertiary 0px 0px 6px 0px; - } - } - - &.is-disabled { - .select-box-kit-header { - background: #e9e9e9; - border-color: #ddd; - } - } - - &.is-highlighted { - .select-box-kit-header { - border: 1px solid $tertiary; - box-shadow: $tertiary 0px 0px 6px 0px; - } - } - - &.is-expanded { - .select-box-kit-wrapper { - display: block; - border: 1px solid $tertiary; - box-shadow: $tertiary 0px 0px 6px 0px; - } - - .select-box-kit-header { - border-radius: 3px 3px 0 0; - } - - .select-box-kit-body { - border-radius: 3px 3px 0 0; - } - } - - .choices { - list-style: none; - margin: 0; - padding: 5px; - } - - .choice-placeholder { - padding: 0 5px; - margin: 2px 0; - border: 1px solid transparent; - display: inline-flex; - flex: 1; - } - - .filter { - display: inline-flex; - align-items: center; - justify-content: flex-start; - margin: 0; - padding: 0; - white-space: nowrap; - } - - .select-box-kit-filter-input, .select-box-kit-filter-input:focus { - border: none; - background: none; - display: inline-block; - width: 100%; - outline: none; - min-width: auto; - padding: 0; - margin: 0; - outline: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - border-radius: 0; - } - - .selected-name { - display: inline-flex; - align-items: center; - justify-content: flex-start; - padding: 0 5px; - margin: 2px 0; - color: $primary; - cursor: default; - 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; - background-color: $primary-low; - - &:focus { - border-color: $primary; - outline: none; - } - - .d-icon { - margin-right: 5px; - color: $primary-medium; - cursor: pointer; - font-size: 12px; - - &:hover { - color: $primary; - } - } - } -} diff --git a/app/assets/stylesheets/common/select-box-kit/select-box-kit.scss b/app/assets/stylesheets/common/select-box-kit/select-box-kit.scss index 56ccf609ab8..05f7d620f58 100644 --- a/app/assets/stylesheets/common/select-box-kit/select-box-kit.scss +++ b/app/assets/stylesheets/common/select-box-kit/select-box-kit.scss @@ -3,6 +3,7 @@ } .select-box-kit { + border: 1px solid transparent; min-width: 220px; -webkit-box-sizing: border-box; box-sizing: border-box; @@ -65,6 +66,9 @@ } .select-box-kit-header { + border: 1px solid transparent; + box-sizing: border-box; + overflow: hidden; -webkit-transition: all .25s; -o-transition: all .25s; transition: all .25s; @@ -138,10 +142,13 @@ .select-box-kit-row { cursor: pointer; + line-height: normal; outline: none; display: -webkit-box; display: -ms-flexbox; display: flex; + -webkit-box-sizing: border-box; + box-sizing: border-box; -webkit-box-align: center; -ms-flex-align: center; align-items: center; @@ -172,10 +179,6 @@ &.is-selected.is-highlighted { background: $tertiary-low; } - - &.none:not(.is-highlighted) { - background: $primary-low; - } } .select-box-kit-collection { @@ -255,8 +258,8 @@ .select-box-kit-wrapper { position: absolute; - top: 0; - left: 0; + top: -1px; + left: -1px; background: none; display: none; -webkit-box-sizing: border-box; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ec4fc40a9fa..2b28082280a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1155,6 +1155,7 @@ en: default_header_text: Select... no_content: No matches found filter_placeholder: Search... + create: "Create {{content}}" emoji_picker: filter_placeholder: Search for emoji diff --git a/test/javascripts/acceptance/admin-suspend-user-test.js.es6 b/test/javascripts/acceptance/admin-suspend-user-test.js.es6 index 4c3d43feeb3..bd1fb803563 100644 --- a/test/javascripts/acceptance/admin-suspend-user-test.js.es6 +++ b/test/javascripts/acceptance/admin-suspend-user-test.js.es6 @@ -44,8 +44,8 @@ QUnit.test("suspend, then unsuspend a user", assert => { andThen(() => { assert.equal(find('.perform-suspend[disabled]').length, 1, 'disabled by default'); - expandSelectBox('.suspend-until .combobox'); - selectBoxSelectRow('tomorrow', { selector: '.suspend-until .combobox'}); + expandSelectBoxKit('.suspend-until .combobox'); + selectBoxKitSelectRow('tomorrow', { selector: '.suspend-until .combobox'}); }); fillIn('.suspend-reason', "for breaking the rules"); diff --git a/test/javascripts/acceptance/category-chooser-test.js.es6 b/test/javascripts/acceptance/category-chooser-test.js.es6 index de39c870945..9f645bc6b00 100644 --- a/test/javascripts/acceptance/category-chooser-test.js.es6 +++ b/test/javascripts/acceptance/category-chooser-test.js.es6 @@ -11,7 +11,7 @@ QUnit.test("does not display uncategorized if not allowed", assert => { visit("/"); click('#create-topic'); - expandSelectBox('.category-chooser'); + ('.category-chooser'); andThen(() => { assert.ok(selectBox('.category-chooser').rowByIndex(0).name() !== 'uncategorized'); diff --git a/test/javascripts/acceptance/category-edit-test.js.es6 b/test/javascripts/acceptance/category-edit-test.js.es6 index d3a99c3a35e..24d1eceef41 100644 --- a/test/javascripts/acceptance/category-edit-test.js.es6 +++ b/test/javascripts/acceptance/category-edit-test.js.es6 @@ -75,9 +75,9 @@ QUnit.test("Subcategory list settings", assert => { click('.edit-category-general'); - expandSelectBox('.edit-category-tab-general .category-chooser'); + expandSelectBoxKit('.edit-category-tab-general .category-chooser'); - selectBoxSelectRow(3, {selector: '.edit-category-tab-general .category-chooser'}); + selectBoxKitSelectRow(3, {selector: '.edit-category-tab-general .category-chooser'}); click('.edit-category-settings'); andThen(() => { diff --git a/test/javascripts/acceptance/search-full-test.js.es6 b/test/javascripts/acceptance/search-full-test.js.es6 index b64ca86dec4..24ad402b30b 100644 --- a/test/javascripts/acceptance/search-full-test.js.es6 +++ b/test/javascripts/acceptance/search-full-test.js.es6 @@ -257,8 +257,8 @@ QUnit.test("update in filter through advanced search ui", assert => { fillIn('.search input.full-page-search', 'none'); click('.search-advanced-btn'); - expandSelectBox('.search-advanced-options .select-box-kit#in'); - selectBoxSelectRow('bookmarks', { selector: '.search-advanced-options .select-box-kit#in' }); + expandSelectBoxKit('.search-advanced-options .select-box-kit#in'); + selectBoxKitSelectRow('bookmarks', { selector: '.search-advanced-options .select-box-kit#in' }); fillIn('.search-advanced-options .select-box-kit#in', 'bookmarks'); andThen(() => { @@ -271,8 +271,8 @@ QUnit.test("update status through advanced search ui", assert => { visit("/search"); fillIn('.search input.full-page-search', 'none'); click('.search-advanced-btn'); - expandSelectBox('.search-advanced-options .select-box-kit#status'); - selectBoxSelectRow('closed', { selector: '.search-advanced-options .select-box-kit#status' }); + expandSelectBoxKit('.search-advanced-options .select-box-kit#status'); + selectBoxKitSelectRow('closed', { selector: '.search-advanced-options .select-box-kit#status' }); fillIn('.search-advanced-options .select-box-kit#status', 'closed'); andThen(() => { @@ -286,8 +286,8 @@ QUnit.test("update post time through advanced search ui", assert => { fillIn('.search input.full-page-search', 'none'); click('.search-advanced-btn'); fillIn('#search-post-date', '2016-10-05'); - expandSelectBox('.search-advanced-options .select-box-kit#postTime'); - selectBoxSelectRow('after', { selector: '.search-advanced-options .select-box-kit#postTime' }); + expandSelectBoxKit('.search-advanced-options .select-box-kit#postTime'); + selectBoxKitSelectRow('after', { selector: '.search-advanced-options .select-box-kit#postTime' }); fillIn('.search-advanced-options .select-box-kit#postTime', 'after'); andThen(() => { diff --git a/test/javascripts/acceptance/search-test.js.es6 b/test/javascripts/acceptance/search-test.js.es6 index c449d17ccce..a5ce94669b3 100644 --- a/test/javascripts/acceptance/search-test.js.es6 +++ b/test/javascripts/acceptance/search-test.js.es6 @@ -89,7 +89,7 @@ QUnit.test("Search with context", assert => { QUnit.test("Right filters are shown to anonymous users", assert => { visit("/search?expanded=true"); - expandSelectBox(".select-box-kit#in"); + expandSelectBoxKit(".select-box-kit#in"); andThen(() => { assert.ok(exists('.select-box-kit#in .select-box-kit-row[data-value=first]')); @@ -115,7 +115,7 @@ QUnit.test("Right filters are shown to logged-in users", assert => { Discourse.reset(); visit("/search?expanded=true"); - expandSelectBox(".select-box-kit#in"); + expandSelectBoxKit(".select-box-kit#in"); andThen(() => { assert.ok(exists('.select-box-kit#in .select-box-kit-row[data-value=first]')); diff --git a/test/javascripts/acceptance/topic-notifications-button-test.js.es6 b/test/javascripts/acceptance/topic-notifications-button-test.js.es6 index 353ac651501..fa21ef4e96a 100644 --- a/test/javascripts/acceptance/topic-notifications-button-test.js.es6 +++ b/test/javascripts/acceptance/topic-notifications-button-test.js.es6 @@ -28,8 +28,8 @@ QUnit.test("Updating topic notification level", assert => { ); }); - expandSelectBox(notificationOptions); - selectBoxSelectRow("3", { selector: notificationOptions}); + expandSelectBoxKit(notificationOptions); + selectBoxKitSelectRow("3", { selector: notificationOptions}); andThen(() => { assert.equal( diff --git a/test/javascripts/acceptance/topic-test.js.es6 b/test/javascripts/acceptance/topic-test.js.es6 index b1f7d80d6d5..72f8518070c 100644 --- a/test/javascripts/acceptance/topic-test.js.es6 +++ b/test/javascripts/acceptance/topic-test.js.es6 @@ -53,9 +53,9 @@ QUnit.test("Updating the topic title and category", assert => { fillIn('#edit-title', 'this is the new title'); - expandSelectBox('.title-wrapper .category-chooser'); + expandSelectBoxKit('.title-wrapper .category-chooser'); - selectBoxSelectRow(4, {selector: '.title-wrapper .category-chooser'}); + selectBoxKitSelectRow(4, {selector: '.title-wrapper .category-chooser'}); click('#topic-title .submit-edit'); diff --git a/test/javascripts/components/categories-admin-dropdown-test.js.es6 b/test/javascripts/components/categories-admin-dropdown-test.js.es6 index 9816850677d..07593db3e62 100644 --- a/test/javascripts/components/categories-admin-dropdown-test.js.es6 +++ b/test/javascripts/components/categories-admin-dropdown-test.js.es6 @@ -10,7 +10,7 @@ componentTest('default', { assert.equal($selectBox.el.find(".d-icon-bars").length, 1); assert.equal($selectBox.el.find(".d-icon-caret-down").length, 1); - expandSelectBox('.categories-admin-dropdown'); + expandSelectBoxKit(); andThen(() => { assert.equal($selectBox.rowByValue("create").name(), "New Category"); diff --git a/test/javascripts/components/category-chooser-test.js.es6 b/test/javascripts/components/category-chooser-test.js.es6 index f5ebfe2685c..d6fca21fd09 100644 --- a/test/javascripts/components/category-chooser-test.js.es6 +++ b/test/javascripts/components/category-chooser-test.js.es6 @@ -6,7 +6,7 @@ componentTest('with value', { template: '{{category-chooser value=2}}', test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "feature"); @@ -18,7 +18,7 @@ componentTest('with excludeCategoryId', { template: '{{category-chooser excludeCategoryId=2}}', test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').rowByValue(2).el.length, 0); @@ -30,7 +30,7 @@ componentTest('with scopedCategoryId', { template: '{{category-chooser scopedCategoryId=2}}', test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').rowByIndex(0).name(), "feature"); @@ -48,7 +48,7 @@ componentTest('with allowUncategorized=null', { }, test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "Select a category…"); @@ -64,7 +64,7 @@ componentTest('with allowUncategorized=null rootNone=true', { }, test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "Select a category…"); @@ -81,7 +81,7 @@ componentTest('with disallowed uncategorized, rootNone and rootNoneLabel', { }, test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "Select a category…"); @@ -97,7 +97,7 @@ componentTest('with allowed uncategorized', { }, test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "uncategorized"); @@ -113,7 +113,7 @@ componentTest('with allowed uncategorized and rootNone', { }, test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "(no category)"); @@ -130,7 +130,7 @@ componentTest('with allowed uncategorized rootNone and rootNoneLabel', { }, test(assert) { - expandSelectBox('.category-chooser'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.category-chooser').header.name(), "root none label"); diff --git a/test/javascripts/components/combo-box-test.js.es6 b/test/javascripts/components/combo-box-test.js.es6 index dcaccdfc1bb..c7f50b5da13 100644 --- a/test/javascripts/components/combo-box-test.js.es6 +++ b/test/javascripts/components/combo-box-test.js.es6 @@ -8,7 +8,7 @@ componentTest('default', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').header.name(), "hello"); @@ -25,7 +25,7 @@ componentTest('with valueAttribute', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').rowByValue(0).name(), "hello"); @@ -41,7 +41,7 @@ componentTest('with nameProperty', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').rowByValue(0).name(), "hello"); @@ -57,7 +57,7 @@ componentTest('with an array as content', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').rowByValue('evil').name(), "evil"); @@ -75,7 +75,7 @@ componentTest('with value and none as a string', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').noneRow.name(), 'none'); @@ -85,7 +85,7 @@ componentTest('with value and none as a string', { assert.equal(this.get('value'), 'trout'); }); - selectBoxSelectRow('', {selector: '.combobox' }); + selectBoxKitSelectRow('__none__', {selector: '.combobox' }); andThen(() => { assert.equal(this.get('value'), null); @@ -102,7 +102,7 @@ componentTest('with value and none as an object', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').noneRow.name(), 'none'); @@ -112,7 +112,7 @@ componentTest('with value and none as an object', { assert.equal(this.get('value'), 'evil'); }); - selectBoxSelectNoneRow({ selector: '.combobox' }); + selectBoxKitSelectNoneRow({ selector: '.combobox' }); andThen(() => { assert.equal(this.get('value'), null); @@ -130,7 +130,7 @@ componentTest('with no value and none as an object', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').header.name(), 'none'); @@ -148,7 +148,7 @@ componentTest('with no value and none string', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').header.name(), 'none'); @@ -164,7 +164,7 @@ componentTest('with no value and no none', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').header.name(), 'evil', 'it sets the first row as value'); @@ -180,15 +180,15 @@ componentTest('with no value and no none', { // }, // // test(assert) { -// expandSelectBox(); +// (); // // andThen(() => assert.equal(find(".select-box-kit-filter-input").length, 1, "it has a search input")); // -// selectBoxFillInFilter("regis"); +// selectBoxKitFillInFilter("regis"); // // andThen(() => assert.equal(selectBox().rows.length, 1, "it filters results")); // -// selectBoxFillInFilter(""); +// selectBoxKitFillInFilter(""); // // andThen(() => { // assert.equal( @@ -207,17 +207,17 @@ componentTest('with no value and no none', { // }, // // test(assert) { -// expandSelectBox(); +// (); // -// selectBoxFillInFilter("rob"); +// selectBoxKitFillInFilter("rob"); // // andThen(() => assert.equal(selectBox().rows.length, 1) ); // -// collapseSelectBox(); +// collapseSelectBoxKit(); // // andThen(() => assert.notOk(selectBox().isExpanded) ); // -// expandSelectBox(); +// (); // // andThen(() => assert.equal(selectBox().rows.length, 1) ); // } @@ -232,7 +232,7 @@ componentTest('with empty string as value', { }, test(assert) { - expandSelectBox('.combobox'); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox('.combobox').header.name(), 'evil', 'it sets the first row as value'); diff --git a/test/javascripts/components/list-setting-test.js.es6 b/test/javascripts/components/list-setting-test.js.es6 new file mode 100644 index 00000000000..c3dd271fb1b --- /dev/null +++ b/test/javascripts/components/list-setting-test.js.es6 @@ -0,0 +1,74 @@ +import componentTest from 'helpers/component-test'; + +moduleForComponent('list-setting', {integration: true}); + +componentTest('default', { + template: '{{list-setting settingValue=settingValue choices=choices}}', + + beforeEach() { + this.set('settingValue', 'bold|italic'); + this.set('choices', ['bold', 'italic', 'underline']); + }, + + test(assert) { + expandSelectBoxKit(); + + andThen(() => { + assert.propEqual(selectBox().header.name(), 'bold,italic'); + }); + } +}); + +componentTest('with only setting value', { + template: '{{list-setting settingValue=settingValue}}', + + beforeEach() { + this.set('settingValue', 'bold|italic'); + }, + + test(assert) { + expandSelectBoxKit(); + + andThen(() => { + assert.propEqual(selectBox().header.name(), 'bold,italic'); + }); + } +}); + +componentTest('interactions', { + template: '{{list-setting settingValue=settingValue choices=choices}}', + + beforeEach() { + this.set('settingValue', 'bold|italic'); + this.set('choices', ['bold', 'italic', 'underline']); + }, + + test(assert) { + expandSelectBoxKit(); + + selectBoxKitSelectRow('underline'); + + andThen(() => { + assert.propEqual(selectBox().header.name(), 'bold,italic,underline'); + }); + + selectBoxKitFillInFilter('strike'); + + andThen(() => { + assert.equal(selectBox().highlightedRow.name(), 'strike'); + }); + + selectBox().keyboard.enter(); + + andThen(() => { + assert.propEqual(selectBox().header.name(), 'bold,italic,underline,strike'); + }); + + selectBox().keyboard.backspace(); + selectBox().keyboard.backspace(); + + andThen(() => { + assert.equal(this.get('choices').length, 3, 'it removes the created content from original list'); + }); + } +}); diff --git a/test/javascripts/components/multi-combo-box-test.js.es6 b/test/javascripts/components/multi-combo-box-test.js.es6 index 9dbe3b5ffec..81f8b209e70 100644 --- a/test/javascripts/components/multi-combo-box-test.js.es6 +++ b/test/javascripts/components/multi-combo-box-test.js.es6 @@ -11,7 +11,99 @@ componentTest('with objects and values', { test(assert) { andThen(() => { - assert.propEqual(selectBox(".multi-combobox").header.name(), 'hello,world'); + assert.propEqual(selectBox().header.name(), 'hello,world'); + }); + } +}); + +componentTest('interactions', { + template: '{{multi-combo-box none=none content=items value=value}}', + + beforeEach() { + I18n.translations[I18n.locale].js.test = {none: 'none'}; + this.set('items', [{id: 1, name: 'regis'}, {id: 2, name: 'sam'}, {id: 3, name: 'robin'}]); + this.set('value', [1, 2]); + }, + + test(assert) { + expandSelectBoxKit(); + + andThen(() => { + assert.equal(selectBox().highlightedRow.name(), 'robin', 'it highlights the first content row'); + }); + + this.set('none', 'test.none'); + + andThen(() => { + assert.equal(selectBox().noneRow.el.length, 1); + assert.equal(selectBox().highlightedRow.name(), 'robin', 'it highlights the first content row'); + }); + + selectBoxKitSelectRow(3); + + andThen(() => { + assert.equal(selectBox().highlightedRow.name(), 'none', 'it highlights none row if no content'); + }); + + selectBoxKitFillInFilter('joffrey'); + + andThen(() => { + assert.equal(selectBox().highlightedRow.name(), 'joffrey', 'it highlights create row when filling filter'); + }); + + selectBox().keyboard.enter(); + + andThen(() => { + assert.equal(selectBox().highlightedRow.name(), 'none', 'it highlights none row after creating content and no content left'); + }); + + selectBox().keyboard.backspace(); + + andThen(() => { + const $lastSelectedName = selectBox().header.el.find('.selected-name').last(); + assert.equal($lastSelectedName.attr('data-name'), 'joffrey'); + assert.ok($lastSelectedName.hasClass('is-highlighted'), 'it highlights the last selected name when using backspace'); + }); + + selectBox().keyboard.backspace(); + + andThen(() => { + const $lastSelectedName = selectBox().header.el.find('.selected-name').last(); + assert.equal($lastSelectedName.attr('data-name'), 'robin', 'it removes the previous highlighted selected content'); + assert.notOk(exists(selectBox().rowByValue('joffrey').el), 'generated content shouldn’t appear in content when removed'); + }); + + selectBox().keyboard.selectAll(); + + andThen(() => { + const $highlightedSelectedNames = selectBox().header.el.find('.selected-name.is-highlighted'); + assert.equal($highlightedSelectedNames.length, 3, 'it highlights each selected name'); + }); + + selectBox().keyboard.backspace(); + + andThen(() => { + const $selectedNames = selectBox().header.el.find('.selected-name'); + assert.equal($selectedNames.length, 0, 'it removed all selected content'); + }); + + andThen(() => { + assert.ok(this.$(".select-box-kit").hasClass("is-focused")); + assert.ok(this.$(".select-box-kit").hasClass("is-expanded")); + }); + + selectBox().keyboard.escape(); + + andThen(() => { + assert.ok(this.$(".select-box-kit").hasClass("is-focused")); + assert.notOk(this.$(".select-box-kit").hasClass("is-expanded")); + }); + + selectBox().keyboard.escape(); + + andThen(() => { + assert.notOk(this.$(".select-box-kit").hasClass("is-focused")); + assert.notOk(this.$(".select-box-kit").hasClass("is-expanded")); }); } }); diff --git a/test/javascripts/components/pinned-button-test.js.es6 b/test/javascripts/components/pinned-button-test.js.es6 index eba26d40eb0..154d321a23c 100644 --- a/test/javascripts/components/pinned-button-test.js.es6 +++ b/test/javascripts/components/pinned-button-test.js.es6 @@ -23,7 +23,7 @@ componentTest('updating the content refreshes the list', { test(assert) { andThen(() => assert.notOk(selectBox().isHidden) ); - expandSelectBox(); + expandSelectBoxKit(); andThen(() => assert.equal(selectBox().selectedRow.name(), "Pinned") ); diff --git a/test/javascripts/components/select-box-test.js.es6 b/test/javascripts/components/select-box-kit-test.js.es6 similarity index 90% rename from test/javascripts/components/select-box-test.js.es6 rename to test/javascripts/components/select-box-kit-test.js.es6 index d5ce4660531..6e0f37a0485 100644 --- a/test/javascripts/components/select-box-test.js.es6 +++ b/test/javascripts/components/select-box-kit-test.js.es6 @@ -10,7 +10,7 @@ componentTest('updating the content refreshes the list', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox().rowByValue(1).name(), "robin"); @@ -29,7 +29,7 @@ componentTest('accepts a value by reference', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.equal( @@ -38,7 +38,7 @@ componentTest('accepts a value by reference', { ); }); - selectBoxSelectRow(1); + selectBoxKitSelectRow(1); andThen(() => { assert.equal(this.get("value"), 1, "it mutates the value"); @@ -58,7 +58,7 @@ componentTest('default search icon', { template: '{{select-box-kit filterable=true}}', test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.ok(exists(selectBox().filter.icon), "it has a the correct icon"); @@ -70,7 +70,7 @@ componentTest('with no search icon', { template: '{{select-box-kit filterable=true filterIcon=null}}', test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox().filter.icon().length, 0, "it has no icon"); @@ -82,7 +82,7 @@ componentTest('custom search icon', { template: '{{select-box-kit filterable=true filterIcon="shower"}}', test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.ok(selectBox().filter.icon().hasClass("d-icon-shower"), "it has a the correct icon"); @@ -93,11 +93,11 @@ componentTest('custom search icon', { componentTest('select-box is expandable', { template: '{{select-box-kit}}', test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => assert.ok(selectBox().isExpanded) ); - collapseSelectBox(); + collapseSelectBoxKit(); andThen(() => assert.notOk(selectBox().isExpanded) ); } @@ -112,7 +112,7 @@ componentTest('accepts custom value/name keys', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox().selectedRow.name(), "robin"); @@ -130,7 +130,7 @@ componentTest('doesn’t render collection content before first expand', { test(assert) { assert.notOk(exists(find(".select-box-kit-collection"))); - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.ok(exists(find(".select-box-kit-collection"))); @@ -146,7 +146,7 @@ componentTest('supports options to limit size', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { const height = find(".select-box-kit-collection").height(); @@ -163,11 +163,11 @@ componentTest('dynamic headerText', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => assert.equal(selectBox().header.name(), "robin") ); - selectBoxSelectRow(2); + selectBoxKitSelectRow(2); andThen(() => { assert.equal(selectBox().header.name(), "regis", "it changes header text"); @@ -186,7 +186,7 @@ componentTest('supports custom row template', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => assert.equal(selectBox().rowByValue(1).el.html().trim(), "robin") ); } @@ -201,7 +201,7 @@ componentTest('supports converting select value to integer', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => assert.equal(selectBox().selectedRow.name(), "régis") ); @@ -224,7 +224,7 @@ componentTest('supports keyboard events', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); selectBox().keyboard.down(); @@ -251,7 +251,7 @@ componentTest('supports keyboard events', { assert.notOk(selectBox().isExpanded, "it collapses the select box when selecting a row"); }); - expandSelectBox(); + expandSelectBoxKit(); selectBox().keyboard.escape(); @@ -259,18 +259,13 @@ componentTest('supports keyboard events', { assert.notOk(selectBox().isExpanded, "it collapses the select box"); }); - expandSelectBox(); + expandSelectBoxKit(); - selectBoxFillInFilter("regis"); - - // andThen(() => { - // assert.equal(selectBox().highlightedRow.title(), "regis", "it highlights the first result"); - // }); + selectBoxKitFillInFilter("regis"); selectBox().keyboard.tab(); andThen(() => { - // assert.equal(selectBox().selectedRow.title(), "regis", "it selects the row when pressing tab"); assert.notOk(selectBox().isExpanded, "it collapses the select box when selecting a row"); }); } diff --git a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 index fbd31aeafcf..899bff4545a 100644 --- a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 +++ b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 @@ -17,7 +17,7 @@ componentTest('default', { }, test(assert) { - expandSelectBox(); + expandSelectBoxKit(); andThen(() => { assert.equal(selectBox().header.name(), "Topic Controls"); @@ -26,7 +26,7 @@ componentTest('default', { assert.equal(selectBox().selectedRow.el.length, 0, "it doesn’t preselect first row"); }); - selectBoxSelectRow("share"); + selectBoxKitSelectRow("share"); andThen(() => { assert.equal(this.get("value"), null, "it resets the value"); diff --git a/test/javascripts/helpers/select-box-helper.js b/test/javascripts/helpers/select-box-kit-helper.js similarity index 75% rename from test/javascripts/helpers/select-box-helper.js rename to test/javascripts/helpers/select-box-kit-helper.js index 546148531ab..6388cff899d 100644 --- a/test/javascripts/helpers/select-box-helper.js +++ b/test/javascripts/helpers/select-box-kit-helper.js @@ -10,7 +10,7 @@ function checkSelectBoxIsNotCollapsed(selectBoxSelector) { } } -Ember.Test.registerAsyncHelper('expandSelectBox', function(app, selectBoxSelector) { +Ember.Test.registerAsyncHelper('expandSelectBoxKit', function(app, selectBoxSelector) { selectBoxSelector = selectBoxSelector || '.select-box-kit'; checkSelectBoxIsNotExpanded(selectBoxSelector); @@ -18,7 +18,7 @@ Ember.Test.registerAsyncHelper('expandSelectBox', function(app, selectBoxSelecto click(selectBoxSelector + ' .select-box-kit-header'); }); -Ember.Test.registerAsyncHelper('collapseSelectBox', function(app, selectBoxSelector) { +Ember.Test.registerAsyncHelper('collapseSelectBoxKit', function(app, selectBoxSelector) { selectBoxSelector = selectBoxSelector || '.select-box-kit'; checkSelectBoxIsNotCollapsed(selectBoxSelector); @@ -26,7 +26,7 @@ Ember.Test.registerAsyncHelper('collapseSelectBox', function(app, selectBoxSelec click(selectBoxSelector + ' .select-box-kit-header'); }); -Ember.Test.registerAsyncHelper('selectBoxSelectRow', function(app, rowValue, options) { +Ember.Test.registerAsyncHelper('selectBoxKitSelectRow', function(app, rowValue, options) { options = options || {}; options.selector = options.selector || '.select-box-kit'; @@ -35,7 +35,7 @@ Ember.Test.registerAsyncHelper('selectBoxSelectRow', function(app, rowValue, opt click(options.selector + " .select-box-kit-row[data-value='" + rowValue + "']"); }); -Ember.Test.registerAsyncHelper('selectBoxSelectNoneRow', function(app, options) { +Ember.Test.registerAsyncHelper('selectBoxKitSelectNoneRow', function(app, options) { options = options || {}; options.selector = options.selector || '.select-box-kit'; @@ -44,7 +44,7 @@ Ember.Test.registerAsyncHelper('selectBoxSelectNoneRow', function(app, options) click(options.selector + " .select-box-kit-row.none"); }); -Ember.Test.registerAsyncHelper('selectBoxFillInFilter', function(app, filter, options) { +Ember.Test.registerAsyncHelper('selectBoxKitFillInFilter', function(app, filter, options) { options = options || {}; options.selector = options.selector || '.select-box-kit'; @@ -52,7 +52,6 @@ Ember.Test.registerAsyncHelper('selectBoxFillInFilter', function(app, filter, op var filterQuerySelector = options.selector + ' .select-box-kit-filter-input'; fillIn(filterQuerySelector, filter); - triggerEvent(filterQuerySelector, 'keyup'); }); function selectBox(selector) { // eslint-disable-line no-unused-vars @@ -88,23 +87,26 @@ function selectBox(selector) { // eslint-disable-line no-unused-vars } function keyboardHelper() { - function createEvent(target, keyCode) { + function createEvent(target, keyCode, options) { target = target || ".select-box-kit-filter-input"; selector = find(selector).find(target); andThen(function() { - var event = jQuery.Event('keydown'); + var event = jQuery.Event(options.type); event.keyCode = keyCode; + if (options && options.metaKey === true) { event.metaKey = true; } find(selector).trigger(event); }); } return { - down: function(target) { createEvent(target, 40); }, - up: function(target) { createEvent(target, 38); }, - escape: function(target) { createEvent(target, 27); }, - enter: function(target) { createEvent(target, 13); }, - tab: function(target) { createEvent(target, 9); } + down: function(target) { createEvent(target, 40, {type: 'keydown'}); }, + up: function(target) { createEvent(target, 38, {type: 'keydown'}); }, + escape: function(target) { createEvent(target, 27, {type: 'keydown'}); }, + enter: function(target) { createEvent(target, 13, {type: 'keypress'}); }, + tab: function(target) { createEvent(target, 9, {type: 'keydown'}); }, + backspace: function(target) { createEvent(target, 8, {type: 'keydown'}); }, + selectAll: function(target) { createEvent(target, 65, {metaKey: true, type: 'keydown'}); }, }; } diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index 97f759b0dc8..df242fa34ed 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -32,7 +32,7 @@ //= require sinon-qunit-1.0.0 //= require helpers/assertions -//= require helpers/select-box-helper +//= require helpers/select-box-kit-helper //= require helpers/qunit-helpers //= require_tree ./fixtures