taggle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }));