FEATURE: support for multi-combo-box

This commit is contained in:
Joffrey JAFFEUX
2017-11-09 10:57:53 -08:00
committed by GitHub
parent 3093074398
commit 0da529010a
58 changed files with 1394 additions and 985 deletions

View File

@ -43,11 +43,11 @@
"asyncRender":true, "asyncRender":true,
"selectDropdown":true, "selectDropdown":true,
"selectBox":true, "selectBox":true,
"expandSelectBox":true, "expandSelectBoxKit":true,
"collapseSelectBox":true, "collapseSelectBoxKit":true,
"selectBoxSelectRow":true, "selectBoxKitSelectRow":true,
"selectBoxSelectNoneRow":true, "selectBoxKitSelectNoneRow":true,
"selectBoxFillInFilter":true, "selectBoxKitFillInFilter":true,
"asyncTestDiscourse":true, "asyncTestDiscourse":true,
"fixture":true, "fixture":true,
"find":true, "find":true,

View File

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

View File

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

View File

@ -1,3 +0,0 @@
<div class="input-setting-list">
<input type="text" value="{{unbound settingValue}}">
</div>

View File

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

View File

@ -8,10 +8,10 @@ export default DropdownSelectBoxComponent.extend({
@on("didReceiveAttrs") @on("didReceiveAttrs")
_setComponentOptions() { _setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({ this.get("headerComponentOptions").setProperties({
shouldDisplaySelectedName: false, shouldDisplaySelectedName: false,
icon: `${iconHTML('bars')}${iconHTML('caret-down')}`.htmlSafe(), icon: `${iconHTML('bars')}${iconHTML('caret-down')}`.htmlSafe(),
})); });
}, },
@computed @computed
@ -38,11 +38,8 @@ export default DropdownSelectBoxComponent.extend({
return items; return items;
}, },
actions: { selectValueFunction(value) {
onSelect(value) { this.get(value)();
value = this.defaultOnSelect(value); this.set("value", null);
this.get(value)();
this.set("value", null);
}
} }
}); });

View File

@ -12,24 +12,24 @@ export default ComboBoxComponent.extend({
castInteger: true, castInteger: true,
allowUncategorized: null, allowUncategorized: null,
filterFunction(computedContent) { filteredContentFunction(computedContent, computedValue, filter) {
const _matchFunction = (filter, text) => { if (isEmpty(filter)) { return computedContent; }
return text.toLowerCase().indexOf(filter) > -1;
};
return (selectBox) => { const _matchFunction = (f, text) => {
const filter = selectBox.get("filter").toLowerCase(); return text.toLowerCase().indexOf(f) > -1;
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 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") @computed("rootNone", "rootNoneLabel")

View File

@ -7,11 +7,7 @@ export default NotificationOptionsComponent.extend({
value: Ember.computed.alias("category.notification_level"), value: Ember.computed.alias("category.notification_level"),
headerComponent: "category-notifications-button/category-notifications-button-header", headerComponent: "category-notifications-button/category-notifications-button-header",
actions: { selectValueFunction(value) {
onSelect(value) { this.get("category").setNotification(value);
value = this.defaultOnSelect(value);
this.get("category").setNotification(value);
this.blur();
}
} }
}); });

View File

@ -12,10 +12,10 @@ export default SelectBoxKitComponent.extend({
@on("didReceiveAttrs") @on("didReceiveAttrs")
_setComboBoxOptions() { _setComboBoxOptions() {
this.set("headerComponentOptions", Ember.Object.create({ this.get("headerComponentOptions").setProperties({
caretUpIcon: this.get("caretUpIcon"), caretUpIcon: this.get("caretUpIcon"),
caretDownIcon: this.get("caretDownIcon"), caretDownIcon: this.get("caretDownIcon"),
clearable: this.get("clearable"), clearable: this.get("clearable"),
})); });
} }
}); });

View File

@ -9,16 +9,11 @@ export default SelectBoxKitComponent.extend({
headerComponent: "dropdown-select-box/dropdown-select-box-header", headerComponent: "dropdown-select-box/dropdown-select-box-header",
rowComponent: "dropdown-select-box/dropdown-select-box-row", rowComponent: "dropdown-select-box/dropdown-select-box-row",
clickOutside() { clickOutside() { this.close(); },
this.close();
},
actions: { didSelectValue() {
onSelect(value) { this._super();
value = this.defaultOnSelect(value);
this.set("value", value);
this.blur(); this.blur();
}
} }
}); });

View File

@ -5,10 +5,7 @@ export default NotificationOptionsComponent.extend({
value: Ember.computed.alias("group.group_user.notification_level"), value: Ember.computed.alias("group.group_user.notification_level"),
i18nPrefix: "groups.notifications", i18nPrefix: "groups.notifications",
actions: { selectValueFunction(value) {
onSelect(value) { this.get("group").setNotification(value, this.get("user.id"));
value = this.defaultOnSelect(value);
this.get("group").setNotification(value, this.get("user.id"));
}
} }
}); });

View File

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

View File

