0
# Tagging Mode
1
2
Advanced tagging functionality allowing users to create new options dynamically through text input.
3
4
## Capabilities
5
6
### Basic Tagging
7
8
Enable tagging mode to allow users to create new options by typing and pressing Enter.
9
10
```typescript { .api }
11
/**
12
* Basic tagging configuration props
13
*/
14
interface BasicTaggingProps {
15
/** Enable tagging functionality (default: false) */
16
taggable?: boolean;
17
18
/** Placeholder text shown when highlighting tag creation (default: 'Press enter to create a tag') */
19
tagPlaceholder?: string;
20
21
/** Position of tag creation option ('top' | 'bottom', default: 'top') */
22
tagPosition?: 'top' | 'bottom';
23
}
24
```
25
26
**Usage Example:**
27
28
```vue
29
<template>
30
<VueMultiselect
31
v-model="selectedTags"
32
:options="availableTags"
33
:multiple="true"
34
:taggable="true"
35
:searchable="true"
36
@tag="addTag"
37
tag-placeholder="Add this as new tag"
38
placeholder="Select or create tags">
39
</VueMultiselect>
40
</template>
41
42
<script>
43
export default {
44
data() {
45
return {
46
selectedTags: [],
47
availableTags: ['Vue.js', 'React', 'Angular']
48
}
49
},
50
methods: {
51
addTag(newTag) {
52
const tag = {
53
name: newTag,
54
code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
55
};
56
this.availableTags.push(tag);
57
this.selectedTags.push(tag);
58
}
59
}
60
}
61
</script>
62
```
63
64
### Tagging Events
65
66
Events emitted during tag creation operations.
67
68
```typescript { .api }
69
/**
70
* Tagging-related events
71
*/
72
interface TaggingEvents {
73
/**
74
* Emitted when user creates a new tag
75
* @param searchQuery - Text entered by user for new tag
76
* @param id - Component identifier
77
*/
78
'@tag': (searchQuery: string, id: string | number) => void;
79
}
80
```
81
82
### Tag Creation Logic
83
84
Control how new tags are created and integrated with existing options.
85
86
```vue
87
<template>
88
<VueMultiselect
89
v-model="selectedSkills"
90
:options="skills"
91
:multiple="true"
92
:taggable="true"
93
:searchable="true"
94
@tag="createSkill"
95
label="name"
96
track-by="id"
97
tag-placeholder="Create new skill"
98
placeholder="Select or add skills">
99
</VueMultiselect>
100
</template>
101
102
<script>
103
export default {
104
data() {
105
return {
106
selectedSkills: [],
107
skills: [
108
{ id: 1, name: 'JavaScript', category: 'Programming' },
109
{ id: 2, name: 'Design', category: 'Creative' }
110
],
111
nextId: 3
112
}
113
},
114
methods: {
115
createSkill(name) {
116
// Validate tag name
117
if (name.length < 2) {
118
alert('Skill name must be at least 2 characters');
119
return;
120
}
121
122
// Check for duplicates
123
const exists = this.skills.some(skill =>
124
skill.name.toLowerCase() === name.toLowerCase()
125
);
126
127
if (exists) {
128
alert('This skill already exists');
129
return;
130
}
131
132
// Create new skill
133
const newSkill = {
134
id: this.nextId++,
135
name: name,
136
category: 'Custom'
137
};
138
139
// Add to options and select it
140
this.skills.push(newSkill);
141
this.selectedSkills.push(newSkill);
142
}
143
}
144
}
145
</script>
146
```
147
148
### Tag Positioning
149
150
Control where the tag creation option appears in the dropdown.
151
152
```typescript { .api }
153
/**
154
* Tag positioning configuration
155
*/
156
interface TagPositioningProps {
157
/**
158
* Position of tag creation option in dropdown
159
* 'top' - Show above search results (default)
160
* 'bottom' - Show below search results
161
*/
162
tagPosition?: 'top' | 'bottom';
163
}
164
```
165
166
**Usage Example:**
167
168
```vue
169
<template>
170
<VueMultiselect
171
v-model="selectedCategories"
172
:options="categories"
173
:multiple="true"
174
:taggable="true"
175
tag-position="bottom"
176
tag-placeholder="Create new category"
177
@tag="addCategory">
178
</VueMultiselect>
179
</template>
180
181
<script>
182
export default {
183
methods: {
184
addCategory(categoryName) {
185
// Add category to bottom of list
186
this.categories.push(categoryName);
187
this.selectedCategories.push(categoryName);
188
}
189
}
190
}
191
</script>
192
```
193
194
### Advanced Tag Creation
195
196
Implement complex tag creation with validation, transformation, and async operations.
197
198
```vue
199
<template>
200
<VueMultiselect
201
v-model="selectedTags"
202
:options="availableTags"
203
:multiple="true"
204
:taggable="true"
205
:loading="isCreatingTag"
206
@tag="createTagAsync"
207
label="name"
208
track-by="id"
209
tag-placeholder="Create and save new tag"
210
placeholder="Select existing or create new tags">
211
212
<template #tag="{ option, remove }">
213
<span class="custom-tag">
214
<span>{{ option.name }}</span>
215
<span v-if="option.isNew" class="tag-new-indicator">NEW</span>
216
<button @click="remove(option)" class="tag-remove">×</button>
217
</span>
218
</template>
219
</VueMultiselect>
220
</template>
221
222
<script>
223
export default {
224
data() {
225
return {
226
selectedTags: [],
227
availableTags: [
228
{ id: 1, name: 'JavaScript', isNew: false },
229
{ id: 2, name: 'Vue.js', isNew: false }
230
],
231
isCreatingTag: false
232
}
233
},
234
methods: {
235
async createTagAsync(tagName) {
236
// Validate tag name
237
if (!this.validateTagName(tagName)) {
238
return;
239
}
240
241
this.isCreatingTag = true;
242
243
try {
244
// Transform tag name
245
const normalizedName = this.normalizeTagName(tagName);
246
247
// Check if tag exists (case-insensitive)
248
const existingTag = this.availableTags.find(tag =>
249
tag.name.toLowerCase() === normalizedName.toLowerCase()
250
);
251
252
if (existingTag) {
253
// Select existing tag instead
254
if (!this.selectedTags.includes(existingTag)) {
255
this.selectedTags.push(existingTag);
256
}
257
return;
258
}
259
260
// Create new tag via API
261
const newTag = await this.saveTagToServer(normalizedName);
262
263
// Add to local state
264
this.availableTags.push(newTag);
265
this.selectedTags.push(newTag);
266
267
} catch (error) {
268
console.error('Failed to create tag:', error);
269
alert('Failed to create tag. Please try again.');
270
} finally {
271
this.isCreatingTag = false;
272
}
273
},
274
275
validateTagName(name) {
276
if (!name || name.trim().length < 2) {
277
alert('Tag name must be at least 2 characters');
278
return false;
279
}
280
281
if (name.length > 50) {
282
alert('Tag name must be less than 50 characters');
283
return false;
284
}
285
286
if (!/^[a-zA-Z0-9\s\-.]+$/.test(name)) {
287
alert('Tag name contains invalid characters');
288
return false;
289
}
290
291
return true;
292
},
293
294
normalizeTagName(name) {
295
return name.trim()
296
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
297
.toLowerCase()
298
.replace(/^\w/, c => c.toUpperCase()); // Capitalize first letter
299
},
300
301
async saveTagToServer(name) {
302
const response = await fetch('/api/tags', {
303
method: 'POST',
304
headers: {
305
'Content-Type': 'application/json'
306
},
307
body: JSON.stringify({ name })
308
});
309
310
if (!response.ok) {
311
throw new Error('Failed to save tag');
312
}
313
314
const tag = await response.json();
315
return {
316
...tag,
317
isNew: true
318
};
319
}
320
}
321
}
322
</script>
323
324
<style>
325
.custom-tag {
326
display: inline-flex;
327
align-items: center;
328
gap: 4px;
329
padding: 4px 8px;
330
background: #e3f2fd;
331
border-radius: 4px;
332
}
333
334
.tag-new-indicator {
335
font-size: 10px;
336
background: #4caf50;
337
color: white;
338
padding: 1px 4px;
339
border-radius: 2px;
340
}
341
342
.tag-remove {
343
background: none;
344
border: none;
345
cursor: pointer;
346
font-size: 14px;
347
color: #666;
348
}
349
</style>
350
```
351
352
### Tag Management
353
354
Methods for managing tags programmatically.
355
356
```typescript { .api }
357
/**
358
* Tag management methods
359
*/
360
interface TagManagementMethods {
361
/** Check if a tag with given text already exists */
362
isExistingOption(query: string): boolean;
363
364
/** Add a new tag programmatically */
365
addTag(tagData: any): void;
366
367
/** Remove a tag from available options */
368
removeTag(tag: any): void;
369
370
/** Get all currently selected tags */
371
getSelectedTags(): any[];
372
}
373
```
374
375
### Tagging with Objects
376
377
Use tagging with object-based options for more complex tag structures.
378
379
```vue
380
<template>
381
<VueMultiselect
382
v-model="selectedTopics"
383
:options="topics"
384
:multiple="true"
385
:taggable="true"
386
@tag="createTopic"
387
label="title"
388
track-by="slug"
389
tag-placeholder="Create new topic">
390
</VueMultiselect>
391
</template>
392
393
<script>
394
export default {
395
data() {
396
return {
397
selectedTopics: [],
398
topics: [
399
{ slug: 'javascript', title: 'JavaScript', color: '#f7df1e' },
400
{ slug: 'vue', title: 'Vue.js', color: '#4fc08d' }
401
]
402
}
403
},
404
methods: {
405
createTopic(title) {
406
const slug = title.toLowerCase()
407
.replace(/[^a-z0-9]+/g, '-')
408
.replace(/(^-|-$)/g, '');
409
410
const newTopic = {
411
slug: slug,
412
title: title,
413
color: this.getRandomColor(),
414
isCustom: true
415
};
416
417
this.topics.push(newTopic);
418
this.selectedTopics.push(newTopic);
419
},
420
421
getRandomColor() {
422
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'];
423
return colors[Math.floor(Math.random() * colors.length)];
424
}
425
}
426
}
427
</script>
428
```
429
430
### Keyboard Shortcuts
431
432
Tagging supports standard keyboard shortcuts for tag creation.
433
434
```typescript { .api }
435
/**
436
* Keyboard shortcuts for tagging
437
*/
438
interface TaggingKeyboardShortcuts {
439
/** Enter key - Create tag from current search query */
440
'Enter': void;
441
442
/** Tab key - Create tag and continue to next field */
443
'Tab': void;
444
445
/** Escape key - Cancel tag creation and close dropdown */
446
'Escape': void;
447
}
448
```
449
450
**Usage Notes:**
451
452
- Press `Enter` while typing to create a new tag
453
- Press `Tab` to create a tag and move focus to next form element
454
- Press `Escape` to cancel tag creation and close dropdown
455
- Tags are created from the current search input value
456
- Tag creation only works when `taggable` prop is true