Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit 7575035

Browse files
authored
Merge pull request #75 from ghiscoding/feature/select-collection-string
feat(select): collection can also be of type String
2 parents c37b1c7 + 863b512 commit 7575035

File tree

6 files changed

+137
-81
lines changed

6 files changed

+137
-81
lines changed

src/app/modules/angular-slickgrid/editors/selectEditor.ts

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ export class SelectEditor implements Editor {
153153
* The current selected values (multiple select) from the collection
154154
*/
155155
get currentValues() {
156+
// collection of strings, just return the filtered string that are equals
157+
if (this.collection.every(x => typeof x === 'string')) {
158+
return this.collection.filter(c => this.$editorElm.val().indexOf(c.toString()) !== -1);
159+
}
160+
161+
// collection of label/value pair
156162
const separatorBetweenLabels = this.collectionOptions && this.collectionOptions.separatorBetweenTextLabels || '';
157163
const isIncludingPrefixSuffix = this.collectionOptions && this.collectionOptions.includePrefixSuffixToSelectedValues || false;
158164

@@ -180,6 +186,12 @@ export class SelectEditor implements Editor {
180186
* The current selected values (single select) from the collection
181187
*/
182188
get currentValue() {
189+
// collection of strings, just return the filtered string that are equals
190+
if (this.collection.every(x => typeof x === 'string')) {
191+
return findOrDefault(this.collection, (c: any) => c.toString() === this.$editorElm.val());
192+
}
193+
194+
// collection of label/value pair
183195
const separatorBetweenLabels = this.collectionOptions && this.collectionOptions.separatorBetweenTextLabels || '';
184196
const isIncludingPrefixSuffix = this.collectionOptions && this.collectionOptions.includePrefixSuffixToSelectedValues || false;
185197
const itemFound = findOrDefault(this.collection, (c: any) => c[this.valueName].toString() === this.$editorElm.val());
@@ -387,37 +399,45 @@ export class SelectEditor implements Editor {
387399
const isRenderHtmlEnabled = this.columnDef.internalColumnEditor.enableRenderHtml || false;
388400
const sanitizedOptions = this.gridOptions && this.gridOptions.sanitizeHtmlOptions || {};
389401

390-
collection.forEach((option: SelectOption) => {
391-
if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) {
392-
throw new Error(`[select-editor] A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example: { collection: [ { value: '1', label: 'One' } ])`);
393-
}
394-
const labelKey = (option.labelKey || option[this.labelName]) as string;
395-
const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey) ? this._translate.instant(labelKey || ' ') : labelKey;
396-
let prefixText = option[this.labelPrefixName] || '';
397-
let suffixText = option[this.labelSuffixName] || '';
398-
let optionLabel = option[this.optionLabel] || '';
399-
optionLabel = optionLabel.toString().replace(/\"/g, '\''); // replace double quotes by single quotes to avoid interfering with regular html
400-
401-
// also translate prefix/suffix if enableTranslateLabel is true and text is a string
402-
prefixText = (this.enableTranslateLabel && prefixText && typeof prefixText === 'string') ? this._translate.instant(prefixText || ' ') : prefixText;
403-
suffixText = (this.enableTranslateLabel && suffixText && typeof suffixText === 'string') ? this._translate.instant(suffixText || ' ') : suffixText;
404-
optionLabel = (this.enableTranslateLabel && optionLabel && typeof optionLabel === 'string') ? this._translate.instant(optionLabel || ' ') : optionLabel;
405-
406-
// add to a temp array for joining purpose and filter out empty text
407-
const tmpOptionArray = [prefixText, labelText, suffixText].filter((text) => text);
408-
let optionText = tmpOptionArray.join(separatorBetweenLabels);
409-
410-
// if user specifically wants to render html text, he needs to opt-in else it will stripped out by default
411-
// also, the 3rd party lib will saninitze any html code unless it's encoded, so we'll do that
412-
if (isRenderHtmlEnabled) {
413-
// sanitize any unauthorized html tags like script and others
414-
// for the remaining allowed tags we'll permit all attributes
415-
const sanitizedText = DOMPurify.sanitize(optionText, sanitizedOptions);
416-
optionText = htmlEncode(sanitizedText);
417-
}
402+
// collection could be an Array of Strings OR Objects
403+
if (collection.every(x => typeof x === 'string')) {
404+
collection.forEach((option: string) => {
405+
options += `<option value="${option}" label="${option}">${option}</option>`;
406+
});
407+
} else {
408+
// array of objects will require a label/value pair unless a customStructure is passed
409+
collection.forEach((option: SelectOption) => {
410+
if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) {
411+
throw new Error(`[select-editor] A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example: { collection: [ { value: '1', label: 'One' } ])`);
412+
}
413+
const labelKey = (option.labelKey || option[this.labelName]) as string;
414+
const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey) ? this._translate.instant(labelKey || ' ') : labelKey;
415+
let prefixText = option[this.labelPrefixName] || '';
416+
let suffixText = option[this.labelSuffixName] || '';
417+
let optionLabel = option[this.optionLabel] || '';
418+
optionLabel = optionLabel.toString().replace(/\"/g, '\''); // replace double quotes by single quotes to avoid interfering with regular html
418419

419-
options += `<option value="${option[this.valueName]}" label="${optionLabel}">${optionText}</option>`;
420-
});
420+
// also translate prefix/suffix if enableTranslateLabel is true and text is a string
421+
prefixText = (this.enableTranslateLabel && prefixText && typeof prefixText === 'string') ? this._translate.instant(prefixText || ' ') : prefixText;
422+
suffixText = (this.enableTranslateLabel && suffixText && typeof suffixText === 'string') ? this._translate.instant(suffixText || ' ') : suffixText;
423+
optionLabel = (this.enableTranslateLabel && optionLabel && typeof optionLabel === 'string') ? this._translate.instant(optionLabel || ' ') : optionLabel;
424+
425+
// add to a temp array for joining purpose and filter out empty text
426+
const tmpOptionArray = [prefixText, labelText, suffixText].filter((text) => text);
427+
let optionText = tmpOptionArray.join(separatorBetweenLabels);
428+
429+
// if user specifically wants to render html text, he needs to opt-in else it will stripped out by default
430+
// also, the 3rd party lib will saninitze any html code unless it's encoded, so we'll do that
431+
if (isRenderHtmlEnabled) {
432+
// sanitize any unauthorized html tags like script and others
433+
// for the remaining allowed tags we'll permit all attributes
434+
const sanitizedText = DOMPurify.sanitize(optionText, sanitizedOptions);
435+
optionText = htmlEncode(sanitizedText);
436+
}
437+
438+
options += `<option value="${option[this.valueName]}" label="${optionLabel}">${optionText}</option>`;
439+
});
440+
}
421441

422442
return `<select id="${this.elementName}" class="ms-filter search-filter" ${this.isMultipleSelect ? 'multiple="multiple"' : ''}>${options}</select>`;
423443
}

src/app/modules/angular-slickgrid/filters/nativeSelectFilter.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,22 @@ export class NativeSelectFilter implements Filter {
106106
const valueName = (this.columnDef.filter.customStructure) ? this.columnDef.filter.customStructure.value : 'value';
107107

108108
let options = '';
109-
optionCollection.forEach((option: any) => {
110-
if (!option || (option[labelName] === undefined && option.labelKey === undefined)) {
111-
throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: model: Filters.select, collection: [ { value: '1', label: 'One' } ]')`);
112-
}
113-
const labelKey = option.labelKey || option[labelName];
114-
const textLabel = ((option.labelKey || this.columnDef.filter.enableTranslateLabel) && this.translate && typeof this.translate.instant === 'function') ? this.translate.instant(labelKey || ' ') : labelKey;
115-
options += `<option value="${option[valueName]}">${textLabel}</option>`;
116-
});
109+
110+
// collection could be an Array of Strings OR Objects
111+
if (optionCollection.every(x => typeof x === 'string')) {
112+
optionCollection.forEach((option: string) => {
113+
options += `<option value="${option}" label="${option}">${option}</option>`;
114+
});
115+
} else {
116+
optionCollection.forEach((option: any) => {
117+
if (!option || (option[labelName] === undefined && option.labelKey === undefined)) {
118+
throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: model: Filters.select, collection: [ { value: '1', label: 'One' } ]')`);
119+
}
120+
const labelKey = option.labelKey || option[labelName];
121+
const textLabel = ((option.labelKey || this.columnDef.filter.enableTranslateLabel) && this.translate && typeof this.translate.instant === 'function') ? this.translate.instant(labelKey || ' ') : labelKey;
122+
options += `<option value="${option[valueName]}">${textLabel}</option>`;
123+
});
124+
}
117125
return `<select class="form-control search-filter">${options}</select>`;
118126
}
119127

src/app/modules/angular-slickgrid/filters/selectFilter.ts

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -319,44 +319,58 @@ export class SelectFilter implements Filter {
319319
const isRenderHtmlEnabled = this.columnFilter && this.columnFilter.enableRenderHtml || false;
320320
const sanitizedOptions = this.gridOptions && this.gridOptions.sanitizeHtmlOptions || {};
321321

322-
optionCollection.forEach((option: SelectOption) => {
323-
if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) {
324-
throw new Error(`[select-filter] A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: model: Filters.multipleSelect, collection: [ { value: '1', label: 'One' } ]')`);
325-
}
326-
const labelKey = (option.labelKey || option[this.labelName]) as string;
327-
const selected = (searchTerms.findIndex((term) => term === option[this.valueName]) >= 0) ? 'selected' : '';
328-
const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey) ? this.translate.instant(labelKey || ' ') : labelKey;
329-
let prefixText = option[this.labelPrefixName] || '';
330-
let suffixText = option[this.labelSuffixName] || '';
331-
let optionLabel = option[this.optionLabel] || '';
332-
optionLabel = optionLabel.toString().replace(/\"/g, '\''); // replace double quotes by single quotes to avoid interfering with regular html
333-
334-
// also translate prefix/suffix if enableTranslateLabel is true and text is a string
335-
prefixText = (this.enableTranslateLabel && prefixText && typeof prefixText === 'string') ? this.translate.instant(prefixText || ' ') : prefixText;
336-
suffixText = (this.enableTranslateLabel && suffixText && typeof suffixText === 'string') ? this.translate.instant(suffixText || ' ') : suffixText;
337-
optionLabel = (this.enableTranslateLabel && optionLabel && typeof optionLabel === 'string') ? this.translate.instant(optionLabel || ' ') : optionLabel;
338-
339-
// add to a temp array for joining purpose and filter out empty text
340-
const tmpOptionArray = [prefixText, labelText, suffixText].filter((text) => text);
341-
let optionText = tmpOptionArray.join(separatorBetweenLabels);
342-
343-
// if user specifically wants to render html text, he needs to opt-in else it will stripped out by default
344-
// also, the 3rd party lib will saninitze any html code unless it's encoded, so we'll do that
345-
if (isRenderHtmlEnabled) {
346-
// sanitize any unauthorized html tags like script and others
347-
// for the remaining allowed tags we'll permit all attributes
348-
const sanitizedText = DOMPurify.sanitize(optionText, sanitizedOptions);
349-
optionText = htmlEncode(sanitizedText);
350-
}
322+
// collection could be an Array of Strings OR Objects
323+
if (optionCollection.every(x => typeof x === 'string')) {
324+
optionCollection.forEach((option: string) => {
325+
const selected = (searchTerms.findIndex((term) => term === option) >= 0) ? 'selected' : '';
326+
options += `<option value="${option}" label="${option}" ${selected}>${option}</option>`;
327+
328+
// if there's at least 1 search term found, we will add the "filled" class for styling purposes
329+
if (selected) {
330+
this.isFilled = true;
331+
}
332+
});
333+
} else {
334+
// array of objects will require a label/value pair unless a customStructure is passed
335+
optionCollection.forEach((option: SelectOption) => {
336+
if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) {
337+
throw new Error(`[select-filter] A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: model: Filters.multipleSelect, collection: [ { value: '1', label: 'One' } ]')`);
338+
}
339+
const labelKey = (option.labelKey || option[this.labelName]) as string;
340+
const selected = (searchTerms.findIndex((term) => term === option[this.valueName]) >= 0) ? 'selected' : '';
341+
const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey) ? this.translate.instant(labelKey || ' ') : labelKey;
342+
let prefixText = option[this.labelPrefixName] || '';
343+
let suffixText = option[this.labelSuffixName] || '';
344+
let optionLabel = option[this.optionLabel] || '';
345+
optionLabel = optionLabel.toString().replace(/\"/g, '\''); // replace double quotes by single quotes to avoid interfering with regular html
346+
347+
// also translate prefix/suffix if enableTranslateLabel is true and text is a string
348+
prefixText = (this.enableTranslateLabel && prefixText && typeof prefixText === 'string') ? this.translate.instant(prefixText || ' ') : prefixText;
349+
suffixText = (this.enableTranslateLabel && suffixText && typeof suffixText === 'string') ? this.translate.instant(suffixText || ' ') : suffixText;
350+
optionLabel = (this.enableTranslateLabel && optionLabel && typeof optionLabel === 'string') ? this.translate.instant(optionLabel || ' ') : optionLabel;
351+
352+
// add to a temp array for joining purpose and filter out empty text
353+
const tmpOptionArray = [prefixText, labelText, suffixText].filter((text) => text);
354+
let optionText = tmpOptionArray.join(separatorBetweenLabels);
355+
356+
// if user specifically wants to render html text, he needs to opt-in else it will stripped out by default
357+
// also, the 3rd party lib will saninitze any html code unless it's encoded, so we'll do that
358+
if (isRenderHtmlEnabled) {
359+
// sanitize any unauthorized html tags like script and others
360+
// for the remaining allowed tags we'll permit all attributes
361+
const sanitizedText = DOMPurify.sanitize(optionText, sanitizedOptions);
362+
optionText = htmlEncode(sanitizedText);
363+
}
351364

352-
// html text of each select option
353-
options += `<option value="${option[this.valueName]}" label="${optionLabel}" ${selected}>${optionText}</option>`;
365+
// html text of each select option
366+
options += `<option value="${option[this.valueName]}" label="${optionLabel}" ${selected}>${optionText}</option>`;
354367

355-
// if there's a search term, we will add the "filled" class for styling purposes
356-
if (selected) {
357-
this.isFilled = true;
358-
}
359-
});
368+
// if there's at least 1 search term found, we will add the "filled" class for styling purposes
369+
if (selected) {
370+
this.isFilled = true;
371+
}
372+
});
373+
}
360374

361375
return `<select class="ms-filter search-filter" ${this.isMultipleSelect ? 'multiple="multiple"' : ''}>${options}</select>`;
362376
}