@ -1,20 +1,32 @@
// Experimental
import SelectBoxKitComponent from "select-box-kit/components/select-box-kit"; import SelectBoxKitComponent from "select-box-kit/components/select-box-kit";
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
const { get, isNone } = Ember; const { get, isNone, isEmpty } = Ember;
export default SelectBoxKitComponent.extend({ export default SelectBoxKitComponent.extend({
classNames: "multi-combobox", classNames: "multi-combo-box",
headerComponent: "multi-combo-box/multi-combo-box-header", headerComponent: "multi-combo-box/multi-combo-box-header",
filterComponent: null, filterComponent: null,
headerText: "select_box.default_header_text", headerText: "select_box.default_header_text",
value: [],
allowAny: true, 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") @computed("filter")
templateForCreateRow() { templateForCreateRow() {
return (rowComponent) => { 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 keyCode = event.keyCode || event.which;
const $filterInput = this.$filterInput(); const $filterInput = this.$filterInput();
if (keyCode === 8) { if (this.get("isFocused") === true && this.get("isExpanded") === false && keyCode === this.keys.BACKSPACE) {
let $lastSelectedValue = $(this.$(".choices .selected-name").last()); this.expand();
return;
}
if ($lastSelectedValue.is(":focus") || $(document.activeElement).is($lastSelectedValue)) { // select all choices
this.send("onDeselect", $lastSelectedValue.data("value")); 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(); $filterInput.focus();
return; return;
} }
if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) {
$lastSelectedValue.click();
return false;
}
if ($filterInput.val() === "") { if ($filterInput.val() === "") {
if ($filterInput.is(":focus")) { if ($filterInput.is(":focus")) {
if ($lastSelectedValue.length > 0) { if ($lastSelectedValue.length > 0) { $lastSelectedValue.click(); }
$lastSelectedValue.focus();
}
} else { } else {
if ($lastSelectedValue.length > 0) { if ($lastSelectedValue.length > 0) {
$lastSelectedValue.focus(); $lastSelectedValue.click();
} else { } else {
$filterInput.focus(); $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.[]") @computed("value.[]")
computedValue(value) { computedValue(value) { return value.map(v => this._castInteger(v)); },
return value.map(v => this._castInteger(v));
},
@computed("computedValue.[]", "computedContent.[]") @computed("value.[]", "computedContent.[]")
selectedContent(computedValue, computedContent) { selectedContent(value, computedContent) {
const contents = []; const contents = [];
computedValue.forEach(cv => { value.forEach(v => {
contents.push(computedContent.findBy("value", cv)); const content = computedContent.findBy("value", v);
if (!isNone(content)) { contents.push(content); }
}); });
return contents; return contents;
}, },
filterFunction(content) { filteredContentFunction(computedContent, computedValue, filter) {
return (selectBox, computedValue) => { computedContent = computedContent.filter(c => {
const filter = selectBox.get("filter").toLowerCase(); return !computedValue.includes(get(c, "value"));
return _.filter(content, c => { });
return !computedValue.includes(get(c, "value")) &&
get(c, "name").toLowerCase().indexOf(filter) > -1; 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: { actions: {
onClearSelection() { onClearSelection() {
this.send("onSelect", []); const values = this.get("selectedContent").map(c => get(c, "value"));
this.send("onDeselect", values);
}, },
onSelect(value) { onHighlight(value) {
this.setProperties({ filter: "", highlightedValue: null }); value = this._originalValueForValue(value);
this.get("value").pushObject(value); this.willHighlightValue(value);
this.set("highlightedValue", value);
this.highlightValueFunction(value);
this.didHighlightValue(value);
}, },
onDeselect(value) { onCreateContent(input) {
this.defaultOnDeselect(value); this.willCreateContent(input);
this.get("value").removeObject(value); 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;
});
} }
}); });

View File

@ -4,37 +4,27 @@ import SelectBoxKitHeaderComponent from "select-box-kit/components/select-box-ki
export default SelectBoxKitHeaderComponent.extend({ export default SelectBoxKitHeaderComponent.extend({
attributeBindings: ["names:data-name"], 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", layoutName: "select-box-kit/templates/components/multi-combo-box/multi-combo-box-header",
selectedNameComponent: Ember.computed.alias("options.selectedNameComponent"),
@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;
},
@on("didRender") @on("didRender")
_positionFilter() { _positionFilter() {
this.$(".filter").width(0); if (this.get("shouldDisplayFilter") === false) { return; }
const $filter = this.$(".filter");
$filter.width(0);
const leftHeaderOffset = this.$().offset().left; const leftHeaderOffset = this.$().offset().left;
const leftFilterOffset = this.$(".filter").offset().left; const leftFilterOffset = $filter.offset().left;
const offset = leftFilterOffset - leftHeaderOffset; const offset = leftFilterOffset - leftHeaderOffset;
const width = this.$().outerWidth(false); const width = this.$().outerWidth(false);
const availableSpace = width - offset; const availableSpace = width - offset;
const $choices = $filter.parent(".choices");
// TODO: avoid magic number 8 const parentRightPadding = parseInt($choices.css("padding-right") , 10);
// TODO: make sure the filter doesn’t end up being very small $filter.width(availableSpace - parentRightPadding * 4);
this.$(".filter").width(availableSpace - 8);
}, },
@computed("selectedContent.[]") @computed("selectedContent.[]")
names(selectedContent) { names(selectedContent) { return selectedContent.map(sc => sc.name).join(","); }
return selectedContent.map(sc => sc.name).join(",");
}
}); });

View File

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

View File

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

View File

@ -22,15 +22,15 @@ export default DropdownSelectBoxComponent.extend({
@on("didReceiveAttrs", "didUpdateAttrs") @on("didReceiveAttrs", "didUpdateAttrs")
_setComponentOptions() { _setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({ this.get("headerComponentOptions").setProperties({
i18nPrefix: this.get("i18nPrefix"), i18nPrefix: this.get("i18nPrefix"),
showFullTitle: this.get("showFullTitle"), showFullTitle: this.get("showFullTitle"),
})); });
this.set("rowComponentOptions", Ember.Object.create({ this.get("rowComponentOptions").setProperties({
i18nPrefix: this.get("i18nPrefix"), i18nPrefix: this.get("i18nPrefix"),
i18nPostfix: this.get("i18nPostfix") i18nPostfix: this.get("i18nPostfix")
})); });
}, },
@computed("computedValue") @computed("computedValue")

View File

@ -11,7 +11,7 @@ export default DropdownSelectBoxHeaderComponent.extend({
@computed("_selectedDetails.icon", "_selectedDetails.key") @computed("_selectedDetails.icon", "_selectedDetails.key")
icon(icon, key) { icon(icon, key) {
return iconHTML(icon, {class: key}).htmlSafe(); return iconHTML(icon, { class: key }).htmlSafe();
}, },
@computed("_selectedDetails.key", "i18nPrefix") @computed("_selectedDetails.key", "i18nPrefix")
@ -20,5 +20,7 @@ export default DropdownSelectBoxHeaderComponent.extend({
}, },
@computed("selectedContent.firstObject.value") @computed("selectedContent.firstObject.value")
_selectedDetails(value) { return buttonDetails(value); } _selectedDetails(value) {
return buttonDetails(value);
}
}); });

View File

@ -48,17 +48,13 @@ export default DropdownSelectBoxComponent.extend({
]; ];
}, },
actions: { selectValueFunction(value) {
onSelect(value) { const topic = this.get("topic");
value = this.defaultOnSelect(value);
const topic = this.get("topic"); if (value === "unpinned") {
topic.clearPin();
if (value === "unpinned") { } else {
topic.clearPin(); topic.rePin();
} else {
topic.rePin();
}
} }
} }
}); });

View File

@ -1,5 +1,5 @@
const { get, isNone, isEmpty, isPresent } = Ember; 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 computed from "ember-addons/ember-computed-decorators";
import UtilsMixin from "select-box-kit/mixins/utils"; import UtilsMixin from "select-box-kit/mixins/utils";
import DomHelpersMixin from "select-box-kit/mixins/dom-helpers"; import DomHelpersMixin from "select-box-kit/mixins/dom-helpers";
@ -22,7 +22,8 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
isExpanded: false, isExpanded: false,
isFocused: false, isFocused: false,
isHidden: false, isHidden: false,
renderBody: false, renderedBodyOnce: false,
renderedFilterOnce: false,
tabindex: 0, tabindex: 0,
scrollableParentSelector: ".modal-body", scrollableParentSelector: ".modal-body",
value: null, value: null,
@ -37,10 +38,12 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
filterPlaceholder: "select_box.filter_placeholder", filterPlaceholder: "select_box.filter_placeholder",
filterIcon: "search", filterIcon: "search",
rowComponent: "select-box-kit/select-box-kit-row", rowComponent: "select-box-kit/select-box-kit-row",
rowComponentOptions: null,
noneRowComponent: "select-box-kit/select-box-kit-none-row", noneRowComponent: "select-box-kit/select-box-kit-none-row",
createRowComponent: "select-box-kit/select-box-kit-create-row", createRowComponent: "select-box-kit/select-box-kit-create-row",
filterComponent: "select-box-kit/select-box-kit-filter", filterComponent: "select-box-kit/select-box-kit-filter",
headerComponent: "select-box-kit/select-box-kit-header", headerComponent: "select-box-kit/select-box-kit-header",
headerComponentOptions: null,
collectionComponent: "select-box-kit/select-box-kit-collection", collectionComponent: "select-box-kit/select-box-kit-collection",
collectionHeight: 200, collectionHeight: 200,
verticalOffset: 0, verticalOffset: 0,
@ -50,126 +53,92 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
allowAny: false, allowAny: false,
allowValueMutation: true, allowValueMutation: true,
autoSelectFirst: true, autoSelectFirst: true,
content: null,
_initialValues: null,
init() { init() {
this._super(); 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) { if ($(window).outerWidth(false) <= 420) {
this.setProperties({ filterable: false, autoFilterable: false }); this.setProperties({ filterable: false, autoFilterable: false });
} }
this._previousScrollParentOverflow = "auto"; if (isNone(this.get("content"))) { this.set("content", []); }
this._previousCSSContext = {}; this.set("value", this._castInteger(this.get("value")));
this.setInitialValues();
}, },
click(event) { setInitialValues() {
event.stopPropagation(); this.set("_initialValues", this.getWithDefault("content", []).map((c) => {
return this._valueForContent(c);
}));
}, },
close() { @computed("computedContent.[]", "computedValue.[]", "filter")
this.setProperties({ isExpanded: false, isFocused: false }); filteredContent(computedContent, computedValue, filter) {
return this.filteredContentFunction(computedContent, computedValue, filter);
}, },
focus() { filteredContentFunction(computedContent, computedValue, filter) {
Ember.run.schedule("afterRender", () => this.$offscreenInput().select() ); if (isEmpty(filter)) { return computedContent; }
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
}, },
blur() { formatRowContent(content) {
Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() ); let originalContent;
},
clickOutside(event) { if (typeof content === "string" || typeof content === "number") {
if ($(event.target).parents(".select-box-kit").length === 1) { originalContent = {};
this.close(); originalContent[this.get("valueAttribute")] = content;
return; originalContent[this.get("nameProperty")] = content;
}
if (this.get("isExpanded") === true) {
this.set("isExpanded", false);
this.focus();
} else { } else {
this.close(); originalContent = content;
}
},
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;
} }
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 { return {
value: this.valueForContent(content), value: this._castInteger(this._valueForContent(content)),
name: this.nameForContent(content), name: this._nameForContent(content),
originalContent: content, locked: false,
meta: { generated: false } originalContent
}; };
}, },
formatContents(contents) { formatContents(contents) {
return contents.map(content => this.formatContent(content)); return contents.map(content => this.formatRowContent(content));
}, },
@computed("filter", "filterable", "autoFilterable") @computed("filter", "filterable", "autoFilterable", "renderedFilterOnce")
computedFilterable(filter, filterable, autoFilterable) { shouldDisplayFilter(filter, filterable, autoFilterable, renderedFilterOnce) {
if (filterable === true) { if (renderedFilterOnce === true || filterable === true) { return true; }
return true; if (filter.length > 0 && autoFilterable === true) { return true; }
}
if (filter.length > 0 && autoFilterable === true) {
return true;
}
return false; return false;
}, },
@computed("computedFilterable", "filter", "allowAny") @computed("filter")
shouldDisplayCreateRow(computedFilterable, filter, allow) { shouldDisplayCreateRow(filter) {
return computedFilterable === true && filter.length > 0 && allow === true; if (this.get("allowAny") === true && filter.length > 0) { return true; }
return false;
}, },
@computed("filter", "allowAny") @computed("filter", "shouldDisplayCreateRow")
createRowContent(filter, allow) { createRowContent(filter, shouldDisplayCreateRow) {
if (allow === true) { if (shouldDisplayCreateRow === true && !this.get("value").includes(filter)) {
return Ember.Object.create({ value: filter, name: filter }); return Ember.Object.create({ value: filter, name: filter });
} }
}, },
@computed("content.[]") @computed("content.[]", "value.[]")
computedContent(content) { computedContent(content) {
this._mutateValue(); this._mutateValue();
return this.formatContents(content || []); return this.formatContents(content || []);
@ -178,10 +147,10 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
@computed("value", "none", "computedContent.firstObject.value") @computed("value", "none", "computedContent.firstObject.value")
computedValue(value, none, firstContentValue) { computedValue(value, none, firstContentValue) {
if (isNone(value) && isNone(none) && this.get("autoSelectFirst") === true) { if (isNone(value) && isNone(none) && this.get("autoSelectFirst") === true) {
return this._castInteger(firstContentValue); return firstContentValue;
} }
return this._castInteger(value); return value;
}, },
@computed @computed
@ -195,90 +164,51 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
@computed("none") @computed("none")
computedNone(none) { computedNone(none) {
if (isNone(none)) { if (isNone(none)) { return null; }
return null;
}
switch (typeof none) { switch (typeof none) {
case "string": case "string":
return Ember.Object.create({ name: I18n.t(none), value: "" }); return Ember.Object.create({ name: I18n.t(none), value: this.noneValue });
default: default:
return this.formatContent(none); return this.formatRowContent(none);
} }
}, },
@computed("computedValue", "computedContent.[]") @computed("computedValue", "computedContent.[]")
selectedContent(computedValue, computedContent) { selectedContent(computedValue, computedContent) {
if (isNone(computedValue)) { if (isNone(computedValue)) { return []; }
return []; return [ computedContent.findBy("value", computedValue) ];
}
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();
}, },
@on("didInsertElement") @on("didInsertElement")
_setupResizeListener() { _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)) { autoHighlightFunction() {
this.set("highlightedValue", get(filteredContent, "firstObject.value")); Ember.run.schedule("afterRender", () => {
return; if (!isNone(this.get("highlightedValue"))) { return; }
}
if (display === true && isEmpty(filteredContent)) { const filteredContent = this.get("filteredContent");
this.set("highlightedValue", this.get("filter")); const display = this.get("shouldDisplayCreateRow");
} const none = this.get("computedNone");
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"));
}
},
@observes("isExpanded") if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) {
_isExpandedChanged() { this.send("onHighlight", get(filteredContent, "firstObject.value"));
if (this.get("isExpanded") === true) { return;
this._applyFixedPosition(); }
this.setProperties({ if (display === true && isEmpty(filteredContent)) {
highlightedValue: this.get("computedValue"), this.send("onHighlight", this.get("filter"));
renderBody: true, }
isFocused: true else if (!isEmpty(filteredContent)) {
}); this.send("onHighlight", get(filteredContent, "firstObject.value"));
} else { }
this._removeFixedPosition(); else if (isEmpty(filteredContent) && isPresent(none) && display === false) {
} this.send("onHighlight", get(none, "value"));
}, }
});
@computed("filter", "computedFilterable", "computedContent.[]", "computedValue.[]")
filteredContent(filter, computedFilterable, computedContent, computedValue) {
if (computedFilterable === false) { return computedContent; }
return this.filterFunction(computedContent)(this, computedValue);
}, },
@computed("scrollableParentSelector") @computed("scrollableParentSelector")
@ -286,191 +216,100 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
return this.$().parents(scrollableParentSelector).first(); 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: { actions: {
onToggle() { onToggle() {
this.toggleProperty("isExpanded"); this.get("isExpanded") === true ? this.collapse() : this.expand();
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);
}, },
onClearSelection() { 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) { onSelect(value) {
value = this.defaultOnSelect(value); if (value === "") { value = null; }
this.set("value", value); this.willSelectValue(value);
this.selectValueFunction(value);
this.didSelectValue(value);
}, },
onDeselect() { onDeselect(value) {
this.defaultOnDeselect(); value = this._originalValueForValue(value);
this.set("value", null); this.willDeselectValue(value);
} this.unsetValueFunction(value);
this.didSelectValue(value);
},
onFilterChange(_filter) {
this.willFilterContent(_filter);
this.set("filter", _filter);
this.didFilterContent(_filter);
},
}, },
defaultOnSelect(value) { clearFilter() {
if (value === "") { value = null; } this.$filterInput().val("");
this.setProperties({ filter: "" });
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 = $(`<div class='select-box-kit-fixed-placeholder-${this.elementId}'></div>`);
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)
});
}, },
@on("didReceiveAttrs") @on("didReceiveAttrs")

