FEATURE: implements minimum selection for select-kit

This commit is contained in:
Joffrey JAFFEUX
2018-04-05 16:45:19 +02:00
committed by GitHub
parent cd6a99a027
commit f0fe16d824
15 changed files with 186 additions and 52 deletions

View File

@ -15,7 +15,7 @@
{{tag-chooser {{tag-chooser
tags=model.parent_tag_name tags=model.parent_tag_name
everyTag=true everyTag=true
limit=1 maximum=1
allowCreate=true allowCreate=true
filterPlaceholder="tagging.groups.parent_tag_placeholder"}} filterPlaceholder="tagging.groups.parent_tag_placeholder"}}
<span class="description">{{i18n 'tagging.groups.parent_tag_description'}}</span> <span class="description">{{i18n 'tagging.groups.parent_tag_description'}}</span>

View File

@ -34,12 +34,12 @@ export default ComboBox.extend(Tags, {
}); });
}); });
this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); this.set("maximum", parseInt(this.get("limit") || this.get("maximum") || this.get("siteSettings.max_tags_per_topic")));
}, },
@computed("hasReachedLimit") @computed("hasReachedMaximum")
caretIcon(hasReachedLimit) { caretIcon(hasReachedMaximum) {
return hasReachedLimit ? null : "plus fa-fw"; return hasReachedMaximum ? null : "plus fa-fw";
}, },
@computed("tags") @computed("tags")
@ -135,6 +135,12 @@ export default ComboBox.extend(Tags, {
content.label = joinedTags; content.label = joinedTags;
} }
if (!this.get("hasReachedMinimum") && isEmpty(this.get("selection"))) {
const key = this.get("minimumLabel") || "select_kit.min_content_not_reached";
const label = I18n.t(key, { count: this.get("minimum") });
content.title = content.name = content.label = label;
}
content.title = content.name = content.value = joinedTags; content.title = content.name = content.value = joinedTags;
return content; return content;

View File

@ -140,10 +140,23 @@ export default SelectKitComponent.extend({
}, },
computeHeaderContent() { computeHeaderContent() {
return { let content = {
title: this.get("title"), title: this.get("title"),
selection: this.get("selection") selection: this.get("selection")
}; };
if (this.get("noneLabel")) {
if (!this.get("hasSelection")) {
content.title = content.name = content.label = I18n.t(this.get("noneLabel"));
}
} else {
if (!this.get("hasReachedMinimum")) {
const key = this.get("minimumLabel") || "select_kit.min_content_not_reached";
content.title = content.name = content.label = I18n.t(key, { count: this.get("minimum") });
}
}
return content;
}, },
@computed("filter") @computed("filter")
@ -154,7 +167,7 @@ export default SelectKitComponent.extend({
}, },
validateSelect() { validateSelect() {
return this._super() && !this.get("hasReachedLimit"); return this._super() && !this.get("hasReachedMaximum");
}, },
@computed("computedValues.[]", "computedContent.[]") @computed("computedValues.[]", "computedContent.[]")

View File