src/app/modules/angular-slickgrid/formatters/collectionEditorFormatter.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@ export const collectionEditorFormatter: Formatter = (row: number, cell: number,
1616
const valueName = (internalColumnEditor.customStructure) ? internalColumnEditor.customStructure.value : 'value';
1717

1818
if (Array.isArray(value)) {
19-
return arrayToCsvFormatter(row,
20-
cell,
21-
value.map((v: any) => findOrDefault(collection, (c: any) => c[valueName] === v)[labelName]),
22-
columnDef,
23-
dataContext);
19+
if (collection.every(x => typeof x === 'string')) {
20+
return arrayToCsvFormatter(row,
21+
cell,
22+
value.map((v: any) => findOrDefault(collection, (c: any) => c === v)),
23+
columnDef,
24+
dataContext);
25+
} else {
26+
return arrayToCsvFormatter(row,
27+
cell,
28+
value.map((v: any) => findOrDefault(collection, (c: any) => c[valueName] === v)[labelName]),
29+
columnDef,
30+
dataContext);
31+
}
2432
}
2533

2634
return findOrDefault(collection, (c: any) => c[valueName] === value)[labelName] || '';

src/app/modules/angular-slickgrid/models/columnEditor.interface.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export interface ColumnEditor {
1212
/** A collection of items/options that will be loaded asynchronously (commonly used with a Select/Multi-Select Editor) */
1313
collectionAsync?: Promise<any> | Observable<any>;
1414

15-
/** A collection of items/options (commonly used with a Select/Multi-Select Editor) */
15+
/**
16+
* A collection of items/options (commonly used with a Select/Multi-Select Editor)
17+
* It can be a collection of string or label/value pair (the pair can be customized via the "customStructure" option)
18+
*/
1619
collection?: any[];
1720

1821
/** We could filter some 1 or more items from the collection */

src/app/modules/angular-slickgrid/models/columnFilter.interface.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ export interface ColumnFilter {
4444
/** A collection of items/options that will be loaded asynchronously (commonly used with a Select/Multi-Select Filter) */
4545
collectionAsync?: Promise<any> | Observable<any> | Subject<any>;
4646

47-
/** A collection of items/options (commonly used with a Select/Multi-Select Filter) */
47+
/**
48+
* A collection of items/options (commonly used with a Select/Multi-Select Filter)
49+
* It can be a collection of string or label/value pair (the pair can be customized via the "customStructure" option)
50+
*/
4851
collection?: any[];
4952

5053
/** Options to change the behavior of the "collection" */

0 commit comments

Comments
 (0)