Skip to content

Commit 16379f8

Browse files
authored
Merge pull request #8 from ghiscoding/feat/use-select-option-label
feat: add new `useSelectOptionLabel` option
2 parents 931525c + 145e955 commit 16379f8

15 files changed

+253
-71
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ New Multiple-Select Options:
4040
- `autoAdjustDropHeight` will automatically adjust the drop (up or down) height with available space
4141
- `autoAdjustDropPosition` will automatically choose best position (top/bottom) with available space
4242
- `autoAdjustDropWidthByTextSize` automatically set the drop width size from the widest list option width
43+
- `useSelectOptionLabel` will use the `<option label="">` (from select option value) that can be used to display shorter selected option values.
44+
- example: value "1,3" instead of "January,March"
45+
- `useSelectOptionLabelToHtml` similar to `useSelectOptionLabel` but also renders HTML.
4346

4447
## Contributions
4548

demo/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
},
2626
"devDependencies": {
2727
"multiple-select-vanilla": "workspace:*",
28-
"sass": "^1.58.0",
28+
"sass": "^1.58.3",
2929
"typescript": "^4.9.5",
30-
"vite": "^4.1.1"
30+
"vite": "^4.1.2"
3131
}
3232
}

demo/src/app-routing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import Options27 from './options/options27';
4040
import Options28 from './options/options28';
4141
import Options29 from './options/options29';
4242
import Options30 from './options/options30';
43+
import Options31 from './options/options31';
4344
import Methods01 from './methods/methods01';
4445
import Methods02 from './methods/methods02';
4546
import Methods03 from './methods/methods03';
@@ -109,6 +110,7 @@ export const exampleRouting = [
109110
{ name: 'options28', view: '/src/options/options28.html', viewModel: Options28, title: 'Label Template' },
110111
{ name: 'options29', view: '/src/options/options29.html', viewModel: Options29, title: 'Auto-Adjust Drop Position' },
111112
{ name: 'options30', view: '/src/options/options30.html', viewModel: Options30, title: 'Auto-Adjust Drop Height/Width' },
113+
{ name: 'options31', view: '/src/options/options31.html', viewModel: Options31, title: 'Use Select Option as Label' },
112114
],
113115
},
114116
{

demo/src/options/options30.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ <h2 class="bd-title">
3030
<label class="col-sm-3 text-end">Adjust drop width by option list content</label>
3131

3232
<div class="col-sm-9">
33-
<select id="select1" multiple="multiple" data-width="75" class="my-container">
33+
<select id="select1" multiple="multiple" data-width="75">
3434
<option value="1">January</option>
3535
<option value="2">February</option>
3636
<option value="3">March</option>
@@ -51,7 +51,7 @@ <h2 class="bd-title">
5151
<label class="col-sm-3 text-end">Resize drop height (width: 200px)</label>
5252

5353
<div class="col-sm-9">
54-
<select id="select2" multiple="multiple" data-width="200" class="my-container">
54+
<select id="select2" multiple="multiple" data-width="200">
5555
<option value="1">January</option>
5656
<option value="2">February</option>
5757
<option value="3">March</option>

demo/src/options/options30.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { multipleSelect, MultipleSelectInstance } from 'multiple-select-vanilla'
33
export default class Example {
44
ms1?: MultipleSelectInstance;
55
ms2?: MultipleSelectInstance;
6+
ms3?: MultipleSelectInstance;
67

78
mount() {
89
this.ms1 = multipleSelect('#select1', {
@@ -12,14 +13,14 @@ export default class Example {
1213
showOkButton: true,
1314
}) as MultipleSelectInstance;
1415

15-
this.ms1 = multipleSelect('#select2', {
16+
this.ms2 = multipleSelect('#select2', {
1617
autoAdjustDropWidthByTextSize: true,
1718
autoAdjustDropHeight: true,
1819
position: 'top',
1920
showOkButton: true,
2021
}) as MultipleSelectInstance;
2122

22-
this.ms2 = multipleSelect('#select3', {
23+
this.ms3 = multipleSelect('#select3', {
2324
autoAdjustDropWidthByTextSize: true,
2425
autoAdjustDropHeight: true,
2526
filter: true,
@@ -31,7 +32,9 @@ export default class Example {
3132
// destroy ms instance(s) to avoid DOM leaks
3233
this.ms1?.destroy();
3334
this.ms2?.destroy();
35+
this.ms3?.destroy();
3436
this.ms1 = undefined;
3537
this.ms2 = undefined;
38+
this.ms3 = undefined;
3639
}
3740
}

demo/src/options/options31.html

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<div class="row mb-2">
2+
<div class="col-md-12 title-desc">
3+
<h2 class="bd-title">
4+
Use Select Option as Label
5+
<span class="float-end links">
6+
Code <span class="fa fa-link"></span>
7+
<span class="small">
8+
<a
9+
target="_blank"
10+
href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/demo/src/options/options08.html"
11+
>html</a
12+
>
13+
|
14+
<a target="_blank" href="https://github.com/ghiscoding/multiple-select-vanilla/blob/main/demo/src/options/options08.ts"
15+
>ts</a
16+
>
17+
</span>
18+
</span>
19+
</h2>
20+
<div class="demo-subtitle">
21+
Use <code>useSelectOptionLabel</code> to display select option value as
22+
<code>&lt;option label=""&gt;&lt;/option&gt;</code> to display shorter text as the selected values in the parent select.
23+
<br />
24+
Use <code>useSelectOptionLabelToHtml</code> which is the same as "useSelectOptionLabel" but will also render html option
25+
values.
26+
</div>
27+
</div>
28+
</div>
29+
30+
<div>
31+
<div class="mb-3 row">
32+
<label class="col-sm-3 text-end">Use Select Option Label</label>
33+
34+
<div class="col-sm-9">
35+
<select id="select1" multiple="multiple" data-width="150">
36+
<option value="1">January</option>
37+
<option value="2">February</option>
38+
<option value="3" selected>March</option>
39+
<option value="4">April</option>
40+
<option value="5">May</option>
41+
<option value="6" selected>June</option>
42+
<option value="7">July</option>
43+
<option value="8">August</option>
44+
<option value="9">September</option>
45+
<option value="10">October</option>
46+
<option value="11">November</option>
47+
<option value="12">December</option>
48+
</select>
49+
</div>
50+
</div>
51+
52+
<div class="mb-3 row">
53+
<label class="col-sm-3 text-end">Use Select Option Label & Render HTML</label>
54+
55+
<div class="col-sm-9">
56+
<select id="select2" multiple="multiple" data-width="150"></select>
57+
</div>
58+
</div>
59+
</div>

demo/src/options/options31.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { multipleSelect, MultipleSelectInstance } from 'multiple-select-vanilla';
2+
3+
export default class Example {
4+
ms1?: MultipleSelectInstance;
5+
ms2?: MultipleSelectInstance;
6+
7+
mount() {
8+
this.ms1 = multipleSelect('#select1', {
9+
useSelectOptionLabel: true,
10+
}) as MultipleSelectInstance;
11+
12+
this.ms2 = multipleSelect('#select2', {
13+
useSelectOptionLabelToHtml: true,
14+
data: [
15+
{
16+
text: '<i class="fa fa-star"></i> January',
17+
value: '<i class="fa fa-star"></i>1',
18+
selected: true,
19+
},
20+
{
21+
text: 'February',
22+
value: '2',
23+
},
24+
{
25+
text: 'March',
26+
value: 3,
27+
},
28+
{
29+
text: 'April',
30+
value: 4,
31+
},
32+
{
33+
text: 'May',
34+
value: 5,
35+
},
36+
{
37+
text: 'June',
38+
value: 6,
39+
},
40+
{
41+
text: 'July',
42+
value: 7,
43+
},
44+
{
45+
text: 'August',
46+
value: 8,
47+
},
48+
{
49+
text: 'September',
50+
value: 9,
51+
},
52+
{
53+
text: 'October',
54+
value: 10,
55+
},
56+
{
57+
text: 'November',
58+
value: 11,
59+
},
60+
{
61+
text: 'December',
62+
value: 12,
63+
},
64+
],
65+
}) as MultipleSelectInstance;
66+
67+
// setTimeout(() => {
68+
// this.ms2?.setSelects(['<i class="fa fa-star"></i>1', 2, 3]);
69+
// }, 2000);
70+
}
71+
72+
unmount() {
73+
// destroy ms instance(s) to avoid DOM leaks
74+
this.ms1?.destroy();
75+
this.ms2?.destroy();
76+
this.ms1 = undefined;
77+
this.ms2 = undefined;
78+
}
79+
}

lib/build-watch.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const env = process.env.NODE_ENV;
1111
runCompilation(process.env.LERNA_FILE_CHANGES.split(','));
1212

1313
function runBuild() {
14+
const startTime = new Date().getTime();
1415
buildSync({
1516
color: true,
1617
entryPoints: ['./src/index.ts'],
@@ -23,6 +24,8 @@ function runBuild() {
2324
// outfile: env === 'production' ? './dist/multiple-select.min.js' : './dist/multiple-select.js',
2425
outfile: 'dist/esm/multiple-select.js',
2526
});
27+
const endTime = new Date().getTime();
28+
console.info(`⚡️ Built in ${endTime - startTime}ms`);
2629
}
2730

2831
async function runCompilation(changedFiles) {

lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"npm-run-all2": "^6.0.4",
6363
"postcss": "^8.4.21",
6464
"postcss-cli": "^10.1.0",
65-
"sass": "^1.58.1",
65+
"sass": "^1.58.3",
6666
"typescript": "^4.9.5"
6767
}
6868
}

lib/src/MultipleSelectInstance.ts

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @author zhixin wen <[email protected]>
33
*/
44
import Constants from './constants';
5-
import { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys } from './utils';
5+
import { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys, stripScripts } from './utils';
66
import {
77
calculateAvailableSpace,
88
createDomElement,
@@ -53,7 +53,7 @@ export class MultipleSelectInstance {
5353
protected elm: HTMLSelectElement,
5454
options?: Partial<Omit<MultipleSelectOption, 'onHardDestroy' | 'onAfterHardDestroy'>>
5555
) {
56-
this.options = Object.assign({}, Constants.DEFAULTS, this.elm.dataset, options);
56+
this.options = Object.assign({}, Constants.DEFAULTS, this.elm.dataset, options) as MultipleSelectOption;
5757
this._bindEventService = new BindingEventService({ distinctEvent: true });
5858
}
5959

@@ -534,7 +534,7 @@ export class MultipleSelectInstance {
534534
<li class="${multiple} ${classes}" ${title} ${style}>
535535
<label class="${row.disabled ? 'disabled' : ''}">
536536
<input type="${type}"
537-
value="${row.value}"
537+
value="${encodeURI(row.value)}"
538538
data-key="${row._key}"
539539
${this.selectItemName}
540540
${row.selected ? ' checked="checked"' : ''}
@@ -879,32 +879,51 @@ export class MultipleSelectInstance {
879879
textSelects = valueSelects;
880880
}
881881

882-
const spanElm = this.choiceElm?.querySelector('span') as HTMLSpanElement;
882+
const spanElm = this.choiceElm?.querySelector<HTMLSpanElement>('span');
883883
const sl = valueSelects.length;
884884
let html = '';
885885

886-
if (sl === 0) {
887-
spanElm.classList.add('ms-placeholder');
888-
spanElm.innerHTML = this.options.placeholder || '';
889-
} else if (sl < this.options.minimumCountSelected) {
890-
html = textSelects.join(this.options.displayDelimiter);
891-
} else if (this.options.formatAllSelected() && sl === this.dataTotal) {
892-
html = this.options.formatAllSelected();
893-
} else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {
894-
html = `${textSelects.slice(0, this.options.minimumCountSelected).join(this.options.displayDelimiter)}...`;
895-
} else if (this.options.formatCountSelected(sl, this.dataTotal) && sl > this.options.minimumCountSelected) {
896-
html = this.options.formatCountSelected(sl, this.dataTotal);
897-
} else {
898-
html = textSelects.join(this.options.displayDelimiter);
899-
}
886+
const getSelectOptionHtml = () => {
887+
if (this.options.useSelectOptionLabel || this.options.useSelectOptionLabelToHtml) {
888+
const labels = valueSelects.join(this.options.delimiter);
889+
return this.options.useSelectOptionLabelToHtml ? stripScripts(labels) : labels;
890+
} else {
891+
return textSelects.join(this.options.displayDelimiter);
892+
}
893+
};
900894

901-
if (html) {
902-
spanElm?.classList.remove('ms-placeholder');
903-
spanElm.innerHTML = html;
904-
}
895+
if (spanElm) {
896+
if (sl === 0) {
897+
spanElm.classList.add('ms-placeholder');
898+
spanElm.innerHTML = this.options.placeholder || '';
899+
} else if (sl < this.options.minimumCountSelected) {
900+
html = getSelectOptionHtml();
901+
} else if (this.options.formatAllSelected() && sl === this.dataTotal) {
902+
html = this.options.formatAllSelected();
903+
} else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {
904+
html = `${textSelects.slice(0, this.options.minimumCountSelected).join(this.options.displayDelimiter)}...`;
905+
} else if (this.options.formatCountSelected(sl, this.dataTotal) && sl > this.options.minimumCountSelected) {
906+
html = this.options.formatCountSelected(sl, this.dataTotal);
907+
} else {
908+
html = getSelectOptionHtml();
909+
}
905910

906-
if (this.options.displayTitle) {
907-
spanElm.title = this.getSelects('text').join('');
911+
if (html) {
912+
spanElm?.classList.remove('ms-placeholder');
913+
if (this.options.useSelectOptionLabelToHtml) {
914+
spanElm.innerHTML = html;
915+
} else {
916+
spanElm.textContent = html;
917+
}
918+
}
919+
920+
if (this.options.displayTitle || this.options.addTitle) {
921+
if (this.options.addTitle) {
922+
console.warn('[Multiple-Select-Vanilla] Please note that the `addTitle` option was replaced with `displayTitle`.');
923+
}
924+
const selectType = this.options.useSelectOptionLabel || this.options.useSelectOptionLabelToHtml ? 'value' : 'text';
925+
spanElm.title = this.getSelects(selectType).join('');
926+
}
908927
}
909928

910929
// set selects to select
@@ -1303,10 +1322,16 @@ export class MultipleSelectInstance {
13031322
}
13041323

13051324
// calculate the "Select All" element width, this text is configurable which is why we recalculate every time
1306-
const selectAllElm = this.dropElm.querySelector('.ms-select-all span') as HTMLSpanElement;
1325+
const selectAllSpanElm = this.dropElm.querySelector('.ms-select-all span') as HTMLSpanElement;
13071326
const dropUlElm = this.dropElm.querySelector('ul') as HTMLUListElement;
13081327

1309-
const selectAllElmWidth = selectAllElm.clientWidth + this.options.selectSidePadding;
1328+
let liPadding = 0;
1329+
const firstLiElm = this.dropElm.querySelector('li'); // get padding of 1st <li> element
1330+
if (firstLiElm) {
1331+
const { paddingLeft, paddingRight } = window.getComputedStyle(firstLiElm);
1332+
liPadding = parseFloat(paddingLeft) + parseFloat(paddingRight);
1333+
}
1334+
const selectAllElmWidth = selectAllSpanElm.clientWidth + liPadding;
13101335
const hasScrollbar = dropUlElm.scrollHeight > dropUlElm.clientHeight;
13111336
const scrollbarWidth = hasScrollbar ? this.getScrollbarWidth() : 0;
13121337
let contentWidth = 0;
@@ -1318,7 +1343,7 @@ export class MultipleSelectInstance {
13181343
});
13191344

13201345
// add a padding & include the browser scrollbar width
1321-
contentWidth += this.options.selectSidePadding + scrollbarWidth;
1346+
contentWidth += liPadding + scrollbarWidth;
13221347

13231348
// make sure the new calculated width is wide enough to include the "Select All" text (this text is configurable)
13241349
if (contentWidth < selectAllElmWidth) {

0 commit comments

Comments
 (0)