taggle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,890 @@
1
+ /* !
2
+ * @author Sean Coker <sean@seancoker.com>
3
+ * @version 1.11.1
4
+ * @url http://sean.is/poppin/tags
5
+ * @license MIT
6
+ * @description Taggle is a dependency-less tagging library
7
+ */
8
+
9
+ (function(root, factory) {
10
+ 'use strict';
11
+ var libName = 'Taggle';
12
+
13
+ /* global define, module */
14
+ if (typeof define === 'function' && define.amd) {
15
+ define([], function() {
16
+ var module = factory();
17
+ root[libName] = module;
18
+ return module;
19
+ });
20
+ }
21
+ else if (typeof module === 'object' && module.exports) {
22
+ module.exports = root[libName] = factory();
23
+ }
24
+ else {
25
+ root[libName] = factory();
26
+ }
27
+ }(this, function() {
28
+ 'use strict';
29
+ /////////////////////
30
+ // Default options //
31
+ /////////////////////
32
+
33
+ var noop = function() {};
34
+ var retTrue = function() {
35
+ return true;
36
+ };
37
+ var BACKSPACE = 8;
38
+ var COMMA = 188;
39
+ var TAB = 9;
40
+ var ENTER = 13;
41
+
42
+ var DEFAULTS = {
43
+ /**
44
+ * Class names to be added on each tag entered
45
+ * @type {String}
46
+ */
47
+ additionalTagClasses: '',
48
+
49
+ /**
50
+ * Allow duplicate tags to be entered in the field?
51
+ * @type {Boolean}
52
+ */
53
+ allowDuplicates: false,
54
+
55
+ /**
56
+ * Allow the saving of a tag on blur, rather than it being
57
+ * removed.
58
+ *
59
+ * @type {Boolean}
60
+ */
61
+ saveOnBlur: false,
62
+
63
+ /**
64
+ * Clear the input value when blurring.
65
+ *
66
+ * @type {Boolean}
67
+ */
68
+ clearOnBlur: true,
69
+
70
+ /**
71
+ * Class name that will be added onto duplicate existant tag
72
+ * @type {String}
73
+ * @todo
74
+ * @deprecated can be handled by onBeforeTagAdd
75
+ */
76
+ duplicateTagClass: '',
77
+
78
+ /**
79
+ * Class added to the container div when focused
80
+ * @type {String}
81
+ */
82
+ containerFocusClass: 'active',
83
+
84
+ /**
85
+ * Should the input be focused when the container is clicked?
86
+ * @type {Bool}
87
+ */
88
+ focusInputOnContainerClick: true,
89
+
90
+ /**
91
+ * Name added to the hidden inputs within each tag
92
+ * @type {String}
93
+ */
94
+ hiddenInputName: 'taggles[]',
95
+
96
+ /**
97
+ * Tags that should be preloaded in the div on load
98
+ * @type {Array}
99
+ */
100
+ tags: [],
101
+
102
+ /**
103
+ * The default delimeter character to split tags on
104
+ * @type {String}
105
+ */
106
+ delimeter: ',',
107
+
108
+ /**
109
+ * Add an ID to each of the tags.
110
+ * @type {Boolean}
111
+ * @todo
112
+ * @deprecated make this the default in next version
113
+ */
114
+ attachTagId: false,
115
+
116
+ /**
117
+ * Tags that the user will be restricted to
118
+ * @type {Array}
119
+ */
120
+ allowedTags: [],
121
+
122
+ /**
123
+ * Tags that the user will not be able to add
124
+ * @type {Array}
125
+ */
126
+ disallowedTags: [],
127
+
128
+ /**
129
+ * Limit the number of tags that can be added
130
+ * @type {Number}
131
+ */
132
+ maxTags: null,
133
+
134
+ /**
135
+ * If within a form, you can specify the tab index flow
136
+ * @type {Number}
137
+ */
138
+ tabIndex: 1,
139
+
140
+ /**
141
+ * Placeholder string to be placed in an empty taggle field
142
+ * @type {String}
143
+ */
144
+ placeholder: 'Enter tags...',
145
+
146
+ /**
147
+ * Keycodes that will add a tag
148
+ * @type {Array}
149
+ */
150
+ submitKeys: [COMMA, TAB, ENTER],
151
+
152
+ /**
153
+ * Preserve case of tags being added ie
154
+ * "tag" is different than "Tag"
155
+ * @type {Boolean}
156
+ */
157
+ preserveCase: false,
158
+
159
+ /**
160
+ * Function hook called with the to-be-added input DOM element.
161
+ *
162
+ * @param {HTMLElement} li The list item to be added
163
+ */
164
+ inputFormatter: noop,
165
+
166
+ /**
167
+ * Function hook called with the to-be-added tag DOM element.
168
+ * Use this function to edit the list item before it is appended
169
+ * to the DOM
170
+ * @param {HTMLElement} li The list item to be added
171
+ */
172
+ tagFormatter: noop,
173
+
174
+ /**
175
+ * Function hook called before a tag is added. Return false
176
+ * to prevent tag from being added
177
+ * @param {String} tag The tag to be added
178
+ */
179
+ onBeforeTagAdd: noop,
180
+
181
+ /**
182
+ * Function hook called when a tag is added
183
+ * @param {Event} event Event triggered when tag was added
184
+ * @param {String} tag The tag added
185
+ */
186
+ onTagAdd: noop,
187
+
188
+ /**
189
+ * Function hook called before a tag is removed. Return false
190
+ * to prevent tag from being removed
191
+ * @param {String} tag The tag to be removed
192
+ */
193
+ onBeforeTagRemove: retTrue,
194
+
195
+ /**
196
+ * Function hook called when a tag is removed
197
+ * @param {Event} event Event triggered when tag was removed
198
+ * @param {String} tag The tag removed
199
+ */
200
+ onTagRemove: noop
201
+ };
202
+
203
+ //////////////////////
204
+ // Helper functions //
205
+ //////////////////////
206
+
207
+ function _extend() {
208
+ var master = arguments[0];
209
+ for (var i = 1, l = arguments.length; i < l; i++) {
210
+ var object = arguments[i];
211
+ for (var key in object) {
212
+ if (object.hasOwnProperty(key)) {
213
+ master[key] = object[key];
214
+ }
215
+ }
216
+ }
217
+
218
+ return master;
219
+ }
220
+
221
+ function _isArray(arr) {
222
+ if (Array.isArray) {
223
+ return Array.isArray(arr);
224
+ }
225
+ return Object.prototype.toString.call(arr) === '[object Array]';
226
+ }
227
+
228
+ function _on(element, eventName, handler) {
229
+ if (element.addEventListener) {
230
+ element.addEventListener(eventName, handler, false);
231
+ }
232
+ else if (element.attachEvent) {
233
+ element.attachEvent('on' + eventName, handler);
234
+ }
235
+ else {
236
+ element['on' + eventName] = handler;
237
+ }
238
+ }
239
+
240
+ function _trim(str) {
241
+ return str.replace(/^\s+|\s+$/g, '');
242
+ }
243
+
244
+ function _setText(el, text) {
245
+ if (window.attachEvent && !window.addEventListener) { // <= IE8
246
+ el.innerText = text;
247
+ }
248
+ else {
249
+ el.textContent = text;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Constructor
255
+ * @param {Mixed} el ID of an element or the actual element
256
+ * @param {Object} options
257
+ */
258
+ var Taggle = function(el, options) {
259
+ this.settings = _extend({}, DEFAULTS, options);
260
+ this.measurements = {
261
+ container: {
262
+ rect: null,
263
+ style: null,
264
+ padding: null
265
+ }
266
+ };
267
+ this.container = el;
268
+ this.tag = {
269
+ values: [],
270
+ elements: []
271
+ };
272
+ this.list = document.createElement('ul');
273
+ this.inputLi = document.createElement('li');
274
+ this.input = document.createElement('input');
275
+ this.sizer = document.createElement('div');
276
+ this.pasting = false;
277
+ this.placeholder = null;
278
+
279
+ if (this.settings.placeholder) {
280
+ this.placeholder = document.createElement('span');
281
+ }
282
+
283
+ if (typeof el === 'string') {
284
+ this.container = document.getElementById(el);
285
+ }
286
+
287
+ this._id = 0;
288
+ this._setMeasurements();
289
+ this._setupTextarea();
290
+ this._attachEvents();
291
+ };
292
+
293
+ /**
294
+ * Gets all the layout measurements up front
295
+ */
296
+ Taggle.prototype._setMeasurements = function() {
297
+ this.measurements.container.rect = this.container.getBoundingClientRect();
298
+ this.measurements.container.style = window.getComputedStyle(this.container);
299
+
300
+ var style = this.measurements.container.style;
301
+ var lpad = parseInt(style['padding-left'] || style.paddingLeft, 10);
302
+ var rpad = parseInt(style['padding-right'] || style.paddingRight, 10);
303
+ var lborder = parseInt(style['border-left-width'] || style.borderLeftWidth, 10);
304
+ var rborder = parseInt(style['border-right-width'] || style.borderRightWidth, 10);
305
+
306
+ this.measurements.container.padding = lpad + rpad + lborder + rborder;
307
+ };
308
+
309
+ /**
310
+ * Setup the div container for tags to be entered
311
+ */
312
+ Taggle.prototype._setupTextarea = function() {
313
+ var fontSize;
314
+
315
+ this.list.className = 'taggle_list';
316
+ this.input.type = 'text';
317
+ // Make sure no left/right padding messes with the input sizing
318
+ this.input.style.paddingLeft = 0;
319
+ this.input.style.paddingRight = 0;
320
+ this.input.className = 'taggle_input';
321
+ this.input.tabIndex = this.settings.tabIndex;
322
+ this.sizer.className = 'taggle_sizer';
323
+
324
+ if (this.settings.tags.length) {
325
+ for (var i = 0, len = this.settings.tags.length; i < len; i++) {
326
+ var taggle = this._createTag(this.settings.tags[i]);
327
+ this.list.appendChild(taggle);
328
+ }
329
+ }
330
+
331
+ if (this.placeholder) {
332
+ this.placeholder.style.opacity = 0;
333
+ this.placeholder.classList.add('taggle_placeholder');
334
+ this.container.appendChild(this.placeholder);
335
+ _setText(this.placeholder, this.settings.placeholder);
336
+
337
+ if (!this.settings.tags.length) {
338
+ this.placeholder.style.opacity = 1;
339
+ }
340
+ }
341
+
342
+ var formattedInput = this.settings.inputFormatter(this.input);
343
+ if (formattedInput) {
344
+ this.input = formattedInput;
345
+ }
346
+
347
+ this.inputLi.appendChild(this.input);
348
+ this.list.appendChild(this.inputLi);
349
+ this.container.appendChild(this.list);
350
+ this.container.appendChild(this.sizer);
351
+ fontSize = window.getComputedStyle(this.input).fontSize;
352
+ this.sizer.style.fontSize = fontSize;
353
+ };
354
+
355
+ /**
356
+ * Attaches neccessary events
357
+ */
358
+ Taggle.prototype._attachEvents = function() {
359
+ var self = this;
360
+
361
+ if (this.settings.focusInputOnContainerClick) {
362
+ _on(this.container, 'click', function() {
363
+ self.input.focus();
364
+ });
365
+ }
366
+
367
+ _on(this.input, 'focus', this._focusInput.bind(this));
368
+ _on(this.input, 'blur', this._blurEvent.bind(this));
369
+ _on(this.input, 'keydown', this._keydownEvents.bind(this));
370
+ _on(this.input, 'keyup', this._keyupEvents.bind(this));
371
+ };
372
+
373
+ /**
374
+ * Resizes the hidden input where user types to fill in the
375
+ * width of the div
376
+ */
377
+ Taggle.prototype._fixInputWidth = function() {
378
+ var width;
379
+ var inputRect;
380
+ var rect;
381
+ var leftPos;
382
+ var padding;
383
+
384
+ this._setMeasurements();
385
+
386
+ // Reset width incase we've broken to the next line on a backspace erase
387
+ this._setInputWidth();
388
+
389
+ inputRect = this.input.getBoundingClientRect();
390
+ rect = this.measurements.container.rect;
391
+ width = ~~rect.width;
392
+ // Could probably just use right - left all the time
393
+ // but eh, this check is mostly for IE8
394
+ if (!width) {
395
+ width = ~~rect.right - ~~rect.left;
396
+ }
397
+ leftPos = ~~inputRect.left - ~~rect.left;
398
+ padding = this.measurements.container.padding;
399
+
400
+ this._setInputWidth(width - leftPos - padding);
401
+ };
402
+
403
+ /**
404
+ * Returns whether or not the specified tag text can be added
405
+ * @param {Event} e event causing the potentially added tag
406
+ * @param {String} text tag value
407
+ * @return {Boolean}
408
+ */
409
+ Taggle.prototype._canAdd = function(e, text) {
410
+ if (!text) {
411
+ return false;
412
+ }
413
+ var limit = this.settings.maxTags;
414
+ if (limit !== null && limit <= this.getTagValues().length) {
415
+ return false;
416
+ }
417
+
418
+ if (this.settings.onBeforeTagAdd(e, text) === false) {
419
+ return false;
420
+ }
421
+
422
+ if (!this.settings.allowDuplicates && this._hasDupes(text)) {
423
+ return false;
424
+ }
425
+
426
+ var sensitive = this.settings.preserveCase;
427
+ var allowed = this.settings.allowedTags;
428
+
429
+ if (allowed.length && !this._tagIsInArray(text, allowed, sensitive)) {
430
+ return false;
431
+ }
432
+
433
+ var disallowed = this.settings.disallowedTags;
434
+ if (disallowed.length && this._tagIsInArray(text, disallowed, sensitive)) {
435
+ return false;
436
+ }
437
+
438
+ return true;
439
+ };
440
+
441
+ /**
442
+ * Returns whether a string is in an array based on case sensitivity
443
+ *
444
+ * @param {String} text string to search for
445
+ * @param {Array} arr array of strings to search through
446
+ * @param {Boolean} caseSensitive
447
+ * @return {Boolean}
448
+ */
449
+ Taggle.prototype._tagIsInArray = function(text, arr, caseSensitive) {
450
+ if (caseSensitive) {
451
+ return arr.indexOf(text) !== -1;
452
+ }
453
+
454
+ var lowercased = [].slice.apply(arr).map(function(str) {
455
+ return str.toLowerCase();
456
+ });
457
+
458
+ return lowercased.indexOf(text) !== -1;
459
+ };
460
+
461
+ /**
462
+ * Appends tag with its corresponding input to the list
463
+ * @param {Event} e
464
+ * @param {String} text
465
+ */
466
+ Taggle.prototype._add = function(e, text) {
467
+ var self = this;
468
+ var values = text || '';
469
+
470
+ if (typeof text !== 'string') {
471
+ values = _trim(this.input.value);
472
+ }
473
+
474
+ values.split(this.settings.delimeter).map(function(val) {
475
+ return self._formatTag(val);
476
+ }).forEach(function(val) {
477
+ if (!self._canAdd(e, val)) {
478
+ return;
479
+ }
480
+
481
+ var li = self._createTag(val);
482
+ var lis = self.list.children;
483
+ var lastLi = lis[lis.length - 1];
484
+ self.list.insertBefore(li, lastLi);
485
+
486
+
487
+ val = self.tag.values[self.tag.values.length - 1];
488
+
489
+ self.settings.onTagAdd(e, val);
490
+
491
+ self.input.value = '';
492
+ self._fixInputWidth();
493
+ self._focusInput();
494
+ });
495
+ };
496
+
497
+ /**
498
+ * Removes last tag if it has already been probed
499
+ * @param {Event} e
500
+ */
501
+ Taggle.prototype._checkLastTag = function(e) {
502
+ e = e || window.event;
503
+
504
+ var taggles = this.container.querySelectorAll('.taggle');
505
+ var lastTaggle = taggles[taggles.length - 1];
506
+ var hotClass = 'taggle_hot';
507
+ var heldDown = this.input.classList.contains('taggle_back');
508
+
509
+ // prevent holding backspace from deleting all tags
510
+ if (this.input.value === '' && e.keyCode === BACKSPACE && !heldDown) {
511
+ if (lastTaggle.classList.contains(hotClass)) {
512
+ this.input.classList.add('taggle_back');
513
+ this._remove(lastTaggle, e);
514
+ this._fixInputWidth();
515
+ this._focusInput();
516
+ }
517
+ else {
518
+ lastTaggle.classList.add(hotClass);
519
+ }
520
+ }
521
+ else if (lastTaggle.classList.contains(hotClass)) {
522
+ lastTaggle.classList.remove(hotClass);
523
+ }
524
+ };
525
+
526
+ /**
527
+ * Setter for the hidden input.
528
+ * @param {Number} width
529
+ */
530
+ Taggle.prototype._setInputWidth = function(width) {
531
+ this.input.style.width = (width || 10) + 'px';
532
+ };
533
+
534
+ /**
535
+ * Checks global tags array if provided tag exists
536
+ * @param {String} text
537
+ * @return {Boolean}
538
+ */
539
+ Taggle.prototype._hasDupes = function(text) {
540
+ var needle = this.tag.values.indexOf(text);
541
+ var tagglelist = this.container.querySelector('.taggle_list');
542
+ var dupes;
543
+
544
+ if (this.settings.duplicateTagClass) {
545
+ dupes = tagglelist.querySelectorAll('.' + this.settings.duplicateTagClass);
546
+ for (var i = 0, len = dupes.length; i < len; i++) {
547
+ dupes[i].classList.remove(this.settings.duplicateTagClass);
548
+ }
549
+ }
550
+
551
+ // if found
552
+ if (needle > -1) {
553
+ if (this.settings.duplicateTagClass) {
554
+ tagglelist.childNodes[needle].classList.add(this.settings.duplicateTagClass);
555
+ }
556
+ return true;
557
+ }
558
+
559
+ return false;
560
+ };
561
+
562
+ /**
563
+ * Checks whether or not the key pressed is acceptable
564
+ * @param {Number} key code
565
+ * @return {Boolean}
566
+ */
567
+ Taggle.prototype._isConfirmKey = function(key) {
568
+ var confirmKey = false;
569
+
570
+ if (this.settings.submitKeys.indexOf(key) > -1) {
571
+ confirmKey = true;
572
+ }
573
+
574
+ return confirmKey;
575
+ };
576
+
577
+ // Event handlers
578
+
579
+ /**
580
+ * Handles focus state of div container.
581
+ */
582
+ Taggle.prototype._focusInput = function() {
583
+ this._fixInputWidth();
584
+
585
+ if (!this.container.classList.contains(this.settings.containerFocusClass)) {
586
+ this.container.classList.add(this.settings.containerFocusClass);
587
+ }
588
+
589
+ if (this.placeholder) {
590
+ this.placeholder.style.opacity = 0;
591
+ }
592
+ };
593
+
594
+ /**
595
+ * Runs all the events that need to happen on a blur
596
+ * @param {Event} e
597
+ */
598
+ Taggle.prototype._blurEvent = function(e) {
599
+ if (this.container.classList.contains(this.settings.containerFocusClass)) {
600
+ this.container.classList.remove(this.settings.containerFocusClass);
601
+ }
602
+
603
+ if (this.settings.saveOnBlur) {
604
+ e = e || window.event;
605
+
606
+ this._listenForEndOfContainer();
607
+
608
+ if (this.input.value !== '') {
609
+ this._confirmValidTagEvent(e);
610
+ return;
611
+ }
612
+
613
+ if (this.tag.values.length) {
614
+ this._checkLastTag(e);
615
+ }
616
+ }
617
+ else if (this.settings.clearOnBlur) {
618
+ this.input.value = '';
619
+ this._setInputWidth();
620
+ }
621
+
622
+ if (!this.tag.values.length && this.placeholder && !this.input.value) {
623
+ this.placeholder.style.opacity = 1;
624
+ }
625
+ };
626
+
627
+ /**
628
+ * Runs all the events that need to run on keydown
629
+ * @param {Event} e
630
+ */
631
+ Taggle.prototype._keydownEvents = function(e) {
632
+ e = e || window.event;
633
+
634
+ var key = e.keyCode;
635
+ this.pasting = false;
636
+
637
+ this._listenForEndOfContainer();
638
+
639
+ if (key === 86 && e.metaKey) {
640
+ this.pasting = true;
641
+ }
642
+
643
+ if (this._isConfirmKey(key) && this.input.value !== '') {
644
+ this._confirmValidTagEvent(e);
645
+ return;
646
+ }
647
+
648
+ if (this.tag.values.length) {
649
+ this._checkLastTag(e);
650
+ }
651
+ };
652
+
653
+ /**
654
+ * Runs all the events that need to run on keyup
655
+ * @param {Event} e
656
+ */
657
+ Taggle.prototype._keyupEvents = function(e) {
658
+ e = e || window.event;
659
+
660
+ this.input.classList.remove('taggle_back');
661
+
662
+ _setText(this.sizer, this.input.value);
663
+
664
+ if (this.pasting && this.input.value !== '') {
665
+ this._add(e);
666
+ this.pasting = false;
667
+ }
668
+ };
669
+
670
+ /**
671
+ * Confirms the inputted value to be converted to a tag
672
+ * @param {Event} e
673
+ */
674
+ Taggle.prototype._confirmValidTagEvent = function(e) {
675
+ e = e || window.event;
676
+
677
+ // prevents from jumping out of textarea
678
+ if (e.preventDefault) {
679
+ e.preventDefault();
680
+ }
681
+ else {
682
+ e.returnValue = false;
683
+ }
684
+
685
+ this._add(e);
686
+ };
687
+
688
+ /**
689
+ * Approximates when the hidden input should break to the next line
690
+ */
691
+ Taggle.prototype._listenForEndOfContainer = function() {
692
+ var width = this.sizer.getBoundingClientRect().width;
693
+ var max = this.measurements.container.rect.width - this.measurements.container.padding;
694
+ var size = parseInt(this.sizer.style.fontSize, 10);
695
+
696
+ // 1.5 just seems to be a good multiplier here
697
+ if (width + (size * 1.5) > parseInt(this.input.style.width, 10)) {
698
+ this.input.style.width = max + 'px';
699
+ }
700
+ };
701
+
702
+ Taggle.prototype._createTag = function(text) {
703
+ var li = document.createElement('li');
704
+ var close = document.createElement('button');
705
+ var hidden = document.createElement('input');
706
+ var span = document.createElement('span');
707
+
708
+ text = this._formatTag(text);
709
+
710
+ close.innerHTML = '&times;';
711
+ close.className = 'close';
712
+ close.type = 'button';
713
+ _on(close, 'click', this._remove.bind(this, close));
714
+
715
+ _setText(span, text);
716
+ span.className = 'taggle_text';
717
+
718
+ li.className = 'taggle ' + this.settings.additionalTagClasses;
719
+
720
+ hidden.type = 'hidden';
721
+ hidden.value = text;
722
+ hidden.name = this.settings.hiddenInputName;
723
+
724
+ li.appendChild(span);
725
+ li.appendChild(close);
726
+ li.appendChild(hidden);
727
+
728
+ var formatted = this.settings.tagFormatter(li);
729
+
730
+ if (typeof formatted !== 'undefined') {
731
+ li = formatted;
732
+ }
733
+
734
+ if (!(li instanceof HTMLElement) || li.tagName !== 'LI') {
735
+ throw new Error('tagFormatter must return an li element');
736
+ }
737
+
738
+ if (this.settings.attachTagId) {
739
+ this._id += 1;
740
+ text = {
741
+ text: text,
742
+ id: this._id
743
+ };
744
+ }
745
+
746
+ this.tag.values.push(text);
747
+ this.tag.elements.push(li);
748
+
749
+ return li;
750
+ };
751
+
752
+ /**
753
+ * Removes tag from the tags collection
754
+ * @param {li} li List item to remove
755
+ * @param {Event} e
756
+ */
757
+ Taggle.prototype._remove = function(li, e) {
758
+ var self = this;
759
+ var text;
760
+ var elem;
761
+ var index;
762
+
763
+ if (li.tagName.toLowerCase() !== 'li') {
764
+ li = li.parentNode;
765
+ }
766
+
767
+ elem = (li.tagName.toLowerCase() === 'a') ? li.parentNode : li;
768
+ index = this.tag.elements.indexOf(elem);
769
+
770
+ text = this.tag.values[index];
771
+
772
+ function done(error) {
773
+ if (error) {
774
+ return;
775
+ }
776
+
777
+ li.parentNode.removeChild(li);
778
+
779
+ // Going to assume the indicies match for now
780
+ self.tag.elements.splice(index, 1);
781
+ self.tag.values.splice(index, 1);
782
+
783
+ self.settings.onTagRemove(e, text);
784
+
785
+ self._focusInput();
786
+ }
787
+
788
+ var ret = this.settings.onBeforeTagRemove(e, text, done);
789
+
790
+ if (!ret) {
791
+ return;
792
+ }
793
+
794
+ done();
795
+ };
796
+
797
+ /**
798
+ * Format the text for a tag
799
+ * @param {String} text Tag text
800
+ * @return {String}
801
+ */
802
+ Taggle.prototype._formatTag = function(text) {
803
+ return this.settings.preserveCase ? text : text.toLowerCase();
804
+ };
805
+
806
+ Taggle.prototype.getTags = function() {
807
+ return {
808
+ elements: this.getTagElements(),
809
+ values: this.getTagValues()
810
+ };
811
+ };
812
+
813
+ // @todo
814
+ // @deprecated use getTags().elements
815
+ Taggle.prototype.getTagElements = function() {
816
+ return this.tag.elements;
817
+ };
818
+
819
+ // @todo
820
+ // @deprecated use getTags().values
821
+ Taggle.prototype.getTagValues = function() {
822
+ return [].slice.apply(this.tag.values);
823
+ };
824
+
825
+ Taggle.prototype.getInput = function() {
826
+ return this.input;
827
+ };
828
+
829
+ Taggle.prototype.getContainer = function() {
830
+ return this.container;
831
+ };
832
+
833
+ Taggle.prototype.add = function(text) {
834
+ var isArr = _isArray(text);
835
+
836
+ if (isArr) {
837
+ for (var i = 0, len = text.length; i < len; i++) {
838
+ if (typeof text[i] === 'string') {
839
+ this._add(null, text[i]);
840
+ }
841
+ }
842
+ }
843
+ else {
844
+ this._add(null, text);
845
+ }
846
+
847
+ return this;
848
+ };
849
+
850
+ Taggle.prototype.remove = function(text, all) {
851
+ var len = this.tag.values.length - 1;
852
+ var found = false;
853
+
854
+ while (len > -1) {
855
+ var tagText = this.tag.values[len];
856
+ if (this.settings.attachTagId) {
857
+ tagText = tagText.text;
858
+ }
859
+
860
+ if (tagText === text) {
861
+ found = true;
862
+ this._remove(this.tag.elements[len]);
863
+ }
864
+
865
+ if (found && !all) {
866
+ break;
867
+ }
868
+
869
+ len--;
870
+ }
871
+
872
+ return this;
873
+ };
874
+
875
+ Taggle.prototype.removeAll = function() {
876
+ for (var i = this.tag.values.length - 1; i >= 0; i--) {
877
+ this._remove(this.tag.elements[i]);
878
+ }
879
+
880
+ return this;
881
+ };
882
+
883
+ Taggle.prototype.setOptions = function(options) {
884
+ this.settings = _extend({}, this.settings, options || {});
885
+
886
+ return this;
887
+ };
888
+
889
+ return Taggle;
890
+ }));