mirror of
https://github.com/discourse/discourse.git
synced 2025-05-23 20:01:08 +08:00
FEATURE: implements minimum selection for select-kit
This commit is contained in:
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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.[]")
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
|
@ -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")));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
computedValue=computedValue
|
computedValue=computedValue
|
||||||
rowComponentOptions=rowComponentOptions
|
rowComponentOptions=rowComponentOptions
|
||||||
noContentRow=noContentRow
|
noContentRow=noContentRow
|
||||||
maxContentRow=maxContentRow
|
validationMessage=validationMessage
|
||||||
}}
|
}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -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}}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
computedValue=computedValue
|
computedValue=computedValue
|
||||||
rowComponentOptions=rowComponentOptions
|
rowComponentOptions=rowComponentOptions
|
||||||
noContentRow=noContentRow
|
noContentRow=noContentRow
|
||||||
maxContentRow=maxContentRow
|
validationMessage=validationMessage
|
||||||
}}
|
}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -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'));
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user