select

Better select widgets in vanilla javascript.  https://p.ce9e.org/select/demo/
git clone https://git.ce9e.org/select.git

commit
44219e6162dbed1af2234f1e2b32cfd5f5e9f661
parent
06ea884731603764305af8bcb73ba32bf057bfc2
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2022-06-17 11:47
add tags.js

Diffstat

M demo/index.html 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A tags.js 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 184 insertions, 0 deletions


diff --git a/demo/index.html b/demo/index.html

@@ -271,6 +271,74 @@
  271   271 		</optgroup>
  272   272 	</datalist>
  273   273 
   -1   274 	<h2>Tags</h2>
   -1   275 
   -1   276 	<p>The separate file <code>tags.js</code> provides a matching component that allows to enter arbitrary text instead of only the provided options. It combines multi-select with native auto-complete.</p>
   -1   277 
   -1   278 	<select multiple data-tags>
   -1   279 		<optgroup label="Alaskan/Hawaiian Time Zone">
   -1   280 			<option value="AK">Alaska</option>
   -1   281 			<option value="HI">Hawaii</option>
   -1   282 		</optgroup>
   -1   283 		<optgroup label="Pacific Time Zone">
   -1   284 			<option value="CA">California</option>
   -1   285 			<option value="NV">Nevada</option>
   -1   286 			<option value="OR">Oregon</option>
   -1   287 			<option value="WA">Washington</option>
   -1   288 		</optgroup>
   -1   289 		<optgroup label="Mountain Time Zone">
   -1   290 			<option value="AZ">Arizona</option>
   -1   291 			<option value="CO">Colorado</option>
   -1   292 			<option value="ID">Idaho</option>
   -1   293 			<option value="MT">Montana</option>
   -1   294 			<option value="NE">Nebraska</option>
   -1   295 			<option value="NM">New Mexico</option>
   -1   296 			<option value="ND">North Dakota</option>
   -1   297 			<option value="UT">Utah</option>
   -1   298 			<option value="WY">Wyoming</option>
   -1   299 		</optgroup>
   -1   300 		<optgroup label="Central Time Zone">
   -1   301 			<option value="AL">Alabama</option>
   -1   302 			<option value="AR">Arkansas</option>
   -1   303 			<option value="IL">Illinois</option>
   -1   304 			<option value="IA">Iowa</option>
   -1   305 			<option value="KS">Kansas</option>
   -1   306 			<option value="KY">Kentucky</option>
   -1   307 			<option value="LA">Louisiana</option>
   -1   308 			<option value="MN">Minnesota</option>
   -1   309 			<option value="MS">Mississippi</option>
   -1   310 			<option value="MO">Missouri</option>
   -1   311 			<option value="OK">Oklahoma</option>
   -1   312 			<option value="SD">South Dakota</option>
   -1   313 			<option value="TX">Texas</option>
   -1   314 			<option value="TN">Tennessee</option>
   -1   315 			<option value="WI">Wisconsin</option>
   -1   316 		</optgroup>
   -1   317 		<optgroup label="Eastern Time Zone">
   -1   318 			<option value="CT">Connecticut</option>
   -1   319 			<option value="DE">Delaware</option>
   -1   320 			<option value="FL">Florida</option>
   -1   321 			<option value="GA">Georgia</option>
   -1   322 			<option value="IN">Indiana</option>
   -1   323 			<option value="ME">Maine</option>
   -1   324 			<option value="MD">Maryland</option>
   -1   325 			<option value="MA">Massachusetts</option>
   -1   326 			<option value="MI">Michigan</option>
   -1   327 			<option value="NH">New Hampshire</option>
   -1   328 			<option value="NJ">New Jersey</option>
   -1   329 			<option value="NY">New York</option>
   -1   330 			<option value="NC">North Carolina</option>
   -1   331 			<option value="OH">Ohio</option>
   -1   332 			<option value="PA">Pennsylvania</option>
   -1   333 			<option value="RI">Rhode Island</option>
   -1   334 			<option value="SC">South Carolina</option>
   -1   335 			<option value="VT">Vermont</option>
   -1   336 			<option value="VA">Virginia</option>
   -1   337 			<option value="WV">West Virginia</option>
   -1   338 		</optgroup>
   -1   339 	</select>
   -1   340 
  274   341 	<script src="../select.js" type="module"></script>
   -1   342 	<script src="../tags.js" type="module"></script>
  275   343 </body>
  276   344 </html>

diff --git a/tags.js b/tags.js

