select-box refactoring

- more flexibility (allows custom title)
- less re-render
- more robustness
This commit is contained in:
Joffrey JAFFEUX
2017-08-29 12:25:54 +02:00
committed by GitHub
parent 3d6c79de6d
commit 861dbe3b51
6 changed files with 116 additions and 119 deletions

View File

@ -17,16 +17,16 @@ export default SelectBoxComponent.extend({
clearable: true, clearable: true,
filterFunction: function() { filterFunction: function(content) {
const _matchFunction = (filter, text) => { const _matchFunction = (filter, text) => {
return text.toLowerCase().indexOf(filter) > -1; return text.toLowerCase().indexOf(filter) > -1;
}; };
return (selectBox) => { return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase(); const filter = selectBox.get("filter").toLowerCase();
return _.filter(selectBox.get("content"), (content) => { return _.filter(content, (c) => {
const category = Category.findById(content[selectBox.get("idKey")]); const category = Category.findById(c[selectBox.get("idKey")]);
const text = content[selectBox.get("textKey")]; const text = c[selectBox.get("textKey")];
if (category && category.get("parentCategory")) { if (category && category.get("parentCategory")) {
const categoryName = category.get("parentCategory.name"); const categoryName = category.get("parentCategory.name");
return _matchFunction(filter, text) || _matchFunction(filter, categoryName); return _matchFunction(filter, text) || _matchFunction(filter, categoryName);
@ -56,7 +56,7 @@ export default SelectBoxComponent.extend({
}, },
// original method is kept for compatibility // original method is kept for compatibility
selectBoxRowTemplate: function() { templateForRow: function() {
return (rowComponent) => this.rowContentTemplate(rowComponent.get("content")); return (rowComponent) => this.rowContentTemplate(rowComponent.get("content"));
}.property(), }.property(),

View File

@ -25,7 +25,7 @@ export default Ember.Component.extend({
value: null, value: null,
selectedContent: null, selectedContent: null,
noContentText: I18n.t("select_box.no_content"), noContentText: I18n.t("select_box.no_content"),
lastHoveredId: null, lastHovered: null,
idKey: "id", idKey: "id",
textKey: "text", textKey: "text",
@ -48,16 +48,33 @@ export default Ember.Component.extend({
castInteger: false, castInteger: false,
filterFunction: function() { filterFunction: function(content) {
return (selectBox) => { return (selectBox) => {
const filter = selectBox.get("filter").toLowerCase(); const filter = selectBox.get("filter").toLowerCase();
return _.filter(selectBox.get("content"), (content) => { return _.filter(content, (c) => {
return content[selectBox.get("textKey")].toLowerCase().indexOf(filter) > -1; return c[selectBox.get("textKey")].toLowerCase().indexOf(filter) > -1;
}); });
}; };
}, },
selectBoxRowTemplate: function() { titleForRow: function() {
return (rowComponent) => {
return rowComponent.get(`content.${this.get("textKey")}`);
};
}.property(),
shouldHighlightRow: function() {
return (rowComponent) => {
const id = this._castInteger(rowComponent.get(`content.${this.get("idKey")}`));
if (Ember.isNone(this.get("lastHovered"))) {
return id === this.get("value");
} else {
return id === this.get("lastHovered");
}
};
}.property(),
templateForRow: function() {
return (rowComponent) => { return (rowComponent) => {
let template = ""; let template = "";
@ -65,7 +82,8 @@ export default Ember.Component.extend({
template += iconHTML(Handlebars.escapeExpression(rowComponent.get("content.icon"))); template += iconHTML(Handlebars.escapeExpression(rowComponent.get("content.icon")));
} }
template += `<p class="text">${Handlebars.escapeExpression(rowComponent.get("text"))}</p>`; const text = rowComponent.get(`content.${this.get("textKey")}`);
template += `<p class="text">${Handlebars.escapeExpression(text)}</p>`;
return template; return template;
}; };
@ -97,9 +115,8 @@ export default Ember.Component.extend({
init() { init() {
this._super(); this._super();
if (!this.get("content")) { const content = this.getWithDefault("content", []);
this.set("content", []); this.set("content", content);
}
if (this.site.isMobileDevice) { if (this.site.isMobileDevice) {
this.set("filterable", false); this.set("filterable", false);
@ -107,8 +124,7 @@ export default Ember.Component.extend({
this.setProperties({ this.setProperties({
value: this._castInteger(this.get("value")), value: this._castInteger(this.get("value")),
componentId: this.elementId, componentId: this.elementId
filteredContent: []
}); });
}, },
@ -208,74 +224,48 @@ export default Ember.Component.extend({
}); });
}, },
@observes("value")
_valueChanged: function() {
if (Ember.isNone(this.get("value"))) {
this.set("lastHoveredId", null);
}
},
@observes("expanded") @observes("expanded")
_expandedChanged: function() { _expandedChanged: function() {
if (this.get("expanded")) { if (this.get("expanded")) {
this.setProperties({ focused: false, renderBody: true }); this.setProperties({ focused: false, renderBody: true });
if (Ember.isNone(this.get("lastHoveredId"))) {
this.set("lastHoveredId", this.get("value"));
}
if (this.get("filterable")) { if (this.get("filterable")) {
Ember.run.schedule("afterRender", () => this.$(".filter-query").focus()); Ember.run.schedule("afterRender", () => this.$(".filter-query").focus());
} }
}; };
}, },
@computed("value", "content") @computed("value", "content.[]")
selectedContent(value, content) { selectedContent(value, content) {
if (Ember.isNone(value)) { if (Ember.isNone(value)) {
return null; return null;
} }
const selectedContent = content.find((c) => { return content.find((c) => {
return c[this.get("idKey")] === value; return this._castInteger(c[this.get("idKey")]) === value;
}); });
if (!Ember.isNone(selectedContent)) {
return this._normalizeContent(selectedContent);
}
return null;
}, },
@computed("headerText", "dynamicHeaderText", "selectedContent.text") @computed("headerText", "dynamicHeaderText", "selectedContent", "textKey")
generatedHeadertext(headerText, dynamic, text) { generatedHeadertext(headerText, dynamic, selectedContent, textKey) {
if (dynamic && !Ember.isNone(text)) { if (dynamic && !Ember.isNone(selectedContent)) {
return text; return selectedContent[textKey];
} }
return headerText; return headerText;
}, },
@observes("content.[]", "filter") @computed("content.[]", "filter")
_filterChanged: function() { filteredContent(content, filter) {
if (Ember.isEmpty(this.get("filter"))) { let filteredContent;
this.set("filteredContent", this._remapContent(this.get("content")));
} else {
const filtered = this.filterFunction()(this);
this.set("filteredContent", this._remapContent(filtered));
}
},
@observes("content.[]", "value") if (Ember.isEmpty(filter)) {
@on("didReceiveAttrs") filteredContent = content;
_contentChanged: function() {
if (!Ember.isNone(this.get("value"))) {
this.set("lastHoveredId", this.get("content")[this.get("idKey")]);
} else { } else {
this.set("lastHoveredId", null); filteredContent = this.filterFunction(content)(this);
} }
this.set("filteredContent", this._remapContent(this.get("content"))); return filteredContent;
}, },
actions: { actions: {
@ -283,38 +273,22 @@ export default Ember.Component.extend({
this.toggleProperty("expanded"); this.toggleProperty("expanded");
}, },
onClearSelection() {
this.set("value", null);
},
onFilterChange(filter) { onFilterChange(filter) {
this.set("filter", filter); this.set("filter", filter);
}, },
onSelectRow(id) { onSelectRow(content) {
this.setProperties({ this.setProperties({
value: this._castInteger(id), value: this._castInteger(content[this.get("idKey")]),
expanded: false expanded: false
}); });
}, },
onHoverRow(id) { onHoverRow(content) {
this.set("lastHoveredId", id); this.set("lastHovered", this._castInteger(content[this.get("idKey")]));
} }
}, },
_remapContent(content) {
return content.map(c => this._normalizeContent(c));
},
_normalizeContent(content) {
return {
id: this._castInteger(content[this.get("idKey")]),
text: content[this.get("textKey")],
icon: content[this.get("iconKey")]
};
},
_positionSelectBoxWrapper() { _positionSelectBoxWrapper() {
const headerHeight = this.$(".select-box-header").outerHeight(); const headerHeight = this.$(".select-box-header").outerHeight();
@ -325,11 +299,11 @@ export default Ember.Component.extend({
}); });
}, },
_castInteger(value) { _castInteger(id) {
if (this.get("castInteger") && Ember.isPresent(value)) { if (this.get("castInteger") === true && Ember.isPresent(id)) {
return parseInt(value, 10); return parseInt(id, 10);
} }
return value; return id;
} }
}); });

View File

@ -1,44 +1,36 @@
import { on, observes } from 'ember-addons/ember-computed-decorators'; import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({ export default Ember.Component.extend({
layoutName: "components/select-box/select-box-row",
classNames: "select-box-row", classNames: "select-box-row",
tagName: "li", tagName: "li",
classNameBindings: ["isHighlighted"], attributeBindings: ["title"],
attributeBindings: ["text:title"], classNameBindings: ["isHighlighted:is-highlighted"],
lastHoveredId: null, @computed("titleForRow")
title(titleForRow) {
return titleForRow(this);
},
@on("init") @computed("templateForRow")
@observes("content", "lastHoveredId", "selectedId", "selectBoxRowTemplate") template(templateForRow) {
_updateTemplate: function() { return templateForRow(this);
this.set("isHighlighted", this._isHighlighted()); },
this.set("text", this.get("content.text"));
this.set("template", this.get("selectBoxRowTemplate")(this)); @computed("shouldHighlightRow", "lastHovered", "value")
isHighlighted(shouldHighlightRow) {
return shouldHighlightRow(this);
}, },
mouseEnter() { mouseEnter() {
this.sendAction("onHover", this.get("content.id")); this.sendAction("onHover", this.get("content"));
}, },
click() { click() {
this.sendAction("onSelect", this.get("content.id")); this.sendAction("onSelect", this.get("content"));
},
didReceiveAttrs() {
this._super();
this.set("isHighlighted", this._isHighlighted());
this.set("text", this.get("content.text"));
},
_isHighlighted() {
if(_.isUndefined(this.get("lastHoveredId"))) {
return this.get("content.id") === this.get("selectedId");
} else {
return this.get("content.id") === this.get("lastHoveredId");
} }
},
}); });

View File

@ -17,7 +17,7 @@
icon=icon icon=icon
clearable=clearable clearable=clearable
expanded=expanded expanded=expanded
selectedId=value value=value
}} }}
<div class="select-box-body"> <div class="select-box-body">
@ -34,12 +34,14 @@
{{component selectBoxCollectionComponent {{component selectBoxCollectionComponent
filteredContent=filteredContent filteredContent=filteredContent
selectBoxRowComponent=selectBoxRowComponent selectBoxRowComponent=selectBoxRowComponent
selectBoxRowTemplate=selectBoxRowTemplate templateForRow=templateForRow
lastHoveredId=lastHoveredId shouldHighlightRow=shouldHighlightRow
titleForRow=titleForRow
lastHovered=lastHovered
onSelectRow=(action "onSelectRow") onSelectRow=(action "onSelectRow")
onHoverRow=(action "onHoverRow") onHoverRow=(action "onHoverRow")
noContentText=noContentText noContentText=noContentText
selectedId=value value=value
}} }}
{{/if}} {{/if}}
</div> </div>

View File

@ -2,11 +2,13 @@
{{#each filteredContent as |content|}} {{#each filteredContent as |content|}}
{{component selectBoxRowComponent {{component selectBoxRowComponent
content=content content=content
selectBoxRowTemplate=selectBoxRowTemplate templateForRow=templateForRow
lastHoveredId=lastHoveredId titleForRow=titleForRow
shouldHighlightRow=shouldHighlightRow
lastHovered=lastHovered
onSelect=onSelectRow onSelect=onSelectRow
onHover=onHoverRow onHover=onHoverRow
selectedId=selectedId value=value
}} }}
{{else}} {{else}}
{{#if noContentText}} {{#if noContentText}}

View File

@ -239,12 +239,12 @@ componentTest('supports options to limit size', {
}); });
componentTest('supports custom row template', { componentTest('supports custom row template', {
template: '{{select-box content=content selectBoxRowTemplate=selectBoxRowTemplate}}', template: '{{select-box content=content templateForRow=templateForRow}}',
beforeEach() { beforeEach() {
this.set("content", [{ id: 1, text: "robin" }]); this.set("content", [{ id: 1, text: "robin" }]);
this.set("selectBoxRowTemplate", (rowComponent) => { this.set("templateForRow", (rowComponent) => {
return `<b>${rowComponent.get("text")}</b>`; return `<b>${rowComponent.get("content.text")}</b>`;
}); });
}, },
@ -258,9 +258,10 @@ componentTest('supports custom row template', {
}); });
componentTest('supports converting select value to integer', { componentTest('supports converting select value to integer', {
template: '{{select-box value=2 content=content castInteger=true}}', template: '{{select-box value=value content=content castInteger=true}}',
beforeEach() { beforeEach() {
this.set("value", 2);
this.set("content", [{ id: "1", text: "robin"}, {id: "2", text: "régis" }]); this.set("content", [{ id: "1", text: "robin"}, {id: "2", text: "régis" }]);
}, },
@ -270,6 +271,15 @@ componentTest('supports converting select value to integer', {
andThen(() => { andThen(() => {
assert.equal(find(".select-box-row.is-highlighted .text").text(), "régis"); assert.equal(find(".select-box-row.is-highlighted .text").text(), "régis");
}); });
andThen(() => {
this.set("value", 3);
this.set("content", [{ id: "3", text: "jeff" }]);
});
andThen(() => {
assert.equal(find(".select-box-row.is-highlighted .text").text(), "jeff", "it works with dynamic content");
});
} }
}); });
@ -335,3 +345,20 @@ componentTest('clearable selection', {
}); });
} }
}); });
componentTest('supports custom row title', {
template: '{{select-box content=content titleForRow=titleForRow}}',
beforeEach() {
this.set("content", [{ id: 1, text: "robin" }]);
this.set("titleForRow", () => "sam" );
},
test(assert) {
click(".select-box-header");
andThen(() => {
assert.equal(find(".select-box-row:first").attr("title"), "sam");
});
}
});