taggle-rails 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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9a8cae34fdd37e7c4faa83600b4802d6eeba186d
4
+ data.tar.gz: 926c92a6d282b44cf85b940ce659e7f3b79dafb1
5
+ SHA512:
6
+ metadata.gz: a4e878966161593b1025b14780982d95a5ca0680fac4ac6a7fb23bf6baf6829d143debda1fab7da679109115089e7cf6694d48cebc7a919fa2b6d19f4c203d9c
7
+ data.tar.gz: 596fa37498399f8a1e3f819dfe8a22c84110c430bee40a6daab0a4727c39adc1364b066844b7bbf6b49c8605c1e3be162dae2f608cd43c5623d14d7d9b08c900
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Tom Conroy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Taggle-Rails
2
+ This gem packages [Taggle.js](https://github.com/okcoker/taggle.js) for easy inclusion in Rails applications.
3
+
4
+ ## Installation
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'taggle-rails'
9
+ ```
10
+
11
+ Add this line to your application's JavaScript manifest:
12
+ ```js
13
+ //= require taggle
14
+ ```
15
+
16
+ Taggle.js includes example CSS that can be used as a default styling for the tagging form. To use this, add this line to your applications's CSS manifest:
17
+ ```css
18
+ *= require taggle
19
+
20
+ ```
21
+
22
+ And then execute:
23
+ ```bash
24
+ $ bundle install
25
+ ```
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,944 @@
1
+ /* !
2
+ * @author Sean Coker <sean@seancoker.com>
3
+ * @version 1.12.0
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
+ * @todo Change this to just "delimiter: ','"
106
+ */
107
+ delimeter: ',',
108
+ delimiter: '',
109
+
110
+ /**
111
+ * Add an ID to each of the tags.
112
+ * @type {Boolean}
113
+ * @todo
114
+ * @deprecated make this the default in next version
115
+ */
116
+ attachTagId: false,
117
+
118
+ /**
119
+ * Tags that the user will be restricted to
120
+ * @type {Array}
121
+ */
122
+ allowedTags: [],
123
+
124
+ /**
125
+ * Tags that the user will not be able to add
126
+ * @type {Array}
127
+ */
128
+ disallowedTags: [],
129
+
130
+ /**
131
+ * Limit the number of tags that can be added
132
+ * @type {Number}
133
+ */
134
+ maxTags: null,
135
+
136
+ /**
137
+ * If within a form, you can specify the tab index flow
138
+ * @type {Number}
139
+ */
140
+ tabIndex: 1,
141
+
142
+ /**
143
+ * Placeholder string to be placed in an empty taggle field
144
+ * @type {String}
145
+ */
146
+ placeholder: 'Enter tags...',
147
+
148
+ /**
149
+ * Keycodes that will add a tag
150
+ * @type {Array}
151
+ */
152
+ submitKeys: [COMMA, TAB, ENTER],
153
+
154
+ /**
155
+ * Preserve case of tags being added ie
156
+ * "tag" is different than "Tag"
157
+ * @type {Boolean}
158
+ */
159
+ preserveCase: false,
160
+
161
+ // @todo bind callback hooks to instance
162
+
163
+ /**
164
+ * Function hook called with the to-be-added input DOM element.
165
+ *
166
+ * @param {HTMLElement} li The list item to be added
167
+ */
168
+ inputFormatter: noop,
169
+
170
+ /**
171
+ * Function hook called with the to-be-added tag DOM element.
172
+ * Use this function to edit the list item before it is appended
173
+ * to the DOM
174
+ * @param {HTMLElement} li The list item to be added
175
+ */
176
+ tagFormatter: noop,
177
+
178
+ /**
179
+ * Function hook called before a tag is added. Return false
180
+ * to prevent tag from being added
181
+ * @param {String} tag The tag to be added
182
+ */
183
+ onBeforeTagAdd: noop,
184
+
185
+ /**
186
+ * Function hook called when a tag is added
187
+ * @param {Event} event Event triggered when tag was added
188
+ * @param {String} tag The tag added
189
+ */
190
+ onTagAdd: noop,
191
+
192
+ /**
193
+ * Function hook called before a tag is removed. Return false
194
+ * to prevent tag from being removed
195
+ * @param {String} tag The tag to be removed
196
+ */
197
+ onBeforeTagRemove: retTrue,
198
+
199
+ /**
200
+ * Function hook called when a tag is removed
201
+ * @param {Event} event Event triggered when tag was removed
202
+ * @param {String} tag The tag removed
203
+ */
204
+ onTagRemove: noop
205
+ };
206
+
207
+ //////////////////////
208
+ // Helper functions //
209
+ //////////////////////
210
+
211
+ function _extend() {
212
+ var master = arguments[0];
213
+ for (var i = 1, l = arguments.length; i < l; i++) {
214
+ var object = arguments[i];
215
+ for (var key in object) {
216
+ if (object.hasOwnProperty(key)) {
217
+ master[key] = object[key];
218
+ }
219
+ }
220
+ }
221
+
222
+ return master;
223
+ }
224
+
225
+ function _isArray(arr) {
226
+ if (Array.isArray) {
227
+ return Array.isArray(arr);
228
+ }
229
+ return Object.prototype.toString.call(arr) === '[object Array]';
230
+ }
231
+
232
+ function _on(element, eventName, handler) {
233
+ if (element.addEventListener) {
234
+ element.addEventListener(eventName, handler, false);
235
+ }
236
+ else if (element.attachEvent) {
237
+ element.attachEvent('on' + eventName, handler);
238
+ }
239
+ else {
240
+ element['on' + eventName] = handler;
241
+ }
242
+ }
243
+
244
+ function _trim(str) {
245
+ return str.replace(/^\s+|\s+$/g, '');
246
+ }
247
+
248
+ function _setText(el, text) {
249
+ if (window.attachEvent && !window.addEventListener) { // <= IE8
250
+ el.innerText = text;
251
+ }
252
+ else {
253
+ el.textContent = text;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Constructor
259
+ * @param {Mixed} el ID of an element or the actual element
260
+ * @param {Object} options
261
+ */
262
+ var Taggle = function(el, options) {
263
+ // @todo uncomment this in next major version
264
+ // for (var key in (options || {})) {
265
+ // if (!DEFAULTS.hasOwnProperty(key)) {
266
+ // throw new Error('"' + key + '" is not a valid option.');
267
+ // }
268
+ // }
269
+
270
+ this.settings = _extend({}, DEFAULTS, options);
271
+ this.measurements = {
272
+ container: {
273
+ rect: null,
274
+ style: null,
275
+ padding: null
276
+ }
277
+ };
278
+ this.container = el;
279
+ this.tag = {
280
+ values: [],
281
+ elements: []
282
+ };
283
+ this.list = document.createElement('ul');
284
+ this.inputLi = document.createElement('li');
285
+ this.input = document.createElement('input');
286
+ this.sizer = document.createElement('div');
287
+ this.pasting = false;
288
+ this.placeholder = null;
289
+ this.data = null;
290
+
291
+ if (this.settings.placeholder) {
292
+ this.placeholder = document.createElement('span');
293
+ }
294
+
295
+ if (typeof el === 'string') {
296
+ this.container = document.getElementById(el);
297
+ }
298
+
299
+ this._id = 0;
300
+ this._setMeasurements();
301
+ this._setupTextarea();
302
+ this._attachEvents();
303
+ };
304
+
305
+ /**
306
+ * Gets all the layout measurements up front
307
+ */
308
+ Taggle.prototype._setMeasurements = function() {
309
+ this.measurements.container.rect = this.container.getBoundingClientRect();
310
+ this.measurements.container.style = window.getComputedStyle(this.container);
311
+
312
+ var style = this.measurements.container.style;
313
+ var lpad = parseInt(style['padding-left'] || style.paddingLeft, 10);
314
+ var rpad = parseInt(style['padding-right'] || style.paddingRight, 10);
315
+ var lborder = parseInt(style['border-left-width'] || style.borderLeftWidth, 10);
316
+ var rborder = parseInt(style['border-right-width'] || style.borderRightWidth, 10);
317
+
318
+ this.measurements.container.padding = lpad + rpad + lborder + rborder;
319
+ };
320
+
321
+ /**
322
+ * Setup the div container for tags to be entered
323
+ */
324
+ Taggle.prototype._setupTextarea = function() {
325
+ var fontSize;
326
+
327
+ this.list.className = 'taggle_list';
328
+ this.input.type = 'text';
329
+ // Make sure no left/right padding messes with the input sizing
330
+ this.input.style.paddingLeft = 0;
331
+ this.input.style.paddingRight = 0;
332
+ this.input.className = 'taggle_input';
333
+ this.input.tabIndex = this.settings.tabIndex;
334
+ this.sizer.className = 'taggle_sizer';
335
+
336
+ if (this.settings.tags.length) {
337
+ for (var i = 0, len = this.settings.tags.length; i < len; i++) {
338
+ var taggle = this._createTag(this.settings.tags[i]);
339
+ this.list.appendChild(taggle);
340
+ }
341
+ }
342
+
343
+ if (this.placeholder) {
344
+ this.placeholder.style.opacity = 0;
345
+ this.placeholder.classList.add('taggle_placeholder');
346
+ this.container.appendChild(this.placeholder);
347
+ _setText(this.placeholder, this.settings.placeholder);
348
+
349
+ if (!this.settings.tags.length) {
350
+ this._showPlaceholder();
351
+ }
352
+ }
353
+
354
+ var formattedInput = this.settings.inputFormatter(this.input);
355
+ if (formattedInput) {
356
+ this.input = formattedInput;
357
+ }
358
+
359
+ this.inputLi.appendChild(this.input);
360
+ this.list.appendChild(this.inputLi);
361
+ this.container.appendChild(this.list);
362
+ this.container.appendChild(this.sizer);
363
+ fontSize = window.getComputedStyle(this.input).fontSize;
364
+ this.sizer.style.fontSize = fontSize;
365
+ };
366
+
367
+ /**
368
+ * Attaches neccessary events
369
+ */
370
+ Taggle.prototype._attachEvents = function() {
371
+ var self = this;
372
+
373
+ if (this.settings.focusInputOnContainerClick) {
374
+ _on(this.container, 'click', function() {
375
+ self.input.focus();
376
+ });
377
+ }
378
+
379
+ _on(this.input, 'focus', this._focusInput.bind(this));
380
+ _on(this.input, 'blur', this._blurEvent.bind(this));
381
+ _on(this.input, 'keydown', this._keydownEvents.bind(this));
382
+ _on(this.input, 'keyup', this._keyupEvents.bind(this));
383
+ };
384
+
385
+ /**
386
+ * Resizes the hidden input where user types to fill in the
387
+ * width of the div
388
+ */
389
+ Taggle.prototype._fixInputWidth = function() {
390
+ var width;
391
+ var inputRect;
392
+ var rect;
393
+ var leftPos;
394
+ var padding;
395
+
396
+ this._setMeasurements();
397
+
398
+ // Reset width incase we've broken to the next line on a backspace erase
399
+ this._setInputWidth();
400
+
401
+ inputRect = this.input.getBoundingClientRect();
402
+ rect = this.measurements.container.rect;
403
+ width = ~~rect.width;
404
+ // Could probably just use right - left all the time
405
+ // but eh, this check is mostly for IE8
406
+ if (!width) {
407
+ width = ~~rect.right - ~~rect.left;
408
+ }
409
+ leftPos = ~~inputRect.left - ~~rect.left;
410
+ padding = this.measurements.container.padding;
411
+
412
+ this._setInputWidth(width - leftPos - padding);
413
+ };
414
+
415
+ /**
416
+ * Returns whether or not the specified tag text can be added
417
+ * @param {Event} e event causing the potentially added tag
418
+ * @param {String} text tag value
419
+ * @return {Boolean}
420
+ */
421
+ Taggle.prototype._canAdd = function(e, text) {
422
+ if (!text) {
423
+ return false;
424
+ }
425
+ var limit = this.settings.maxTags;
426
+ if (limit !== null && limit <= this.getTagValues().length) {
427
+ return false;
428
+ }
429
+
430
+ if (this.settings.onBeforeTagAdd(e, text) === false) {
431
+ return false;
432
+ }
433
+
434
+ if (!this.settings.allowDuplicates && this._hasDupes(text)) {
435
+ return false;
436
+ }
437
+
438
+ var sensitive = this.settings.preserveCase;
439
+ var allowed = this.settings.allowedTags;
440
+
441
+ if (allowed.length && !this._tagIsInArray(text, allowed, sensitive)) {
442
+ return false;
443
+ }
444
+
445
+ var disallowed = this.settings.disallowedTags;
446
+ if (disallowed.length && this._tagIsInArray(text, disallowed, sensitive)) {
447
+ return false;
448
+ }
449
+
450
+ return true;
451
+ };
452
+
453
+ /**
454
+ * Returns whether a string is in an array based on case sensitivity
455
+ *
456
+ * @param {String} text string to search for
457
+ * @param {Array} arr array of strings to search through
458
+ * @param {Boolean} caseSensitive
459
+ * @return {Boolean}
460
+ */
461
+ Taggle.prototype._tagIsInArray = function(text, arr, caseSensitive) {
462
+ if (caseSensitive) {
463
+ return arr.indexOf(text) !== -1;
464
+ }
465
+
466
+ var lowercased = [].slice.apply(arr).map(function(str) {
467
+ return str.toLowerCase();
468
+ });
469
+
470
+ return lowercased.indexOf(text) !== -1;
471
+ };
472
+
473
+ /**
474
+ * Appends tag with its corresponding input to the list
475
+ * @param {Event} e
476
+ * @param {String} text
477
+ */
478
+ Taggle.prototype._add = function(e, text) {
479
+ var self = this;
480
+ var values = text || '';
481
+
482
+ if (typeof text !== 'string') {
483
+ values = _trim(this.input.value);
484
+ }
485
+
486
+ var delimiter = this.settings.delimiter || this.settings.delimeter;
487
+
488
+ values.split(delimiter).map(function(val) {
489
+ return self._formatTag(val);
490
+ }).forEach(function(val) {
491
+ if (!self._canAdd(e, val)) {
492
+ return;
493
+ }
494
+
495
+ var li = self._createTag(val);
496
+ var lis = self.list.children;
497
+ var lastLi = lis[lis.length - 1];
498
+ self.list.insertBefore(li, lastLi);
499
+
500
+
501
+ val = self.tag.values[self.tag.values.length - 1];
502
+
503
+ self.settings.onTagAdd(e, val);
504
+
505
+ self.input.value = '';
506
+ self._fixInputWidth();
507
+ self._focusInput();
508
+ });
509
+ };
510
+
511
+ /**
512
+ * Removes last tag if it has already been probed
513
+ * @param {Event} e
514
+ */
515
+ Taggle.prototype._checkLastTag = function(e) {
516
+ e = e || window.event;
517
+
518
+ var taggles = this.container.querySelectorAll('.taggle');
519
+ var lastTaggle = taggles[taggles.length - 1];
520
+ var hotClass = 'taggle_hot';
521
+ var heldDown = this.input.classList.contains('taggle_back');
522
+
523
+ // prevent holding backspace from deleting all tags
524
+ if (this.input.value === '' && e.keyCode === BACKSPACE && !heldDown) {
525
+ if (lastTaggle.classList.contains(hotClass)) {
526
+ this.input.classList.add('taggle_back');
527
+ this._remove(lastTaggle, e);
528
+ this._fixInputWidth();
529
+ this._focusInput();
530
+ }
531
+ else {
532
+ lastTaggle.classList.add(hotClass);
533
+ }
534
+ }
535
+ else if (lastTaggle.classList.contains(hotClass)) {
536
+ lastTaggle.classList.remove(hotClass);
537
+ }
538
+ };
539
+
540
+ /**
541
+ * Setter for the hidden input.
542
+ * @param {Number} width
543
+ */
544
+ Taggle.prototype._setInputWidth = function(width) {
545
+ this.input.style.width = (width || 10) + 'px';
546
+ };
547
+
548
+ /**
549
+ * Checks global tags array if provided tag exists
550
+ * @param {String} text
551
+ * @return {Boolean}
552
+ */
553
+ Taggle.prototype._hasDupes = function(text) {
554
+ var needle = this.tag.values.indexOf(text);
555
+ var tagglelist = this.container.querySelector('.taggle_list');
556
+ var dupes;
557
+
558
+ if (this.settings.duplicateTagClass) {
559
+ dupes = tagglelist.querySelectorAll('.' + this.settings.duplicateTagClass);
560
+ for (var i = 0, len = dupes.length; i < len; i++) {
561
+ dupes[i].classList.remove(this.settings.duplicateTagClass);
562
+ }
563
+ }
564
+
565
+ // if found
566
+ if (needle > -1) {
567
+ if (this.settings.duplicateTagClass) {
568
+ tagglelist.childNodes[needle].classList.add(this.settings.duplicateTagClass);
569
+ }
570
+ return true;
571
+ }
572
+
573
+ return false;
574
+ };
575
+
576
+ /**
577
+ * Checks whether or not the key pressed is acceptable
578
+ * @param {Number} key code
579
+ * @return {Boolean}
580
+ */
581
+ Taggle.prototype._isConfirmKey = function(key) {
582
+ var confirmKey = false;
583
+
584
+ if (this.settings.submitKeys.indexOf(key) > -1) {
585
+ confirmKey = true;
586
+ }
587
+
588
+ return confirmKey;
589
+ };
590
+
591
+ // Event handlers
592
+
593
+ /**
594
+ * Handles focus state of div container.
595
+ */
596
+ Taggle.prototype._focusInput = function() {
597
+ this._fixInputWidth();
598
+
599
+ if (!this.container.classList.contains(this.settings.containerFocusClass)) {
600
+ this.container.classList.add(this.settings.containerFocusClass);
601
+ }
602
+
603
+ if (this.placeholder) {
604
+ this.placeholder.style.opacity = 0;
605
+ }
606
+ };
607
+
608
+ /**
609
+ * Runs all the events that need to happen on a blur
610
+ * @param {Event} e
611
+ */
612
+ Taggle.prototype._blurEvent = function(e) {
613
+ if (this.container.classList.contains(this.settings.containerFocusClass)) {
614
+ this.container.classList.remove(this.settings.containerFocusClass);
615
+ }
616
+
617
+ if (this.settings.saveOnBlur) {
618
+ e = e || window.event;
619
+
620
+ this._listenForEndOfContainer();
621
+
622
+ if (this.input.value !== '') {
623
+ this._confirmValidTagEvent(e);
624
+ return;
625
+ }
626
+
627
+ if (this.tag.values.length) {
628
+ this._checkLastTag(e);
629
+ }
630
+ }
631
+ else if (this.settings.clearOnBlur) {
632
+ this.input.value = '';
633
+ this._setInputWidth();
634
+ }
635
+
636
+ if (!this.tag.values.length && !this.input.value) {
637
+ this._showPlaceholder();
638
+ }
639
+ };
640
+
641
+ /**
642
+ * Runs all the events that need to run on keydown
643
+ * @param {Event} e
644
+ */
645
+ Taggle.prototype._keydownEvents = function(e) {
646
+ e = e || window.event;
647
+
648
+ var key = e.keyCode;
649
+ this.pasting = false;
650
+
651
+ this._listenForEndOfContainer();
652
+
653
+ if (key === 86 && e.metaKey) {
654
+ this.pasting = true;
655
+ }
656
+
657
+ if (this._isConfirmKey(key) && this.input.value !== '') {
658
+ this._confirmValidTagEvent(e);
659
+ return;
660
+ }
661
+
662
+ if (this.tag.values.length) {
663
+ this._checkLastTag(e);
664
+ }
665
+ };
666
+
667
+ /**
668
+ * Runs all the events that need to run on keyup
669
+ * @param {Event} e
670
+ */
671
+ Taggle.prototype._keyupEvents = function(e) {
672
+ e = e || window.event;
673
+
674
+ this.input.classList.remove('taggle_back');
675
+
676
+ _setText(this.sizer, this.input.value);
677
+
678
+ if (this.pasting && this.input.value !== '') {
679
+ this._add(e);
680
+ this.pasting = false;
681
+ }
682
+ };
683
+
684
+ /**
685
+ * Confirms the inputted value to be converted to a tag
686
+ * @param {Event} e
687
+ */
688
+ Taggle.prototype._confirmValidTagEvent = function(e) {
689
+ e = e || window.event;
690
+
691
+ // prevents from jumping out of textarea
692
+ if (e.preventDefault) {
693
+ e.preventDefault();
694
+ }
695
+ else {
696
+ e.returnValue = false;
697
+ }
698
+
699
+ this._add(e);
700
+ };
701
+
702
+ /**
703
+ * Approximates when the hidden input should break to the next line
704
+ */
705
+ Taggle.prototype._listenForEndOfContainer = function() {
706
+ var width = this.sizer.getBoundingClientRect().width;
707
+ var max = this.measurements.container.rect.width - this.measurements.container.padding;
708
+ var size = parseInt(this.sizer.style.fontSize, 10);
709
+
710
+ // 1.5 just seems to be a good multiplier here
711
+ if (width + (size * 1.5) > parseInt(this.input.style.width, 10)) {
712
+ this.input.style.width = max + 'px';
713
+ }
714
+ };
715
+
716
+ Taggle.prototype._createTag = function(text) {
717
+ var li = document.createElement('li');
718
+ var close = document.createElement('button');
719
+ var hidden = document.createElement('input');
720
+ var span = document.createElement('span');
721
+
722
+ text = this._formatTag(text);
723
+
724
+ close.innerHTML = '&times;';
725
+ close.className = 'close';
726
+ close.type = 'button';
727
+ _on(close, 'click', this._remove.bind(this, close));
728
+
729
+ _setText(span, text);
730
+ span.className = 'taggle_text';
731
+
732
+ li.className = 'taggle ' + this.settings.additionalTagClasses;
733
+
734
+ hidden.type = 'hidden';
735
+ hidden.value = text;
736
+ hidden.name = this.settings.hiddenInputName;
737
+
738
+ li.appendChild(span);
739
+ li.appendChild(close);
740
+ li.appendChild(hidden);
741
+
742
+ var formatted = this.settings.tagFormatter(li);
743
+
744
+ if (typeof formatted !== 'undefined') {
745
+ li = formatted;
746
+ }
747
+
748
+ if (!(li instanceof HTMLElement) || li.tagName !== 'LI') {
749
+ throw new Error('tagFormatter must return an li element');
750
+ }
751
+
752
+ if (this.settings.attachTagId) {
753
+ this._id += 1;
754
+ text = {
755
+ text: text,
756
+ id: this._id
757
+ };
758
+ }
759
+
760
+ this.tag.values.push(text);
761
+ this.tag.elements.push(li);
762
+
763
+ return li;
764
+ };
765
+
766
+ Taggle.prototype._showPlaceholder = function() {
767
+ if (this.placeholder) {
768
+ this.placeholder.style.opacity = 1;
769
+ }
770
+ };
771
+
772
+ /**
773
+ * Removes tag from the tags collection
774
+ * @param {li} li List item to remove
775
+ * @param {Event} e
776
+ */
777
+ Taggle.prototype._remove = function(li, e) {
778
+ var self = this;
779
+ var text;
780
+ var elem;
781
+ var index;
782
+
783
+ if (li.tagName.toLowerCase() !== 'li') {
784
+ li = li.parentNode;
785
+ }
786
+
787
+ elem = (li.tagName.toLowerCase() === 'a') ? li.parentNode : li;
788
+ index = this.tag.elements.indexOf(elem);
789
+
790
+ text = this.tag.values[index];
791
+
792
+ function done(error) {
793
+ if (error) {
794
+ return;
795
+ }
796
+
797
+ li.parentNode.removeChild(li);
798
+
799
+ // Going to assume the indicies match for now
800
+ self.tag.elements.splice(index, 1);
801
+ self.tag.values.splice(index, 1);
802
+
803
+ self.settings.onTagRemove(e, text);
804
+
805
+ self._focusInput();
806
+ }
807
+
808
+ var ret = this.settings.onBeforeTagRemove(e, text, done);
809
+
810
+ if (!ret) {
811
+ return;
812
+ }
813
+
814
+ done();
815
+ };
816
+
817
+ /**
818
+ * Format the text for a tag
819
+ * @param {String} text Tag text
820
+ * @return {String}
821
+ */
822
+ Taggle.prototype._formatTag = function(text) {
823
+ return this.settings.preserveCase ? text : text.toLowerCase();
824
+ };
825
+
826
+ Taggle.prototype.getTags = function() {
827
+ return {
828
+ elements: this.getTagElements(),
829
+ values: this.getTagValues()
830
+ };
831
+ };
832
+
833
+ // @todo
834
+ // @deprecated use getTags().elements
835
+ Taggle.prototype.getTagElements = function() {
836
+ return this.tag.elements;
837
+ };
838
+
839
+ // @todo
840
+ // @deprecated use getTags().values
841
+ Taggle.prototype.getTagValues = function() {
842
+ return [].slice.apply(this.tag.values);
843
+ };
844
+
845
+ Taggle.prototype.getInput = function() {
846
+ return this.input;
847
+ };
848
+
849
+ Taggle.prototype.getContainer = function() {
850
+ return this.container;
851
+ };
852
+
853
+ Taggle.prototype.add = function(text) {
854
+ var isArr = _isArray(text);
855
+
856
+ if (isArr) {
857
+ for (var i = 0, len = text.length; i < len; i++) {
858
+ if (typeof text[i] === 'string') {
859
+ this._add(null, text[i]);
860
+ }
861
+ }
862
+ }
863
+ else {
864
+ this._add(null, text);
865
+ }
866
+
867
+ return this;
868
+ };
869
+
870
+ Taggle.prototype.remove = function(text, all) {
871
+ var len = this.tag.values.length - 1;
872
+ var found = false;
873
+
874
+ while (len > -1) {
875
+ var tagText = this.tag.values[len];
876
+ if (this.settings.attachTagId) {
877
+ tagText = tagText.text;
878
+ }
879
+
880
+ if (tagText === text) {
881
+ found = true;
882
+ this._remove(this.tag.elements[len]);
883
+ }
884
+
885
+ if (found && !all) {
886
+ break;
887
+ }
888
+
889
+ len--;
890
+ }
891
+
892
+ return this;
893
+ };
894
+
895
+ Taggle.prototype.removeAll = function() {
896
+ for (var i = this.tag.values.length - 1; i >= 0; i--) {
897
+ this._remove(this.tag.elements[i]);
898
+ }
899
+
900
+ this._showPlaceholder();
901
+
902
+ return this;
903
+ };
904
+
905
+ Taggle.prototype.setOptions = function(options) {
906
+ this.settings = _extend({}, this.settings, options || {});
907
+
908
+ return this;
909
+ };
910
+
911
+ Taggle.prototype.enable = function() {
912
+ var buttons = [].slice.call(this.container.querySelectorAll('button'));
913
+ var inputs = [].slice.call(this.container.querySelectorAll('input'));
914
+
915
+ buttons.concat(inputs).forEach(function(el) {
916
+ el.removeAttribute('disabled');
917
+ });
918
+
919
+ return this;
920
+ };
921
+
922
+ Taggle.prototype.disable = function() {
923
+ var buttons = [].slice.call(this.container.querySelectorAll('button'));
924
+ var inputs = [].slice.call(this.container.querySelectorAll('input'));
925
+
926
+ buttons.concat(inputs).forEach(function(el) {
927
+ el.setAttribute('disabled', '');
928
+ });
929
+
930
+ return this;
931
+ };
932
+
933
+ Taggle.prototype.setData = function(data) {
934
+ this.data = data;
935
+
936
+ return this;
937
+ };
938
+
939
+ Taggle.prototype.getData = function() {
940
+ return this.data;
941
+ };
942
+
943
+ return Taggle;
944
+ }));