bootstrap_tokenfield_rails 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: []