View File

@ -2,5 +2,5 @@ export default Ember.Component.extend({
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-filter", layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-filter",
classNames: "select-box-kit-filter", classNames: "select-box-kit-filter",
classNameBindings: ["isFocused", "isHidden"], classNameBindings: ["isFocused", "isHidden"],
isHidden: Ember.computed.not("filterable"), isHidden: Ember.computed.not("shouldDisplayFilter")
}); });

View File

@ -8,12 +8,15 @@ export default Ember.Component.extend(UtilsMixin, {
layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-row", layoutName: "select-box-kit/templates/components/select-box-kit/select-box-kit-row",
classNames: "select-box-kit-row", classNames: "select-box-kit-row",
tagName: "li", tagName: "li",
tabIndex: -1,
attributeBindings: [ attributeBindings: [
"tabIndex",
"title", "title",
"content.value:data-value", "content.value:data-value",
"content.name:data-name" "content.name:data-name"
], ],
classNameBindings: ["isHighlighted", "isSelected"], classNameBindings: ["isHighlighted", "isSelected"],
clicked: false,
title: Ember.computed.alias("content.name"), title: Ember.computed.alias("content.name"),
@ -23,17 +26,15 @@ export default Ember.Component.extend(UtilsMixin, {
@on("didReceiveAttrs") @on("didReceiveAttrs")
_setSelectionState() { _setSelectionState() {
const contentValue = this.get("content.value"); const contentValue = this.get("content.value");
this.set("isSelected", this.get("value") === contentValue); 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") @on("willDestroyElement")
_clearDebounce() { _clearDebounce() {
const hoverDebounce = this.get("hoverDebounce"); const hoverDebounce = this.get("hoverDebounce");
if (isPresent(hoverDebounce)) { run.cancel(hoverDebounce); }
if (isPresent(hoverDebounce)) {
run.cancel(hoverDebounce);
}
}, },
@computed("content.originalContent.icon", "content.originalContent.iconClass") @computed("content.originalContent.icon", "content.originalContent.iconClass")
@ -50,7 +51,14 @@ export default Ember.Component.extend(UtilsMixin, {
}, },
click() { 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() { _sendOnHighlightAction() {

View File

@ -6,11 +6,7 @@ export default NotificationOptionsComponent.extend({
showFullTitle: false, showFullTitle: false,
headerComponent: "tag-notifications-button/tag-notifications-button-header", headerComponent: "tag-notifications-button/tag-notifications-button-header",
actions: { selectValueFunction(value) {
onSelect(value) { this.sendAction("action", value);
value = this.defaultOnSelect(value);
this.sendAction("action", value);
this.blur();
}
} }
}); });

View File

@ -39,38 +39,34 @@ export default ComboBoxComponent.extend({
return content; return content;
}, },
actions: { selectValueFunction(value) {
onSelect(value) { const topic = this.get("topic");
value = this.defaultOnSelect(value);
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 this.set("value", value);
if (!topic.get("id")) {
return;
}
this.set("value", value); const refresh = () => this.send("onDeselect", value);
const refresh = () => this.set("value", null); switch(value) {
case "invite":
switch(value) { this.attrs.showInvite();
case "invite": refresh();
this.attrs.showInvite(); break;
refresh(); case "bookmark":
break; topic.toggleBookmark().then(() => refresh() );
case "bookmark": break;
topic.toggleBookmark().then(() => refresh() ); case "share":
break; this.appEvents.trigger("share:url", topic.get("shareUrl"), $("#topic-footer-buttons"));
case "share": refresh();
this.appEvents.trigger("share:url", topic.get("shareUrl"), $("#topic-footer-buttons")); break;
refresh(); case "flag":
break; this.attrs.showFlagTopic();
case "flag": refresh();
this.attrs.showFlagTopic(); break;
refresh();
break;
}
} }
} }
}); });

View File

@ -24,17 +24,11 @@ export default NotificationOptionsComponent.extend({
this.appEvents.off("topic-notifications-button:changed"); this.appEvents.off("topic-notifications-button:changed");
}, },
actions: { selectValueFunction(value) {
onSelect(value) { if (value !== this.get("value")) {
if (value !== this.get("computedValue")) { this.get("topic.details").updateNotifications(value);
this.get("topic.details").updateNotifications(value);
}
this.set("value", value);
this.defaultOnSelect(value);
this.blur();
} }
this.set("value", value);
} }
}); });

