-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.html
315 lines (309 loc) · 16 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<meta content="#000" name="theme-color">
<meta content="width=device-width" name="viewport">
<meta content="Better tags input interaction with JavaScript." name="description">
<title>Tag Picker 4.1.0</title>
<link href="../layout/index.min.css" rel="stylesheet">
<link href="index.min.css" rel="stylesheet">
<style>
body {
margin-left: auto;
margin-right: auto;
max-width: 60rem;
}
.tag-picker,
.tag-picker__tag,
.tag-picker__text {
border-width: 2px;
}
.tag-picker {
border-color: #000;
color: #000;
flex: 1;
}
.tag-picker--focus {
border-color: #00f;
}
.tag-picker__tag {
background: #f00;
border-color: transparent;
gap: 0.5em;
padding-left: 0.5em;
padding-right: 0.5em;
}
.tag-picker__tag:focus {
border-color: #000;
outline: 0;
}
.tag-picker__tag--selected,
.tag-picker__tag--selected:focus {
background: #c00;
}
.tag-picker__tags {
gap: 2px;
padding: 2px;
}
.tag-picker__text {
padding-left: 0.5em;
padding-right: 0.5em;
}
.tag-picker__x {
color: #fff;
}
</style>
</head>
<body>
<p role="alert">Do you like this project? Please support my <a href="https://github.com/mecha-cms">Mecha CMS</a> project too. Thank you!</p>
<header>
<hgroup>
<h1>Tag Picker 4.1.0</h1>
<p>Tag Picker is a JavaScript application designed to simplify the management of comma-separated lists of words, offering a minimalist interface for an enhanced user experience. Users can easily add tags by typing a word and pressing the <kbd>,</kbd> key (by default), or remove tags with a quick click (or by using common keys such as the <kbd>Backspace</kbd> or <kbd>Delete</kbd> keys). Developers can easily integrate and customize this application styles to match the design of their web project.</p>
</hgroup>
<p>It supports native keyboard interaction as if it were plain text. You should be able to copy, cut, and paste from/to the mask. Click a tag to select it, press the <kbd>Control</kbd> key while clicking the tag to select multiple tags. You can also select the tags using the combination of <kbd>Shift</kbd> and <kbd>ArrowLeft</kbd> or <kbd>ArrowRight</kbd> keys.</p>
</header>
<main>
<form method="get" target="_blank">
<p role="group"><input id="tags" name="tags" placeholder="foo, bar, baz" type="text" value="foo, bar"> <button type="submit">Submit</button> <button type="reset">Reset</button></p>
</form>
<h2 id="usage"><a href="#usage">#</a> Usage</h2>
<div role="note">
<p><strong>Note:</strong> CSS variables have been removed since version 4.0.0. From now on, I will only provide a minimal style just to make viable interaction. Expect it to look ugly by default. The main goal of this application is to be able to integrate it seamlessly into your web design. By providing a minimal style, it is easy to add other styles to it to adapt it to your own web design. Have a look at the source code on this page to see how I adapted the style to resemble the default style of the previous version.</p>
</div>
<h3>Browser</h3>
<pre><code><!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<link href="<a href="index.min.css" target="_blank">./index.min.css</a>" rel="stylesheet">
<style>
.tag-picker {
width: 100%;
}
</style>
</head>
<body>
<p>
<input type="text">
</p>
<script src="<a href="index.min.js" target="_blank">./index.min.js</a>"></script>
<script>
const picker = new TagPicker(document.querySelector('input'));
</script>
</body>
</html></code></pre>
<h3>Node.js</h3>
<p>Functions and methods in this application are mostly native JavaScript and are intended for use by the browser. Node.js doesn’t know about the DOM, so this kind of practice will probably be used more often to build new browser packages than to be used directly in the Node.js server.</p>
<h4>CommonJS</h4>
<pre><code>const TagPicker = require('@taufik-nurrohman/tag-picker').default;
const picker = new TagPicker(document.querySelector('input'));</code></pre>
<h4>ECMAScript</h4>
<pre><code>import TagPicker from '@taufik-nurrohman/tag-picker';
const picker = new TagPicker(document.querySelector('input'));</code></pre>
<h2 id="tests"><a href="#tests">#</a> Tests</h2>
<ul>
<li><a href="test.html" target="_blank">No Idea?</a></li>
<li><a href="test/attach,detach.html" target="_blank">Attach, Detach</a></li>
<li><a href="test/copy,cut,paste.html" target="_blank">Copy, Cut, and Paste Events</a></li>
<li><a href="test/escape.html" target="_blank">Disabled Characters</a></li>
<li><a href="test/disabled.html" target="_blank">Disabled Input</a></li>
<li><a href="test/filter.html" target="_blank">Filtered Characters</a></li>
<li><a href="test/focus-auto.html" target="_blank">Focus Automatically</a></li>
<li><a href="test/focus.html" target="_blank">Focus States</a></li>
<li><a href="test/form.html" target="_blank">Form Events</a></li>
<li><a href="test/max.html" target="_blank">Limit Maximum Tags</a></li>
<li><a href="test/min.html" target="_blank">Limit Minimum Tags</a></li>
<li><a href="test/read-only.html" target="_blank">Read-Only Input</a></li>
<li><a href="test/required.html" target="_blank">Required Input</a></li>
<li><a href="test/hint.html" target="_blank">Set Input Hint</a></li>
<li><a href="test/value.html" target="_blank">Set Input Value</a></li>
<li><a href="test/not.html" target="_blank">Validate Tag Name</a></li>
</ul>
<h2 id="tweaks"><a href="#tweaks">#</a> Tweaks</h2>
<ul>
<li><a href="tweak/bootstrap5.html" target="_blank">Bootstrap v5.3 Style</a></li>
<li><a href="tweak/focus.hint.html" target="_blank">Hide Input Hint on Focus</a></li>
<li><a href="tweak/blur.html" target="_blank">Insert Tag Name on Blur</a></li>
<li><a href="tweak/invalid.html" target="_blank">Invalid State (Custom)</a></li>
<li><a href="tweak/array.html" target="_blank">Tags as Array</a></li>
<li><a href="tweak/alert.html" target="_blank">Warn for Duplicate Tags with Alert Message</a></li>
<li><a href="tweak/description.html" target="_blank">Warn for Duplicate Tags with Description Below the Input</a></li>
<li><a href="tweak/target.html" target="_blank">Warn for Duplicate Tags with Flash Highlight</a></li>
<li><a href="tweak/windows98.html" target="_blank">Windows 98 Style</a></li>
</ul>
<h2 id="constructor"><a href="#constructor">#</a> Constructor</h2>
<pre><code>const picker = new TagPicker(self, join = ', ');</code></pre>
<pre><code>const picker = new TagPicker(self, state = {
escape: [','],
join: ', ',
max: Infinity,
min: 0,
pattern: null,
with: []
});</code></pre>
<h2 id="parameters"><a href="#parameters">#</a> Parameters</h2>
<h3><code>self</code></h3>
<p>The <code><input></code> element.</p>
<h3><code>join</code></h3>
<p>The character to join the tags (usually a comma followed by a space).</p>
<h3><code>state</code></h3>
<p>The configuration data.</p>
<h3><code>state.escape</code></h3>
<p>List of characters to trigger the tag insertion. It is <code>[',']</code> by default, which means that you cannot have a <code>','</code> character in the tag name. You can also add <code>'\n'</code> and <code>'\t'</code> to the list so that it will insert a tag when you press the <kbd>Tab</kbd> or <kbd>Enter</kbd> key.</p>
<h3><code>state.join</code></h3>
<p>The character to join the tags (usually a comma followed by a space).</p>
<h3><code>state.max</code></h3>
<p>The maximum number of tags that can be inserted.</p>
<h3><code>state.min</code></h3>
<p>The minimum number of tags that must be inserted to be able to submit the form. The default value of this state is set to <code>0</code>, but if the <code>required</code> attribute is present on the <code><input></code> element, then the default value of this state will be set to <code>1</code>.</p>
<h3><code>state.pattern</code></h3>
<p>If defined, tag insertion will be performed only if tag name is matched with the pattern.</p>
<h3><code>state.with</code></h3>
<p>List of callable functions or objects containing an <code>attach()</code> method to be invoked each time the application is initialized. A very simple “plugin” system.</p>
<h2 id="methods"><a href="#methods">#</a> Methods</h2>
<h3>Instance Methods</h3>
<p>Instance methods are methods available through the results of a <code>TagPicker</code> construct.</p>
<h4><code>picker.attach(self, state)</code></h4>
<p>Re-initializes the application and its extensions after it has been detached.</p>
<pre><code>picker.attach();</code></pre>
<h4><code>picker.blur()</code></h4>
<p>Blurs from the tag picker’s input.</p>
<pre><code>picker.blur();</code></pre>
<h4><code>picker.detach()</code></h4>
<p>Disables the application methods (except for the <code>attach()</code> method) and executes the <code>detach()</code> method of the extensions, if they are present.</p>
<pre><code>picker.detach();</code></pre>
<h4><code>picker.fire(event, data, that)</code></h4>
<p>Fires an event.</p>
<pre><code>picker.fire('change', []);</code></pre>
<h4><code>picker.focus(mode = true)</code></h4>
<p>Focuses to the tag picker’s input. If it has a text, it will select the text as well.</p>
<pre><code>picker.focus(); // Focus and select the text
picker.focus(-1); // Focus and put the caret to the start of the text
picker.focus(+1); // Focus and put the caret to the end of the text</code></pre>
<h4><code>picker.get(name)</code></h4>
<p>Returns the tag position in the list, starting from <code>0</code>.</p>
<pre><code>if (null !== picker.get('foo')) { … }</code></pre>
<h4><code>picker.let(name)</code></h4>
<p>Removes a tag by its name.</p>
<pre><code>picker.let('foo'); // Remove “foo” tag
picker.let(); // Reset to the initial value</code></pre>
<h4><code>picker.off(event, then)</code></h4>
<p>Removes an event.</p>
<pre><code>picker.off('change'); // Remove all events from the `change` event container
picker.off('change', onChange); // Remove `onChange` event from the `change` event container</code></pre>
<h4><code>picker.on(event, then)</code></h4>
<p>Adds a new event.</p>
<pre><code>picker.on('change', function () {
console.log(this.value.split(this.state.join));
});</code></pre>
<pre><code>function onChange() {
console.log(this.value.split(this.state.join));
}
picker.on('change', onChange);</code></pre>
<h4><code>picker.set(name, at = -1)</code></h4>
<p>Adds a new tag with the given name.</p>
<pre><code>picker.set('foo'); // Append “foo” tag
picker.set('bar', 0); // Prepend “bar” tag
picker.set('baz', 2); // Insert “baz” tag at index 2 in the list</code></pre>
<h3>Static Methods</h3>
<p>Static methods are methods available directly on the <code>TagPicker</code> object.</p>
<h4><code>TagPicker.from(self, state)</code></h4>
<p>Creates a new <code>TagPicker</code> instance.</p>
<pre><code>const picker = TagPicker.from(document.querySelector('input'));</code></pre>
<h4><code>TagPicker.of(self)</code></h4>
<p>Gets <code>TagPicker</code> instance of an element.</p>
<pre><code>document.querySelectorAll('input').forEach(self => {
const picker = TagPicker.of(self);
});</code></pre>
<h2 id="properties"><a href="#properties">#</a> Properties</h2>
<h3>Instance Properties</h3>
<p>Instance properties are properties available through the results of a <code>TagPicker</code> construct.</p>
<h4><code>picker.hooks</code></h4>
<p>Returns the events data.</p>
<pre><code>console.log(picker.hooks);</code></pre>
<h4><code>picker.mask</code></h4>
<p>Returns the tag picker’s mask.</p>
<pre><code>picker.mask.classList.add(picker.state.n + '--dark');</code></pre>
<h4><code>picker.self</code></h4>
<p>Returns the <code><input></code> element.</p>
<pre><code>console.log(picker.self.getAttribute('name'));</code></pre>
<h4><code>picker.state</code></h4>
<p>Returns the application states if any.</p>
<pre><code>console.log(picker.state);</code></pre>
<h4><code>picker.text</code></h4>
<p>Gets or sets the current text in the tag picker’s input.</p>
<pre><code>console.log(picker.text);</code></pre>
<pre><code>picker.text = 'foo, bar, baz';</code></pre>
<h4><code>picker.value</code></h4>
<p>Proxy that passes to the <code>picker.self.value</code> property, with additional actions that are executed while the value is being set.</p>
<pre><code>console.log(picker.value);</code></pre>
<pre><code>picker.on('change', function () {
console.log(this.value);
});
picker.value = 'foo, bar, baz';</code></pre>
<h3>Static Properties</h3>
<p>Static properties are properties available directly on the <code>TagPicker</code> object.</p>
<h4><code>TagPicker.state</code></h4>
<p>Returns the default values of <code>picker.state</code>.</p>
<pre><code>const picker = new TagPicker(document.querySelector('input'), {
foo: ['bar', 'baz', 'qux']
});
console.log([TagPicker.state, picker.state]);</code></pre>
<h4><code>TagPicker.version</code></h4>
<p>Returns the application version.</p>
<h2 id="extensions"><a href="#extensions">#</a> Extensions</h2>
<h3>Anatomy of an Extension</h3>
<p>Extension as a function:</p>
<pre><code>function Extension(self, state = {}) {
this.a = 1;
this.b = function () {};
return this;
}
Object.defineProperty(Extension, 'name', {
value: 'Extension'
});</code></pre>
<p>Extension as an object:</p>
<pre><code>const Extension = {
attach: function (self, state = {}) {},
detach: function (self, state = {}) {},
name: 'Extension'
};</code></pre>
<h3>Usage of an Extension</h3>
<p>As a core extension:</p>
<pre><code>TagPicker.state.with.push(Extension);</code></pre>
<p>As an optional extension:</p>
<pre><code>const picker = new TagPicker(document.querySelector('input'), {
with: [Extension]
});</code></pre>
<h3>List of Extensions</h3>
<ul>
<li><a aria-disabled="true">Clear Feature</a></li>
<li><a aria-disabled="true">History Feature</a></li>
<li><a aria-disabled="true">Options Feature</a></li>
<li><a href="../tag-picker.sort/index.html">Sortable Feature</a></li>
</ul>
<h2 id="license"><a href="#license">#</a> License</h2>
<p>Use it for free, pay if you get paid. So, you’ve just benefited financially after using this project? It’s a good idea to <a href="https://paypal.me/tatautaufik" target="_blank">share a little financial support</a> with this open source project too. Your support will motivate me to do any further development, as well as to provide voluntary support to overcome problems related to this project.</p>
<p>Thank you! ❤️</p>
</main>
<footer>
<hr>
<p>© 2024 <a href="/">Taufik Nurrohman</a></p>
</footer>
<script src="index.min.js"></script>
<script>
const picker = new TagPicker(document.forms[0].tags);
picker.on('change', function () {
console.log(this.value);
});
picker.on('has.tag', tag => {
alert('Tag “' + tag + '” already exists.');
});
picker.on('reset', function () {
this.focus();
});
</script>
</body>
</html>