@ -25,7 +25,8 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
"isLeftAligned", "isLeftAligned",
"isRightAligned", "isRightAligned",
"hasSelection", "hasSelection",
"hasReachedLimit", "hasReachedMaximum",
"hasReachedMinimum",
], ],
isDisabled: false, isDisabled: false,
isExpanded: false, isExpanded: false,
@ -71,6 +72,10 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
collectionHeader: null, collectionHeader: null,
allowAutoSelectFirst: true, allowAutoSelectFirst: true,
highlightedSelection: null, highlightedSelection: null,
maximum: null,
minimum: null,
minimumLabel: null,
maximumLabel: null,
init() { init() {
this._super(); this._super();
@ -188,14 +193,22 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
} }
}, },
validateCreate() { return !this.get("hasReachedLimit"); }, validateCreate() { return !this.get("hasReachedMaximum"); },
validateSelect() { return !this.get("hasReachedLimit"); }, validateSelect() { return !this.get("hasReachedMaximum"); },
@computed("limit", "selection.[]") @computed("maximum", "selection.[]")
hasReachedLimit(limit, selection) { hasReachedMaximum(maximum, selection) {
if (!limit) return false; if (!maximum) return false;
return selection.length >= limit; selection = makeArray(selection);
return selection.length >= maximum;
},
@computed("minimum", "selection.[]")
hasReachedMinimum(minimum, selection) {
if (!minimum) return true;
selection = makeArray(selection);
return selection.length >= minimum;
}, },
@computed("shouldFilter", "allowAny", "filter") @computed("shouldFilter", "allowAny", "filter")
@ -212,10 +225,16 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
} }
}, },
@computed("hasReachedLimit", "limit") @computed("hasReachedMaximum", "hasReachedMinimum", "isExpanded")
maxContentRow(hasReachedLimit, limit) { validationMessage(hasReachedMaximum, hasReachedMinimum) {
if (hasReachedLimit) { if (hasReachedMaximum && this.get("maximum")) {
return I18n.t("select_kit.max_content_reached", { count: limit }); const key = this.get("maximumLabel") || "select_kit.max_content_reached";
return I18n.t(key, { count: this.get("maximum") });
}
if (!hasReachedMinimum && this.get("minimum")) {
const key = this.get("minimumLabel") || "select_kit.min_content_not_reached";
return I18n.t(key, { count: this.get("minimum") });
} }
}, },
@ -227,9 +246,9 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
return false; return false;
}, },
@computed("computedValue", "filter", "collectionComputedContent.[]", "hasReachedLimit", "isLoading") @computed("computedValue", "filter", "collectionComputedContent.[]", "hasReachedMaximum", "isLoading")
shouldDisplayCreateRow(computedValue, filter, collectionComputedContent, hasReachedLimit, isLoading) { shouldDisplayCreateRow(computedValue, filter, collectionComputedContent, hasReachedMaximum, isLoading) {
if (isLoading || hasReachedLimit) return false; if (isLoading || hasReachedMaximum) return false;
if (collectionComputedContent.map(c => c.value).includes(filter)) return false; if (collectionComputedContent.map(c => c.value).includes(filter)) return false;
if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true; if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true;
return false; return false;

View File

@ -91,7 +91,7 @@ export default SelectKitComponent.extend({
name: this.get("selection.name") || this.get("noneRowComputedContent.name") name: this.get("selection.name") || this.get("noneRowComputedContent.name")
}; };
if (!this.get("hasSelection") && this.get("noneLabel")) { if (this.get("noneLabel") && !this.get("hasSelection")) {
content.title = content.name = I18n.t(this.get("noneLabel")); content.title = content.name = I18n.t(this.get("noneLabel"));
} }
@ -134,7 +134,7 @@ export default SelectKitComponent.extend({
return selection !== this.get("noneRowComputedContent") && !isNone(selection); return selection !== this.get("noneRowComputedContent") && !isNone(selection);
}, },
@computed("computedValue", "filter", "collectionComputedContent.[]", "hasReachedLimit") @computed("computedValue", "filter", "collectionComputedContent.[]", "hasReachedMaximum", "hasReachedMinimum")
shouldDisplayCreateRow(computedValue, filter) { shouldDisplayCreateRow(computedValue, filter) {
return this._super() && computedValue !== filter; return this._super() && computedValue !== filter;
}, },

View File

@ -37,7 +37,7 @@ export default MultiSelectComponent.extend(Tags, {
}); });
if (!this.get("unlimitedTagCount")) { if (!this.get("unlimitedTagCount")) {
this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); this.set("maximum", parseInt(this.get("limit") || this.get("maximum") || this.get("siteSettings.max_tags_per_topic")));
} }
}, },

View File

@ -33,7 +33,7 @@ export default Ember.Mixin.create({
}, },
validateCreate(term) { validateCreate(term) {
if (this.get("hasReachedLimit") || !this.site.get("can_create_tag")) { if (this.get("hasReachedMaximum") || !this.site.get("can_create_tag")) {
return false; return false;
} }

View File

@ -41,7 +41,7 @@
computedValue=computedValue computedValue=computedValue
rowComponentOptions=rowComponentOptions rowComponentOptions=rowComponentOptions
noContentRow=noContentRow noContentRow=noContentRow
maxContentRow=maxContentRow validationMessage=validationMessage
}} }}
{{/unless}} {{/unless}}
{{/if}} {{/if}}

View File