@@ -0,0 +1,116 @@
   -1     1 var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
   -1     2 
   -1     3 var KEYS = {
   -1     4 	BACKSPACE: 8,
   -1     5 	ENTER: 13,
   -1     6 };
   -1     7 
   -1     8 var randomString = function(length) {
   -1     9 	var result = [];
   -1    10 	for (var i = 0; i < length; i++) {
   -1    11 		var k = Math.floor(Math.random() * chars.length);
   -1    12 		result.push(chars[k]);
   -1    13 	}
   -1    14 	return result.join('');
   -1    15 };
   -1    16 
   -1    17 export class TagInput {
   -1    18 	constructor(id, original) {
   -1    19 		this.id = id;
   -1    20 		this.original = original;
   -1    21 
   -1    22 		this.createElements();
   -1    23 		original.hidden = true;
   -1    24 		original.before(this.wrapper);
   -1    25 	}
   -1    26 
   -1    27 	createElements() {
   -1    28 		this.wrapper = document.createElement('div');
   -1    29 
   -1    30 		this.values = document.createElement('ul');
   -1    31 		this.values.className = 'select__values';
   -1    32 		this.values.setAttribute('aria-live', 'polite');
   -1    33 		this.wrapper.append(this.values);
   -1    34 
   -1    35 		this.input = document.createElement('input');
   -1    36 		this.input.className = 'form-control';
   -1    37 		this.wrapper.append(this.input);
   -1    38 
   -1    39 		this.datalist = document.createElement('datalist');
   -1    40 		this.datalist.innerHTML = this.original.innerHTML;
   -1    41 		this.datalist.id = this.id + '-list';
   -1    42 		this.input.setAttribute('list', this.datalist.id);
   -1    43 		this.wrapper.append(this.datalist);
   -1    44 
   -1    45 		this.input.disabled = this.original.disabled;
   -1    46 
   -1    47 		this.input.onkeydown = this.onkeydown.bind(this);
   -1    48 		this.input.onchange = this.onchange.bind(this);
   -1    49 
   -1    50 		this.updateValue();
   -1    51 	}
   -1    52 
   -1    53 	setValue(value) {
   -1    54 		var option = Array.from(this.original.options).find(op => op.value === value);
   -1    55 		if (!option) {
   -1    56 			option = document.createElement('option');
   -1    57 			option.setAttribute('data-tag-custom', '');
   -1    58 			option.value = value;
   -1    59 			option.label = value;
   -1    60 			this.original.append(option);
   -1    61 		}
   -1    62 		option.selected = true;
   -1    63 		this.original.dispatchEvent(new Event('change'));
   -1    64 		this.updateValue();
   -1    65 	}
   -1    66 
   -1    67 	onkeydown(event) {
   -1    68 		if (event.keyCode === KEYS.BACKSPACE) {
   -1    69 			if (!this.input.value) {
   -1    70 				event.preventDefault();
   -1    71 				var n = this.original.selectedOptions.length;
   -1    72 				if (n) {
   -1    73 					var op = this.original.selectedOptions[n - 1];
   -1    74 					op.selected = false;
   -1    75 					this.updateValue();
   -1    76 					this.input.value = op.value;
   -1    77 				}
   -1    78 			}
   -1    79 		} else if (event.keyCode === KEYS.ENTER) {
   -1    80 			if (this.input.value) {
   -1    81 				this.onchange(event);
   -1    82 			}
   -1    83 		}
   -1    84 	}
   -1    85 
   -1    86 	onchange(event) {
   -1    87 		if (this.input.value) {
   -1    88 			event.preventDefault();
   -1    89 			this.setValue(this.input.value);
   -1    90 		}
   -1    91 	}
   -1    92 
   -1    93 	updateValue() {
   -1    94 		this.input.value = '';
   -1    95 		this.values.innerHTML = '';
   -1    96 		Array.from(this.original.options).forEach((op, i) => {
   -1    97 			if (op.selected) {
   -1    98 				var li = document.createElement('li');
   -1    99 				li.textContent = op.label;
   -1   100 				li.className = 'badge bg-secondary me-1';
   -1   101 				li.onclick = () => {
   -1   102 					op.selected = false;
   -1   103 					li.remove();
   -1   104 					this.input.focus();
   -1   105 				};
   -1   106 				this.values.append(li);
   -1   107 			} else if (op.hasAttribute('data-tag-custom')) {
   -1   108 				op.remove();
   -1   109 			}
   -1   110 		});
   -1   111 	}
   -1   112 }
   -1   113 
   -1   114 Array.from(document.querySelectorAll('[data-tags]')).forEach(el => {
   -1   115 	new TagInput(randomString(), el);
   -1   116 });