View File

@ -1,3 +1,5 @@
import { on } from "ember-addons/ember-computed-decorators";
export default Ember.Mixin.create({ export default Ember.Mixin.create({
init() { init() {
this._super(); this._super();
@ -8,46 +10,237 @@ export default Ember.Mixin.create({
this.collectionSelector = ".select-box-kit-collection"; this.collectionSelector = ".select-box-kit-collection";
this.headerSelector = ".select-box-kit-header"; this.headerSelector = ".select-box-kit-header";
this.bodySelector = ".select-box-kit-body"; this.bodySelector = ".select-box-kit-body";
this.wrapperSelector = ".select-box-kit-wrapper";
}, },
$findRowByValue(value) { $findRowByValue(value) { return this.$(`${this.rowSelector}[data-value='${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() { $highlightedRow() { return this.$rows().filter(".is-highlighted"); },
return this.$(this.headerSelector);
$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() { @on("willDestroyElement")
return this.$(this.bodySelector); _clearState() {
$(window).off("resize.select-box-kit");
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
}, },
$collection() { // make sure we don’t propagate a click outside component
return this.$(this.collectionSelector); // 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() { // force the component in a known default state
return this.$(this.rowSelector); focus() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().focus() );
}, },
$highlightedRow() { expand() {
return this.$rows().filter(".is-highlighted"); if (this.get("isExpanded") === true) { return; }
this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true });
this.focus();
this.autoHighlightFunction();
}, },
$selectedRow() { collapse() {
return this.$rows().filter(".is-selected"); this.set("isExpanded", false);
Ember.run.schedule("afterRender", () => this._removeFixedPosition() );
}, },
$offscreenInput() { // make sure we close/unfocus the component when clicked outside
return this.$(this.offscreenInputSelector); clickOutside(event) {
if ($(event.target).parents(".select-box-kit").length === 1) {
this.close();
return false;
}
this.unfocus();
return;
}, },
$filterInput() { // lose focus of the component in two steps
return this.$(this.filterInputSelector); // 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) { _killEvent(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); 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 = $(`<div class='select-box-kit-fixed-placeholder-${this.elementId}'></div>`);
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)
});
},
}); });

View File

@ -1,5 +1,3 @@
const { isEmpty } = Ember;
export default Ember.Mixin.create({ export default Ember.Mixin.create({
init() { init() {
this._super(); this._super();
@ -35,9 +33,13 @@ export default Ember.Mixin.create({
.off("focus.select-box-kit") .off("focus.select-box-kit")
.off("focusin.select-box-kit") .off("focusin.select-box-kit")
.off("blur.select-box-kit") .off("blur.select-box-kit")
.off("keypress.select-box-kit")
.off("keydown.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() { didInsertElement() {
@ -70,58 +72,67 @@ export default Ember.Mixin.create({
.on("keydown.select-box-kit", (event) => { .on("keydown.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which; 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) { 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: case this.keys.ENTER:
if (this.get("isExpanded") === false) { if (this.get("isExpanded") === false) {
this.set("isExpanded", true); this.expand();
} else { } else if (this.$highlightedRow().length === 1) {
this.send("onSelect", this.$highlightedRow().data("value")); this.$highlightedRow().click();
} }
return false;
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;
case this.keys.BACKSPACE: case this.keys.BACKSPACE:
this._killEvent(event); return event;
return;
} }
if (this._isSpecialKey(keyCode) === false && event.metaKey === false) { if (this._isSpecialKey(keyCode) === false && event.metaKey === false) {
this.setProperties({ this.expand();
isExpanded: true,
filter: String.fromCharCode(keyCode)
});
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() 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; const keyCode = event.keyCode || event.which;
if ([ if ([
@ -133,67 +144,75 @@ export default Ember.Mixin.create({
return true; return true;
} }
if (keyCode === this.keys.TAB && this.get("isExpanded") === false) {
return true;
}
if (this._isSpecialKey(keyCode) === true) { if (this._isSpecialKey(keyCode) === true) {
this.$offscreenInput().focus().trigger(event); this.$offscreenInput().focus().trigger(event);
return false;
} }
return true; return true;
}); });
}, },
_handleArrowKey(keyCode) { _handleEscOnKeyDown(event) {
if (isEmpty(this.get("filteredContent"))) { 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; return;
} }
Ember.run.schedule("afterRender", () => { const direction = keyCode === 38 ? -1 : 1;
switch (keyCode) {
case 38: Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32);
Ember.run.throttle(this, this._handleUpArrow, 32);
break;
default:
Ember.run.throttle(this, this._handleDownArrow, 32);
}
});
}, },
_moveHighlight(direction) { _moveHighlight(direction, $rows) {
const $rows = this.$rows();
const currentIndex = $rows.index(this.$highlightedRow()); const currentIndex = $rows.index(this.$highlightedRow());
let nextIndex = currentIndex + direction;
let nextIndex = 0; if (nextIndex < 0) {
nextIndex = $rows.length - 1;
if (currentIndex < 0) { } else if (nextIndex >= $rows.length) {
nextIndex = 0; nextIndex = 0;
} else if (currentIndex + direction < $rows.length) {
nextIndex = currentIndex + direction;
} }
this._rowSelection($rows, nextIndex); this._rowSelection($rows, nextIndex);
}, },
_handleDownArrow() { this._moveHighlight(1); },
_handleUpArrow() { this._moveHighlight(-1); },
_rowSelection($rows, nextIndex) { _rowSelection($rows, nextIndex) {
const highlightableValue = $rows.eq(nextIndex).data("value"); const highlightableValue = $rows.eq(nextIndex).attr("data-value");
const $highlightableRow = this.$findRowByValue(highlightableValue); const $highlightableRow = this.$findRowByValue(highlightableValue);
this.send("onHighlight", highlightableValue);
Ember.run.schedule("afterRender", () => { Ember.run.schedule("afterRender", () => {
const $collection = this.$collection(); $highlightableRow.trigger("mouseover").focus();
const currentOffset = $collection.offset().top + this.focus();
$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);
}
}); });
}, },

View File

@ -1,9 +1,57 @@
const { get, isNone } = Ember;
export default Ember.Mixin.create({ 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) { _castInteger(value) {
if (this.get("castInteger") === true && Ember.isPresent(value)) { if (this.get("castInteger") === true && Ember.isPresent(value)) {
return parseInt(value, 10); 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"));
},
}); });

View File

@ -1,30 +1,13 @@
<ul class="choices"> <ul class="choices">
{{#each selectedContent as |selectedContent|}} {{#each selectedContent as |selectedContent|}}
<li tabindex="-1" data-value={{selectedContent.value}} data-name={{selectedContent.name}} class="selected-name"> {{component selectedNameComponent onDeselect=onDeselect content=selectedContent}}
<span class="delete-icon" {{action onDeselect selectedContent.value bubbles=false}}>
{{d-icon "times"}}
</span>
<span class="name">
{{selectedContent.name}}
</span>
</li>
{{else}}
{{#if shouldDisplayFilterPlaceholder}}
<li class="choice-placeholder">
{{text}}
</li>
{{/if}}
{{/each}} {{/each}}
<li class="filter"> <li class="filter">
{{input {{component "select-box-kit/select-box-kit-filter"
class="select-box-kit-filter-input" onFilterChange=onFilterChange
key-up=onFilterChange shouldDisplayFilter=shouldDisplayFilter
autocomplete="off" isFocused=isFocused
autocorrect="off" filter=filter
autocapitalize="off"
spellcheck=false
value=filter
}} }}
</li> </li>
</ul> </ul>

View File

@ -0,0 +1,9 @@
<span class="name">
{{#unless isLocked}}
<span class="delete-icon" {{action onDeselect content.value bubbles=false}}>
{{d-icon "times"}}
</span>
{{/unless}}
{{content.name}}
</span>

View File

@ -19,6 +19,7 @@
onToggle=(action "onToggle") onToggle=(action "onToggle")
onFilterChange=(action "onFilterChange") onFilterChange=(action "onFilterChange")
onClearSelection=(action "onClearSelection") onClearSelection=(action "onClearSelection")
shouldDisplayFilter=shouldDisplayFilter
options=headerComponentOptions options=headerComponentOptions
}} }}
@ -26,16 +27,14 @@
{{component filterComponent {{component filterComponent
onFilterChange=(action "onFilterChange") onFilterChange=(action "onFilterChange")
icon=filterIcon icon=filterIcon
filter=filter shouldDisplayFilter=shouldDisplayFilter
filterable=computedFilterable
isFocused=isFocused isFocused=isFocused
placeholder=(i18n filterPlaceholder) placeholder=(i18n filterPlaceholder)
tabindex=tabindex filter=filter
}} }}
{{#if renderBody}} {{#if renderedBodyOnce}}
{{component collectionComponent {{component collectionComponent
shouldDisplayCreateRow=shouldDisplayCreateRow
none=computedNone none=computedNone
createRowContent=createRowContent createRowContent=createRowContent
selectedContent=selectedContent selectedContent=selectedContent
@ -43,13 +42,9 @@
rowComponent=rowComponent rowComponent=rowComponent
noneRowComponent=noneRowComponent noneRowComponent=noneRowComponent
createRowComponent=createRowComponent createRowComponent=createRowComponent
iconForRow=iconForRow
templateForRow=templateForRow templateForRow=templateForRow
templateForNoneRow=templateForNoneRow templateForNoneRow=templateForNoneRow
templateForCreateRow=templateForCreateRow templateForCreateRow=templateForCreateRow
shouldHighlightRow=shouldHighlightRow
shouldSelectRow=shouldSelectRow
titleForRow=titleForRow
onClearSelection=(action "onClearSelection") onClearSelection=(action "onClearSelection")
onSelect=(action "onSelect") onSelect=(action "onSelect")
onHighlight=(action "onHighlight") onHighlight=(action "onHighlight")

View File

@ -3,8 +3,6 @@
{{component noneRowComponent {{component noneRowComponent
content=none content=none
templateForRow=templateForNoneRow templateForRow=templateForNoneRow
titleForRow=titleForRow
iconForRow=iconForRow
highlightedValue=highlightedValue highlightedValue=highlightedValue
onClearSelection=onClearSelection onClearSelection=onClearSelection
onHighlight=onHighlight onHighlight=onHighlight
@ -14,12 +12,11 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if shouldDisplayCreateRow}} {{#if createRowContent}}
{{component createRowComponent {{component createRowComponent
content=createRowContent content=createRowContent
templateForRow=templateForCreateRow templateForRow=templateForCreateRow
titleForRow=titleForRow titleForRow=titleForRow
iconForRow=iconForRow
highlightedValue=highlightedValue highlightedValue=highlightedValue
onHighlight=onHighlight onHighlight=onHighlight
onCreateContent=onCreateContent onCreateContent=onCreateContent
@ -33,7 +30,6 @@
content=content content=content
templateForRow=templateForRow templateForRow=templateForRow
titleForRow=titleForRow titleForRow=titleForRow
iconForRow=iconForRow
highlightedValue=highlightedValue highlightedValue=highlightedValue
onSelect=onSelect onSelect=onSelect
onHighlight=onHighlight onHighlight=onHighlight

View File

@ -1,8 +1,8 @@
{{input {{input
tabindex=tabindex tabindex=-1
class="select-box-kit-filter-input" class="select-box-kit-filter-input"
placeholder=placeholder placeholder=placeholder
key-down=onFilterChange key-up=onFilterChange
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
autocapitalize="off" autocapitalize="off"

View File

@ -215,9 +215,8 @@ $mobile-breakpoint: 700px;
.select-box-kit { .select-box-kit {
width: 350px; width: 350px;
} }
.select-box-kit.multi-combo-box {
.select-box-kit-header { width: 500px;
height: 28px;
} }
} }
@ -620,6 +619,10 @@ section.details {
text-align: left; text-align: left;
margin-left: 0; margin-left: 0;
} }
.select-box-kit {
width: inherit;
}
} }
.long-value { .long-value {
width: 800px; width: 800px;
@ -701,7 +704,7 @@ section.details {
.ace_editor { .ace_editor {
pointer-events:none; pointer-events:none;
.ace_cursor { .ace_cursor {
visibility: hidden; visibility: hidden;
} }

View File

@ -89,14 +89,6 @@
white-space: normal; white-space: normal;
} }
} }
&.is-highlighted {
background: $tertiary-low;
}
&:hover {
background: $highlight-medium;
}
} }
.select-box-kit-collection { .select-box-kit-collection {

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
} }
.select-box-kit { .select-box-kit {
border: 1px solid transparent;
min-width: 220px; min-width: 220px;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
@ -65,6 +66,9 @@
} }
.select-box-kit-header { .select-box-kit-header {
border: 1px solid transparent;
box-sizing: border-box;
overflow: hidden;
-webkit-transition: all .25s; -webkit-transition: all .25s;
-o-transition: all .25s; -o-transition: all .25s;
transition: all .25s; transition: all .25s;
@ -138,10 +142,13 @@
.select-box-kit-row { .select-box-kit-row {
cursor: pointer; cursor: pointer;
line-height: normal;
outline: none; outline: none;
display: -webkit-box; display: -webkit-box;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-align: center; -webkit-box-align: center;
-ms-flex-align: center; -ms-flex-align: center;
align-items: center; align-items: center;
@ -172,10 +179,6 @@
&.is-selected.is-highlighted { &.is-selected.is-highlighted {
background: $tertiary-low; background: $tertiary-low;
} }
&.none:not(.is-highlighted) {
background: $primary-low;
}
} }
.select-box-kit-collection { .select-box-kit-collection {
@ -255,8 +258,8 @@
.select-box-kit-wrapper { .select-box-kit-wrapper {
position: absolute; position: absolute;
top: 0; top: -1px;
left: 0; left: -1px;
background: none; background: none;
display: none; display: none;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;

View File

@ -1155,6 +1155,7 @@ en:
default_header_text: Select... default_header_text: Select...
no_content: No matches found no_content: No matches found
filter_placeholder: Search... filter_placeholder: Search...
create: "Create {{content}}"
emoji_picker: emoji_picker:
filter_placeholder: Search for emoji filter_placeholder: Search for emoji

View File

@ -44,8 +44,8 @@ QUnit.test("suspend, then unsuspend a user", assert => {
andThen(() => { andThen(() => {
assert.equal(find('.perform-suspend[disabled]').length, 1, 'disabled by default'); assert.equal(find('.perform-suspend[disabled]').length, 1, 'disabled by default');
expandSelectBox('.suspend-until .combobox'); expandSelectBoxKit('.suspend-until .combobox');
selectBoxSelectRow('tomorrow', { selector: '.suspend-until .combobox'}); selectBoxKitSelectRow('tomorrow', { selector: '.suspend-until .combobox'});
}); });
fillIn('.suspend-reason', "for breaking the rules"); fillIn('.suspend-reason', "for breaking the rules");

View File

@ -11,7 +11,7 @@ QUnit.test("does not display uncategorized if not allowed", assert => {
visit("/"); visit("/");
click('#create-topic'); click('#create-topic');
expandSelectBox('.category-chooser'); ('.category-chooser');
andThen(() => { andThen(() => {
assert.ok(selectBox('.category-chooser').rowByIndex(0).name() !== 'uncategorized'); assert.ok(selectBox('.category-chooser').rowByIndex(0).name() !== 'uncategorized');

View File

@ -75,9 +75,9 @@ QUnit.test("Subcategory list settings", assert => {
click('.edit-category-general'); 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'); click('.edit-category-settings');
andThen(() => { andThen(() => {

View File

@ -257,8 +257,8 @@ QUnit.test("update in filter through advanced search ui", assert => {
fillIn('.search input.full-page-search', 'none'); fillIn('.search input.full-page-search', 'none');
click('.search-advanced-btn'); click('.search-advanced-btn');
expandSelectBox('.search-advanced-options .select-box-kit#in'); expandSelectBoxKit('.search-advanced-options .select-box-kit#in');
selectBoxSelectRow('bookmarks', { selector: '.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'); fillIn('.search-advanced-options .select-box-kit#in', 'bookmarks');
andThen(() => { andThen(() => {
@ -271,8 +271,8 @@ QUnit.test("update status through advanced search ui", assert => {
visit("/search"); visit("/search");
fillIn('.search input.full-page-search', 'none'); fillIn('.search input.full-page-search', 'none');
click('.search-advanced-btn'); click('.search-advanced-btn');
expandSelectBox('.search-advanced-options .select-box-kit#status'); expandSelectBoxKit('.search-advanced-options .select-box-kit#status');
selectBoxSelectRow('closed', { selector: '.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'); fillIn('.search-advanced-options .select-box-kit#status', 'closed');
andThen(() => { andThen(() => {
@ -286,8 +286,8 @@ QUnit.test("update post time through advanced search ui", assert => {
fillIn('.search input.full-page-search', 'none'); fillIn('.search input.full-page-search', 'none');
click('.search-advanced-btn'); click('.search-advanced-btn');
fillIn('#search-post-date', '2016-10-05'); fillIn('#search-post-date', '2016-10-05');
expandSelectBox('.search-advanced-options .select-box-kit#postTime'); expandSelectBoxKit('.search-advanced-options .select-box-kit#postTime');
selectBoxSelectRow('after', { selector: '.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'); fillIn('.search-advanced-options .select-box-kit#postTime', 'after');
andThen(() => { andThen(() => {

View File

@ -89,7 +89,7 @@ QUnit.test("Search with context", assert => {
QUnit.test("Right filters are shown to anonymous users", assert => { QUnit.test("Right filters are shown to anonymous users", assert => {
visit("/search?expanded=true"); visit("/search?expanded=true");
expandSelectBox(".select-box-kit#in"); expandSelectBoxKit(".select-box-kit#in");
andThen(() => { andThen(() => {
assert.ok(exists('.select-box-kit#in .select-box-kit-row[data-value=first]')); 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(); Discourse.reset();
visit("/search?expanded=true"); visit("/search?expanded=true");
expandSelectBox(".select-box-kit#in"); expandSelectBoxKit(".select-box-kit#in");
andThen(() => { andThen(() => {
assert.ok(exists('.select-box-kit#in .select-box-kit-row[data-value=first]')); assert.ok(exists('.select-box-kit#in .select-box-kit-row[data-value=first]'));

View File

@ -28,8 +28,8 @@ QUnit.test("Updating topic notification level", assert => {
); );
}); });
expandSelectBox(notificationOptions); expandSelectBoxKit(notificationOptions);
selectBoxSelectRow("3", { selector: notificationOptions}); selectBoxKitSelectRow("3", { selector: notificationOptions});
andThen(() => { andThen(() => {
assert.equal( assert.equal(

View File

@ -53,9 +53,9 @@ QUnit.test("Updating the topic title and category", assert => {
fillIn('#edit-title', 'this is the new title'); 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'); click('#topic-title .submit-edit');

View File

@ -10,7 +10,7 @@ componentTest('default', {
assert.equal($selectBox.el.find(".d-icon-bars").length, 1); assert.equal($selectBox.el.find(".d-icon-bars").length, 1);
assert.equal($selectBox.el.find(".d-icon-caret-down").length, 1); assert.equal($selectBox.el.find(".d-icon-caret-down").length, 1);
expandSelectBox('.categories-admin-dropdown'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal($selectBox.rowByValue("create").name(), "New Category"); assert.equal($selectBox.rowByValue("create").name(), "New Category");

View File

@ -6,7 +6,7 @@ componentTest('with value', {
template: '{{category-chooser value=2}}', template: '{{category-chooser value=2}}',
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "feature"); assert.equal(selectBox('.category-chooser').header.name(), "feature");
@ -18,7 +18,7 @@ componentTest('with excludeCategoryId', {
template: '{{category-chooser excludeCategoryId=2}}', template: '{{category-chooser excludeCategoryId=2}}',
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').rowByValue(2).el.length, 0); assert.equal(selectBox('.category-chooser').rowByValue(2).el.length, 0);
@ -30,7 +30,7 @@ componentTest('with scopedCategoryId', {
template: '{{category-chooser scopedCategoryId=2}}', template: '{{category-chooser scopedCategoryId=2}}',
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').rowByIndex(0).name(), "feature"); assert.equal(selectBox('.category-chooser').rowByIndex(0).name(), "feature");
@ -48,7 +48,7 @@ componentTest('with allowUncategorized=null', {
}, },
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "Select a category&hellip;"); assert.equal(selectBox('.category-chooser').header.name(), "Select a category&hellip;");
@ -64,7 +64,7 @@ componentTest('with allowUncategorized=null rootNone=true', {
}, },
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "Select a category&hellip;"); assert.equal(selectBox('.category-chooser').header.name(), "Select a category&hellip;");
@ -81,7 +81,7 @@ componentTest('with disallowed uncategorized, rootNone and rootNoneLabel', {
}, },
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "Select a category&hellip;"); assert.equal(selectBox('.category-chooser').header.name(), "Select a category&hellip;");
@ -97,7 +97,7 @@ componentTest('with allowed uncategorized', {
}, },
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "uncategorized"); assert.equal(selectBox('.category-chooser').header.name(), "uncategorized");
@ -113,7 +113,7 @@ componentTest('with allowed uncategorized and rootNone', {
}, },
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "(no category)"); assert.equal(selectBox('.category-chooser').header.name(), "(no category)");
@ -130,7 +130,7 @@ componentTest('with allowed uncategorized rootNone and rootNoneLabel', {
}, },
test(assert) { test(assert) {
expandSelectBox('.category-chooser'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.category-chooser').header.name(), "root none label"); assert.equal(selectBox('.category-chooser').header.name(), "root none label");

View File

@ -8,7 +8,7 @@ componentTest('default', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').header.name(), "hello"); assert.equal(selectBox('.combobox').header.name(), "hello");
@ -25,7 +25,7 @@ componentTest('with valueAttribute', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').rowByValue(0).name(), "hello"); assert.equal(selectBox('.combobox').rowByValue(0).name(), "hello");
@ -41,7 +41,7 @@ componentTest('with nameProperty', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').rowByValue(0).name(), "hello"); assert.equal(selectBox('.combobox').rowByValue(0).name(), "hello");
@ -57,7 +57,7 @@ componentTest('with an array as content', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').rowByValue('evil').name(), "evil"); assert.equal(selectBox('.combobox').rowByValue('evil').name(), "evil");
@ -75,7 +75,7 @@ componentTest('with value and none as a string', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').noneRow.name(), 'none'); 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'); assert.equal(this.get('value'), 'trout');
}); });
selectBoxSelectRow('', {selector: '.combobox' }); selectBoxKitSelectRow('__none__', {selector: '.combobox' });
andThen(() => { andThen(() => {
assert.equal(this.get('value'), null); assert.equal(this.get('value'), null);
@ -102,7 +102,7 @@ componentTest('with value and none as an object', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').noneRow.name(), 'none'); 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'); assert.equal(this.get('value'), 'evil');
}); });
selectBoxSelectNoneRow({ selector: '.combobox' }); selectBoxKitSelectNoneRow({ selector: '.combobox' });
andThen(() => { andThen(() => {
assert.equal(this.get('value'), null); assert.equal(this.get('value'), null);
@ -130,7 +130,7 @@ componentTest('with no value and none as an object', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').header.name(), 'none'); assert.equal(selectBox('.combobox').header.name(), 'none');
@ -148,7 +148,7 @@ componentTest('with no value and none string', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').header.name(), 'none'); assert.equal(selectBox('.combobox').header.name(), 'none');
@ -164,7 +164,7 @@ componentTest('with no value and no none', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').header.name(), 'evil', 'it sets the first row as value'); 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) { // test(assert) {
// expandSelectBox(); // ();
// //
// andThen(() => assert.equal(find(".select-box-kit-filter-input").length, 1, "it has a search input")); // 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")); // andThen(() => assert.equal(selectBox().rows.length, 1, "it filters results"));
// //
// selectBoxFillInFilter(""); // selectBoxKitFillInFilter("");
// //
// andThen(() => { // andThen(() => {
// assert.equal( // assert.equal(
@ -207,17 +207,17 @@ componentTest('with no value and no none', {
// }, // },
// //
// test(assert) { // test(assert) {
// expandSelectBox(); // ();
// //
// selectBoxFillInFilter("rob"); // selectBoxKitFillInFilter("rob");
// //
// andThen(() => assert.equal(selectBox().rows.length, 1) ); // andThen(() => assert.equal(selectBox().rows.length, 1) );
// //
// collapseSelectBox(); // collapseSelectBoxKit();
// //
// andThen(() => assert.notOk(selectBox().isExpanded) ); // andThen(() => assert.notOk(selectBox().isExpanded) );
// //
// expandSelectBox(); // ();
// //
// andThen(() => assert.equal(selectBox().rows.length, 1) ); // andThen(() => assert.equal(selectBox().rows.length, 1) );
// } // }
@ -232,7 +232,7 @@ componentTest('with empty string as value', {
}, },
test(assert) { test(assert) {
expandSelectBox('.combobox'); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox('.combobox').header.name(), 'evil', 'it sets the first row as value'); assert.equal(selectBox('.combobox').header.name(), 'evil', 'it sets the first row as value');

View File

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

View File

@ -11,7 +11,99 @@ componentTest('with objects and values', {
test(assert) { test(assert) {
andThen(() => { 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"));
}); });
} }
}); });

View File

@ -23,7 +23,7 @@ componentTest('updating the content refreshes the list', {
test(assert) { test(assert) {
andThen(() => assert.notOk(selectBox().isHidden) ); andThen(() => assert.notOk(selectBox().isHidden) );
expandSelectBox(); expandSelectBoxKit();
andThen(() => assert.equal(selectBox().selectedRow.name(), "Pinned") ); andThen(() => assert.equal(selectBox().selectedRow.name(), "Pinned") );

View File

@ -10,7 +10,7 @@ componentTest('updating the content refreshes the list', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox().rowByValue(1).name(), "robin"); assert.equal(selectBox().rowByValue(1).name(), "robin");
@ -29,7 +29,7 @@ componentTest('accepts a value by reference', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal( assert.equal(
@ -38,7 +38,7 @@ componentTest('accepts a value by reference', {
); );
}); });
selectBoxSelectRow(1); selectBoxKitSelectRow(1);
andThen(() => { andThen(() => {
assert.equal(this.get("value"), 1, "it mutates the value"); assert.equal(this.get("value"), 1, "it mutates the value");
@ -58,7 +58,7 @@ componentTest('default search icon', {
template: '{{select-box-kit filterable=true}}', template: '{{select-box-kit filterable=true}}',
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.ok(exists(selectBox().filter.icon), "it has a the correct icon"); 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}}', template: '{{select-box-kit filterable=true filterIcon=null}}',
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox().filter.icon().length, 0, "it has no icon"); 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"}}', template: '{{select-box-kit filterable=true filterIcon="shower"}}',
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.ok(selectBox().filter.icon().hasClass("d-icon-shower"), "it has a the correct icon"); 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', { componentTest('select-box is expandable', {
template: '{{select-box-kit}}', template: '{{select-box-kit}}',
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => assert.ok(selectBox().isExpanded) ); andThen(() => assert.ok(selectBox().isExpanded) );
collapseSelectBox(); collapseSelectBoxKit();
andThen(() => assert.notOk(selectBox().isExpanded) ); andThen(() => assert.notOk(selectBox().isExpanded) );
} }
@ -112,7 +112,7 @@ componentTest('accepts custom value/name keys', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox().selectedRow.name(), "robin"); assert.equal(selectBox().selectedRow.name(), "robin");
@ -130,7 +130,7 @@ componentTest('doesn’t render collection content before first expand', {
test(assert) { test(assert) {
assert.notOk(exists(find(".select-box-kit-collection"))); assert.notOk(exists(find(".select-box-kit-collection")));
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.ok(exists(find(".select-box-kit-collection"))); assert.ok(exists(find(".select-box-kit-collection")));
@ -146,7 +146,7 @@ componentTest('supports options to limit size', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
const height = find(".select-box-kit-collection").height(); const height = find(".select-box-kit-collection").height();
@ -163,11 +163,11 @@ componentTest('dynamic headerText', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => assert.equal(selectBox().header.name(), "robin") ); andThen(() => assert.equal(selectBox().header.name(), "robin") );
selectBoxSelectRow(2); selectBoxKitSelectRow(2);
andThen(() => { andThen(() => {
assert.equal(selectBox().header.name(), "regis", "it changes header text"); assert.equal(selectBox().header.name(), "regis", "it changes header text");
@ -186,7 +186,7 @@ componentTest('supports custom row template', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => assert.equal(selectBox().rowByValue(1).el.html().trim(), "<b>robin</b>") ); andThen(() => assert.equal(selectBox().rowByValue(1).el.html().trim(), "<b>robin</b>") );
} }
@ -201,7 +201,7 @@ componentTest('supports converting select value to integer', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => assert.equal(selectBox().selectedRow.name(), "régis") ); andThen(() => assert.equal(selectBox().selectedRow.name(), "régis") );
@ -224,7 +224,7 @@ componentTest('supports keyboard events', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
selectBox().keyboard.down(); selectBox().keyboard.down();
@ -251,7 +251,7 @@ componentTest('supports keyboard events', {
assert.notOk(selectBox().isExpanded, "it collapses the select box when selecting a row"); assert.notOk(selectBox().isExpanded, "it collapses the select box when selecting a row");
}); });
expandSelectBox(); expandSelectBoxKit();
selectBox().keyboard.escape(); selectBox().keyboard.escape();
@ -259,18 +259,13 @@ componentTest('supports keyboard events', {
assert.notOk(selectBox().isExpanded, "it collapses the select box"); assert.notOk(selectBox().isExpanded, "it collapses the select box");
}); });
expandSelectBox(); expandSelectBoxKit();
selectBoxFillInFilter("regis"); selectBoxKitFillInFilter("regis");
// andThen(() => {
// assert.equal(selectBox().highlightedRow.title(), "regis", "it highlights the first result");
// });
selectBox().keyboard.tab(); selectBox().keyboard.tab();
andThen(() => { 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"); assert.notOk(selectBox().isExpanded, "it collapses the select box when selecting a row");
}); });
} }

View File

@ -17,7 +17,7 @@ componentTest('default', {
}, },
test(assert) { test(assert) {
expandSelectBox(); expandSelectBoxKit();
andThen(() => { andThen(() => {
assert.equal(selectBox().header.name(), "Topic Controls"); 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"); assert.equal(selectBox().selectedRow.el.length, 0, "it doesn’t preselect first row");
}); });
selectBoxSelectRow("share"); selectBoxKitSelectRow("share");
andThen(() => { andThen(() => {
assert.equal(this.get("value"), null, "it resets the value"); assert.equal(this.get("value"), null, "it resets the value");

View File

@ -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'; selectBoxSelector = selectBoxSelector || '.select-box-kit';
checkSelectBoxIsNotExpanded(selectBoxSelector); checkSelectBoxIsNotExpanded(selectBoxSelector);
@ -18,7 +18,7 @@ Ember.Test.registerAsyncHelper('expandSelectBox', function(app, selectBoxSelecto
click(selectBoxSelector + ' .select-box-kit-header'); 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'; selectBoxSelector = selectBoxSelector || '.select-box-kit';
checkSelectBoxIsNotCollapsed(selectBoxSelector); checkSelectBoxIsNotCollapsed(selectBoxSelector);
@ -26,7 +26,7 @@ Ember.Test.registerAsyncHelper('collapseSelectBox', function(app, selectBoxSelec
click(selectBoxSelector + ' .select-box-kit-header'); 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 = options || {};
options.selector = options.selector || '.select-box-kit'; 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 + "']"); 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 = options || {};
options.selector = options.selector || '.select-box-kit'; 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"); 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 = options || {};
options.selector = options.selector || '.select-box-kit'; 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'; var filterQuerySelector = options.selector + ' .select-box-kit-filter-input';
fillIn(filterQuerySelector, filter); fillIn(filterQuerySelector, filter);
triggerEvent(filterQuerySelector, 'keyup');
}); });
function selectBox(selector) { // eslint-disable-line no-unused-vars 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 keyboardHelper() {
function createEvent(target, keyCode) { function createEvent(target, keyCode, options) {
target = target || ".select-box-kit-filter-input"; target = target || ".select-box-kit-filter-input";
selector = find(selector).find(target); selector = find(selector).find(target);
andThen(function() { andThen(function() {
var event = jQuery.Event('keydown'); var event = jQuery.Event(options.type);
event.keyCode = keyCode; event.keyCode = keyCode;
if (options && options.metaKey === true) { event.metaKey = true; }
find(selector).trigger(event); find(selector).trigger(event);
}); });
} }
return { return {
down: function(target) { createEvent(target, 40); }, down: function(target) { createEvent(target, 40, {type: 'keydown'}); },
up: function(target) { createEvent(target, 38); }, up: function(target) { createEvent(target, 38, {type: 'keydown'}); },
escape: function(target) { createEvent(target, 27); }, escape: function(target) { createEvent(target, 27, {type: 'keydown'}); },
enter: function(target) { createEvent(target, 13); }, enter: function(target) { createEvent(target, 13, {type: 'keypress'}); },
tab: function(target) { createEvent(target, 9); } 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'}); },
}; };
} }

View File

@ -32,7 +32,7 @@
//= require sinon-qunit-1.0.0 //= require sinon-qunit-1.0.0
//= require helpers/assertions //= require helpers/assertions
//= require helpers/select-box-helper //= require helpers/select-box-kit-helper
//= require helpers/qunit-helpers //= require helpers/qunit-helpers
//= require_tree ./fixtures //= require_tree ./fixtures