@ -30,26 +30,25 @@
}} }}
{{/if}} {{/if}}
{{#if maxContentRow}} {{#if noContentRow}}
<li class="select-kit-row max-content"> <li class="select-kit-row no-content">
{{maxContentRow}} {{noContentRow}}
</li> </li>
{{else}} {{else}}
{{#if noContentRow}} {{#if validationMessage}}
<li class="select-kit-row no-content"> <div class="validation-message">
{{noContentRow}} {{validationMessage}}
</li> </div>
{{else}}
{{#each collectionComputedContent as |computedContent|}}
{{component rowComponent
computedContent=computedContent
highlighted=highlighted
computedValue=computedValue
templateForRow=templateForRow
onClickRow=onClickRow
onMouseoverRow=onMouseoverRow
options=rowComponentOptions
}}
{{/each}}
{{/if}} {{/if}}
{{#each collectionComputedContent as |computedContent|}}
{{component rowComponent
computedContent=computedContent
highlighted=highlighted
computedValue=computedValue
templateForRow=templateForRow
onClickRow=onClickRow
onMouseoverRow=onMouseoverRow
options=rowComponentOptions
}}
{{/each}}
{{/if}} {{/if}}

View File

@ -40,7 +40,7 @@
computedValue=computedValue computedValue=computedValue
rowComponentOptions=rowComponentOptions rowComponentOptions=rowComponentOptions
noContentRow=noContentRow noContentRow=noContentRow
maxContentRow=maxContentRow validationMessage=validationMessage
}} }}
{{/if}} {{/if}}
</div> </div>

View File

@ -171,11 +171,6 @@
white-space: nowrap; white-space: nowrap;
} }
&.max-content {
white-space: nowrap;
color: $danger;
}
.name { .name {
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
@ -211,6 +206,14 @@
padding: 0; padding: 0;
max-height: 200px; max-height: 200px;
.validation-message {
white-space: nowrap;
color: $danger;
flex: 1 0 auto;
margin: 5px;
padding: 0 2px;
}
.select-kit-collection { .select-kit-collection {
padding: 0; padding: 0;
margin: 0; margin: 0;

View File

@ -1250,7 +1250,8 @@ en:
no_content: No matches found no_content: No matches found
filter_placeholder: Search... filter_placeholder: Search...
create: "Create: '{{content}}'" create: "Create: '{{content}}'"
max_content_reached: "You can only select {{count}} items." max_content_reached: "You can only select {{count}} item(s)."
min_content_not_reached: "Select at least {{count}} item(s)."
emoji_picker: emoji_picker:
filter_placeholder: Search for emoji filter_placeholder: Search for emoji

View File

@ -160,3 +160,44 @@ componentTest('with limitMatches', {
andThen(() => assert.equal(this.get('subject').el().find(".select-kit-row").length, 2)); andThen(() => assert.equal(this.get('subject').el().find(".select-kit-row").length, 2));
} }
}); });
componentTest('with minimum', {
template: '{{multi-select content=content minimum=1}}',
beforeEach() {
this.set('content', ['sam', 'jeff', 'neil']);
},
test(assert) {
this.get('subject').expand();
andThen(() => assert.equal(this.get('subject').validationMessage(), 'Select at least 1 item(s).'));
this.get('subject').selectRowByValue('sam');
andThen(() => {
assert.equal(this.get('subject').header().label(), 'sam');
});
}
});
componentTest('with minimumLabel', {
template: '{{multi-select content=content minimum=1 minimumLabel="test.minimum"}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { minimum: 'min %{count}' };
this.set('content', ['sam', 'jeff', 'neil']);
},
test(assert) {
this.get('subject').expand();
andThen(() => assert.equal(this.get('subject').validationMessage(), 'min 1'));
this.get('subject').selectRowByValue('jeff');
andThen(() => {
assert.equal(this.get('subject').header().label(), 'jeff');
});
}
});

View File

@ -501,3 +501,44 @@ componentTest('with limitMatches', {
andThen(() => assert.equal(this.get('subject').el().find(".select-kit-row").length, 2)); andThen(() => assert.equal(this.get('subject').el().find(".select-kit-row").length, 2));
} }
}); });
componentTest('with minimum', {
template: '{{single-select content=content minimum=1 allowAutoSelectFirst=false}}',
beforeEach() {
this.set('content', ['sam', 'jeff', 'neil']);
},
test(assert) {
this.get('subject').expand();
andThen(() => assert.equal(this.get('subject').validationMessage(), 'Select at least 1 item(s).'));
this.get('subject').selectRowByValue('sam');
andThen(() => {
assert.equal(this.get('subject').header().label(), 'sam');
});
}
});
componentTest('with minimumLabel', {
template: '{{single-select content=content minimum=1 minimumLabel="test.minimum" allowAutoSelectFirst=false}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { minimum: 'min %{count}' };
this.set('content', ['sam', 'jeff', 'neil']);
},
test(assert) {
this.get('subject').expand();
andThen(() => assert.equal(this.get('subject').validationMessage(), 'min 1'));
this.get('subject').selectRowByValue('jeff');
andThen(() => {
assert.equal(this.get('subject').header().label(), 'jeff');
});
}
});

View File

@ -63,6 +63,7 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
return { return {
value: function() { return header.attr('data-value'); }, value: function() { return header.attr('data-value'); },
name: function() { return header.attr('data-name'); }, name: function() { return header.attr('data-name'); },
label: function() { return header.text().trim(); },
icon: function() { return header.find('.icon'); }, icon: function() { return header.find('.icon'); },
title: function() { return header.attr('title'); }, title: function() { return header.attr('title'); },
el: function() { return header; } el: function() { return header; }
@ -183,6 +184,16 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
return rowHelper(find(selector).find('.select-kit-row.none')); return rowHelper(find(selector).find('.select-kit-row.none'));
}, },
validationMessage: function() {
var validationMessage = find(selector).find('.validation-message');
if (validationMessage.length) {
return validationMessage.html().trim();
} else {
return null;
}
},
selectedRow: function() { selectedRow: function() {
return rowHelper(find(selector).find('.select-kit-row.is-selected')); return rowHelper(find(selector).find('.select-kit-row.is-selected'));
}, },