select-kit DOM mixin refactoring

This commits improves code readability, performance and rendering precision.
This commit is contained in:
Joffrey JAFFEUX 2017-12-28 16:12:45 +01:00 committed by GitHub
parent 81427e26ea
commit 8fd683ab19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 76 deletions

View File

@ -29,7 +29,6 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
renderedBodyOnce: false, renderedBodyOnce: false,
renderedFilterOnce: false, renderedFilterOnce: false,
tabindex: 0, tabindex: 0,
scrollableParentSelector: ".modal-body",
none: null, none: null,
highlightedValue: null, highlightedValue: null,
noContentLabel: "select_kit.no_content", noContentLabel: "select_kit.no_content",
@ -68,8 +67,6 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
this._super(); this._super();
this.noneValue = "__none__"; this.noneValue = "__none__";
this._previousScrollParentOverflow = "auto";
this._previousCSSContext = {};
this.set("headerComponentOptions", Ember.Object.create()); this.set("headerComponentOptions", Ember.Object.create());
this.set("rowComponentOptions", Ember.Object.create()); this.set("rowComponentOptions", Ember.Object.create());
this.set("computedContent", []); this.set("computedContent", []);

View File

@ -4,12 +4,16 @@ export default Ember.Mixin.create({
init() { init() {
this._super(); this._super();
this._previousScrollParentOverflow = null;
this._previousCSSContext = null;
this.filterInputSelector = ".filter-input"; this.filterInputSelector = ".filter-input";
this.rowSelector = ".select-kit-row"; this.rowSelector = ".select-kit-row";
this.collectionSelector = ".select-kit-collection"; this.collectionSelector = ".select-kit-collection";
this.headerSelector = ".select-kit-header"; this.headerSelector = ".select-kit-header";
this.bodySelector = ".select-kit-body"; this.bodySelector = ".select-kit-body";
this.wrapperSelector = ".select-kit-wrapper"; this.wrapperSelector = ".select-kit-wrapper";
this.scrollableParentSelector = ".modal-body";
this.fixedPlaceholderSelector = `.select-kit-fixed-placeholder-${this.elementId}`;
}, },
$findRowByValue(value) { return this.$(`${this.rowSelector}[data-value='${value}']`); }, $findRowByValue(value) { return this.$(`${this.rowSelector}[data-value='${value}']`); },
@ -18,15 +22,16 @@ export default Ember.Mixin.create({
$body() { return this.$(this.bodySelector); }, $body() { return this.$(this.bodySelector); },
$wrapper() { return this.$(this.wrapperSelector); },
$collection() { return this.$(this.collectionSelector); }, $collection() { return this.$(this.collectionSelector); },
$rows(withHidden) { $scrollableParent() { return $(this.scrollableParentSelector); },
if (withHidden === true) { $fixedPlaceholder() { return $(this.fixedPlaceholderSelector); },
return this.$(`${this.rowSelector}:not(.no-content)`);
} else { $rows() {
return this.$(`${this.rowSelector}:not(.no-content):not(.is-hidden)`); return this.$(`${this.rowSelector}:not(.no-content):not(.is-hidden)`);
}
}, },
$highlightedRow() { return this.$rows().filter(".is-highlighted"); }, $highlightedRow() { return this.$rows().filter(".is-highlighted"); },
@ -36,8 +41,7 @@ export default Ember.Mixin.create({
$filterInput() { return this.$(this.filterInputSelector); }, $filterInput() { return this.$(this.filterInputSelector); },
@on("didRender") @on("didRender")
_ajustPosition() { _adjustPosition() {
$(`.select-kit-fixed-placeholder-${this.elementId}`).remove();
this.$collection().css("max-height", this.get("collectionHeight")); this.$collection().css("max-height", this.get("collectionHeight"));
this._applyFixedPosition(); this._applyFixedPosition();
this._applyDirection(); this._applyDirection();
@ -46,7 +50,7 @@ export default Ember.Mixin.create({
@on("willDestroyElement") @on("willDestroyElement")
_clearState() { _clearState() {
$(`.select-kit-fixed-placeholder-${this.elementId}`).remove(); this.$fixedPlaceholder().remove();
}, },
// use to collapse and remove focus // use to collapse and remove focus
@ -102,17 +106,14 @@ export default Ember.Mixin.create({
_applyDirection() { _applyDirection() {
let options = { left: "auto", bottom: "auto", top: "auto" }; let options = { left: "auto", bottom: "auto", top: "auto" };
const dHeader = $(".d-header")[0]; const discourseHeader = $(".d-header")[0];
const dHeaderBounds = dHeader ? dHeader.getBoundingClientRect() : {top: 0, height: 0}; const discourseHeaderHeight = discourseHeader ? (discourseHeader.getBoundingClientRect().top + this._computedStyle(discourseHeader, "height")) : 0;
const dHeaderHeight = dHeaderBounds.top + dHeaderBounds.height; const bodyHeight = this._computedStyle(this.$body()[0], "height");
const bodyHeight = this.$body()[0].getBoundingClientRect().height;
const windowWidth = $(window).width(); const windowWidth = $(window).width();
const windowHeight = $(window).height(); const componentHeight = this._computedStyle(this.get("element"), "height");
const boundingRect = this.get("element").getBoundingClientRect(); const componentWidth = this._computedStyle(this.get("element"), "width");
const componentHeight = boundingRect.height; const offsetTop = this.get("element").getBoundingClientRect().top;
const componentWidth = boundingRect.width; const offsetBottom = this.get("element").getBoundingClientRect().bottom;
const offsetTop = boundingRect.top;
const offsetBottom = boundingRect.bottom;
if (this.get("fullWidthOnMobile") && (this.site && this.site.isMobileDevice)) { if (this.get("fullWidthOnMobile") && (this.site && this.site.isMobileDevice)) {
const margin = 10; const margin = 10;
@ -121,10 +122,10 @@ export default Ember.Mixin.create({
options.width = windowWidth - margin * 2; options.width = windowWidth - margin * 2;
options.maxWidth = options.minWidth = "unset"; options.maxWidth = options.minWidth = "unset";
} else { } else {
const bodyWidth = this.$body()[0].getBoundingClientRect().width; const bodyWidth = this._computedStyle(this.$body()[0], "width");
if ($("html").css("direction") === "rtl") { if (this._isRTL()) {
const horizontalSpacing = boundingRect.right; const horizontalSpacing = this.get("element").getBoundingClientRect().right;
const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0; const hasHorizontalSpace = horizontalSpacing - (this.get("horizontalOffset") + bodyWidth) > 0;
if (hasHorizontalSpace) { if (hasHorizontalSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false }); this.setProperties({ isLeftAligned: true, isRightAligned: false });
@ -134,7 +135,7 @@ export default Ember.Mixin.create({
options.right = - (bodyWidth - componentWidth + this.get("horizontalOffset")); options.right = - (bodyWidth - componentWidth + this.get("horizontalOffset"));
} }
} else { } else {
const horizontalSpacing = boundingRect.left; const horizontalSpacing = this.get("element").getBoundingClientRect().left;
const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0); const hasHorizontalSpace = (windowWidth - (this.get("horizontalOffset") + horizontalSpacing + bodyWidth) > 0);
if (hasHorizontalSpace) { if (hasHorizontalSpace) {
this.setProperties({ isLeftAligned: true, isRightAligned: false }); this.setProperties({ isLeftAligned: true, isRightAligned: false });
@ -147,87 +148,109 @@ export default Ember.Mixin.create({
} }
const fullHeight = this.get("verticalOffset") + bodyHeight + componentHeight; const fullHeight = this.get("verticalOffset") + bodyHeight + componentHeight;
const hasBelowSpace = windowHeight - offsetBottom - fullHeight > 0; const hasBelowSpace = $(window).height() - offsetBottom - fullHeight > 0;
const hasAboveSpace = offsetTop - fullHeight - dHeaderHeight > 0; const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight > 0;
const headerHeight = this._computedStyle(this.$header()[0], "height");
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) { if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
this.setProperties({ isBelow: true, isAbove: false }); this.setProperties({ isBelow: true, isAbove: false });
options.top = this.$header()[0].getBoundingClientRect().height + this.get("verticalOffset"); options.top = headerHeight + this.get("verticalOffset");
} else { } else {
this.setProperties({ isBelow: false, isAbove: true }); this.setProperties({ isBelow: false, isAbove: true });
options.bottom = this.$header()[0].getBoundingClientRect().height + this.get("verticalOffset"); options.bottom = headerHeight + this.get("verticalOffset");
} }
this.$body().css(options); this.$body().css(options);
}, },
_applyFixedPosition() { _applyFixedPosition() {
if (this.get("isExpanded") !== true) { return; } if (this.get("isExpanded") !== true) return;
if (this.$fixedPlaceholder().length === 1) return;
if (this.$scrollableParent().length === 0) return;
const scrollableParent = this.$().parents(this.get("scrollableParentSelector")); const width = this._computedStyle(this.get("element"), "width");
if (scrollableParent.length === 0) { return; } const height = this._computedStyle(this.get("element"), "height");
const boundingRect = this.get("element").getBoundingClientRect(); this._previousScrollParentOverflow = this._previousScrollParentOverflow ||
const width = boundingRect.width; this.$scrollableParent().css("overflow");
const height = boundingRect.height;
const $placeholder = $(`<div class='select-kit-fixed-placeholder-${this.elementId}'></div>`);
this._previousScrollParentOverflow = this._previousScrollParentOverflow || scrollableParent.css("overflow"); this._previousCSSContext = this._previousCSSContext || {
scrollableParent.css({ overflow: "hidden" }); width,
this._previousCSSContext = {
minWidth: this.$().css("min-width"), minWidth: this.$().css("min-width"),
maxWidth: this.$().css("max-width") maxWidth: this.$().css("max-width"),
top: this.$().css("top"),
left: this.$().css("left"),
marginLeft: this.$().css("margin-left"),
marginRight: this.$().css("margin-right"),
position: this.$().css("position")
}; };
const componentStyles = { const componentStyles = {
position: "fixed", top: this.get("element").getBoundingClientRect().top,
"margin-top": -scrollableParent.scrollTop(),
width, width,
left: this.get("element").getBoundingClientRect().left,
marginLeft: 0,
marginRight: 0,
minWidth: "unset", minWidth: "unset",
maxWidth: "unset" maxWidth: "unset",
position: "fixed"
}; };
if ($("html").css("direction") === "rtl") { const $placeholderTemplate = $(`<div class='select-kit-fixed-placeholder-${this.elementId}'></div>`);
componentStyles.marginRight = -width; $placeholderTemplate.css({
} else { display: "inline-block",
componentStyles.marginLeft = -width; width,
} height,
"vertical-align": "middle"
});
$placeholder.css({ display: "inline-block", width, height, "vertical-align": "middle" }); this.$()
.before($placeholderTemplate)
.css(componentStyles);
this.$().before($placeholder).css(componentStyles); this.$scrollableParent().css({ overflow: "hidden" });
}, },
_removeFixedPosition() { _removeFixedPosition() {
$(`.select-kit-fixed-placeholder-${this.elementId}`).remove(); this.$fixedPlaceholder().remove();
if (!this.element || this.isDestroying || this.isDestroyed) { return; } if (!this.element || this.isDestroying || this.isDestroyed) return;
if (this.$scrollableParent().length === 0) return;
const scrollableParent = this.$().parents(this.get("scrollableParentSelector")); this.$().css(this._previousCSSContext || {});
if (scrollableParent.length === 0) { return; } this.$scrollableParent().css("overflow", this._previousScrollParentOverflow || {});
const css = jQuery.extend(
this._previousCSSContext,
{
top: "auto",
left: "auto",
"margin-left": "auto",
"margin-right": "auto",
"margin-top": "auto",
position: "relative"
}
);
this.$().css(css);
scrollableParent.css("overflow", this._previousScrollParentOverflow);
}, },
_positionWrapper() { _positionWrapper() {
const headerBoundingRect = this.$header()[0].getBoundingClientRect(); const elementWidth = this._computedStyle(this.get("element"), "width");
const headerHeight = this._computedStyle(this.$header()[0], "height");
const bodyHeight = this._computedStyle(this.$body()[0], "height");
this.$(this.wrapperSelector).css({ this.$wrapper().css({
width: this.get("element").getBoundingClientRect().width, width: elementWidth,
height: headerBoundingRect.height + this.$body()[0].getBoundingClientRect().height height: headerHeight + bodyHeight
}); });
}, },
_isRTL() {
return $("html").css("direction") === "rtl";
},
_computedStyle(element, style) {
if (!element) return 0;
let value;
if (window.getComputedStyle) {
value = window.getComputedStyle(element, null)[style];
} else {
value = $(element).css(style);
}
return this._getFloat(value);
},
_getFloat(value) {
value = parseFloat(value);
return $.isNumeric(value) ? value : 0;
}
}); });

View File

@ -38,8 +38,12 @@ export default Ember.Mixin.create({
$(document) $(document)
.on("mousedown.select-kit", event => { .on("mousedown.select-kit", event => {
event.stopPropagation();
if (!this.get("renderedBodyOnce")) return;
if (!this.get("isFocused")) return;
if (Ember.isNone(this.get("element"))) return; if (Ember.isNone(this.get("element"))) return;
if (this.get("element").contains(event.target)) return; if (Ember.$.contains(this.get("element"), event.target)) return;
this.didClickOutside(event); this.didClickOutside(event);
}); });