tawork 0.0.17 → 0.0.18

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,1022 @@
1
+ /*!
2
+ * bootstrap-tokenfield
3
+ * https://github.com/sliptree/bootstrap-tokenfield
4
+ * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
5
+ */
6
+
7
+ (function (factory) {
8
+ if (typeof define === 'function' && define.amd) {
9
+ // AMD. Register as an anonymous module.
10
+ define(['jquery'], factory);
11
+ } else if (typeof exports === 'object') {
12
+ // For CommonJS and CommonJS-like environments where a window with jQuery
13
+ // is present, execute the factory with the jQuery instance from the window object
14
+ // For environments that do not inherently posses a window with a document
15
+ // (such as Node.js), expose a Tokenfield-making factory as module.exports
16
+ // This accentuates the need for the creation of a real window or passing in a jQuery instance
17
+ // e.g. require("bootstrap-tokenfield")(window); or require("bootstrap-tokenfield")($);
18
+ module.exports = global.window && global.window.$ ?
19
+ factory( global.window.$ ) :
20
+ function( input ) {
21
+ if ( !input.$ && !input.fn ) {
22
+ throw new Error( "Tokenfield requires a window object with jQuery or a jQuery instance" );
23
+ }
24
+ return factory( input.$ || input );
25
+ };
26
+ } else {
27
+ // Browser globals
28
+ factory(jQuery);
29
+ }
30
+ }(function ($, window) {
31
+
32
+ "use strict"; // jshint ;_;
33
+
34
+ /* TOKENFIELD PUBLIC CLASS DEFINITION
35
+ * ============================== */
36
+
37
+ var Tokenfield = function (element, options) {
38
+ var _self = this
39
+
40
+ this.$element = $(element)
41
+ this.textDirection = this.$element.css('direction');
42
+
43
+ // Extend options
44
+ this.options = $.extend(true, {}, $.fn.tokenfield.defaults, { tokens: this.$element.val() }, this.$element.data(), options)
45
+
46
+ // Setup delimiters and trigger keys
47
+ this._delimiters = (typeof this.options.delimiter === 'string') ? [this.options.delimiter] : this.options.delimiter
48
+ this._triggerKeys = $.map(this._delimiters, function (delimiter) {
49
+ return delimiter.charCodeAt(0);
50
+ });
51
+ this._firstDelimiter = this._delimiters[0];
52
+
53
+ // Check for whitespace, dash and special characters
54
+ var whitespace = $.inArray(' ', this._delimiters)
55
+ , dash = $.inArray('-', this._delimiters)
56
+
57
+ if (whitespace >= 0)
58
+ this._delimiters[whitespace] = '\\s'
59
+
60
+ if (dash >= 0) {
61
+ delete this._delimiters[dash]
62
+ this._delimiters.unshift('-')
63
+ }
64
+
65
+ var specialCharacters = ['\\', '$', '[', '{', '^', '.', '|', '?', '*', '+', '(', ')']
66
+ $.each(this._delimiters, function (index, char) {
67
+ var pos = $.inArray(char, specialCharacters)
68
+ if (pos >= 0) _self._delimiters[index] = '\\' + char;
69
+ });
70
+
71
+ // Store original input width
72
+ var elRules = (window && typeof window.getMatchedCSSRules === 'function') ? window.getMatchedCSSRules( element ) : null
73
+ , elStyleWidth = element.style.width
74
+ , elCSSWidth
75
+ , elWidth = this.$element.width()
76
+
77
+ if (elRules) {
78
+ $.each( elRules, function (i, rule) {
79
+ if (rule.style.width) {
80
+ elCSSWidth = rule.style.width;
81
+ }
82
+ });
83
+ }
84
+
85
+ // Move original input out of the way
86
+ var hidingPosition = $('body').css('direction') === 'rtl' ? 'right' : 'left',
87
+ originalStyles = { position: this.$element.css('position') };
88
+ originalStyles[hidingPosition] = this.$element.css(hidingPosition);
89
+
90
+ this.$element
91
+ .data('original-styles', originalStyles)
92
+ .data('original-tabindex', this.$element.prop('tabindex'))
93
+ .css('position', 'absolute')
94
+ .css(hidingPosition, '-10000px')
95
+ .prop('tabindex', -1)
96
+
97
+ // Create a wrapper
98
+ this.$wrapper = $('<div class="tokenfield form-control" />')
99
+ if (this.$element.hasClass('input-lg')) this.$wrapper.addClass('input-lg')
100
+ if (this.$element.hasClass('input-sm')) this.$wrapper.addClass('input-sm')
101
+ if (this.textDirection === 'rtl') this.$wrapper.addClass('rtl')
102
+
103
+ // Create a new input
104
+ var id = this.$element.prop('id') || new Date().getTime() + '' + Math.floor((1 + Math.random()) * 100)
105
+ this.$input = $('<input type="text" class="token-input" autocomplete="off" />')
106
+ .appendTo( this.$wrapper )
107
+ .prop( 'placeholder', this.$element.prop('placeholder') )
108
+ .prop( 'id', id + '-tokenfield' )
109
+ .prop( 'tabindex', this.$element.data('original-tabindex') )
110
+
111
+ // Re-route original input label to new input
112
+ var $label = $( 'label[for="' + this.$element.prop('id') + '"]' )
113
+ if ( $label.length ) {
114
+ $label.prop( 'for', this.$input.prop('id') )
115
+ }
116
+
117
+ // Set up a copy helper to handle copy & paste
118
+ this.$copyHelper = $('<input type="text" />').css('position', 'absolute').css(hidingPosition, '-10000px').prop('tabindex', -1).prependTo( this.$wrapper )
119
+
120
+ // Set wrapper width
121
+ if (elStyleWidth) {
122
+ this.$wrapper.css('width', elStyleWidth);
123
+ }
124
+ else if (elCSSWidth) {
125
+ this.$wrapper.css('width', elCSSWidth);
126
+ }
127
+ // If input is inside inline-form with no width set, set fixed width
128
+ else if (this.$element.parents('.form-inline').length) {
129
+ this.$wrapper.width( elWidth )
130
+ }
131
+
132
+ // Set tokenfield disabled, if original or fieldset input is disabled
133
+ if (this.$element.prop('disabled') || this.$element.parents('fieldset[disabled]').length) {
134
+ this.disable();
135
+ }
136
+
137
+ // Set up mirror for input auto-sizing
138
+ this.$mirror = $('<span style="position:absolute; top:-999px; left:0; white-space:pre;"/>');
139
+ this.$input.css('min-width', this.options.minWidth + 'px')
140
+ $.each([
141
+ 'fontFamily',
142
+ 'fontSize',
143
+ 'fontWeight',
144
+ 'fontStyle',
145
+ 'letterSpacing',
146
+ 'textTransform',
147
+ 'wordSpacing',
148
+ 'textIndent'
149
+ ], function (i, val) {
150
+ _self.$mirror[0].style[val] = _self.$input.css(val);
151
+ });
152
+ this.$mirror.appendTo( 'body' )
153
+
154
+ // Insert tokenfield to HTML
155
+ this.$wrapper.insertBefore( this.$element )
156
+ this.$element.prependTo( this.$wrapper )
157
+
158
+ // Calculate inner input width
159
+ this.update()
160
+
161
+ // Create initial tokens, if any
162
+ this.setTokens(this.options.tokens, false, false)
163
+
164
+ // Start listening to events
165
+ this.listen()
166
+
167
+ // Initialize autocomplete, if necessary
168
+ if ( ! $.isEmptyObject( this.options.autocomplete ) ) {
169
+ var side = this.textDirection === 'rtl' ? 'right' : 'left'
170
+ var autocompleteOptions = $.extend({
171
+ minLength: this.options.showAutocompleteOnFocus ? 0 : null,
172
+ position: { my: side + " top", at: side + " bottom", of: this.$wrapper }
173
+ }, this.options.autocomplete )
174
+ this.$input.autocomplete( autocompleteOptions )
175
+ }
176
+
177
+ // Initialize typeahead, if necessary
178
+ if ( ! $.isEmptyObject( this.options.typeahead ) ) {
179
+ var typeaheadOptions = $.extend({
180
+ minLength: this.options.showAutocompleteOnFocus ? 0 : null
181
+ }, this.options.typeahead)
182
+ this.$input.typeahead( null, typeaheadOptions )
183
+ this.typeahead = true
184
+ }
185
+
186
+ this.$element.trigger('tokenfield:initialize')
187
+ }
188
+
189
+ Tokenfield.prototype = {
190
+
191
+ constructor: Tokenfield
192
+
193
+ , createToken: function (attrs, triggerChange) {
194
+ if (typeof attrs === 'string') {
195
+ attrs = { value: attrs, label: attrs }
196
+ }
197
+
198
+ if (typeof triggerChange === 'undefined') {
199
+ triggerChange = true
200
+ }
201
+
202
+ var _self = this
203
+ , value = $.trim(attrs.value)
204
+ , label = attrs.label && attrs.label.length ? $.trim(attrs.label) : value
205
+
206
+ if (!value.length || !label.length || value.length < this.options.minLength) return
207
+
208
+ if (this.options.limit && this.getTokens().length >= this.options.limit) return
209
+
210
+ // Allow changing token data before creating it
211
+ var prepareEvent = $.Event('tokenfield:preparetoken')
212
+ prepareEvent.token = {
213
+ value: value,
214
+ label: label
215
+ }
216
+ this.$element.trigger( prepareEvent )
217
+
218
+ if (!prepareEvent.token) return
219
+
220
+ value = prepareEvent.token.value
221
+ label = prepareEvent.token.label
222
+
223
+ // Check for duplicates
224
+ if (!this.options.allowDuplicates && $.grep(this.getTokens(), function (token) {
225
+ return token.value === value
226
+ }).length) {
227
+ // Allow listening to when duplicates get prevented
228
+ var preventDuplicateEvent = $.Event('tokenfield:preventduplicate')
229
+ preventDuplicateEvent.token = {
230
+ value: value,
231
+ label: label
232
+ }
233
+ this.$element.trigger( preventDuplicateEvent )
234
+ // Add duplicate warning class to existing token for 250ms
235
+ var duplicate = this.$wrapper.find( '.token[data-value="' + value + '"]' ).addClass('duplicate')
236
+ setTimeout(function() {
237
+ duplicate.removeClass('duplicate');
238
+ }, 250)
239
+ return false
240
+ }
241
+
242
+ var token = $('<div class="token" />')
243
+ .attr('data-value', value)
244
+ .append('<span class="token-label" />')
245
+ .append('<a href="#" class="close" tabindex="-1">&times;</a>')
246
+
247
+ // Insert token into HTML
248
+ if (this.$input.hasClass('tt-input')) {
249
+ this.$input.parent().before( token )
250
+ } else {
251
+ this.$input.before( token )
252
+ }
253
+ this.$input.css('width', this.options.minWidth + 'px')
254
+
255
+ var tokenLabel = token.find('.token-label')
256
+ , closeButton = token.find('.close')
257
+
258
+ // Determine maximum possible token label width
259
+ if (!this.maxTokenWidth) {
260
+ this.maxTokenWidth =
261
+ this.$wrapper.width() - closeButton.outerWidth() -
262
+ parseInt(closeButton.css('margin-left'), 10) -
263
+ parseInt(closeButton.css('margin-right'), 10) -
264
+ parseInt(token.css('border-left-width'), 10) -
265
+ parseInt(token.css('border-right-width'), 10) -
266
+ parseInt(token.css('padding-left'), 10) -
267
+ parseInt(token.css('padding-right'), 10)
268
+ parseInt(tokenLabel.css('border-left-width'), 10) -
269
+ parseInt(tokenLabel.css('border-right-width'), 10) -
270
+ parseInt(tokenLabel.css('padding-left'), 10) -
271
+ parseInt(tokenLabel.css('padding-right'), 10)
272
+ parseInt(tokenLabel.css('margin-left'), 10) -
273
+ parseInt(tokenLabel.css('margin-right'), 10)
274
+ }
275
+
276
+ tokenLabel
277
+ .text(label)
278
+ .css('max-width', this.maxTokenWidth)
279
+
280
+ // Listen to events
281
+ token
282
+ .on('mousedown', function (e) {
283
+ if (_self.disabled) return false;
284
+ _self.preventDeactivation = true
285
+ })
286
+ .on('click', function (e) {
287
+ if (_self.disabled) return false;
288
+ _self.preventDeactivation = false
289
+
290
+ if (e.ctrlKey || e.metaKey) {
291
+ e.preventDefault()
292
+ return _self.toggle( token )
293
+ }
294
+
295
+ _self.activate( token, e.shiftKey, e.shiftKey )
296
+ })
297
+ .on('dblclick', function (e) {
298
+ if (_self.disabled || !_self.options.allowEditing ) return false;
299
+ _self.edit( token )
300
+ })
301
+
302
+ closeButton
303
+ .on('click', $.proxy(this.remove, this))
304
+
305
+ var createEvent = $.Event('tokenfield:createtoken')
306
+ createEvent.token = prepareEvent.token
307
+ createEvent.relatedTarget = token.get(0)
308
+ this.$element.trigger( createEvent )
309
+
310
+ var changeEvent = $.Event('change')
311
+ changeEvent.initiator = 'tokenfield'
312
+ if (triggerChange) {
313
+ this.$element.val( this.getTokensList() ).trigger( changeEvent )
314
+ }
315
+ this.update()
316
+
317
+ return this.$input.get(0)
318
+ }
319
+
320
+ , setTokens: function (tokens, add, triggerChange) {
321
+ if (!tokens) return
322
+
323
+ if (!add) this.$wrapper.find('.token').remove()
324
+
325
+ if (typeof triggerChange === 'undefined') {
326
+ triggerChange = true
327
+ }
328
+
329
+ if (typeof tokens === 'string') {
330
+ if (this._delimiters.length) {
331
+ // Split based on delimiters
332
+ tokens = tokens.split( new RegExp( '[' + this._delimiters.join('') + ']' ) )
333
+ } else {
334
+ tokens = [tokens];
335
+ }
336
+ }
337
+
338
+ var _self = this
339
+ $.each(tokens, function (i, token) {
340
+ _self.createToken(token, triggerChange)
341
+ })
342
+
343
+ return this.$element.get(0)
344
+ }
345
+
346
+ , getTokenData: function(token) {
347
+ var data = token.map(function() {
348
+ var $token = $(this);
349
+ return {
350
+ value: $token.attr('data-value'),
351
+ label: $token.find('.token-label').text()
352
+ }
353
+ }).get();
354
+
355
+ if (data.length == 1) {
356
+ data = data[0];
357
+ }
358
+
359
+ return data;
360
+ }
361
+
362
+ , getTokens: function(active) {
363
+ var self = this
364
+ , tokens = []
365
+ , activeClass = active ? '.active' : '' // get active tokens only
366
+ this.$wrapper.find( '.token' + activeClass ).each( function() {
367
+ tokens.push( self.getTokenData( $(this) ) )
368
+ })
369
+ return tokens
370
+ }
371
+
372
+ , getTokensList: function(delimiter, beautify, active) {
373
+ delimiter = delimiter || this._firstDelimiter
374
+ beautify = ( typeof beautify !== 'undefined' && beautify !== null ) ? beautify : this.options.beautify
375
+
376
+ var separator = delimiter + ( beautify && delimiter !== ' ' ? ' ' : '')
377
+ return $.map( this.getTokens(active), function (token) {
378
+ return token.value
379
+ }).join(separator)
380
+ }
381
+
382
+ , getInput: function() {
383
+ return this.$input.val()
384
+ }
385
+
386
+ , listen: function () {
387
+ var _self = this
388
+
389
+ this.$element
390
+ .on('change', $.proxy(this.change, this))
391
+
392
+ this.$wrapper
393
+ .on('mousedown',$.proxy(this.focusInput, this))
394
+
395
+ this.$input
396
+ .on('focus', $.proxy(this.focus, this))
397
+ .on('blur', $.proxy(this.blur, this))
398
+ .on('paste', $.proxy(this.paste, this))
399
+ .on('keydown', $.proxy(this.keydown, this))
400
+ .on('keypress', $.proxy(this.keypress, this))
401
+ .on('keyup', $.proxy(this.keyup, this))
402
+
403
+ this.$copyHelper
404
+ .on('focus', $.proxy(this.focus, this))
405
+ .on('blur', $.proxy(this.blur, this))
406
+ .on('keydown', $.proxy(this.keydown, this))
407
+ .on('keyup', $.proxy(this.keyup, this))
408
+
409
+ // Secondary listeners for input width calculation
410
+ this.$input
411
+ .on('keypress', $.proxy(this.update, this))
412
+ .on('keyup', $.proxy(this.update, this))
413
+
414
+ this.$input
415
+ .on('autocompletecreate', function() {
416
+ // Set minimum autocomplete menu width
417
+ var $_menuElement = $(this).data('ui-autocomplete').menu.element
418
+
419
+ var minWidth = _self.$wrapper.outerWidth() -
420
+ parseInt( $_menuElement.css('border-left-width'), 10 ) -
421
+ parseInt( $_menuElement.css('border-right-width'), 10 )
422
+
423
+ $_menuElement.css( 'min-width', minWidth + 'px' )
424
+ })
425
+ .on('autocompleteselect', function (e, ui) {
426
+ if (_self.createToken( ui.item )) {
427
+ _self.$input.val('')
428
+ if (_self.$input.data( 'edit' )) {
429
+ _self.unedit(true)
430
+ }
431
+ }
432
+ return false
433
+ })
434
+ .on('typeahead:selected', function (e, datum, dataset) {
435
+ // Create token
436
+ if (_self.createToken( datum )) {
437
+ _self.$input.typeahead('val', '')
438
+ if (_self.$input.data( 'edit' )) {
439
+ _self.unedit(true)
440
+ }
441
+ }
442
+ })
443
+ .on('typeahead:autocompleted', function (e, datum, dataset) {
444
+ _self.createToken( _self.$input.val() )
445
+ _self.$input.typeahead('val', '')
446
+ if (_self.$input.data( 'edit' )) {
447
+ _self.unedit(true)
448
+ }
449
+ })
450
+
451
+ // Listen to window resize
452
+ $(window).on('resize', $.proxy(this.update, this ))
453
+
454
+ }
455
+
456
+ , keydown: function (e) {
457
+
458
+ if (!this.focused) return
459
+
460
+ var _self = this
461
+
462
+ switch(e.keyCode) {
463
+ case 8: // backspace
464
+ if (!this.$input.is(document.activeElement)) break
465
+ this.lastInputValue = this.$input.val()
466
+ break
467
+
468
+ case 37: // left arrow
469
+ leftRight( this.textDirection === 'rtl' ? 'next': 'prev' )
470
+ break
471
+
472
+ case 38: // up arrow
473
+ upDown('prev')
474
+ break
475
+
476
+ case 39: // right arrow
477
+ leftRight( this.textDirection === 'rtl' ? 'prev': 'next' )
478
+ break
479
+
480
+ case 40: // down arrow
481
+ upDown('next')
482
+ break
483
+
484
+ case 65: // a (to handle ctrl + a)
485
+ if (this.$input.val().length > 0 || !(e.ctrlKey || e.metaKey)) break
486
+ this.activateAll()
487
+ e.preventDefault()
488
+ break
489
+
490
+ case 9: // tab
491
+ case 13: // enter
492
+
493
+ // We will handle creating tokens from autocomplete in autocomplete events
494
+ if (this.$input.data('ui-autocomplete') && this.$input.data('ui-autocomplete').menu.element.find("li:has(a.ui-state-focus)").length) break
495
+
496
+ // We will handle creating tokens from typeahead in typeahead events
497
+ if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-cursor').length ) break
498
+ if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-hint').val().length) break
499
+
500
+ // Create token
501
+ if (this.$input.is(document.activeElement) && this.$input.val().length || this.$input.data('edit')) {
502
+ return this.createTokensFromInput(e, this.$input.data('edit'));
503
+ }
504
+
505
+ // Edit token
506
+ if (e.keyCode === 13) {
507
+ if (!this.$copyHelper.is(document.activeElement) || this.$wrapper.find('.token.active').length !== 1) break
508
+ if (!_self.options.allowEditing) break
509
+ this.edit( this.$wrapper.find('.token.active') )
510
+ }
511
+ }
512
+
513
+ function leftRight(direction) {
514
+ if (_self.$input.is(document.activeElement)) {
515
+ if (_self.$input.val().length > 0) return
516
+
517
+ direction += 'All'
518
+ var token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction]('.token:first') : _self.$input[direction]('.token:first')
519
+ if (!token.length) return
520
+
521
+ _self.preventInputFocus = true
522
+ _self.preventDeactivation = true
523
+
524
+ _self.activate( token )
525
+ e.preventDefault()
526
+
527
+ } else {
528
+ _self[direction]( e.shiftKey )
529
+ e.preventDefault()
530
+ }
531
+ }
532
+
533
+ function upDown(direction) {
534
+ if (!e.shiftKey) return
535
+
536
+ if (_self.$input.is(document.activeElement)) {
537
+ if (_self.$input.val().length > 0) return
538
+
539
+ var token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction + 'All']('.token:first') : _self.$input[direction + 'All']('.token:first')
540
+ if (!token.length) return
541
+
542
+ _self.activate( token )
543
+ }
544
+
545
+ var opposite = direction === 'prev' ? 'next' : 'prev'
546
+ , position = direction === 'prev' ? 'first' : 'last'
547
+
548
+ _self.firstActiveToken[opposite + 'All']('.token').each(function() {
549
+ _self.deactivate( $(this) )
550
+ })
551
+
552
+ _self.activate( _self.$wrapper.find('.token:' + position), true, true )
553
+ e.preventDefault()
554
+ }
555
+
556
+ this.lastKeyDown = e.keyCode
557
+ }
558
+
559
+ , keypress: function(e) {
560
+ this.lastKeyPressCode = e.keyCode
561
+ this.lastKeyPressCharCode = e.charCode
562
+
563
+ // Comma
564
+ if ($.inArray( e.charCode, this._triggerKeys) !== -1 && this.$input.is(document.activeElement)) {
565
+ if (this.$input.val()) {
566
+ this.createTokensFromInput(e)
567
+ }
568
+ return false;
569
+ }
570
+ }
571
+
572
+ , keyup: function (e) {
573
+ this.preventInputFocus = false
574
+
575
+ if (!this.focused) return
576
+
577
+ switch(e.keyCode) {
578
+ case 8: // backspace
579
+ if (this.$input.is(document.activeElement)) {
580
+ if (this.$input.val().length || this.lastInputValue.length && this.lastKeyDown === 8) break
581
+
582
+ this.preventDeactivation = true
583
+ var prev = this.$input.hasClass('tt-input') ? this.$input.parent().prevAll('.token:first') : this.$input.prevAll('.token:first')
584
+
585
+ if (!prev.length) break
586
+
587
+ this.activate( prev )
588
+ } else {
589
+ this.remove(e)
590
+ }
591
+ break
592
+
593
+ case 46: // delete
594
+ this.remove(e, 'next')
595
+ break
596
+ }
597
+ this.lastKeyUp = e.keyCode
598
+ }
599
+
600
+ , focus: function (e) {
601
+ this.focused = true
602
+ this.$wrapper.addClass('focus')
603
+
604
+ if (this.$input.is(document.activeElement)) {
605
+ this.$wrapper.find('.active').removeClass('active')
606
+ this.firstActiveToken = null
607
+
608
+ if (this.options.showAutocompleteOnFocus) {
609
+ this.search()
610
+ }
611
+ }
612
+ }
613
+
614
+ , blur: function (e) {
615
+
616
+ this.focused = false
617
+ this.$wrapper.removeClass('focus')
618
+
619
+ if (!this.preventDeactivation && !this.$element.is(document.activeElement)) {
620
+ this.$wrapper.find('.active').removeClass('active')
621
+ this.firstActiveToken = null
622
+ }
623
+
624
+ if (!this.preventCreateTokens && (this.$input.data('edit') && !this.$input.is(document.activeElement) || this.options.createTokensOnBlur )) {
625
+ this.createTokensFromInput(e)
626
+ }
627
+
628
+ this.preventDeactivation = false
629
+ this.preventCreateTokens = false
630
+ }
631
+
632
+ , paste: function (e) {
633
+ var _self = this
634
+
635
+ // Add tokens to existing ones
636
+ setTimeout(function () {
637
+ _self.createTokensFromInput(e)
638
+ }, 1)
639
+ }
640
+
641
+ , change: function (e) {
642
+ if ( e.initiator === 'tokenfield' ) return // Prevent loops
643
+
644
+ this.setTokens( this.$element.val() )
645
+ }
646
+
647
+ , createTokensFromInput: function (e, focus) {
648
+ if (this.$input.val().length < this.options.minLength)
649
+ return // No input, simply return
650
+
651
+ var tokensBefore = this.getTokensList()
652
+ this.setTokens( this.$input.val(), true )
653
+
654
+ if (tokensBefore == this.getTokensList() && this.$input.val().length)
655
+ return false // No tokens were added, do nothing (prevent form submit)
656
+
657
+ if (this.$input.hasClass('tt-input')) {
658
+ // Typeahead acts weird when simply setting input value to empty,
659
+ // so we set the query to empty instead
660
+ this.$input.typeahead('val', '')
661
+ } else {
662
+ this.$input.val('')
663
+ }
664
+
665
+ if (this.$input.data( 'edit' )) {
666
+ this.unedit(focus)
667
+ }
668
+
669
+ return false // Prevent form being submitted
670
+ }
671
+
672
+ , next: function (add) {
673
+ if (add) {
674
+ var firstActive = this.$wrapper.find('.active:first')
675
+ , deactivate = firstActive && this.firstActiveToken ? firstActive.index() < this.firstActiveToken.index() : false
676
+
677
+ if (deactivate) return this.deactivate( firstActive )
678
+ }
679
+
680
+ var active = this.$wrapper.find('.active:last')
681
+ , next = active.nextAll('.token:first')
682
+
683
+ if (!next.length) {
684
+ this.$input.focus()
685
+ return
686
+ }
687
+
688
+ this.activate(next, add)
689
+ }
690
+
691
+ , prev: function (add) {
692
+
693
+ if (add) {
694
+ var lastActive = this.$wrapper.find('.active:last')
695
+ , deactivate = lastActive && this.firstActiveToken ? lastActive.index() > this.firstActiveToken.index() : false
696
+
697
+ if (deactivate) return this.deactivate( lastActive )
698
+ }
699
+
700
+ var active = this.$wrapper.find('.active:first')
701
+ , prev = active.prevAll('.token:first')
702
+
703
+ if (!prev.length) {
704
+ prev = this.$wrapper.find('.token:first')
705
+ }
706
+
707
+ if (!prev.length && !add) {
708
+ this.$input.focus()
709
+ return
710
+ }
711
+
712
+ this.activate( prev, add )
713
+ }
714
+
715
+ , activate: function (token, add, multi, remember) {
716
+
717
+ if (!token) return
718
+
719
+ if (this.$wrapper.find('.token.active').length === this.$wrapper.find('.token').length) return
720
+
721
+ if (typeof remember === 'undefined') var remember = true
722
+
723
+ if (multi) var add = true
724
+
725
+ this.$copyHelper.focus()
726
+
727
+ if (!add) {
728
+ this.$wrapper.find('.active').removeClass('active')
729
+ if (remember) {
730
+ this.firstActiveToken = token
731
+ } else {
732
+ delete this.firstActiveToken
733
+ }
734
+ }
735
+
736
+ if (multi && this.firstActiveToken) {
737
+ // Determine first active token and the current tokens indicies
738
+ // Account for the 1 hidden textarea by subtracting 1 from both
739
+ var i = this.firstActiveToken.index() - 2
740
+ , a = token.index() - 2
741
+ , _self = this
742
+
743
+ this.$wrapper.find('.token').slice( Math.min(i, a) + 1, Math.max(i, a) ).each( function() {
744
+ _self.activate( $(this), true )
745
+ })
746
+ }
747
+
748
+ token.addClass('active')
749
+ this.$copyHelper.val( this.getTokensList( null, null, true ) ).select()
750
+ }
751
+
752
+ , activateAll: function() {
753
+ var _self = this
754
+
755
+ this.$wrapper.find('.token').each( function (i) {
756
+ _self.activate($(this), i !== 0, false, false)
757
+ })
758
+ }
759
+
760
+ , deactivate: function(token) {
761
+ if (!token) return
762
+
763
+ token.removeClass('active')
764
+ this.$copyHelper.val( this.getTokensList( null, null, true ) ).select()
765
+ }
766
+
767
+ , toggle: function(token) {
768
+ if (!token) return
769
+
770
+ token.toggleClass('active')
771
+ this.$copyHelper.val( this.getTokensList( null, null, true ) ).select()
772
+ }
773
+
774
+ , edit: function (token) {
775
+ if (!token) return
776
+
777
+ var value = token.data('value')
778
+ , label = token.find('.token-label').text()
779
+
780
+ // Allow changing input value before editing
781
+ var editEvent = $.Event('tokenfield:edittoken')
782
+ editEvent.token = {
783
+ value: value,
784
+ label: label
785
+ }
786
+ editEvent.relatedTarget = token.get(0)
787
+ this.$element.trigger( editEvent )
788
+
789
+ if (!editEvent.token) return
790
+
791
+ value = editEvent.token.value
792
+ label = editEvent.token.label
793
+
794
+ token.find('.token-label').text(value)
795
+ var tokenWidth = token.outerWidth()
796
+
797
+ var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input
798
+
799
+ token.replaceWith( $_input )
800
+
801
+ this.preventCreateTokens = true
802
+
803
+ this.$input.val( value )
804
+ .select()
805
+ .data( 'edit', true )
806
+ .width( tokenWidth )
807
+
808
+ this.update();
809
+ }
810
+
811
+ , unedit: function (focus) {
812
+ var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input
813
+ $_input.appendTo( this.$wrapper )
814
+
815
+ this.$input.data('edit', false)
816
+ this.$mirror.text('')
817
+
818
+ this.update()
819
+
820
+ // Because moving the input element around in DOM
821
+ // will cause it to lose focus, we provide an option
822
+ // to re-focus the input after appending it to the wrapper
823
+ if (focus) {
824
+ var _self = this
825
+ setTimeout(function () {
826
+ _self.$input.focus()
827
+ }, 1)
828
+ }
829
+ }
830
+
831
+ , remove: function (e, direction) {
832
+ if (this.$input.is(document.activeElement) || this.disabled) return
833
+
834
+ var token = (e.type === 'click') ? $(e.target).closest('.token') : this.$wrapper.find('.token.active')
835
+
836
+ if (e.type !== 'click') {
837
+ if (!direction) var direction = 'prev'
838
+ this[direction]()
839
+
840
+ // Was this the first token?
841
+ if (direction === 'prev') var firstToken = token.first().prevAll('.token:first').length === 0
842
+ }
843
+
844
+ // Prepare events
845
+
846
+ var removeEvent = $.Event('tokenfield:removetoken')
847
+ removeEvent.token = this.getTokenData( token )
848
+
849
+ var changeEvent = $.Event('change')
850
+ changeEvent.initiator = 'tokenfield'
851
+
852
+ // Remove token from DOM
853
+ token.remove()
854
+
855
+ // Trigger events
856
+ this.$element.val( this.getTokensList() ).trigger( removeEvent ).trigger( changeEvent )
857
+
858
+ // Focus, when necessary:
859
+ // When there are no more tokens, or if this was the first token
860
+ // and it was removed with backspace or it was clicked on
861
+ if (!this.$wrapper.find('.token').length || e.type === 'click' || firstToken) this.$input.focus()
862
+
863
+ // Adjust input width
864
+ this.$input.css('width', this.options.minWidth + 'px')
865
+ this.update()
866
+
867
+ e.preventDefault()
868
+ e.stopPropagation()
869
+ }
870
+
871
+ , update: function (e) {
872
+ var value = this.$input.val()
873
+ , inputLeftPadding = parseInt(this.$input.css('padding-left'), 10)
874
+ , inputRightPadding = parseInt(this.$input.css('padding-right'), 10)
875
+ , inputPadding = inputLeftPadding + inputRightPadding
876
+
877
+ if (this.$input.data('edit')) {
878
+
879
+ if (!value) {
880
+ value = this.$input.prop("placeholder")
881
+ }
882
+ if (value === this.$mirror.text()) return
883
+
884
+ this.$mirror.text(value)
885
+
886
+ var mirrorWidth = this.$mirror.width() + 10;
887
+ if ( mirrorWidth > this.$wrapper.width() ) {
888
+ return this.$input.width( this.$wrapper.width() )
889
+ }
890
+
891
+ this.$input.width( mirrorWidth )
892
+ }
893
+ else {
894
+ this.$input.css( 'width', this.options.minWidth + 'px' )
895
+ if (this.textDirection === 'rtl') {
896
+ return this.$input.width( this.$input.offset().left + this.$input.outerWidth() - this.$wrapper.offset().left - parseInt(this.$wrapper.css('padding-left'), 10) - inputPadding - 1 )
897
+ }
898
+ this.$input.width( this.$wrapper.offset().left + this.$wrapper.width() + parseInt(this.$wrapper.css('padding-left'), 10) - this.$input.offset().left - inputPadding )
899
+ }
900
+ }
901
+
902
+ , focusInput: function (e) {
903
+ if ($(e.target).closest('.token').length || $(e.target).closest('.token-input').length) return
904
+ // Focus only after the current call stack has cleared,
905
+ // otherwise has no effect.
906
+ // Reason: mousedown is too early - input will lose focus
907
+ // after mousedown. However, since the input may be moved
908
+ // in DOM, there may be no click or mouseup event triggered.
909
+ var _self = this
910
+ setTimeout(function() {
911
+ _self.$input.focus()
912
+ }, 0)
913
+ }
914
+
915
+ , search: function () {
916
+ if ( this.$input.data('ui-autocomplete') ) {
917
+ this.$input.autocomplete('search')
918
+ }
919
+ }
920
+
921
+ , disable: function () {
922
+ this.disabled = true;
923
+ this.$input.prop('disabled', true);
924
+ this.$element.prop('disabled', true);
925
+ this.$wrapper.addClass('disabled');
926
+ }
927
+
928
+ , enable: function () {
929
+ this.disabled = false;
930
+ this.$input.prop('disabled', false);
931
+ this.$element.prop('disabled', false);
932
+ this.$wrapper.removeClass('disabled');
933
+ }
934
+
935
+ , destroy: function() {
936
+ // Set field value
937
+ this.$element.val( this.getTokensList() );
938
+ // Restore styles and properties
939
+ this.$element.css( this.$element.data('original-styles') );
940
+ this.$element.prop( 'tabindex', this.$element.data('original-tabindex') );
941
+
942
+ // Re-route tokenfield labele to original input
943
+ var $label = $( 'label[for="' + this.$input.prop('id') + '"]' )
944
+ if ( $label.length ) {
945
+ $label.prop( 'for', this.$element.prop('id') )
946
+ }
947
+
948
+ // Move original element outside of tokenfield wrapper
949
+ this.$element.insertBefore( this.$wrapper );
950
+
951
+ // Remove tokenfield-related data
952
+ this.$element.removeData('original-styles');
953
+ this.$element.removeData('original-tabindex');
954
+ this.$element.removeData('bs.tokenfield');
955
+
956
+ // Remove tokenfield from DOM
957
+ this.$wrapper.remove();
958
+
959
+ var $_element = this.$element;
960
+ delete this;
961
+
962
+ return $_element;
963
+ }
964
+
965
+ }
966
+
967
+
968
+ /* TOKENFIELD PLUGIN DEFINITION
969
+ * ======================== */
970
+
971
+ var old = $.fn.tokenfield
972
+
973
+ $.fn.tokenfield = function (option, param) {
974
+ var value
975
+ , args = []
976
+
977
+ Array.prototype.push.apply( args, arguments );
978
+
979
+ var elements = this.each(function () {
980
+ var $this = $(this)
981
+ , data = $this.data('bs.tokenfield')
982
+ , options = typeof option == 'object' && option
983
+
984
+ if (typeof option === 'string' && data && data[option]) {
985
+ args.shift()
986
+ value = data[option].apply(data, args)
987
+ } else {
988
+ if (!data && typeof option !== 'string' && !param) $this.data('bs.tokenfield', (data = new Tokenfield(this, options)))
989
+ }
990
+ })
991
+
992
+ return typeof value !== 'undefined' ? value : elements;
993
+ }
994
+
995
+ $.fn.tokenfield.defaults = {
996
+ minWidth: 60,
997
+ minLength: 0,
998
+ allowDuplicates: false,
999
+ allowEditing: true,
1000
+ limit: 0,
1001
+ autocomplete: {},
1002
+ typeahead: {},
1003
+ showAutocompleteOnFocus: false,
1004
+ createTokensOnBlur: false,
1005
+ delimiter: ',',
1006
+ beautify: true
1007
+ }
1008
+
1009
+ $.fn.tokenfield.Constructor = Tokenfield
1010
+
1011
+
1012
+ /* TOKENFIELD NO CONFLICT
1013
+ * ================== */
1014
+
1015
+ $.fn.tokenfield.noConflict = function () {
1016
+ $.fn.tokenfield = old
1017
+ return this
1018
+ }
1019
+
1020
+ return Tokenfield;
1021
+
1022
+ }));