474 lines
13 KiB
JavaScript
474 lines
13 KiB
JavaScript
const { get, isNone, isEmpty, isPresent } = Ember;
|
|
import { on, observes } 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";
|
|
import KeyboardMixin from "select-box-kit/mixins/keyboard";
|
|
|
|
export default Ember.Component.extend(UtilsMixin, DomHelpersMixin, KeyboardMixin, {
|
|
layoutName: "select-box-kit/templates/components/select-box-kit",
|
|
classNames: "select-box-kit",
|
|
classNameBindings: [
|
|
"isFocused",
|
|
"isExpanded",
|
|
"isDisabled",
|
|
"isHidden",
|
|
"isAbove",
|
|
"isBelow",
|
|
"isLeftAligned",
|
|
"isRightAligned"
|
|
],
|
|
isDisabled: false,
|
|
isExpanded: false,
|
|
isFocused: false,
|
|
isHidden: false,
|
|
renderBody: false,
|
|
tabindex: 0,
|
|
scrollableParentSelector: ".modal-body",
|
|
value: null,
|
|
none: null,
|
|
highlightedValue: null,
|
|
noContentLabel: "select_box.no_content",
|
|
valueAttribute: "id",
|
|
nameProperty: "name",
|
|
autoFilterable: false,
|
|
filterable: false,
|
|
filter: "",
|
|
filterPlaceholder: "select_box.filter_placeholder",
|
|
filterIcon: "search",
|
|
rowComponent: "select-box-kit/select-box-kit-row",
|
|
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",
|
|
collectionComponent: "select-box-kit/select-box-kit-collection",
|
|
collectionHeight: 200,
|
|
verticalOffset: 0,
|
|
horizontalOffset: 0,
|
|
fullWidthOnMobile: false,
|
|
castInteger: false,
|
|
allowAny: false,
|
|
|
|
init() {
|
|
this._super();
|
|
|
|
if ($(window).outerWidth(false) <= 420) {
|
|
this.setProperties({ filterable: false, autoFilterable: false });
|
|
}
|
|
|
|
if (isNone(this.get("none")) && isEmpty(this.get("value")) && !isEmpty(this.get("content"))) {
|
|
Ember.run.scheduleOnce("sync", () => {
|
|
const firstValue = this.get(`content.0.${this.get("valueAttribute")}`);
|
|
this.set("value", firstValue);
|
|
});
|
|
}
|
|
|
|
this._previousScrollParentOverflow = "auto";
|
|
this._previousCSSContext = {};
|
|
},
|
|
|
|
close() {
|
|
this.setProperties({ isExpanded: false, isFocused: false });
|
|
},
|
|
|
|
focus() {
|
|
Ember.run.schedule("afterRender", () => this.$offscreenInput().select() );
|
|
},
|
|
|
|
blur() {
|
|
Ember.run.schedule("afterRender", () => this.$offscreenInput().blur() );
|
|
},
|
|
|
|
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();
|
|
} 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;
|
|
}
|
|
|
|
if (typeof content === "object") {
|
|
return get(content, this.get("nameProperty"));
|
|
}
|
|
|
|
return content;
|
|
},
|
|
|
|
valueForContent(content) {
|
|
switch (typeof content) {
|
|
case "string":
|
|
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 }
|
|
};
|
|
},
|
|
|
|
formatContents(contents) {
|
|
return contents.map(content => this.formatContent(content));
|
|
},
|
|
|
|
@computed("filter", "filterable", "autoFilterable")
|
|
computedFilterable(filter, filterable, autoFilterable) {
|
|
if (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", "allowAny")
|
|
createRowContent(filter, allow) {
|
|
if (allow === true) {
|
|
return Ember.Object.create({ value: filter, name: filter });
|
|
}
|
|
},
|
|
|
|
@computed("content.[]")
|
|
computedContent(content) {
|
|
return this.formatContents(content || []);
|
|
},
|
|
|
|
@computed("value", "none", "computedContent.firstObject.value")
|
|
computedValue(value, none, firstContentValue) {
|
|
if (isNone(value) && isNone(none)) {
|
|
return this._castInteger(firstContentValue);
|
|
}
|
|
|
|
return this._castInteger(value);
|
|
},
|
|
|
|
@computed
|
|
templateForRow() { return () => null; },
|
|
|
|
@computed
|
|
templateForNoneRow() { return () => null; },
|
|
|
|
@computed
|
|
templateForCreateRow() { return () => null; },
|
|
|
|
@computed("none")
|
|
computedNone(none) {
|
|
if (isNone(none)) {
|
|
return null;
|
|
}
|
|
|
|
switch (typeof none) {
|
|
case "string":
|
|
return Ember.Object.create({ name: I18n.t(none), value: "" });
|
|
default:
|
|
return this.formatContent(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();
|
|
},
|
|
|
|
@on("didInsertElement")
|
|
_setupResizeListener() {
|
|
$(window).on("resize.select-box-kit", () => this.set("isExpanded", false) );
|
|
},
|
|
|
|
@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;
|
|
}
|
|
|
|
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"));
|
|
}
|
|
},
|
|
|
|
@observes("isExpanded")
|
|
_isExpandedChanged() {
|
|
if (this.get("isExpanded") === true) {
|
|
this._applyFixedPosition();
|
|
|
|
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);
|
|
},
|
|
|
|
@computed("scrollableParentSelector")
|
|
scrollableParent(scrollableParentSelector) {
|
|
return this.$().parents(scrollableParentSelector).first();
|
|
},
|
|
|
|
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);
|
|
},
|
|
|
|
onClearSelection() {
|
|
this.send("onSelect", null);
|
|
},
|
|
|
|
onSelect(value) {
|
|
value = this.defaultOnSelect(value);
|
|
this.set("value", value);
|
|
},
|
|
|
|
onDeselect() {
|
|
this.defaultOnDeselect();
|
|
this.set("value", null);
|
|
}
|
|
},
|
|
|
|
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 discourseHeaderHeight = $(".d-header").outerHeight(false);
|
|
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 - discourseHeaderHeight > 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._previousCSSContext = {
|
|
minWidth: this.$().css("min-width"),
|
|
maxWidth: this.$().css("max-width")
|
|
};
|
|
this.get("scrollableParent").css({ overflow: "hidden" });
|
|
|
|
this.$()
|
|
.before($placeholder.css({
|
|
display: "inline-block",
|
|
width,
|
|
height,
|
|
"vertical-align": "middle"
|
|
}))
|
|
.css({
|
|
direction: $("html").css("direction"),
|
|
position: "fixed",
|
|
"margin-top": -this.get("scrollableParent").scrollTop(),
|
|
"margin-left": -width,
|
|
width,
|
|
minWidth: "unset",
|
|
maxWidth: "unset"
|
|
});
|
|
},
|
|
|
|
_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-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)
|
|
});
|
|
}
|
|
});
|