367 lines
9.7 KiB
JavaScript
367 lines
9.7 KiB
JavaScript
import SelectKitComponent from "select-kit/components/select-kit";
|
|
import {
|
|
default as discourseComputed,
|
|
on
|
|
} from "discourse-common/utils/decorators";
|
|
const { get, isNone, isEmpty, makeArray, run } = Ember;
|
|
import {
|
|
applyOnSelectPluginApiCallbacks,
|
|
applyOnSelectNonePluginApiCallbacks
|
|
} from "select-kit/mixins/plugin-api";
|
|
|
|
export default SelectKitComponent.extend({
|
|
pluginApiIdentifiers: ["multi-select"],
|
|
layoutName: "select-kit/templates/components/multi-select",
|
|
classNames: "multi-select",
|
|
headerComponent: "multi-select/multi-select-header",
|
|
headerText: "select_kit.default_header_text",
|
|
allowAny: true,
|
|
allowInitialValueMutation: false,
|
|
autoFilterable: true,
|
|
selectedNameComponent: "multi-select/selected-name",
|
|
filterIcon: null,
|
|
filterComponent: "multi-select/multi-select-filter",
|
|
computedValues: null,
|
|
values: null,
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
|
|
this.set("computedValues", []);
|
|
|
|
if (isNone(this.values)) {
|
|
this.set("values", []);
|
|
}
|
|
|
|
this.headerComponentOptions.setProperties({
|
|
selectedNameComponent: this.selectedNameComponent
|
|
});
|
|
},
|
|
|
|
@on("didRender")
|
|
_setChoicesMaxWidth() {
|
|
const width = this.$body().outerWidth(false);
|
|
if (width > 0) {
|
|
this.element.querySelector(".choices").style.maxWidth = `${width}px`;
|
|
}
|
|
},
|
|
|
|
@on("didUpdateAttrs", "init")
|
|
_compute() {
|
|
run.scheduleOnce("afterRender", () => {
|
|
this.willComputeAttributes();
|
|
let content = this.content || [];
|
|
let asyncContent = this.asyncContent || [];
|
|
content = this.willComputeContent(content);
|
|
asyncContent = this.willComputeAsyncContent(asyncContent);
|
|
let values = this._beforeWillComputeValues(this.values);
|
|
content = this.computeContent(content);
|
|
asyncContent = this.computeAsyncContent(asyncContent);
|
|
content = this._beforeDidComputeContent(content);
|
|
asyncContent = this._beforeDidComputeAsyncContent(asyncContent);
|
|
values = this.willComputeValues(values);
|
|
values = this.computeValues(values);
|
|
values = this._beforeDidComputeValues(values);
|
|
this.didComputeContent(content);
|
|
this.didComputeAsyncContent(asyncContent);
|
|
this.didComputeValues(values);
|
|
this.didComputeAttributes();
|
|
});
|
|
},
|
|
|
|
@discourseComputed("filter", "shouldDisplayCreateRow")
|
|
createRowComputedContent(filter, shouldDisplayCreateRow) {
|
|
if (shouldDisplayCreateRow) {
|
|
let content = this.createContentFromInput(filter);
|
|
return this.computeContentItem(content, { created: true });
|
|
}
|
|
},
|
|
|
|
@discourseComputed("filter", "computedValues")
|
|
shouldDisplayCreateRow(filter, computedValues) {
|
|
return this._super() && !computedValues.includes(filter);
|
|
},
|
|
|
|
@discourseComputed
|
|
shouldDisplayFilter() {
|
|
return true;
|
|
},
|
|
|
|
_beforeWillComputeValues(values) {
|
|
return values.map(v => this._cast(v === "" ? null : v));
|
|
},
|
|
willComputeValues(values) {
|
|
return values;
|
|
},
|
|
computeValues(values) {
|
|
return values;
|
|
},
|
|
_beforeDidComputeValues(values) {
|
|
this.setProperties({ computedValues: values });
|
|
return values;
|
|
},
|
|
didComputeValues(values) {
|
|
return values;
|
|
},
|
|
|
|
mutateAttributes() {
|
|
run.next(() => {
|
|
if (this.isDestroyed || this.isDestroying) return;
|
|
|
|
this.mutateContent(this.computedContent);
|
|
this.mutateValues(this.computedValues);
|
|
});
|
|
},
|
|
mutateValues(computedValues) {
|
|
this.set("values", computedValues);
|
|
},
|
|
mutateContent() {},
|
|
|
|
forceValues(values) {
|
|
this.mutateValues(values);
|
|
this._compute();
|
|
},
|
|
|
|
filterComputedContent(computedContent, computedValues, filter) {
|
|
return computedContent.filter(c => {
|
|
return this._normalize(get(c, "name")).indexOf(filter) > -1;
|
|
});
|
|
},
|
|
|
|
@discourseComputed("computedAsyncContent.[]", "computedValues.[]")
|
|
filteredAsyncComputedContent(computedAsyncContent, computedValues) {
|
|
computedAsyncContent = computedAsyncContent.filter(c => {
|
|
return !computedValues.includes(get(c, "value"));
|
|
});
|
|
|
|
if (this.limitMatches) {
|
|
return computedAsyncContent.slice(0, this.limitMatches);
|
|
}
|
|
|
|
return computedAsyncContent;
|
|
},
|
|
|
|
@discourseComputed("computedContent.[]", "computedValues.[]", "filter")
|
|
filteredComputedContent(computedContent, computedValues, filter) {
|
|
computedContent = computedContent.filter(c => {
|
|
return !computedValues.includes(get(c, "value"));
|
|
});
|
|
|
|
if (this.shouldFilter) {
|
|
computedContent = this.filterComputedContent(
|
|
computedContent,
|
|
computedValues,
|
|
this._normalize(filter)
|
|
);
|
|
}
|
|
|
|
if (this.limitMatches) {
|
|
return computedContent.slice(0, this.limitMatches);
|
|
}
|
|
|
|
return computedContent;
|
|
},
|
|
|
|
computeHeaderContent() {
|
|
let content = {
|
|
title: this.title,
|
|
selection: this.selection
|
|
};
|
|
|
|
if (this.noneLabel) {
|
|
if (!this.hasSelection) {
|
|
content.title = content.name = content.label = I18n.t(this.noneLabel);
|
|
}
|
|
} else {
|
|
if (!this.hasReachedMinimum) {
|
|
const key = this.minimumLabel || "select_kit.min_content_not_reached";
|
|
content.title = content.name = content.label = I18n.t(key, {
|
|
count: this.minimum
|
|
});
|
|
}
|
|
}
|
|
|
|
return content;
|
|
},
|
|
|
|
@discourseComputed("filter")
|
|
templateForCreateRow() {
|
|
return rowComponent => {
|
|
return I18n.t("select_kit.create", {
|
|
content: rowComponent.get("computedContent.name")
|
|
});
|
|
};
|
|
},
|
|
|
|
validateSelect() {
|
|
return this._super() && !this.hasReachedMaximum;
|
|
},
|
|
|
|
@discourseComputed("computedValues.[]", "computedContent.[]")
|
|
selection(computedValues, computedContent) {
|
|
const selected = [];
|
|
|
|
computedValues.forEach(v => {
|
|
const value = computedContent.findBy("value", v);
|
|
if (value) selected.push(value);
|
|
});
|
|
|
|
return selected;
|
|
},
|
|
|
|
@discourseComputed("selection.[]")
|
|
hasSelection(selection) {
|
|
return !isEmpty(selection);
|
|
},
|
|
|
|
didPressTab(event) {
|
|
if (isEmpty(this.filter) && !this.highlighted) {
|
|
this.$header().focus();
|
|
this.close(event);
|
|
return true;
|
|
}
|
|
|
|
if (this.highlighted && this.isExpanded) {
|
|
this._destroyEvent(event);
|
|
this.focus();
|
|
this.select(this.highlighted);
|
|
return false;
|
|
} else {
|
|
this.close(event);
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
autoHighlight() {
|
|
run.schedule("afterRender", () => {
|
|
if (!this.isExpanded) return;
|
|
if (!this.renderedBodyOnce) return;
|
|
if (this.highlighted) return;
|
|
|
|
if (isEmpty(this.collectionComputedContent)) {
|
|
if (this.createRowComputedContent) {
|
|
this.highlight(this.createRowComputedContent);
|
|
} else if (this.noneRowComputedContent && this.hasSelection) {
|
|
this.highlight(this.noneRowComputedContent);
|
|
}
|
|
} else {
|
|
this.highlight(this.get("collectionComputedContent.firstObject"));
|
|
}
|
|
});
|
|
},
|
|
|
|
select(computedContentItem) {
|
|
if (
|
|
!computedContentItem ||
|
|
computedContentItem.__sk_row_type === "noneRow"
|
|
) {
|
|
applyOnSelectNonePluginApiCallbacks(this.pluginApiIdentifiers, this);
|
|
this._boundaryActionHandler("onSelectNone");
|
|
this.clearSelection();
|
|
return;
|
|
}
|
|
|
|
if (computedContentItem.__sk_row_type === "noopRow") {
|
|
applyOnSelectPluginApiCallbacks(
|
|
this.pluginApiIdentifiers,
|
|
computedContentItem.value,
|
|
this
|
|
);
|
|
|
|
this._boundaryActionHandler("onSelect", computedContentItem.value);
|
|
return;
|
|
}
|
|
|
|
if (computedContentItem.__sk_row_type === "createRow") {
|
|
if (
|
|
!this.computedValues.includes(computedContentItem.value) &&
|
|
this.validateCreate(computedContentItem.value)
|
|
) {
|
|
this.willCreate(computedContentItem);
|
|
|
|
computedContentItem.__sk_row_type = null;
|
|
this.computedContent.pushObject(computedContentItem);
|
|
|
|
run.schedule("afterRender", () => {
|
|
this.didCreate(computedContentItem);
|
|
this._boundaryActionHandler("onCreate");
|
|
});
|
|
|
|
this.select(computedContentItem);
|
|
return;
|
|
} else {
|
|
this._boundaryActionHandler("onCreateFailure");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.validateSelect(computedContentItem)) {
|
|
this.willSelect(computedContentItem);
|
|
this.clearFilter();
|
|
this.setProperties({ highlighted: null });
|
|
this.computedValues.pushObject(computedContentItem.value);
|
|
|
|
run.next(() => this.mutateAttributes());
|
|
|
|
run.schedule("afterRender", () => {
|
|
this.didSelect(computedContentItem);
|
|
|
|
applyOnSelectPluginApiCallbacks(
|
|
this.pluginApiIdentifiers,
|
|
computedContentItem.value,
|
|
this
|
|
);
|
|
|
|
this.autoHighlight();
|
|
|
|
this._boundaryActionHandler("onSelect", computedContentItem.value);
|
|
});
|
|
} else {
|
|
this._boundaryActionHandler("onSelectFailure");
|
|
}
|
|
},
|
|
|
|
deselect(rowComputedContentItems) {
|
|
this.willDeselect(rowComputedContentItems);
|
|
|
|
rowComputedContentItems = makeArray(rowComputedContentItems);
|
|
const generatedComputedContents = this._filterRemovableComputedContents(
|
|
makeArray(rowComputedContentItems)
|
|
);
|
|
this.setProperties({ highlighted: null, highlightedSelection: [] });
|
|
this.computedValues.removeObjects(
|
|
rowComputedContentItems.map(r => r.value)
|
|
);
|
|
this.computedContent.removeObjects([
|
|
...rowComputedContentItems,
|
|
...generatedComputedContents
|
|
]);
|
|
|
|
run.next(() => {
|
|
this.mutateAttributes();
|
|
|
|
run.schedule("afterRender", () => {
|
|
this.didDeselect(rowComputedContentItems);
|
|
this.autoHighlight();
|
|
|
|
if (!this.isDestroying && !this.isDestroyed) {
|
|
this._positionWrapper();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
close(event) {
|
|
this.clearHighlightSelection();
|
|
|
|
this._super(event);
|
|
},
|
|
|
|
unfocus(event) {
|
|
this.clearHighlightSelection();
|
|
|
|
this._super(event);
|
|
}
|
|
});
|