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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1394 additions and 985 deletions

View File

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

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

View File

@ -12,24 +12,24 @@ export default ComboBoxComponent.extend({
castInteger: true,
allowUncategorized: null,
filterFunction(computedContent) {
const _matchFunction = (filter, text) => {
return text.toLowerCase().indexOf(filter) > -1;
};
filteredContentFunction(computedContent, computedValue, filter) {
if (isEmpty(filter)) { return computedContent; }
return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(computedContent, c => {
const category = Category.findById(get(c, "value"));
const text = get(c, "name");
if (category && category.get("parentCategory")) {
const categoryName = category.get("parentCategory.name");
return _matchFunction(filter, text) || _matchFunction(filter, categoryName);
} else {
return _matchFunction(filter, text);
}
});
const _matchFunction = (f, text) => {
return text.toLowerCase().indexOf(f) > -1;
};
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
const category = Category.findById(get(c, "value"));
const text = get(c, "name");
if (category && category.get("parentCategory")) {
const categoryName = category.get("parentCategory.name");
return _matchFunction(lowerFilter, text) || _matchFunction(lowerFilter, categoryName);
} else {
return _matchFunction(lowerFilter, text);
}
});
},
@computed("rootNone", "rootNoneLabel")

View File

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

View File

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

View File

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

View File

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

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 computed from "ember-addons/ember-computed-decorators";
const { get, isNone } = Ember;
const { get, isNone, isEmpty } = Ember;
export default SelectBoxKitComponent.extend({
classNames: "multi-combobox",
classNames: "multi-combo-box",
headerComponent: "multi-combo-box/multi-combo-box-header",
filterComponent: null,
headerText: "select_box.default_header_text",
value: [],
allowAny: true,
allowValueMutation: false,
autoSelectFirst: false,
autoFilterable: true,
selectedNameComponent: "multi-combo-box/selected-name",
init() {
this._super();
if (isNone(this.get("value"))) { this.set("value", []); }
this.set("headerComponentOptions", Ember.Object.create({
selectedNameComponent: this.get("selectedNameComponent")
}));
},
@computed("filter")
templateForCreateRow() {
return (rowComponent) => {
return `Create: ${rowComponent.get("content.name")}`;
return I18n.t("select_box.create", { content: rowComponent.get("content.name")});
};
},
@ -22,78 +34,207 @@ export default SelectBoxKitComponent.extend({
const keyCode = event.keyCode || event.which;
const $filterInput = this.$filterInput();
if (keyCode === 8) {
let $lastSelectedValue = $(this.$(".choices .selected-name").last());
if (this.get("isFocused") === true && this.get("isExpanded") === false && keyCode === this.keys.BACKSPACE) {
this.expand();
return;
}
if ($lastSelectedValue.is(":focus") || $(document.activeElement).is($lastSelectedValue)) {
this.send("onDeselect", $lastSelectedValue.data("value"));
// select all choices
if (event.metaKey === true && keyCode === 65 && isEmpty(this.get("filter"))) {
this.$(".choices .selected-name:not(.is-locked)").addClass("is-highlighted");
return;
}
// clear selection when multiple
if (Ember.isEmpty(this.get("filter")) && this.$(".selected-name.is-highlighted").length >= 1 && keyCode === this.keys.BACKSPACE) {
const highlightedValues = [];
$.each(this.$(".selected-name.is-highlighted"), (i, el) => {
highlightedValues.push($(el).attr("data-value"));
});
this.send("onDeselect", highlightedValues);
return;
}
// try to remove last item from the list
if (Ember.isEmpty(this.get("filter")) && keyCode === this.keys.BACKSPACE) {
let $lastSelectedValue = $(this.$(".choices .selected-name:not(.is-locked)").last());
if ($lastSelectedValue.length === 0) { return; }
if ($lastSelectedValue.hasClass("is-highlighted") || $(document.activeElement).is($lastSelectedValue)) {
this.send("onDeselect", this.get("selectedContent.lastObject.value"));
$filterInput.focus();
return;
}
if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) {
$lastSelectedValue.click();
return false;
}
if ($filterInput.val() === "") {
if ($filterInput.is(":focus")) {
if ($lastSelectedValue.length > 0) {
$lastSelectedValue.focus();
}
if ($lastSelectedValue.length > 0) { $lastSelectedValue.click(); }
} else {
if ($lastSelectedValue.length > 0) {
$lastSelectedValue.focus();
$lastSelectedValue.click();
} else {
$filterInput.focus();
}
}
}
} else {
$filterInput.focus();
this._super(event);
}
},
@computed("none")
computedNone(none) {
if (!isNone(none)) {
this.set("none", { name: I18n.t(none), value: "" });
}
},
@computed("value.[]")
computedValue(value) {
return value.map(v => this._castInteger(v));
},
computedValue(value) { return value.map(v => this._castInteger(v)); },
@computed("computedValue.[]", "computedContent.[]")
selectedContent(computedValue, computedContent) {
@computed("value.[]", "computedContent.[]")
selectedContent(value, computedContent) {
const contents = [];
computedValue.forEach(cv => {
contents.push(computedContent.findBy("value", cv));
value.forEach(v => {
const content = computedContent.findBy("value", v);
if (!isNone(content)) { contents.push(content); }
});
return contents;
},
filterFunction(content) {
return (selectBox, computedValue) => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, c => {
return !computedValue.includes(get(c, "value")) &&
get(c, "name").toLowerCase().indexOf(filter) > -1;
});
};
filteredContentFunction(computedContent, computedValue, filter) {
computedContent = computedContent.filter(c => {
return !computedValue.includes(get(c, "value"));
});
if (isEmpty(filter)) { return computedContent; }
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
},
willCreateContent() {
this.set("highlightedValue", null);
},
didCreateContent() {
this.clearFilter();
this.autoHighlightFunction();
},
createContentFunction(input) {
if (!this.get("content").includes(input)) {
this.get("content").pushObject(input);
this.get("value").pushObject(input);
}
},
deselectValuesFunction(values) {
const contents = this._computeRemovableContentsForValues(values);
this.get("value").removeObjects(values);
this.get("content").removeObjects(contents);
},
highlightValueFunction(value) {
this.set("highlightedValue", value);
},
selectValuesFunction(values) {
this.get("value").pushObjects(values);
},
willSelectValues() {
this.expand();
this.set("highlightedValue", null);
},
didSelectValues() {
this.focus();
this.clearFilter();
this.autoHighlightFunction();
},
willDeselectValues() {
this.set("highlightedValue", null);
},
didDeselectValues() {
this.autoHighlightFunction();
},
willHighlightValue() {},
didHighlightValue() {},
autoHighlightFunction() {
Ember.run.schedule("afterRender", () => {
if (this.get("isExpanded") === false) { return; }
if (this.get("renderedBodyOnce") === false) { return; }
if (!isNone(this.get("highlightedValue"))) { return; }
if (isEmpty(this.get("filteredContent"))) {
if (!isEmpty(this.get("filter"))) {
this.send("onHighlight", this.get("filter"));
} else if (this.get("none") && !isEmpty(this.get("selectedContent"))) {
this.send("onHighlight", this.noneValue);
}
} else {
this.send("onHighlight", this.get("filteredContent.firstObject.value"));
}
});
},
actions: {
onClearSelection() {
this.send("onSelect", []);
const values = this.get("selectedContent").map(c => get(c, "value"));
this.send("onDeselect", values);
},
onSelect(value) {
this.setProperties({ filter: "", highlightedValue: null });
this.get("value").pushObject(value);
onHighlight(value) {
value = this._originalValueForValue(value);
this.willHighlightValue(value);
this.set("highlightedValue", value);
this.highlightValueFunction(value);
this.didHighlightValue(value);
},
onDeselect(value) {
this.defaultOnDeselect(value);
this.get("value").removeObject(value);
onCreateContent(input) {
this.willCreateContent(input);
this.createContentFunction(input);
this.didCreateContent(input);
},
onSelect(values) {
values = Ember.makeArray(values).map(v => this._originalValueForValue(v));
this.willSelectValues(values);
this.selectValuesFunction(values);
this.didSelectValues(values);
},
onDeselect(values) {
values = Ember.makeArray(this._computeRemovableValues(values));
this.willDeselectValues(values);
this.deselectValuesFunction(values);
this.didSelectValues(values);
}
},
_computeRemovableContentsForValues(values) {
const removableContents = [];
values.forEach(v => {
if (!this.get("_initialValues").includes(v)) {
const content = this._contentForValue(v);
if (!isNone(content)) { removableContents.push(content); }
}
});
return removableContents;
},
_computeRemovableValues(values) {
return Ember.makeArray(values)
.map(v => this._originalValueForValue(v))
.filter(v => {
return get(this._computedContentForValue(v), "locked") !== true;
});
}
});

View File

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

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")
_setComponentOptions() {
this.set("headerComponentOptions", Ember.Object.create({
this.get("headerComponentOptions").setProperties({
i18nPrefix: this.get("i18nPrefix"),
showFullTitle: this.get("showFullTitle"),
}));
});
this.set("rowComponentOptions", Ember.Object.create({
this.get("rowComponentOptions").setProperties({
i18nPrefix: this.get("i18nPrefix"),
i18nPostfix: this.get("i18nPostfix")
}));
});
},
@computed("computedValue")

View File

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

View File

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

View File

@ -1,5 +1,5 @@
const { get, isNone, isEmpty, isPresent } = Ember;
import { on, observes } from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
import computed from "ember-addons/ember-computed-decorators";
import UtilsMixin from "select-box-kit/mixins/utils";
import DomHelpersMixin from "select-box-kit/mixins/dom-helpers";
@ -22,7 +22,8 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
isExpanded: false,
isFocused: false,
isHidden: false,
renderBody: false,
renderedBodyOnce: false,
renderedFilterOnce: false,
tabindex: 0,
scrollableParentSelector: ".modal-body",
value: null,
@ -37,10 +38,12 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
filterPlaceholder: "select_box.filter_placeholder",
filterIcon: "search",
rowComponent: "select-box-kit/select-box-kit-row",
rowComponentOptions: null,
noneRowComponent: "select-box-kit/select-box-kit-none-row",
createRowComponent: "select-box-kit/select-box-kit-create-row",
filterComponent: "select-box-kit/select-box-kit-filter",
headerComponent: "select-box-kit/select-box-kit-header",
headerComponentOptions: null,
collectionComponent: "select-box-kit/select-box-kit-collection",
collectionHeight: 200,
verticalOffset: 0,
@ -50,126 +53,92 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
allowAny: false,
allowValueMutation: true,
autoSelectFirst: true,
content: null,
_initialValues: null,
init() {
this._super();
this.noneValue = "__none__";
this._previousScrollParentOverflow = "auto";
this._previousCSSContext = {};
this.set("headerComponentOptions", Ember.Object.create());
this.set("rowComponentOptions", Ember.Object.create());
if ($(window).outerWidth(false) <= 420) {
this.setProperties({ filterable: false, autoFilterable: false });
}
this._previousScrollParentOverflow = "auto";
this._previousCSSContext = {};
if (isNone(this.get("content"))) { this.set("content", []); }
this.set("value", this._castInteger(this.get("value")));
this.setInitialValues();
},
click(event) {
event.stopPropagation();
setInitialValues() {
this.set("_initialValues", this.getWithDefault("content", []).map((c) => {
return this._valueForContent(c);
}));
},
close() {
this.setProperties({ isExpanded: false, isFocused: false });
@computed("computedContent.[]", "computedValue.[]", "filter")
filteredContent(computedContent, computedValue, filter) {
return this.filteredContentFunction(computedContent, computedValue, filter);
},
focus() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().select() );
filteredContentFunction(computedContent, computedValue, filter) {
if (isEmpty(filter)) { return computedContent; }
const lowerFilter = filter.toLowerCase();
return computedContent.filter(c => {
return get(c, "name").toLowerCase().indexOf(lowerFilter) > -1;
});
},
blur() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() );
},
formatRowContent(content) {
let originalContent;
clickOutside(event) {
if ($(event.target).parents(".select-box-kit").length === 1) {
this.close();
return;
}
if (this.get("isExpanded") === true) {
this.set("isExpanded", false);
this.focus();
if (typeof content === "string" || typeof content === "number") {
originalContent = {};
originalContent[this.get("valueAttribute")] = content;
originalContent[this.get("nameProperty")] = content;
} else {
this.close();
}
},
createFunction(input) {
return (selectedBox) => {
const formatedContent = selectedBox.formatContent(input);
formatedContent.meta.generated = true;
return formatedContent;
};
},
filterFunction(content) {
return selectBox => {
const filter = selectBox.get("filter").toLowerCase();
return _.filter(content, c => {
return get(c, "name").toLowerCase().indexOf(filter) > -1;
});
};
},
nameForContent(content) {
if (isNone(content)) {
return null;
originalContent = content;
}
if (typeof content === "object") {
return get(content, this.get("nameProperty"));
}
return content;
},
valueForContent(content) {
switch (typeof content) {
case "string":
case "number":
return this._castInteger(content);
default:
return this._castInteger(get(content, this.get("valueAttribute")));
}
},
formatContent(content) {
return {
value: this.valueForContent(content),
name: this.nameForContent(content),
originalContent: content,
meta: { generated: false }
value: this._castInteger(this._valueForContent(content)),
name: this._nameForContent(content),
locked: false,
originalContent
};
},
formatContents(contents) {
return contents.map(content => this.formatContent(content));
return contents.map(content => this.formatRowContent(content));
},
@computed("filter", "filterable", "autoFilterable")
computedFilterable(filter, filterable, autoFilterable) {
if (filterable === true) {
return true;
}
if (filter.length > 0 && autoFilterable === true) {
return true;
}
@computed("filter", "filterable", "autoFilterable", "renderedFilterOnce")
shouldDisplayFilter(filter, filterable, autoFilterable, renderedFilterOnce) {
if (renderedFilterOnce === true || filterable === true) { return true; }
if (filter.length > 0 && autoFilterable === true) { return true; }
return false;
},
@computed("computedFilterable", "filter", "allowAny")
shouldDisplayCreateRow(computedFilterable, filter, allow) {
return computedFilterable === true && filter.length > 0 && allow === true;
@computed("filter")
shouldDisplayCreateRow(filter) {
if (this.get("allowAny") === true && filter.length > 0) { return true; }
return false;
},
@computed("filter", "allowAny")
createRowContent(filter, allow) {
if (allow === true) {
@computed("filter", "shouldDisplayCreateRow")
createRowContent(filter, shouldDisplayCreateRow) {
if (shouldDisplayCreateRow === true && !this.get("value").includes(filter)) {
return Ember.Object.create({ value: filter, name: filter });
}
},
@computed("content.[]")
@computed("content.[]", "value.[]")
computedContent(content) {
this._mutateValue();
return this.formatContents(content || []);
@ -178,10 +147,10 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
@computed("value", "none", "computedContent.firstObject.value")
computedValue(value, none, firstContentValue) {
if (isNone(value) && isNone(none) && this.get("autoSelectFirst") === true) {
return this._castInteger(firstContentValue);
return firstContentValue;
}
return this._castInteger(value);
return value;
},
@computed
@ -195,90 +164,51 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
@computed("none")
computedNone(none) {
if (isNone(none)) {
return null;
}
if (isNone(none)) { return null; }
switch (typeof none) {
case "string":
return Ember.Object.create({ name: I18n.t(none), value: "" });
return Ember.Object.create({ name: I18n.t(none), value: this.noneValue });
default:
return this.formatContent(none);
return this.formatRowContent(none);
}
},
@computed("computedValue", "computedContent.[]")
selectedContent(computedValue, computedContent) {
if (isNone(computedValue)) {
return [];
}
return [ computedContent.findBy("value", this._castInteger(computedValue)) ];
},
@on("didRender")
_configureSelectBoxDOM() {
if (this.get("isExpanded") === true) {
Ember.run.schedule("afterRender", () => {
this.$collection().css("max-height", this.get("collectionHeight"));
this._applyDirection();
this._positionWrapper();
});
}
},
@on("willDestroyElement")
_cleanHandlers() {
$(window).off("resize.select-box-kit");
this._removeFixedPosition();
if (isNone(computedValue)) { return []; }
return [ computedContent.findBy("value", computedValue) ];
},
@on("didInsertElement")
_setupResizeListener() {
$(window).on("resize.select-box-kit", () => this.set("isExpanded", false) );
$(window).on("resize.select-box-kit", () => this.collapse() );
},
@observes("filter", "filteredContent.[]", "shouldDisplayCreateRow")
_setHighlightedValue() {
const filteredContent = this.get("filteredContent");
const display = this.get("shouldDisplayCreateRow");
const none = this.get("computedNone");
if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) {
this.set("highlightedValue", get(filteredContent, "firstObject.value"));
return;
}
autoHighlightFunction() {
Ember.run.schedule("afterRender", () => {
if (!isNone(this.get("highlightedValue"))) { return; }
if (display === true && isEmpty(filteredContent)) {
this.set("highlightedValue", this.get("filter"));
}
else if (!isEmpty(filteredContent)) {
this.set("highlightedValue", get(filteredContent, "firstObject.value"));
}
else if (isEmpty(filteredContent) && isPresent(none) && display === false) {
this.set("highlightedValue", get(none, "value"));
}
},
const filteredContent = this.get("filteredContent");
const display = this.get("shouldDisplayCreateRow");
const none = this.get("computedNone");
@observes("isExpanded")
_isExpandedChanged() {
if (this.get("isExpanded") === true) {
this._applyFixedPosition();
if (isNone(this.get("highlightedValue")) && !isEmpty(filteredContent)) {
this.send("onHighlight", get(filteredContent, "firstObject.value"));
return;
}
this.setProperties({
highlightedValue: this.get("computedValue"),
renderBody: true,
isFocused: true
});
} else {
this._removeFixedPosition();
}
},
@computed("filter", "computedFilterable", "computedContent.[]", "computedValue.[]")
filteredContent(filter, computedFilterable, computedContent, computedValue) {
if (computedFilterable === false) { return computedContent; }
return this.filterFunction(computedContent)(this, computedValue);
if (display === true && isEmpty(filteredContent)) {
this.send("onHighlight", this.get("filter"));
}
else if (!isEmpty(filteredContent)) {
this.send("onHighlight", get(filteredContent, "firstObject.value"));
}
else if (isEmpty(filteredContent) && isPresent(none) && display === false) {
this.send("onHighlight", get(none, "value"));
}
});
},
@computed("scrollableParentSelector")
@ -286,191 +216,100 @@ export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin
return this.$().parents(scrollableParentSelector).first();
},
willFilterContent() {
this.expand();
this.set("highlightedValue", null);
},
didFilterContent() {
this.set("renderedFilterOnce", true);
this.autoHighlightFunction();
},
willCreateContent() { },
createContentFunction(input) {
this.get("content").pushObject(input);
this.send("onSelect", input);
},
didCreateContent() {
this.clearFilter();
this.autoHighlightFunction();
},
willHighlightValue() {},
highlightValueFunction(value) {
this.set("highlightedValue", value);
},
didHighlightValue() {},
willSelectValue() {
this.clearFilter();
this.set("highlightedValue", null);
},
selectValueFunction(value) {
this.set("value", value);
},
didSelectValue() {
this.collapse();
this.focus();
},
willDeselectValue() {
this.set("highlightedValue", null);
},
unsetValueFunction() {
this.set("value", null);
},
didDeselectValue() {
this.focus();
},
actions: {
onToggle() {
this.toggleProperty("isExpanded");
if (this.get("isExpanded") === true) { this.focus(); }
},
onCreateContent(input) {
const content = this.createFunction(input)(this);
this.get("computedContent").pushObject(content);
this.send("onSelect", content.value);
},
onFilterChange(filter) {
this.set("filter", filter);
},
onHighlight(value) {
this.set("highlightedValue", value);
this.get("isExpanded") === true ? this.collapse() : this.expand();
},
onClearSelection() {
this.send("onSelect", null);
this.send("onDeselect", this.get("value"));
},
onHighlight(value) {
value = this._originalValueForValue(value);
this.willHighlightValue(value);
this.set("highlightedValue", value);
this.highlightValueFunction(value);
this.didHighlightValue(value);
},
onCreateContent(input) {
this.willCreateContent(input);
this.createContentFunction(input);
this.didCreateContent(input);
},
onSelect(value) {
value = this.defaultOnSelect(value);
this.set("value", value);
if (value === "") { value = null; }
this.willSelectValue(value);
this.selectValueFunction(value);
this.didSelectValue(value);
},
onDeselect() {
this.defaultOnDeselect();
this.set("value", null);
}
onDeselect(value) {
value = this._originalValueForValue(value);
this.willDeselectValue(value);
this.unsetValueFunction(value);
this.didSelectValue(value);
},
onFilterChange(_filter) {
this.willFilterContent(_filter);
this.set("filter", _filter);
this.didFilterContent(_filter);
},
},
defaultOnSelect(value) {
if (value === "") { value = null; }
this.setProperties({
highlightedValue: null,
isExpanded: false,
filter: ""
});
this.focus();
return value;
},
defaultOnDeselect(value) {
const content = this.get("computedContent").findBy("value", value);
if (!isNone(content) && get(content, "meta.generated") === true) {
this.get("computedContent").removeObject(content);
}
},
_applyDirection() {
let options = { left: "auto", bottom: "auto", top: "auto" };
const dHeader = $(".d-header")[0];
const dHeaderBounds = dHeader ? dHeader.getBoundingClientRect() : {top: 0, height: 0};
const dHeaderHeight = dHeaderBounds.top + dHeaderBounds.height;
const headerHeight = this.$header().outerHeight(false);
const headerWidth = this.$header().outerWidth(false);
const bodyHeight = this.$body().outerHeight(false);
const windowWidth = $(window).width();
const windowHeight = $(window).height();
const boundingRect = this.get("element").getBoundingClientRect();
const offsetTop = boundingRect.top;
const offsetBottom = boundingRect.bottom;
if (this.get("fullWidthOnMobile") && windowWidth <= 420) {
const margin = 10;
const relativeLeft = this.$().offset().left - $(window).scrollLeft();
options.left = margin - relativeLeft;
options.width = windowWidth - margin * 2;
options.maxWidth = options.minWidth = "unset";
} else {
const bodyWidth = this.$body().outerWidth(false);
if ($("html").css("direction") === "rtl") {
const horizontalSpacing = boundingRect.right;
const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0;
if (hasHorizontalSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false });
options.left = bodyWidth + this.get("horizontalOffset");
} else {
this.setProperties({ isLeftAligned: false, isRightAligned: true });
options.right = - (bodyWidth - headerWidth + this.get("horizontalOffset"));
}
} else {
const horizontalSpacing = boundingRect.left;
const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0);
if (hasHorizontalSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false });
options.left = this.get("horizontalOffset");
} else {
this.setProperties({ isLeftAligned: false, isRightAligned: true });
options.right = this.get("horizontalOffset");
}
}
}
const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight;
const hasBelowSpace = windowHeight - offsetBottom - componentHeight > 0;
const hasAboveSpace = offsetTop - componentHeight - dHeaderHeight > 0;
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
this.setProperties({ isBelow: true, isAbove: false });
options.top = headerHeight + this.get("verticalOffset");
} else {
this.setProperties({ isBelow: false, isAbove: true });
options.bottom = headerHeight + this.get("verticalOffset");
}
this.$body().css(options);
},
_applyFixedPosition() {
const width = this.$().outerWidth(false);
const height = this.$header().outerHeight(false);
if (this.get("scrollableParent").length === 0) { return; }
const $placeholder = $(`<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)
});
clearFilter() {
this.$filterInput().val("");
this.setProperties({ filter: "" });
},
@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",
classNames: "select-box-kit-filter",
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",
classNames: "select-box-kit-row",
tagName: "li",
tabIndex: -1,
attributeBindings: [
"tabIndex",
"title",
"content.value:data-value",
"content.name:data-name"
],
classNameBindings: ["isHighlighted", "isSelected"],
clicked: false,
title: Ember.computed.alias("content.name"),
@ -23,17 +26,15 @@ export default Ember.Component.extend(UtilsMixin, {
@on("didReceiveAttrs")
_setSelectionState() {
const contentValue = this.get("content.value");
this.set("isSelected", this.get("value") === contentValue);
this.set("isHighlighted", this._castInteger(this.get("highlightedValue")) === this._castInteger(contentValue));
this.set("isHighlighted", this.get("highlightedValue") === contentValue);
},
@on("willDestroyElement")
_clearDebounce() {
const hoverDebounce = this.get("hoverDebounce");
if (isPresent(hoverDebounce)) {
run.cancel(hoverDebounce);
}
if (isPresent(hoverDebounce)) { run.cancel(hoverDebounce); }
},
@computed("content.originalContent.icon", "content.originalContent.iconClass")
@ -50,7 +51,14 @@ export default Ember.Component.extend(UtilsMixin, {
},
click() {
this.sendAction("onSelect", this.get("content.value"));
this._sendOnSelectAction();
},
_sendOnSelectAction() {
if (this.get("clicked") === false) {
this.set("clicked", true);
this.sendAction("onSelect", this.get("content.value"));
}
},
_sendOnHighlightAction() {

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
import { on } from "ember-addons/ember-computed-decorators";
export default Ember.Mixin.create({
init() {
this._super();
@ -8,46 +10,237 @@ export default Ember.Mixin.create({
this.collectionSelector = ".select-box-kit-collection";
this.headerSelector = ".select-box-kit-header";
this.bodySelector = ".select-box-kit-body";
this.wrapperSelector = ".select-box-kit-wrapper";
},
$findRowByValue(value) {
return this.$(`${this.rowSelector}[data-value='${value}']`);
$findRowByValue(value) { return this.$(`${this.rowSelector}[data-value='${value}']`); },
$header() { return this.$(this.headerSelector); },
$body() { return this.$(this.bodySelector); },
$collection() { return this.$(this.collectionSelector); },
$rows(withHidden) {
if (withHidden === true) {
return this.$(`${this.rowSelector}:not(.no-content)`);
} else {
return this.$(`${this.rowSelector}:not(.no-content):not(.is-hidden)`);
}
},
$header() {
return this.$(this.headerSelector);
$highlightedRow() { return this.$rows().filter(".is-highlighted"); },
$selectedRow() { return this.$rows().filter(".is-selected"); },
$offscreenInput() { return this.$(this.offscreenInputSelector); },
$filterInput() { return this.$(this.filterInputSelector); },
@on("didRender")
_ajustPosition() {
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
this.$collection().css("max-height", this.get("collectionHeight"));
this._applyFixedPosition();
this._applyDirection();
this._positionWrapper();
},
$body() {
return this.$(this.bodySelector);
@on("willDestroyElement")
_clearState() {
$(window).off("resize.select-box-kit");
$(`.select-box-kit-fixed-placeholder-${this.elementId}`).remove();
},
$collection() {
return this.$(this.collectionSelector);
// make sure we dont propagate a click outside component
// to avoid closing a modal containing the component for example
click(event) { this._killEvent(event); },
// use to collapse and remove focus
close() {
this.collapse();
this.setProperties({ isFocused: false });
},
$rows() {
return this.$(this.rowSelector);
// force the component in a known default state
focus() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().focus() );
},
$highlightedRow() {
return this.$rows().filter(".is-highlighted");
expand() {
if (this.get("isExpanded") === true) { return; }
this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true });
this.focus();
this.autoHighlightFunction();
},
$selectedRow() {
return this.$rows().filter(".is-selected");
collapse() {
this.set("isExpanded", false);
Ember.run.schedule("afterRender", () => this._removeFixedPosition() );
},
$offscreenInput() {
return this.$(this.offscreenInputSelector);
// make sure we close/unfocus the component when clicked outside
clickOutside(event) {
if ($(event.target).parents(".select-box-kit").length === 1) {
this.close();
return false;
}
this.unfocus();
return;
},
$filterInput() {
return this.$(this.filterInputSelector);
// lose focus of the component in two steps
// first collapase and keep focus and then remove focus
unfocus() {
this.set("highlightedValue", null);
if (this.get("isExpanded") === true) {
this.collapse();
this.focus();
} else {
this.close();
}
},
blur() {
Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() );
},
_killEvent(event) {
event.preventDefault();
event.stopPropagation();
}
},
_applyDirection() {
let options = { left: "auto", bottom: "auto", top: "auto" };
const dHeader = $(".d-header")[0];
const dHeaderBounds = dHeader ? dHeader.getBoundingClientRect() : {top: 0, height: 0};
const dHeaderHeight = dHeaderBounds.top + dHeaderBounds.height;
const headerHeight = this.$header().outerHeight(false);
const headerWidth = this.$header().outerWidth(false);
const bodyHeight = this.$body().outerHeight(false);
const windowWidth = $(window).width();
const windowHeight = $(window).height();
const boundingRect = this.get("element").getBoundingClientRect();
const offsetTop = boundingRect.top;
const offsetBottom = boundingRect.bottom;
if (this.get("fullWidthOnMobile") && windowWidth <= 420) {
const margin = 10;
const relativeLeft = this.$().offset().left - $(window).scrollLeft();
options.left = margin - relativeLeft;
options.width = windowWidth - margin * 2;
options.maxWidth = options.minWidth = "unset";
} else {
const bodyWidth = this.$body().outerWidth(false);
if ($("html").css("direction") === "rtl") {
const horizontalSpacing = boundingRect.right;
const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0;
if (hasHorizontalSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false });
options.left = bodyWidth + this.get("horizontalOffset");
} else {
this.setProperties({ isLeftAligned: false, isRightAligned: true });
options.right = - (bodyWidth - headerWidth + this.get("horizontalOffset"));
}
} else {
const horizontalSpacing = boundingRect.left;
const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0);
if (hasHorizontalSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false });
options.left = this.get("horizontalOffset");
} else {
this.setProperties({ isLeftAligned: false, isRightAligned: true });
options.right = this.get("horizontalOffset");
}
}
}
const componentHeight = this.get("verticalOffset") + bodyHeight + headerHeight;
const hasBelowSpace = windowHeight - offsetBottom - componentHeight > 0;
const hasAboveSpace = offsetTop - componentHeight - dHeaderHeight > 0;
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
this.setProperties({ isBelow: true, isAbove: false });
options.top = headerHeight + this.get("verticalOffset");
} else {
this.setProperties({ isBelow: false, isAbove: true });
options.bottom = headerHeight + this.get("verticalOffset");
}
this.$body().css(options);
},
_applyFixedPosition() {
if (this.get("scrollableParent").length === 0) { return; }
const width = this.$().outerWidth(false);
const height = this.$().outerHeight(false);
const $placeholder = $(`<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({
init() {
this._super();
@ -35,9 +33,13 @@ export default Ember.Mixin.create({
.off("focus.select-box-kit")
.off("focusin.select-box-kit")
.off("blur.select-box-kit")
.off("keypress.select-box-kit")
.off("keydown.select-box-kit");
this.$filterInput().off("keydown.select-box-kit");
this.$filterInput()
.off("change.select-box-kit")
.off("keypress.select-box-kit")
.off("keydown.select-box-kit");
},
didInsertElement() {
@ -70,58 +72,67 @@ export default Ember.Mixin.create({
.on("keydown.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.TAB) { this._handleTabOnKeyDown(event); }
if (keyCode === this.keys.ESC) { this._handleEscOnKeyDown(event); }
if (keyCode === this.keys.UP || keyCode === this.keys.DOWN) {
this._handleArrowKey(keyCode, event);
}
if (keyCode === this.keys.BACKSPACE) {
this.expand();
if (this.$filterInput().is(":visible")) {
this.$filterInput().focus().trigger(event).trigger("change");
}
return event;
}
return true;
})
.on("keypress.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
switch (keyCode) {
case this.keys.UP:
case this.keys.DOWN:
if (this.get("isExpanded") === false) {
this.set("isExpanded", true);
}
Ember.run.schedule("actions", () => {
this._handleArrowKey(keyCode);
});
this._killEvent(event);
return;
case this.keys.ENTER:
if (this.get("isExpanded") === false) {
this.set("isExpanded", true);
} else {
this.send("onSelect", this.$highlightedRow().data("value"));
this.expand();
} else if (this.$highlightedRow().length === 1) {
this.$highlightedRow().click();
}
this._killEvent(event);
return;
case this.keys.TAB:
if (this.get("isExpanded") === false) {
return true;
} else {
this.send("onSelect", this.$highlightedRow().data("value"));
return;
}
case this.keys.ESC:
this.close();
this._killEvent(event);
return;
return false;
case this.keys.BACKSPACE:
this._killEvent(event);
return;
return event;
}
if (this._isSpecialKey(keyCode) === false && event.metaKey === false) {
this.setProperties({
isExpanded: true,
filter: String.fromCharCode(keyCode)
});
this.expand();
Ember.run.schedule("afterRender", () => this.$filterInput().focus() );
if (this.get("filterable") === true || this.get("autoFilterable")) {
this.set("renderedFilterOnce", true);
}
Ember.run.schedule("afterRender", () => {
this.$filterInput()
.focus()
.val(this.$filterInput().val() + String.fromCharCode(keyCode));
});
}
});
this.$filterInput()
.on(`keydown.select-box-kit`, (event) => {
.on("change.select-box-kit", (event) => {
this.send("onFilterChange", $(event.target).val());
})
.on("keydown.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.TAB) { this._handleTabOnKeyDown(event); }
if (keyCode === this.keys.ESC) { this._handleEscOnKeyDown(event); }
if (keyCode === this.keys.UP || keyCode === this.keys.DOWN) {
this._handleArrowKey(keyCode, event);
}
})
.on("keypress.select-box-kit", (event) => {
const keyCode = event.keyCode || event.which;
if ([
@ -133,67 +144,75 @@ export default Ember.Mixin.create({
return true;
}
if (keyCode === this.keys.TAB && this.get("isExpanded") === false) {
return true;
}
if (this._isSpecialKey(keyCode) === true) {
this.$offscreenInput().focus().trigger(event);
return false;
}
return true;
});
},
_handleArrowKey(keyCode) {
if (isEmpty(this.get("filteredContent"))) {
_handleEscOnKeyDown(event) {
this.unfocus();
this._killEvent(event);
},
_handleTabOnKeyDown(event) {
if (this.get("isExpanded") === false) {
this.unfocus();
return true;
} else if (this.$highlightedRow().length === 1) {
this._killEvent(event);
this.$highlightedRow().click();
this.focus();
} else {
this.unfocus();
return true;
}
return false;
},
_handleArrowKey(keyCode, event) {
if (this.get("isExpanded") === false) { this.expand(); }
this._killEvent(event);
const $rows = this.$rows();
if ($rows.length <= 0) { return; }
if ($rows.length === 1) {
this._rowSelection($rows, 0);
return;
}
Ember.run.schedule("afterRender", () => {
switch (keyCode) {
case 38:
Ember.run.throttle(this, this._handleUpArrow, 32);
break;
default:
Ember.run.throttle(this, this._handleDownArrow, 32);
}
});
const direction = keyCode === 38 ? -1 : 1;
Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32);
},
_moveHighlight(direction) {
const $rows = this.$rows();
_moveHighlight(direction, $rows) {
const currentIndex = $rows.index(this.$highlightedRow());
let nextIndex = currentIndex + direction;
let nextIndex = 0;
if (currentIndex < 0) {
if (nextIndex < 0) {
nextIndex = $rows.length - 1;
} else if (nextIndex >= $rows.length) {
nextIndex = 0;
} else if (currentIndex + direction < $rows.length) {
nextIndex = currentIndex + direction;
}
this._rowSelection($rows, nextIndex);
},
_handleDownArrow() { this._moveHighlight(1); },
_handleUpArrow() { this._moveHighlight(-1); },
_rowSelection($rows, nextIndex) {
const highlightableValue = $rows.eq(nextIndex).data("value");
const highlightableValue = $rows.eq(nextIndex).attr("data-value");
const $highlightableRow = this.$findRowByValue(highlightableValue);
this.send("onHighlight", highlightableValue);
Ember.run.schedule("afterRender", () => {
const $collection = this.$collection();
const currentOffset = $collection.offset().top +
$collection.outerHeight(false);
const nextBottom = $highlightableRow.offset().top +
$highlightableRow.outerHeight(false);
const nextOffset = $collection.scrollTop() + nextBottom - currentOffset;
if (nextIndex === 0) {
$collection.scrollTop(0);
} else if (nextBottom > currentOffset) {
$collection.scrollTop(nextOffset);
}
$highlightableRow.trigger("mouseover").focus();
this.focus();
});
},

View File

@ -1,9 +1,57 @@
const { get, isNone } = Ember;
export default Ember.Mixin.create({
_nameForContent(content) {
if (isNone(content)) {
return null;
}
if (typeof content === "object") {
return get(content, this.get("nameProperty"));
}
return content;
},
_castInteger(value) {
if (this.get("castInteger") === true && Ember.isPresent(value)) {
return parseInt(value, 10);
}
return Ember.isNone(value) ? value : value.toString();
}
return value;
},
_valueForContent(content) {
switch (typeof content) {
case "string":
case "number":
return content;
default:
return get(content, this.get("valueAttribute"));
}
},
_contentForValue(value) {
return this.get("content").find(c => {
if (this._valueForContent(c) === value) { return true; }
});
},
_computedContentForValue(value) {
const searchedValue = value.toString();
return this.get("computedContent").find(c => {
if (c.value.toString() === searchedValue) { return true; }
});
},
_originalValueForValue(value) {
if (isNone(value)) { return null; }
if (value === this.noneValue) { return this.noneValue; }
const computedContent = this._computedContentForValue(value);
if (isNone(computedContent)) { return value; }
return get(computedContent.originalContent, this.get("valueAttribute"));
},
});

View File

@ -1,30 +1,13 @@
<ul class="choices">
{{#each selectedContent as |selectedContent|}}
<li tabindex="-1" data-value={{selectedContent.value}} data-name={{selectedContent.name}} class="selected-name">
<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}}
{{component selectedNameComponent onDeselect=onDeselect content=selectedContent}}
{{/each}}
<li class="filter">
{{input
class="select-box-kit-filter-input"
key-up=onFilterChange
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck=false
value=filter
{{component "select-box-kit/select-box-kit-filter"
onFilterChange=onFilterChange
shouldDisplayFilter=shouldDisplayFilter
isFocused=isFocused
filter=filter
}}
</li>
</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")
onFilterChange=(action "onFilterChange")
onClearSelection=(action "onClearSelection")
shouldDisplayFilter=shouldDisplayFilter
options=headerComponentOptions
}}
@ -26,16 +27,14 @@
{{component filterComponent
onFilterChange=(action "onFilterChange")
icon=filterIcon
filter=filter
filterable=computedFilterable
shouldDisplayFilter=shouldDisplayFilter
isFocused=isFocused
placeholder=(i18n filterPlaceholder)
tabindex=tabindex
filter=filter
}}
{{#if renderBody}}
{{#if renderedBodyOnce}}
{{component collectionComponent
shouldDisplayCreateRow=shouldDisplayCreateRow
none=computedNone
createRowContent=createRowContent
selectedContent=selectedContent
@ -43,13 +42,9 @@
rowComponent=rowComponent
noneRowComponent=noneRowComponent
createRowComponent=createRowComponent
iconForRow=iconForRow
templateForRow=templateForRow
templateForNoneRow=templateForNoneRow
templateForCreateRow=templateForCreateRow
shouldHighlightRow=shouldHighlightRow
shouldSelectRow=shouldSelectRow
titleForRow=titleForRow
onClearSelection=(action "onClearSelection")
onSelect=(action "onSelect")
onHighlight=(action "onHighlight")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -75,9 +75,9 @@ QUnit.test("Subcategory list settings", assert => {
click('.edit-category-general');
expandSelectBox('.edit-category-tab-general .category-chooser');
expandSelectBoxKit('.edit-category-tab-general .category-chooser');
selectBoxSelectRow(3, {selector: '.edit-category-tab-general .category-chooser'});
selectBoxKitSelectRow(3, {selector: '.edit-category-tab-general .category-chooser'});
click('.edit-category-settings');
andThen(() => {

View File

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

View File

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

View File

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

View File

@ -53,9 +53,9 @@ QUnit.test("Updating the topic title and category", assert => {
fillIn('#edit-title', 'this is the new title');
expandSelectBox('.title-wrapper .category-chooser');
expandSelectBoxKit('.title-wrapper .category-chooser');
selectBoxSelectRow(4, {selector: '.title-wrapper .category-chooser'});
selectBoxKitSelectRow(4, {selector: '.title-wrapper .category-chooser'});
click('#topic-title .submit-edit');

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-caret-down").length, 1);
expandSelectBox('.categories-admin-dropdown');
expandSelectBoxKit();
andThen(() => {
assert.equal($selectBox.rowByValue("create").name(), "New Category");

View File

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

View File

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

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) {
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 shouldnt 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) {
andThen(() => assert.notOk(selectBox().isHidden) );
expandSelectBox();
expandSelectBoxKit();
andThen(() => assert.equal(selectBox().selectedRow.name(), "Pinned") );

View File

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

View File

@ -17,7 +17,7 @@ componentTest('default', {
},
test(assert) {
expandSelectBox();
expandSelectBoxKit();
andThen(() => {
assert.equal(selectBox().header.name(), "Topic Controls");
@ -26,7 +26,7 @@ componentTest('default', {
assert.equal(selectBox().selectedRow.el.length, 0, "it doesnt preselect first row");
});
selectBoxSelectRow("share");
selectBoxKitSelectRow("share");
andThen(() => {
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';
checkSelectBoxIsNotExpanded(selectBoxSelector);
@ -18,7 +18,7 @@ Ember.Test.registerAsyncHelper('expandSelectBox', function(app, selectBoxSelecto
click(selectBoxSelector + ' .select-box-kit-header');
});
Ember.Test.registerAsyncHelper('collapseSelectBox', function(app, selectBoxSelector) {
Ember.Test.registerAsyncHelper('collapseSelectBoxKit', function(app, selectBoxSelector) {
selectBoxSelector = selectBoxSelector || '.select-box-kit';
checkSelectBoxIsNotCollapsed(selectBoxSelector);
@ -26,7 +26,7 @@ Ember.Test.registerAsyncHelper('collapseSelectBox', function(app, selectBoxSelec
click(selectBoxSelector + ' .select-box-kit-header');
});
Ember.Test.registerAsyncHelper('selectBoxSelectRow', function(app, rowValue, options) {
Ember.Test.registerAsyncHelper('selectBoxKitSelectRow', function(app, rowValue, options) {
options = options || {};
options.selector = options.selector || '.select-box-kit';
@ -35,7 +35,7 @@ Ember.Test.registerAsyncHelper('selectBoxSelectRow', function(app, rowValue, opt
click(options.selector + " .select-box-kit-row[data-value='" + rowValue + "']");
});
Ember.Test.registerAsyncHelper('selectBoxSelectNoneRow', function(app, options) {
Ember.Test.registerAsyncHelper('selectBoxKitSelectNoneRow', function(app, options) {
options = options || {};
options.selector = options.selector || '.select-box-kit';
@ -44,7 +44,7 @@ Ember.Test.registerAsyncHelper('selectBoxSelectNoneRow', function(app, options)
click(options.selector + " .select-box-kit-row.none");
});
Ember.Test.registerAsyncHelper('selectBoxFillInFilter', function(app, filter, options) {
Ember.Test.registerAsyncHelper('selectBoxKitFillInFilter', function(app, filter, options) {
options = options || {};
options.selector = options.selector || '.select-box-kit';
@ -52,7 +52,6 @@ Ember.Test.registerAsyncHelper('selectBoxFillInFilter', function(app, filter, op
var filterQuerySelector = options.selector + ' .select-box-kit-filter-input';
fillIn(filterQuerySelector, filter);
triggerEvent(filterQuerySelector, 'keyup');
});
function selectBox(selector) { // eslint-disable-line no-unused-vars
@ -88,23 +87,26 @@ function selectBox(selector) { // eslint-disable-line no-unused-vars
}
function keyboardHelper() {
function createEvent(target, keyCode) {
function createEvent(target, keyCode, options) {
target = target || ".select-box-kit-filter-input";
selector = find(selector).find(target);
andThen(function() {
var event = jQuery.Event('keydown');
var event = jQuery.Event(options.type);
event.keyCode = keyCode;
if (options && options.metaKey === true) { event.metaKey = true; }
find(selector).trigger(event);
});
}
return {
down: function(target) { createEvent(target, 40); },
up: function(target) { createEvent(target, 38); },
escape: function(target) { createEvent(target, 27); },
enter: function(target) { createEvent(target, 13); },
tab: function(target) { createEvent(target, 9); }
down: function(target) { createEvent(target, 40, {type: 'keydown'}); },
up: function(target) { createEvent(target, 38, {type: 'keydown'}); },
escape: function(target) { createEvent(target, 27, {type: 'keydown'}); },
enter: function(target) { createEvent(target, 13, {type: 'keypress'}); },
tab: function(target) { createEvent(target, 9, {type: 'keydown'}); },
backspace: function(target) { createEvent(target, 8, {type: 'keydown'}); },
selectAll: function(target) { createEvent(target, 65, {metaKey: true, type: 'keydown'}); },
};
}

View File

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