bootstrap_tokenfield_rails 0.0.3

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