/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js |
@@ -0,0 +1,1029 @@ |
/*! |
* bootstrap-tokenfield |
* https://github.com/sliptree/bootstrap-tokenfield |
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT |
*/ |
|
(function (factory) { |
if (typeof define === 'function' && define.amd) { |
// AMD. Register as an anonymous module. |
define(['jquery'], factory); |
} else if (typeof exports === 'object') { |
// For CommonJS and CommonJS-like environments where a window with jQuery |
// is present, execute the factory with the jQuery instance from the window object |
// For environments that do not inherently posses a window with a document |
// (such as Node.js), expose a Tokenfield-making factory as module.exports |
// This accentuates the need for the creation of a real window or passing in a jQuery instance |
// e.g. require("bootstrap-tokenfield")(window); or require("bootstrap-tokenfield")($); |
module.exports = global.window && global.window.$ ? |
factory( global.window.$ ) : |
function( input ) { |
if ( !input.$ && !input.fn ) { |
throw new Error( "Tokenfield requires a window object with jQuery or a jQuery instance" ); |
} |
return factory( input.$ || input ); |
}; |
} else { |
// Browser globals |
factory(jQuery, window); |
} |
}(function ($, window) { |
|
"use strict"; // jshint ;_; |
|
/* TOKENFIELD PUBLIC CLASS DEFINITION |
* ============================== */ |
|
var Tokenfield = function (element, options) { |
var _self = this |
|
this.$element = $(element) |
this.textDirection = this.$element.css('direction'); |
|
// Extend options |
this.options = $.extend(true, {}, $.fn.tokenfield.defaults, { tokens: this.$element.val() }, this.$element.data(), options) |
|
// Setup delimiters and trigger keys |
this._delimiters = (typeof this.options.delimiter === 'string') ? [this.options.delimiter] : this.options.delimiter |
this._triggerKeys = $.map(this._delimiters, function (delimiter) { |
return delimiter.charCodeAt(0); |
}); |
this._firstDelimiter = this._delimiters[0]; |
|
// Check for whitespace, dash and special characters |
var whitespace = $.inArray(' ', this._delimiters) |
, dash = $.inArray('-', this._delimiters) |
|
if (whitespace >= 0) |
this._delimiters[whitespace] = '\\s' |
|
if (dash >= 0) { |
delete this._delimiters[dash] |
this._delimiters.unshift('-') |
} |
|
var specialCharacters = ['\\', '$', '[', '{', '^', '.', '|', '?', '*', '+', '(', ')'] |
$.each(this._delimiters, function (index, character) { |
var pos = $.inArray(character, specialCharacters) |
if (pos >= 0) _self._delimiters[index] = '\\' + character; |
}); |
|
// Store original input width |
var elRules = (window && typeof window.getMatchedCSSRules === 'function') ? window.getMatchedCSSRules( element ) : null |
, elStyleWidth = element.style.width |
, elCSSWidth |
, elWidth = this.$element.width() |
|
if (elRules) { |
$.each( elRules, function (i, rule) { |
if (rule.style.width) { |
elCSSWidth = rule.style.width; |
} |
}); |
} |
|
// Move original input out of the way |
var hidingPosition = $('body').css('direction') === 'rtl' ? 'right' : 'left', |
originalStyles = { position: this.$element.css('position') }; |
originalStyles[hidingPosition] = this.$element.css(hidingPosition); |
|
this.$element |
.data('original-styles', originalStyles) |
.data('original-tabindex', this.$element.prop('tabindex')) |
.css('position', 'absolute') |
.css(hidingPosition, '-10000px') |
.prop('tabindex', -1) |
|
// Create a wrapper |
this.$wrapper = $('<div class="tokenfield form-control" />') |
if (this.$element.hasClass('input-lg')) this.$wrapper.addClass('input-lg') |
if (this.$element.hasClass('input-sm')) this.$wrapper.addClass('input-sm') |
if (this.textDirection === 'rtl') this.$wrapper.addClass('rtl') |
|
// Create a new input |
var id = this.$element.prop('id') || new Date().getTime() + '' + Math.floor((1 + Math.random()) * 100) |
this.$input = $('<input type="'+this.options.inputType+'" class="token-input" autocomplete="off" />') |
.appendTo( this.$wrapper ) |
.prop( 'placeholder', this.$element.prop('placeholder') ) |
.prop( 'id', id + '-tokenfield' ) |
.prop( 'tabindex', this.$element.data('original-tabindex') ) |
|
// Re-route original input label to new input |
var $label = $( 'label[for="' + this.$element.prop('id') + '"]' ) |
if ( $label.length ) { |
$label.prop( 'for', this.$input.prop('id') ) |
} |
|
// Set up a copy helper to handle copy & paste |
this.$copyHelper = $('<input type="text" />').css('position', 'absolute').css(hidingPosition, '-10000px').prop('tabindex', -1).prependTo( this.$wrapper ) |
|
// Set wrapper width |
if (elStyleWidth) { |
this.$wrapper.css('width', elStyleWidth); |
} |
else if (elCSSWidth) { |
this.$wrapper.css('width', elCSSWidth); |
} |
// If input is inside inline-form with no width set, set fixed width |
else if (this.$element.parents('.form-inline').length) { |
this.$wrapper.width( elWidth ) |
} |
|
// Set tokenfield disabled, if original or fieldset input is disabled |
if (this.$element.prop('disabled') || this.$element.parents('fieldset[disabled]').length) { |
this.disable(); |
} |
|
// Set tokenfield readonly, if original input is readonly |
if (this.$element.prop('readonly')) { |
this.readonly(); |
} |
|
// Set up mirror for input auto-sizing |
this.$mirror = $('<span style="position:absolute; top:-999px; left:0; white-space:pre;"/>'); |
this.$input.css('min-width', this.options.minWidth + 'px') |
$.each([ |
'fontFamily', |
'fontSize', |
'fontWeight', |
'fontStyle', |
'letterSpacing', |
'textTransform', |
'wordSpacing', |
'textIndent' |
], function (i, val) { |
_self.$mirror[0].style[val] = _self.$input.css(val); |
}); |
this.$mirror.appendTo( 'body' ) |
|
// Insert tokenfield to HTML |
this.$wrapper.insertBefore( this.$element ) |
this.$element.prependTo( this.$wrapper ) |
|
// Calculate inner input width |
this.update() |
|
// Create initial tokens, if any |
this.setTokens(this.options.tokens, false, ! this.$element.val() && this.options.tokens ) |
|
// Start listening to events |
this.listen() |
|
// Initialize autocomplete, if necessary |
if ( ! $.isEmptyObject( this.options.autocomplete ) ) { |
var side = this.textDirection === 'rtl' ? 'right' : 'left' |
, autocompleteOptions = $.extend({ |
minLength: this.options.showAutocompleteOnFocus ? 0 : null, |
position: { my: side + " top", at: side + " bottom", of: this.$wrapper } |
}, this.options.autocomplete ) |
|
this.$input.autocomplete( autocompleteOptions ) |
} |
|
// Initialize typeahead, if necessary |
if ( ! $.isEmptyObject( this.options.typeahead ) ) { |
|
var typeaheadOptions = this.options.typeahead |
, defaults = { |
minLength: this.options.showAutocompleteOnFocus ? 0 : null |
} |
, args = $.isArray( typeaheadOptions ) ? typeaheadOptions : [typeaheadOptions, typeaheadOptions] |
|
args[0] = $.extend( {}, defaults, args[0] ) |
|
this.$input.typeahead.apply( this.$input, args ) |
this.typeahead = true |
} |
} |
|
Tokenfield.prototype = { |
|
constructor: Tokenfield |
|
, createToken: function (attrs, triggerChange) { |
var _self = this |
|
if (typeof attrs === 'string') { |
attrs = { value: attrs, label: attrs } |
} else { |
// Copy objects to prevent contamination of data sources. |
attrs = $.extend( {}, attrs ) |
} |
|
if (typeof triggerChange === 'undefined') { |
triggerChange = true |
} |
|
// Normalize label and value |
attrs.value = $.trim(attrs.value.toString()); |
attrs.label = attrs.label && attrs.label.length ? $.trim(attrs.label) : attrs.value |
|
// Bail out if has no value or label, or label is too short |
if (!attrs.value.length || !attrs.label.length || attrs.label.length <= this.options.minLength) return |
|
// Bail out if maximum number of tokens is reached |
if (this.options.limit && this.getTokens().length >= this.options.limit) return |
|
// Allow changing token data before creating it |
var createEvent = $.Event('tokenfield:createtoken', { attrs: attrs }) |
this.$element.trigger(createEvent) |
|
// Bail out if there if attributes are empty or event was defaultPrevented |
if (!createEvent.attrs || createEvent.isDefaultPrevented()) return |
|
var $token = $('<div class="token" />') |
.append('<span class="token-label" />') |
.append('<a href="#" class="close" tabindex="-1">×</a>') |
.data('attrs', attrs) |
|
// Insert token into HTML |
if (this.$input.hasClass('tt-input')) { |
// If the input has typeahead enabled, insert token before it's parent |
this.$input.parent().before( $token ) |
} else { |
this.$input.before( $token ) |
} |
|
// Temporarily set input width to minimum |
this.$input.css('width', this.options.minWidth + 'px') |
|
var $tokenLabel = $token.find('.token-label') |
, $closeButton = $token.find('.close') |
|
// Determine maximum possible token label width |
if (!this.maxTokenWidth) { |
this.maxTokenWidth = |
this.$wrapper.width() - $closeButton.outerWidth() - |
parseInt($closeButton.css('margin-left'), 10) - |
parseInt($closeButton.css('margin-right'), 10) - |
parseInt($token.css('border-left-width'), 10) - |
parseInt($token.css('border-right-width'), 10) - |
parseInt($token.css('padding-left'), 10) - |
parseInt($token.css('padding-right'), 10) |
parseInt($tokenLabel.css('border-left-width'), 10) - |
parseInt($tokenLabel.css('border-right-width'), 10) - |
parseInt($tokenLabel.css('padding-left'), 10) - |
parseInt($tokenLabel.css('padding-right'), 10) |
parseInt($tokenLabel.css('margin-left'), 10) - |
parseInt($tokenLabel.css('margin-right'), 10) |
} |
|
$tokenLabel |
.text(attrs.label) |
.css('max-width', this.maxTokenWidth) |
|
// Listen to events on token |
$token |
.on('mousedown', function (e) { |
if (_self._disabled || _self._readonly) return false |
_self.preventDeactivation = true |
}) |
.on('click', function (e) { |
if (_self._disabled || _self._readonly) return false |
_self.preventDeactivation = false |
|
if (e.ctrlKey || e.metaKey) { |
e.preventDefault() |
return _self.toggle( $token ) |
} |
|
_self.activate( $token, e.shiftKey, e.shiftKey ) |
}) |
.on('dblclick', function (e) { |
if (_self._disabled || _self._readonly || !_self.options.allowEditing ) return false |
_self.edit( $token ) |
}) |
|
$closeButton |
.on('click', $.proxy(this.remove, this)) |
|
// Trigger createdtoken event on the original field |
// indicating that the token is now in the DOM |
this.$element.trigger($.Event('tokenfield:createdtoken', { |
attrs: attrs, |
relatedTarget: $token.get(0) |
})) |
|
// Trigger change event on the original field |
if (triggerChange) { |
this.$element.val( this.getTokensList() ).trigger( $.Event('change', { initiator: 'tokenfield' }) ) |
} |
|
// Update tokenfield dimensions |
this.update() |
|
// Return original element |
return this.$element.get(0) |
} |
|
, setTokens: function (tokens, add, triggerChange) { |
if (!tokens) return |
|
if (!add) this.$wrapper.find('.token').remove() |
|
if (typeof triggerChange === 'undefined') { |
triggerChange = true |
} |
|
if (typeof tokens === 'string') { |
if (this._delimiters.length) { |
// Split based on delimiters |
tokens = tokens.split( new RegExp( '[' + this._delimiters.join('') + ']' ) ) |
} else { |
tokens = [tokens]; |
} |
} |
|
var _self = this |
$.each(tokens, function (i, attrs) { |
_self.createToken(attrs, triggerChange) |
}) |
|
return this.$element.get(0) |
} |
|
, getTokenData: function($token) { |
var data = $token.map(function() { |
var $token = $(this); |
return $token.data('attrs') |
}).get(); |
|
if (data.length == 1) { |
data = data[0]; |
} |
|
return data; |
} |
|
, getTokens: function(active) { |
var self = this |
, tokens = [] |
, activeClass = active ? '.active' : '' // get active tokens only |
this.$wrapper.find( '.token' + activeClass ).each( function() { |
tokens.push( self.getTokenData( $(this) ) ) |
}) |
return tokens |
} |
|
, getTokensList: function(delimiter, beautify, active) { |
delimiter = delimiter || this._firstDelimiter |
beautify = ( typeof beautify !== 'undefined' && beautify !== null ) ? beautify : this.options.beautify |
|
var separator = delimiter + ( beautify && delimiter !== ' ' ? ' ' : '') |
return $.map( this.getTokens(active), function (token) { |
return token.value |
}).join(separator) |
} |
|
, getInput: function() { |
return this.$input.val() |
} |
|
, listen: function () { |
var _self = this |
|
this.$element |
.on('change', $.proxy(this.change, this)) |
|
this.$wrapper |
.on('mousedown',$.proxy(this.focusInput, this)) |
|
this.$input |
.on('focus', $.proxy(this.focus, this)) |
.on('blur', $.proxy(this.blur, this)) |
.on('paste', $.proxy(this.paste, this)) |
.on('keydown', $.proxy(this.keydown, this)) |
.on('keypress', $.proxy(this.keypress, this)) |
.on('keyup', $.proxy(this.keyup, this)) |
|
this.$copyHelper |
.on('focus', $.proxy(this.focus, this)) |
.on('blur', $.proxy(this.blur, this)) |
.on('keydown', $.proxy(this.keydown, this)) |
.on('keyup', $.proxy(this.keyup, this)) |
|
// Secondary listeners for input width calculation |
this.$input |
.on('keypress', $.proxy(this.update, this)) |
.on('keyup', $.proxy(this.update, this)) |
|
this.$input |
.on('autocompletecreate', function() { |
// Set minimum autocomplete menu width |
var $_menuElement = $(this).data('ui-autocomplete').menu.element |
|
var minWidth = _self.$wrapper.outerWidth() - |
parseInt( $_menuElement.css('border-left-width'), 10 ) - |
parseInt( $_menuElement.css('border-right-width'), 10 ) |
|
$_menuElement.css( 'min-width', minWidth + 'px' ) |
}) |
.on('autocompleteselect', function (e, ui) { |
if (_self.createToken( ui.item )) { |
_self.$input.val('') |
if (_self.$input.data( 'edit' )) { |
_self.unedit(true) |
} |
} |
return false |
}) |
.on('typeahead:selected typeahead:autocompleted', function (e, datum, dataset) { |
// Create token |
if (_self.createToken( datum )) { |
_self.$input.typeahead('val', '') |
if (_self.$input.data( 'edit' )) { |
_self.unedit(true) |
} |
} |
}) |
|
// Listen to window resize |
$(window).on('resize', $.proxy(this.update, this )) |
|
} |
|
, keydown: function (e) { |
|
if (!this.focused) return |
|
var _self = this |
|
switch(e.keyCode) { |
case 8: // backspace |
if (!this.$input.is(document.activeElement)) break |
this.lastInputValue = this.$input.val() |
break |
|
case 37: // left arrow |
leftRight( this.textDirection === 'rtl' ? 'next': 'prev' ) |
break |
|
case 38: // up arrow |
upDown('prev') |
break |
|
case 39: // right arrow |
leftRight( this.textDirection === 'rtl' ? 'prev': 'next' ) |
break |
|
case 40: // down arrow |
upDown('next') |
break |
|
case 65: // a (to handle ctrl + a) |
if (this.$input.val().length > 0 || !(e.ctrlKey || e.metaKey)) break |
this.activateAll() |
e.preventDefault() |
break |
|
case 9: // tab |
case 13: // enter |
|
// We will handle creating tokens from autocomplete in autocomplete events |
if (this.$input.data('ui-autocomplete') && this.$input.data('ui-autocomplete').menu.element.find("li:has(a.ui-state-focus), li.ui-state-focus").length) break |
|
// We will handle creating tokens from typeahead in typeahead events |
if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-cursor').length ) break |
if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-hint').val() && this.$wrapper.find('.tt-hint').val().length) break |
|
// Create token |
if (this.$input.is(document.activeElement) && this.$input.val().length || this.$input.data('edit')) { |
return this.createTokensFromInput(e, this.$input.data('edit')); |
} |
|
// Edit token |
if (e.keyCode === 13) { |
if (!this.$copyHelper.is(document.activeElement) || this.$wrapper.find('.token.active').length !== 1) break |
if (!_self.options.allowEditing) break |
this.edit( this.$wrapper.find('.token.active') ) |
} |
} |
|
function leftRight(direction) { |
if (_self.$input.is(document.activeElement)) { |
if (_self.$input.val().length > 0) return |
|
direction += 'All' |
var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction]('.token:first') : _self.$input[direction]('.token:first') |
if (!$token.length) return |
|
_self.preventInputFocus = true |
_self.preventDeactivation = true |
|
_self.activate( $token ) |
e.preventDefault() |
|
} else { |
_self[direction]( e.shiftKey ) |
e.preventDefault() |
} |
} |
|
function upDown(direction) { |
if (!e.shiftKey) return |
|
if (_self.$input.is(document.activeElement)) { |
if (_self.$input.val().length > 0) return |
|
var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction + 'All']('.token:first') : _self.$input[direction + 'All']('.token:first') |
if (!$token.length) return |
|
_self.activate( $token ) |
} |
|
var opposite = direction === 'prev' ? 'next' : 'prev' |
, position = direction === 'prev' ? 'first' : 'last' |
|
_self.$firstActiveToken[opposite + 'All']('.token').each(function() { |
_self.deactivate( $(this) ) |
}) |
|
_self.activate( _self.$wrapper.find('.token:' + position), true, true ) |
e.preventDefault() |
} |
|
this.lastKeyDown = e.keyCode |
} |
|
, keypress: function(e) { |
|
// Comma |
if ($.inArray( e.which, this._triggerKeys) !== -1 && this.$input.is(document.activeElement)) { |
if (this.$input.val()) { |
this.createTokensFromInput(e) |
} |
return false; |
} |
} |
|
, keyup: function (e) { |
this.preventInputFocus = false |
|
if (!this.focused) return |
|
switch(e.keyCode) { |
case 8: // backspace |
if (this.$input.is(document.activeElement)) { |
if (this.$input.val().length || this.lastInputValue.length && this.lastKeyDown === 8) break |
|
this.preventDeactivation = true |
var $prevToken = this.$input.hasClass('tt-input') ? this.$input.parent().prevAll('.token:first') : this.$input.prevAll('.token:first') |
|
if (!$prevToken.length) break |
|
this.activate( $prevToken ) |
} else { |
this.remove(e) |
} |
break |
|
case 46: // delete |
this.remove(e, 'next') |
break |
} |
this.lastKeyUp = e.keyCode |
} |
|
, focus: function (e) { |
this.focused = true |
this.$wrapper.addClass('focus') |
|
if (this.$input.is(document.activeElement)) { |
this.$wrapper.find('.active').removeClass('active') |
this.$firstActiveToken = null |
|
if (this.options.showAutocompleteOnFocus) { |
this.search() |
} |
} |
} |
|
, blur: function (e) { |
|
this.focused = false |
this.$wrapper.removeClass('focus') |
|
if (!this.preventDeactivation && !this.$element.is(document.activeElement)) { |
this.$wrapper.find('.active').removeClass('active') |
this.$firstActiveToken = null |
} |
|
if (!this.preventCreateTokens && (this.$input.data('edit') && !this.$input.is(document.activeElement) || this.options.createTokensOnBlur )) { |
this.createTokensFromInput(e) |
} |
|
this.preventDeactivation = false |
this.preventCreateTokens = false |
} |
|
, paste: function (e) { |
var _self = this |
|
// Add tokens to existing ones |
if (_self.options.allowPasting) { |
setTimeout(function () { |
_self.createTokensFromInput(e) |
}, 1) |
} |
} |
|
, change: function (e) { |
if ( e.initiator === 'tokenfield' ) return // Prevent loops |
|
this.setTokens( this.$element.val() ) |
} |
|
, createTokensFromInput: function (e, focus) { |
if (this.$input.val().length < this.options.minLength) |
return // No input, simply return |
|
var tokensBefore = this.getTokensList() |
this.setTokens( this.$input.val(), true ) |
|
if (tokensBefore == this.getTokensList() && this.$input.val().length) |
return false // No tokens were added, do nothing (prevent form submit) |
|
if (this.$input.hasClass('tt-input')) { |
// Typeahead acts weird when simply setting input value to empty, |
// so we set the query to empty instead |
this.$input.typeahead('val', '') |
} else { |
this.$input.val('') |
} |
|
if (this.$input.data( 'edit' )) { |
this.unedit(focus) |
} |
|
return false // Prevent form being submitted |
} |
|
, next: function (add) { |
if (add) { |
var $firstActiveToken = this.$wrapper.find('.active:first') |
, deactivate = $firstActiveToken && this.$firstActiveToken ? $firstActiveToken.index() < this.$firstActiveToken.index() : false |
|
if (deactivate) return this.deactivate( $firstActiveToken ) |
} |
|
var $lastActiveToken = this.$wrapper.find('.active:last') |
, $nextToken = $lastActiveToken.nextAll('.token:first') |
|
if (!$nextToken.length) { |
this.$input.focus() |
return |
} |
|
this.activate($nextToken, add) |
} |
|
, prev: function (add) { |
|
if (add) { |
var $lastActiveToken = this.$wrapper.find('.active:last') |
, deactivate = $lastActiveToken && this.$firstActiveToken ? $lastActiveToken.index() > this.$firstActiveToken.index() : false |
|
if (deactivate) return this.deactivate( $lastActiveToken ) |
} |
|
var $firstActiveToken = this.$wrapper.find('.active:first') |
, $prevToken = $firstActiveToken.prevAll('.token:first') |
|
if (!$prevToken.length) { |
$prevToken = this.$wrapper.find('.token:first') |
} |
|
if (!$prevToken.length && !add) { |
this.$input.focus() |
return |
} |
|
this.activate( $prevToken, add ) |
} |
|
, activate: function ($token, add, multi, remember) { |
|
if (!$token) return |
|
if (typeof remember === 'undefined') var remember = true |
|
if (multi) var add = true |
|
this.$copyHelper.focus() |
|
if (!add) { |
this.$wrapper.find('.active').removeClass('active') |
if (remember) { |
this.$firstActiveToken = $token |
} else { |
delete this.$firstActiveToken |
} |
} |
|
if (multi && this.$firstActiveToken) { |
// Determine first active token and the current tokens indicies |
// Account for the 1 hidden textarea by subtracting 1 from both |
var i = this.$firstActiveToken.index() - 2 |
, a = $token.index() - 2 |
, _self = this |
|
this.$wrapper.find('.token').slice( Math.min(i, a) + 1, Math.max(i, a) ).each( function() { |
_self.activate( $(this), true ) |
}) |
} |
|
$token.addClass('active') |
this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() |
} |
|
, activateAll: function() { |
var _self = this |
|
this.$wrapper.find('.token').each( function (i) { |
_self.activate($(this), i !== 0, false, false) |
}) |
} |
|
, deactivate: function($token) { |
if (!$token) return |
|
$token.removeClass('active') |
this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() |
} |
|
, toggle: function($token) { |
if (!$token) return |
|
$token.toggleClass('active') |
this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() |
} |
|
, edit: function ($token) { |
if (!$token) return |
|
var attrs = $token.data('attrs') |
|
// Allow changing input value before editing |
var options = { attrs: attrs, relatedTarget: $token.get(0) } |
var editEvent = $.Event('tokenfield:edittoken', options) |
this.$element.trigger( editEvent ) |
|
// Edit event can be cancelled if default is prevented |
if (editEvent.isDefaultPrevented()) return |
|
$token.find('.token-label').text(attrs.value) |
var tokenWidth = $token.outerWidth() |
|
var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input |
|
$token.replaceWith( $_input ) |
|
this.preventCreateTokens = true |
|
this.$input.val( attrs.value ) |
.select() |
.data( 'edit', true ) |
.width( tokenWidth ) |
|
this.update(); |
|
// Indicate that token is now being edited, and is replaced with an input field in the DOM |
this.$element.trigger($.Event('tokenfield:editedtoken', options )) |
} |
|
, unedit: function (focus) { |
var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input |
$_input.appendTo( this.$wrapper ) |
|
this.$input.data('edit', false) |
this.$mirror.text('') |
|
this.update() |
|
// Because moving the input element around in DOM |
// will cause it to lose focus, we provide an option |
// to re-focus the input after appending it to the wrapper |
if (focus) { |
var _self = this |
setTimeout(function () { |
_self.$input.focus() |
}, 1) |
} |
} |
|
, remove: function (e, direction) { |
if (this.$input.is(document.activeElement) || this._disabled || this._readonly) return |
|
var $token = (e.type === 'click') ? $(e.target).closest('.token') : this.$wrapper.find('.token.active') |
|
if (e.type !== 'click') { |
if (!direction) var direction = 'prev' |
this[direction]() |
|
// Was it the first token? |
if (direction === 'prev') var firstToken = $token.first().prevAll('.token:first').length === 0 |
} |
|
// Prepare events and their options |
var options = { attrs: this.getTokenData( $token ), relatedTarget: $token.get(0) } |
, removeEvent = $.Event('tokenfield:removetoken', options) |
|
this.$element.trigger(removeEvent); |
|
// Remove event can be intercepted and cancelled |
if (removeEvent.isDefaultPrevented()) return |
|
var removedEvent = $.Event('tokenfield:removedtoken', options) |
, changeEvent = $.Event('change', { initiator: 'tokenfield' }) |
|
// Remove token from DOM |
$token.remove() |
|
// Trigger events |
this.$element.val( this.getTokensList() ).trigger( removedEvent ).trigger( changeEvent ) |
|
// Focus, when necessary: |
// When there are no more tokens, or if this was the first token |
// and it was removed with backspace or it was clicked on |
if (!this.$wrapper.find('.token').length || e.type === 'click' || firstToken) this.$input.focus() |
|
// Adjust input width |
this.$input.css('width', this.options.minWidth + 'px') |
this.update() |
|
// Cancel original event handlers |
e.preventDefault() |
e.stopPropagation() |
} |
|
/** |
* Update tokenfield dimensions |
*/ |
, update: function (e) { |
var value = this.$input.val() |
, inputPaddingLeft = parseInt(this.$input.css('padding-left'), 10) |
, inputPaddingRight = parseInt(this.$input.css('padding-right'), 10) |
, inputPadding = inputPaddingLeft + inputPaddingRight |
|
if (this.$input.data('edit')) { |
|
if (!value) { |
value = this.$input.prop("placeholder") |
} |
if (value === this.$mirror.text()) return |
|
this.$mirror.text(value) |
|
var mirrorWidth = this.$mirror.width() + 10; |
if ( mirrorWidth > this.$wrapper.width() ) { |
return this.$input.width( this.$wrapper.width() ) |
} |
|
this.$input.width( mirrorWidth ) |
} |
else { |
var w = (this.textDirection === 'rtl') |
? this.$input.offset().left + this.$input.outerWidth() - this.$wrapper.offset().left - parseInt(this.$wrapper.css('padding-left'), 10) - inputPadding - 1 |
: this.$wrapper.offset().left + this.$wrapper.width() + parseInt(this.$wrapper.css('padding-left'), 10) - this.$input.offset().left - inputPadding; |
// |
// some usecases pre-render widget before attaching to DOM, |
// dimensions returned by jquery will be NaN -> we default to 100% |
// so placeholder won't be cut off. |
isNaN(w) ? this.$input.width('100%') : this.$input.width(w); |
} |
} |
|
, focusInput: function (e) { |
if ( $(e.target).closest('.token').length || $(e.target).closest('.token-input').length || $(e.target).closest('.tt-dropdown-menu').length ) return |
// Focus only after the current call stack has cleared, |
// otherwise has no effect. |
// Reason: mousedown is too early - input will lose focus |
// after mousedown. However, since the input may be moved |
// in DOM, there may be no click or mouseup event triggered. |
var _self = this |
setTimeout(function() { |
_self.$input.focus() |
}, 0) |
} |
|
, search: function () { |
if ( this.$input.data('ui-autocomplete') ) { |
this.$input.autocomplete('search') |
} |
} |
|
, disable: function () { |
this.setProperty('disabled', true); |
} |
|
, enable: function () { |
this.setProperty('disabled', false); |
} |
|
, readonly: function () { |
this.setProperty('readonly', true); |
} |
|
, writeable: function () { |
this.setProperty('readonly', false); |
} |
|
, setProperty: function(property, value) { |
this['_' + property] = value; |
this.$input.prop(property, value); |
this.$element.prop(property, value); |
this.$wrapper[ value ? 'addClass' : 'removeClass' ](property); |
} |
|
, destroy: function() { |
// Set field value |
this.$element.val( this.getTokensList() ); |
// Restore styles and properties |
this.$element.css( this.$element.data('original-styles') ); |
this.$element.prop( 'tabindex', this.$element.data('original-tabindex') ); |
|
// Re-route tokenfield label to original input |
var $label = $( 'label[for="' + this.$input.prop('id') + '"]' ) |
if ( $label.length ) { |
$label.prop( 'for', this.$element.prop('id') ) |
} |
|
// Move original element outside of tokenfield wrapper |
this.$element.insertBefore( this.$wrapper ); |
|
// Remove tokenfield-related data |
this.$element.removeData('original-styles') |
.removeData('original-tabindex') |
.removeData('bs.tokenfield'); |
|
// Remove tokenfield from DOM |
this.$wrapper.remove(); |
this.$mirror.remove(); |
|
var $_element = this.$element; |
|
return $_element; |
} |
|
} |
|
|
/* TOKENFIELD PLUGIN DEFINITION |
* ======================== */ |
|
var old = $.fn.tokenfield |
|
$.fn.tokenfield = function (option, param) { |
var value |
, args = [] |
|
Array.prototype.push.apply( args, arguments ); |
|
var elements = this.each(function () { |
var $this = $(this) |
, data = $this.data('bs.tokenfield') |
, options = typeof option == 'object' && option |
|
if (typeof option === 'string' && data && data[option]) { |
args.shift() |
value = data[option].apply(data, args) |
} else { |
if (!data && typeof option !== 'string' && !param) { |
$this.data('bs.tokenfield', (data = new Tokenfield(this, options))) |
$this.trigger('tokenfield:initialize') |
} |
} |
}) |
|
return typeof value !== 'undefined' ? value : elements; |
} |
|
$.fn.tokenfield.defaults = { |
minWidth: 60, |
minLength: 0, |
allowEditing: true, |
allowPasting: true, |
limit: 0, |
autocomplete: {}, |
typeahead: {}, |
showAutocompleteOnFocus: false, |
createTokensOnBlur: false, |
delimiter: ',', |
beautify: true, |
inputType: 'text' |
} |
|
$.fn.tokenfield.Constructor = Tokenfield |
|
|
/* TOKENFIELD NO CONFLICT |
* ================== */ |
|
$.fn.tokenfield.noConflict = function () { |
$.fn.tokenfield = old |
return this |
} |
|
return Tokenfield; |
|
})); |
/bower_components/bootstrap-tokenfield/docs-assets/css/docs.css |
@@ -0,0 +1,1117 @@ |
/* |
* Bootstrap Tokenfield Documentation |
* Special styles for presenting Bootstrap's documentation and code examples. |
* |
* Table of contents: |
* |
* Scaffolding |
* Main navigation |
* Footer |
* Social buttons |
* Homepage |
* Page headers |
* Old docs callout |
* Ads |
* Side navigation |
* Docs sections |
* Callouts |
* Grid styles |
* Examples |
* Code snippets (highlight) |
* Responsive tests |
* Glyphicons |
* Customizer |
* Miscellaneous |
*/ |
|
|
/* |
* Scaffolding |
* |
* Update the basics of our documents to prep for docs content. |
*/ |
|
body { |
position: relative; /* For scrollyspy */ |
} |
|
/* Keep code small in tables on account of limited space */ |
.table code { |
font-size: 13px; |
font-weight: normal; |
} |
|
/* Outline button for use within the docs */ |
.btn-outline { |
color: #563d7c; |
background-color: #fff; |
border-color: #e5e5e5; |
} |
.btn-outline:hover, |
.btn-outline:focus, |
.btn-outline:active { |
color: #fff; |
background-color: #563d7c; |
border-color: #563d7c; |
} |
|
/* Inverted outline button (white on dark) */ |
.btn-outline-inverse { |
color: #fff; |
background-color: transparent; |
border-color: #cdbfe3; |
} |
.btn-outline-inverse:hover, |
.btn-outline-inverse:focus, |
.btn-outline-inverse:active { |
color: #563d7c; |
text-shadow: none; |
background-color: #fff; |
border-color: #fff; |
} |
|
|
/* |
* Main navigation |
* |
* Turn the `.navbar` at the top of the docs purple. |
*/ |
|
.bs-docs-nav { |
text-shadow: 0 -1px 0 rgba(0,0,0,.15); |
background-color: #563d7c; |
border-color: #463265; |
box-shadow: 0 1px 0 rgba(255,255,255,.1); |
} |
.bs-docs-nav .navbar-collapse { |
border-color: #463265; |
} |
.bs-docs-nav .navbar-brand { |
color: #fff; |
} |
.bs-docs-nav .navbar-nav > li > a { |
color: #cdbfe3; |
} |
.bs-docs-nav .navbar-nav > li > a:hover { |
color: #fff; |
} |
.bs-docs-nav .navbar-nav > .active > a, |
.bs-docs-nav .navbar-nav > .active > a:hover { |
color: #fff; |
background-color: #463265; |
} |
.bs-docs-nav .navbar-toggle { |
border-color: #563d7c; |
} |
.bs-docs-nav .navbar-toggle:hover { |
background-color: #463265; |
border-color: #463265; |
} |
|
|
/* |
* Footer |
* |
* Separated section of content at the bottom of all pages, save the homepage. |
*/ |
|
.bs-footer { |
padding-top: 40px; |
padding-bottom: 30px; |
margin-top: 100px; |
color: #777; |
text-align: center; |
border-top: 1px solid #e5e5e5; |
} |
.footer-links { |
margin: 10px 0; |
padding-left: 0; |
} |
.footer-links li { |
display: inline; |
padding: 0 2px; |
} |
.footer-links li:first-child { |
padding-left: 0; |
} |
|
@media (min-width: 768px) { |
.bs-footer { |
text-align: left; |
} |
.bs-footer p { |
margin-bottom: 0; |
} |
} |
|
|
/* |
* Social buttons |
* |
* Twitter and GitHub social action buttons (for homepage and footer). |
*/ |
|
.bs-social { |
margin-top: 20px; |
margin-bottom: 20px; |
text-align: center; |
} |
.bs-social-buttons { |
display: inline-block; |
margin-bottom: 0; |
padding-left: 0; |
list-style: none; |
} |
.bs-social-buttons li { |
display: inline-block; |
line-height: 1; |
padding: 5px 8px; |
} |
.bs-social-buttons .twitter-follow-button { |
width: 225px !important; |
} |
.bs-social-buttons .twitter-share-button { |
width: 98px !important; |
} |
/* Style the GitHub buttons via CSS instead of inline attributes */ |
.github-btn { |
border: 0; |
overflow: hidden; |
} |
|
@media screen and (min-width: 768px) { |
.bs-social { |
text-align: left; |
} |
.bs-social-buttons li:first-child { |
padding-left: 0; |
} |
} |
|
|
/* |
* Topography, yo! |
* |
* Apply the map background via base64 and relevant colors where we need 'em. |
*/ |
|
.bs-docs-home, |
.bs-header { |
color: #cdbfe3; |
background-color: #563d7c; |
background-image: url(data:image/png;base64,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); |
} |
|
|
/* |
* Homepage |
* |
* Tweaks to the custom homepage and the masthead (main jumbotron). |
*/ |
|
/* Masthead (headings and download button) */ |
.bs-masthead { |
position: relative; |
padding: 30px 15px; |
text-align: center; |
text-shadow: 0 1px 0 rgba(0,0,0,.15); |
} |
.bs-masthead h1 { |
font-size: 50px; |
line-height: 1; |
color: #fff; |
} |
.bs-masthead .btn-outline { |
margin-top: 20px; |
margin-bottom: 20px; |
padding: 18px 24px; |
font-size: 21px; |
} |
|
/* Links to project-level content like the repo, Expo, etc */ |
.bs-masthead-links { |
margin-top: 20px; |
margin-bottom: 20px; |
padding: 0 15px; |
list-style: none; |
text-align: center; |
} |
.bs-masthead-links li { |
display: inline; |
} |
.bs-masthead-links li + li { |
margin-left: 20px; |
} |
.bs-masthead-links a { |
color: #fff; |
} |
|
@media screen and (min-width: 768px) { |
.bs-masthead { |
text-align: left; |
padding-top: 140px; |
padding-bottom: 140px; |
} |
.bs-masthead h1 { |
font-size: 100px; |
} |
.bs-masthead .lead { |
margin-right: 25%; |
font-size: 30px; |
} |
.bs-masthead-links { |
padding: 0; |
text-align: left; |
} |
} |
|
|
/* |
* Page headers |
* |
* Jumbotron-esque headers at the top of every page that's not the homepage. |
*/ |
|
|
/* Page headers */ |
.bs-header { |
padding: 30px 15px 40px; /* side padding builds on .container 15px, so 30px */ |
font-size: 16px; |
text-align: center; |
text-shadow: 0 1px 0 rgba(0,0,0,.15); |
} |
.bs-header h1 { |
color: #fff; |
} |
.bs-header p { |
font-weight: 300; |
line-height: 1.5; |
} |
.bs-header .container { |
position: relative; |
} |
|
@media screen and (min-width: 768px) { |
.bs-header { |
font-size: 21px; |
text-align: left; |
} |
.bs-header h1 { |
font-size: 60px; |
line-height: 1; |
} |
} |
|
@media screen and (min-width: 992px) { |
.bs-header h1, |
.bs-header p { |
margin-right: 380px; |
} |
} |
|
|
/* |
* Carbon ads |
* |
* Single display ad that shows on all pages (except homepage) in page headers. |
* The hella `!important` is required for any pre-set property. |
*/ |
|
.carbonad { |
width: auto !important; |
margin: 50px -30px -40px !important; |
padding: 20px !important; |
overflow: hidden; /* clearfix */ |
height: auto !important; |
font-size: 13px !important; |
line-height: 16px !important; |
text-align: left; |
background: #463265 !important; |
border: 0 !important; |
box-shadow: inset 0 3px 5px rgba(0,0,0,.075); |
} |
.carbonad-img { |
margin: 0 !important; |
} |
.carbonad-text, |
.carbonad-tag { |
float: none !important; |
display: block !important; |
width: auto !important; |
height: auto !important; |
margin-left: 145px !important; |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important; |
} |
.carbonad-text { |
padding-top: 0 !important; |
} |
.carbonad-tag { |
color: #cdbfe3 !important; |
text-align: left !important; |
} |
.carbonad-text a, |
.carbonad-tag a { |
color: #fff !important; |
} |
.carbonad #azcarbon > img { |
display: none; /* hide what I assume are tracking images */ |
} |
|
@media screen and (min-width: 768px) { |
.carbonad { |
margin: 0 !important; |
border-radius: 4px; |
box-shadow: inset 0 3px 5px rgba(0,0,0,.075), 0 1px 0 rgba(255,255,255,.1); |
} |
} |
|
@media screen and (min-width: 992px) { |
.carbonad { |
position: absolute; |
top: 20px; |
right: 0; |
padding: 15px !important; |
width: 330px !important; |
min-height: 132px; |
} |
} |
|
|
/* |
* Callout for 2.3.2 docs |
* |
* Only appears below page headers (not on the homepage). The homepage gets its |
* own link with the masthead links. |
*/ |
|
.bs-old-docs { |
padding: 15px 20px; |
color: #777; |
background-color: #fafafa; |
border-top: 1px solid #fff; |
border-bottom: 1px solid #e5e5e5; |
} |
.bs-old-docs strong { |
color: #555; |
} |
|
|
/* |
* Side navigation |
* |
* Scrollspy and affixed enhanced navigation to highlight sections and secondary |
* sections of docs content. |
*/ |
|
/* By default it's not affixed in mobile views, so undo that */ |
.bs-sidebar.affix { |
position: static; |
} |
|
/* First level of nav */ |
.bs-sidenav { |
margin-top: 30px; |
margin-bottom: 30px; |
padding-top: 10px; |
padding-bottom: 10px; |
text-shadow: 0 1px 0 #fff; |
background-color: #f7f5fa; |
border-radius: 5px; |
} |
|
/* All levels of nav */ |
.bs-sidebar .nav > li > a { |
display: block; |
color: #716b7a; |
padding: 5px 20px; |
} |
.bs-sidebar .nav > li > a:hover, |
.bs-sidebar .nav > li > a:focus { |
text-decoration: none; |
background-color: #e5e3e9; |
border-right: 1px solid #dbd8e0; |
} |
.bs-sidebar .nav > .active > a, |
.bs-sidebar .nav > .active:hover > a, |
.bs-sidebar .nav > .active:focus > a { |
font-weight: bold; |
color: #563d7c; |
background-color: transparent; |
border-right: 1px solid #563d7c; |
} |
|
/* Nav: second level (shown on .active) */ |
.bs-sidebar .nav .nav { |
display: none; /* Hide by default, but at >768px, show it */ |
margin-bottom: 8px; |
} |
.bs-sidebar .nav .nav > li > a { |
padding-top: 3px; |
padding-bottom: 3px; |
padding-left: 30px; |
font-size: 90%; |
} |
|
/* Show and affix the side nav when space allows it */ |
@media screen and (min-width: 992px) { |
.bs-sidebar .nav > .active > ul { |
display: block; |
} |
/* Widen the fixed sidebar */ |
.bs-sidebar.affix, |
.bs-sidebar.affix-bottom { |
width: 213px; |
} |
.bs-sidebar.affix { |
position: fixed; /* Undo the static from mobile first approach */ |
top: 30px; |
} |
.bs-sidebar.affix-bottom { |
position: absolute; /* Undo the static from mobile first approach */ |
} |
.bs-sidebar.affix-bottom .bs-sidenav, |
.bs-sidebar.affix .bs-sidenav { |
margin-top: 0; |
margin-bottom: 0; |
} |
} |
@media screen and (min-width: 1200px) { |
/* Widen the fixed sidebar again */ |
.bs-sidebar.affix-bottom, |
.bs-sidebar.affix { |
width: 263px; |
} |
} |
|
|
/* |
* Docs sections |
* |
* Content blocks for each component or feature. |
*/ |
|
/* Space things out */ |
.bs-docs-section + .bs-docs-section { |
padding-top: 40px; |
} |
|
/* Janky fix for preventing navbar from overlapping */ |
h1[id] { |
padding-top: 80px; |
margin-top: -45px; |
} |
|
|
/* |
* Callouts |
* |
* Not quite alerts, but custom and helpful notes for folks reading the docs. |
* Requires a base and modifier class. |
*/ |
|
/* Common styles for all types */ |
.bs-callout { |
margin: 20px 0; |
padding: 15px 30px 15px 15px; |
border-left: 5px solid #eee; |
} |
.bs-callout h4 { |
margin-top: 0; |
} |
.bs-callout p:last-child { |
margin-bottom: 0; |
} |
.bs-callout code, |
.bs-callout .highlight { |
background-color: #fff; |
} |
|
/* Variations */ |
.bs-callout-danger { |
background-color: #fcf2f2; |
border-color: #dFb5b4; |
} |
.bs-callout-warning { |
background-color: #fefbed; |
border-color: #f1e7bc; |
} |
.bs-callout-info { |
background-color: #f0f7fd; |
border-color: #d0e3f0; |
} |
|
|
/* |
* Grid examples |
* |
* Highlight the grid columns within the docs so folks can see their padding, |
* alignment, sizing, etc. |
*/ |
|
.show-grid { |
margin-bottom: 15px; |
} |
.show-grid [class^="col-"] { |
padding-top: 10px; |
padding-bottom: 10px; |
background-color: #eee; |
border: 1px solid #ddd; |
background-color: rgba(86,61,124,.15); |
border: 1px solid rgba(86,61,124,.2); |
} |
|
|
/* |
* Examples |
* |
* Isolated sections of example content for each component or feature. Usually |
* followed by a code snippet. |
*/ |
|
.bs-example { |
position: relative; |
padding: 45px 15px 15px; |
margin: 0 -15px 15px; |
background-color: #fafafa; |
box-shadow: inset 0 3px 6px rgba(0,0,0,.05); |
border-color: #e5e5e5 #eee #eee; |
border-style: solid; |
border-width: 1px 0; |
} |
/* Echo out a label for the example */ |
.bs-example:after { |
content: "Example"; |
position: absolute; |
top: 15px; |
left: 15px; |
font-size: 12px; |
font-weight: bold; |
color: #bbb; |
text-transform: uppercase; |
letter-spacing: 1px; |
} |
|
/* Tweak display of the code snippets when following an example */ |
.bs-example + .highlight { |
margin: -15px -15px 15px; |
border-radius: 0; |
border-width: 0 0 1px; |
} |
|
/* Make the examples and snippets not full-width */ |
@media screen and (min-width: 768px) { |
.bs-example { |
margin-left: 0; |
margin-right: 0; |
background-color: #fff; |
border-width: 1px; |
border-color: #ddd; |
border-radius: 4px 4px 0 0; |
box-shadow: none; |
} |
.bs-example + .highlight { |
margin-top: -16px; |
margin-left: 0; |
margin-right: 0; |
border-width: 1px; |
border-bottom-left-radius: 4px; |
border-bottom-right-radius: 4px; |
} |
} |
|
/* Tweak content of examples for optimum awesome */ |
.bs-example > p:last-child, |
.bs-example > ul:last-child, |
.bs-example > ol:last-child, |
.bs-example > blockquote:last-child, |
.bs-example > .form-control:last-child, |
.bs-example > .table:last-child, |
.bs-example > .navbar:last-child, |
.bs-example > .jumbotron:last-child, |
.bs-example > .alert:last-child, |
.bs-example > .panel:last-child, |
.bs-example > .list-group:last-child, |
.bs-example > .well:last-child, |
.bs-example > .progress:last-child, |
.bs-example > .table-responsive:last-child > .table { |
margin-bottom: 0; |
} |
.bs-example > p > .close { |
float: none; |
} |
|
/* Typography */ |
.bs-example-type .table td:last-child { |
color: #999; |
vertical-align: middle; |
} |
.bs-example-type .table td { |
padding: 15px 0; |
border-color: #eee; |
} |
.bs-example-type .table tr:first-child td { |
border-top: 0; |
} |
.bs-example-type h1, |
.bs-example-type h2, |
.bs-example-type h3, |
.bs-example-type h4, |
.bs-example-type h5, |
.bs-example-type h6 { |
margin: 0; |
} |
|
/* Images */ |
.bs-example > .img-circle, |
.bs-example > .img-rounded, |
.bs-example > .img-thumbnail { |
margin: 5px; |
} |
|
/* Buttons */ |
.bs-example > .btn, |
.bs-example > .btn-group { |
margin-top: 5px; |
margin-bottom: 5px; |
} |
.bs-example > .btn-toolbar + .btn-toolbar { |
margin-top: 10px; |
} |
|
/* Forms */ |
.bs-example-control-sizing select, |
.bs-example-control-sizing input[type="text"] + input[type="text"] { |
margin-top: 10px; |
} |
.bs-example-form .input-group { |
margin-bottom: 10px; |
} |
.bs-example > textarea.form-control { |
resize: vertical; |
} |
|
/* List groups */ |
.bs-example > .list-group { |
max-width: 400px; |
} |
|
/* Navbars */ |
.bs-example .navbar:last-child { |
margin-bottom: 0; |
} |
.bs-navbar-top-example, |
.bs-navbar-bottom-example { |
z-index: 1; |
padding: 0; |
overflow: hidden; /* cut the drop shadows off */ |
} |
.bs-navbar-top-example .navbar-header, |
.bs-navbar-bottom-example .navbar-header { |
margin-left: 0; |
} |
.bs-navbar-top-example .navbar-fixed-top, |
.bs-navbar-bottom-example .navbar-fixed-bottom { |
position: relative; |
margin-left: 0; |
margin-right: 0; |
} |
.bs-navbar-top-example { |
padding-bottom: 45px; |
} |
.bs-navbar-top-example:after { |
top: auto; |
bottom: 15px; |
} |
.bs-navbar-top-example .navbar-fixed-top { |
top: -1px; |
} |
.bs-navbar-bottom-example { |
padding-top: 45px; |
} |
.bs-navbar-bottom-example .navbar-fixed-bottom { |
bottom: -1px; |
} |
.bs-navbar-bottom-example .navbar { |
margin-bottom: 0; |
} |
@media (min-width: 768px) { |
.bs-navbar-top-example .navbar-fixed-top, |
.bs-navbar-bottom-example .navbar-fixed-bottom { |
position: absolute; |
} |
.bs-navbar-top-example { |
border-radius: 0 0 4px 4px; |
} |
.bs-navbar-bottom-example { |
border-radius: 4px 4px 0 0; |
} |
} |
|
/* Pagination */ |
.bs-example .pagination { |
margin-top: 10px; |
margin-bottom: 10px; |
} |
|
/* Pager */ |
.bs-example > .pager { |
margin-top: 0; |
} |
|
/* Example modals */ |
.bs-example-modal { |
background-color: #f5f5f5; |
} |
.bs-example-modal .modal { |
position: relative; |
top: auto; |
right: auto; |
left: auto; |
bottom: auto; |
z-index: 1; |
display: block; |
} |
.bs-example-modal .modal-dialog { |
left: auto; |
margin-left: auto; |
margin-right: auto; |
} |
|
/* Example dropdowns */ |
.bs-example > .dropdown > .dropdown-menu { |
position: static; |
display: block; |
margin-bottom: 5px; |
} |
|
/* Example tabbable tabs */ |
.bs-example-tabs .nav-tabs { |
margin-bottom: 15px; |
} |
|
/* Tooltips */ |
.bs-example-tooltips { |
text-align: center; |
} |
.bs-example-tooltips > .btn { |
margin-top: 5px; |
margin-bottom: 5px; |
} |
|
/* Popovers */ |
.bs-example-popover { |
padding-bottom: 24px; |
background-color: #f9f9f9; |
} |
.bs-example-popover .popover { |
position: relative; |
display: block; |
float: left; |
width: 260px; |
margin: 20px; |
} |
|
/* Scrollspy demo on fixed height div */ |
.scrollspy-example { |
position: relative; |
height: 200px; |
margin-top: 10px; |
overflow: auto; |
} |
|
|
/* |
* Code snippets |
* |
* Generated via Pygments and Jekyll, these are snippets of HTML, CSS, and JS. |
*/ |
|
.highlight { |
display: none; /* hidden by default, until >480px */ |
padding: 9px 14px; |
margin-bottom: 14px; |
background-color: #f7f7f9; |
border: 1px solid #e1e1e8; |
border-radius: 4px; |
} |
.highlight pre { |
padding: 0; |
margin-top: 0; |
margin-bottom: 0; |
background-color: transparent; |
border: 0; |
white-space: nowrap; |
} |
.highlight pre code { |
font-size: inherit; |
color: #333; /* Effectively the base text color */ |
} |
.highlight pre .lineno { |
display: inline-block; |
width: 22px; |
padding-right: 5px; |
margin-right: 10px; |
text-align: right; |
color: #bebec5; |
} |
|
/* Show code snippets when we have the space */ |
@media screen and (min-width: 481px) { |
.highlight { |
display: block; |
} |
} |
|
|
/* |
* Responsive tests |
* |
* Generate a set of tests to show the responsive utilities in action. |
*/ |
|
/* Responsive (scrollable) doc tables */ |
.table-responsive .highlight pre { |
white-space: normal; |
} |
|
/* Utility classes table */ |
.bs-table th small, |
.responsive-utilities th small { |
display: block; |
font-weight: normal; |
color: #999; |
} |
.responsive-utilities tbody th { |
font-weight: normal; |
} |
.responsive-utilities td { |
text-align: center; |
} |
.responsive-utilities td.is-visible { |
color: #468847; |
background-color: #dff0d8 !important; |
} |
.responsive-utilities td.is-hidden { |
color: #ccc; |
background-color: #f9f9f9 !important; |
} |
|
/* Responsive tests */ |
.responsive-utilities-test { |
margin-top: 5px; |
} |
.responsive-utilities-test .col-xs-6 { |
margin-bottom: 10px; |
} |
.responsive-utilities-test span { |
padding: 15px 10px; |
font-size: 14px; |
font-weight: bold; |
line-height: 1.1; |
text-align: center; |
border-radius: 4px; |
} |
.visible-on .col-xs-6 .hidden-xs, |
.visible-on .col-xs-6 .hidden-sm, |
.visible-on .col-xs-6 .hidden-md, |
.visible-on .col-xs-6 .hidden-lg, |
.hidden-on .col-xs-6 .visible-xs, |
.hidden-on .col-xs-6 .visible-sm, |
.hidden-on .col-xs-6 .visible-md, |
.hidden-on .col-xs-6 .visible-lg { |
color: #999; |
border: 1px solid #ddd; |
} |
.visible-on .col-xs-6 .visible-xs, |
.visible-on .col-xs-6 .visible-sm, |
.visible-on .col-xs-6 .visible-md, |
.visible-on .col-xs-6 .visible-lg, |
.hidden-on .col-xs-6 .hidden-xs, |
.hidden-on .col-xs-6 .hidden-sm, |
.hidden-on .col-xs-6 .hidden-md, |
.hidden-on .col-xs-6 .hidden-lg { |
color: #468847; |
background-color: #dff0d8; |
border: 1px solid #d6e9c6; |
} |
|
|
/* |
* Glyphicons |
* |
* Special styles for displaying the icons and their classes in the docs. |
*/ |
|
.bs-glyphicons { |
padding-left: 0; |
padding-bottom: 1px; |
margin-bottom: 20px; |
list-style: none; |
overflow: hidden; |
} |
.bs-glyphicons li { |
float: left; |
width: 25%; |
height: 115px; |
padding: 10px; |
margin: 0 -1px -1px 0; |
font-size: 12px; |
line-height: 1.4; |
text-align: center; |
border: 1px solid #ddd; |
} |
.bs-glyphicons .glyphicon { |
display: block; |
margin: 5px auto 10px; |
font-size: 24px; |
} |
.bs-glyphicons li:hover { |
background-color: rgba(86,61,124,.1); |
} |
|
@media (min-width: 768px) { |
.bs-glyphicons li { |
width: 12.5%; |
} |
} |
|
|
/* |
* Customizer |
* |
* Since this is so form control heavy, we have quite a few styles to customize |
* the display of inputs, headings, and more. Also included are all the download |
* buttons and actions. |
*/ |
|
.bs-customizer .toggle { |
float: right; |
margin-top: 85px; /* On account of ghetto navbar fix */ |
} |
|
/* Headings and form contrls */ |
.bs-customizer label { |
margin-top: 10px; |
font-weight: 500; |
color: #444; |
} |
.bs-customizer h2 { |
margin-top: 0; |
margin-bottom: 5px; |
padding-top: 30px; |
} |
.bs-customizer h4 { |
margin-top: 15px; |
} |
.bs-customizer input[type="text"] { |
font-family: Menlo, Monaco, Consolas, "Courier New", monospace; |
background-color: #fafafa; |
} |
.bs-customizer .help-block { |
font-size: 12px; |
} |
|
/* For the variables, use regular weight */ |
#less-section label { |
font-weight: normal; |
} |
|
/* Downloads */ |
.bs-customize-download .btn-outline { |
padding: 20px; |
} |
|
/* Error handling */ |
.bs-customizer-alert { |
position: fixed; |
top: 51px; |
left: 0; |
right: 0; |
z-index: 1030; |
padding: 15px 0; |
color: #fff; |
background-color: #d9534f; |
box-shadow: inset 0 1px 0 rgba(255,255,255,.25); |
border-bottom: 1px solid #b94441; |
} |
.bs-customizer-alert .close { |
margin-top: -4px; |
font-size: 24px; |
} |
.bs-customizer-alert p { |
margin-bottom: 0; |
} |
.bs-customizer-alert .glyphicon { |
margin-right: 5px; |
} |
.bs-customizer-alert pre { |
margin: 10px 0 0; |
color: #fff; |
background-color: #a83c3a; |
border-color: #973634; |
box-shadow: inset 0 2px 4px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1); |
} |
|
|
/* |
* Miscellaneous |
* |
* Odds and ends for optimum docs display. |
*/ |
|
/* Examples gallery: space out content better */ |
.bs-examples h4 { |
margin-bottom: 5px; |
} |
.bs-examples p { |
margin-bottom: 20px; |
} |
|
/* Pseudo :focus state for showing how it looks in the docs */ |
#focusedInput { |
border-color: rgba(82,168,236,.8); |
outline: 0; |
outline: thin dotted \9; /* IE6-9 */ |
-moz-box-shadow: 0 0 8px rgba(82,168,236,.6); |
box-shadow: 0 0 8px rgba(82,168,236,.6); |
} |
|
/* Better spacing on download options in getting started */ |
.bs-docs-dl-options h4 { |
margin-top: 15px; |
margin-bottom: 5px; |
} |
|
/* Feature highlights */ |
|
#feature-highlights { |
font-size: 0.7em; |
list-style: none; |
padding: 0; |
} |
#exampleInlineTags { |
width: 400px; |
} |
@media (min-width: 768px) { |
#feature-highlights { |
padding: inherit; |
list-style: disc |
} |
} |
@media (min-width: 992px) { |
#feature-highlights { |
list-style: disc; |
position: absolute; |
top: 20px; |
right: 0; |
font-size: 0.9em; |
} |
} |
|
/* Fixed width input examples */ |
.fluid-tokenfield { |
width: 50%; |
} |
.fixed-tokenfield { |
width: 300px; |
} |
/bower_components/bootstrap-tokenfield/docs-assets/js/typeahead.bundle.js |
@@ -0,0 +1,1658 @@ |
/*! |
* typeahead.js 0.10.1 |
* https://github.com/twitter/typeahead.js |
* Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT |
*/ |
|
(function($) { |
var _ = { |
isMsie: function() { |
return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; |
}, |
isBlankString: function(str) { |
return !str || /^\s*$/.test(str); |
}, |
escapeRegExChars: function(str) { |
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); |
}, |
isString: function(obj) { |
return typeof obj === "string"; |
}, |
isNumber: function(obj) { |
return typeof obj === "number"; |
}, |
isArray: $.isArray, |
isFunction: $.isFunction, |
isObject: $.isPlainObject, |
isUndefined: function(obj) { |
return typeof obj === "undefined"; |
}, |
bind: $.proxy, |
each: function(collection, cb) { |
$.each(collection, reverseArgs); |
function reverseArgs(index, value) { |
return cb(value, index); |
} |
}, |
map: $.map, |
filter: $.grep, |
every: function(obj, test) { |
var result = true; |
if (!obj) { |
return result; |
} |
$.each(obj, function(key, val) { |
if (!(result = test.call(null, val, key, obj))) { |
return false; |
} |
}); |
return !!result; |
}, |
some: function(obj, test) { |
var result = false; |
if (!obj) { |
return result; |
} |
$.each(obj, function(key, val) { |
if (result = test.call(null, val, key, obj)) { |
return false; |
} |
}); |
return !!result; |
}, |
mixin: $.extend, |
getUniqueId: function() { |
var counter = 0; |
return function() { |
return counter++; |
}; |
}(), |
templatify: function templatify(obj) { |
return $.isFunction(obj) ? obj : template; |
function template() { |
return String(obj); |
} |
}, |
defer: function(fn) { |
setTimeout(fn, 0); |
}, |
debounce: function(func, wait, immediate) { |
var timeout, result; |
return function() { |
var context = this, args = arguments, later, callNow; |
later = function() { |
timeout = null; |
if (!immediate) { |
result = func.apply(context, args); |
} |
}; |
callNow = immediate && !timeout; |
clearTimeout(timeout); |
timeout = setTimeout(later, wait); |
if (callNow) { |
result = func.apply(context, args); |
} |
return result; |
}; |
}, |
throttle: function(func, wait) { |
var context, args, timeout, result, previous, later; |
previous = 0; |
later = function() { |
previous = new Date(); |
timeout = null; |
result = func.apply(context, args); |
}; |
return function() { |
var now = new Date(), remaining = wait - (now - previous); |
context = this; |
args = arguments; |
if (remaining <= 0) { |
clearTimeout(timeout); |
timeout = null; |
previous = now; |
result = func.apply(context, args); |
} else if (!timeout) { |
timeout = setTimeout(later, remaining); |
} |
return result; |
}; |
}, |
noop: function() {} |
}; |
var VERSION = "0.10.1"; |
var LruCache = function(root, undefined) { |
function LruCache(maxSize) { |
this.maxSize = maxSize || 100; |
this.size = 0; |
this.hash = {}; |
this.list = new List(); |
} |
_.mixin(LruCache.prototype, { |
set: function set(key, val) { |
var tailItem = this.list.tail, node; |
if (this.size >= this.maxSize) { |
this.list.remove(tailItem); |
delete this.hash[tailItem.key]; |
} |
if (node = this.hash[key]) { |
node.val = val; |
this.list.moveToFront(node); |
} else { |
node = new Node(key, val); |
this.list.add(node); |
this.hash[key] = node; |
this.size++; |
} |
}, |
get: function get(key) { |
var node = this.hash[key]; |
if (node) { |
this.list.moveToFront(node); |
return node.val; |
} |
} |
}); |
function List() { |
this.head = this.tail = null; |
} |
_.mixin(List.prototype, { |
add: function add(node) { |
if (this.head) { |
node.next = this.head; |
this.head.prev = node; |
} |
this.head = node; |
this.tail = this.tail || node; |
}, |
remove: function remove(node) { |
node.prev ? node.prev.next = node.next : this.head = node.next; |
node.next ? node.next.prev = node.prev : this.tail = node.prev; |
}, |
moveToFront: function(node) { |
this.remove(node); |
this.add(node); |
} |
}); |
function Node(key, val) { |
this.key = key; |
this.val = val; |
this.prev = this.next = null; |
} |
return LruCache; |
}(this); |
var PersistentStorage = function() { |
var ls, methods; |
try { |
ls = window.localStorage; |
ls.setItem("~~~", "!"); |
ls.removeItem("~~~"); |
} catch (err) { |
ls = null; |
} |
function PersistentStorage(namespace) { |
this.prefix = [ "__", namespace, "__" ].join(""); |
this.ttlKey = "__ttl__"; |
this.keyMatcher = new RegExp("^" + this.prefix); |
} |
if (ls && window.JSON) { |
methods = { |
_prefix: function(key) { |
return this.prefix + key; |
}, |
_ttlKey: function(key) { |
return this._prefix(key) + this.ttlKey; |
}, |
get: function(key) { |
if (this.isExpired(key)) { |
this.remove(key); |
} |
return decode(ls.getItem(this._prefix(key))); |
}, |
set: function(key, val, ttl) { |
if (_.isNumber(ttl)) { |
ls.setItem(this._ttlKey(key), encode(now() + ttl)); |
} else { |
ls.removeItem(this._ttlKey(key)); |
} |
return ls.setItem(this._prefix(key), encode(val)); |
}, |
remove: function(key) { |
ls.removeItem(this._ttlKey(key)); |
ls.removeItem(this._prefix(key)); |
return this; |
}, |
clear: function() { |
var i, key, keys = [], len = ls.length; |
for (i = 0; i < len; i++) { |
if ((key = ls.key(i)).match(this.keyMatcher)) { |
keys.push(key.replace(this.keyMatcher, "")); |
} |
} |
for (i = keys.length; i--; ) { |
this.remove(keys[i]); |
} |
return this; |
}, |
isExpired: function(key) { |
var ttl = decode(ls.getItem(this._ttlKey(key))); |
return _.isNumber(ttl) && now() > ttl ? true : false; |
} |
}; |
} else { |
methods = { |
get: _.noop, |
set: _.noop, |
remove: _.noop, |
clear: _.noop, |
isExpired: _.noop |
}; |
} |
_.mixin(PersistentStorage.prototype, methods); |
return PersistentStorage; |
function now() { |
return new Date().getTime(); |
} |
function encode(val) { |
return JSON.stringify(_.isUndefined(val) ? null : val); |
} |
function decode(val) { |
return JSON.parse(val); |
} |
}(); |
var Transport = function() { |
var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); |
function Transport(o) { |
o = o || {}; |
this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; |
this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; |
} |
Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { |
maxPendingRequests = num; |
}; |
Transport.resetCache = function clearCache() { |
requestCache = new LruCache(10); |
}; |
_.mixin(Transport.prototype, { |
_get: function(url, o, cb) { |
var that = this, jqXhr; |
if (jqXhr = pendingRequests[url]) { |
jqXhr.done(done); |
} else if (pendingRequestsCount < maxPendingRequests) { |
pendingRequestsCount++; |
pendingRequests[url] = this._send(url, o).done(done).always(always); |
} else { |
this.onDeckRequestArgs = [].slice.call(arguments, 0); |
} |
function done(resp) { |
cb && cb(resp); |
requestCache.set(url, resp); |
} |
function always() { |
pendingRequestsCount--; |
delete pendingRequests[url]; |
if (that.onDeckRequestArgs) { |
that._get.apply(that, that.onDeckRequestArgs); |
that.onDeckRequestArgs = null; |
} |
} |
}, |
get: function(url, o, cb) { |
var that = this, resp; |
if (_.isFunction(o)) { |
cb = o; |
o = {}; |
} |
if (resp = requestCache.get(url)) { |
_.defer(function() { |
cb && cb(resp); |
}); |
} else { |
this._get(url, o, cb); |
} |
return !!resp; |
} |
}); |
return Transport; |
function callbackToDeferred(fn) { |
return function customSendWrapper(url, o) { |
var deferred = $.Deferred(); |
fn(url, o, onSuccess, onError); |
return deferred; |
function onSuccess(resp) { |
_.defer(function() { |
deferred.resolve(resp); |
}); |
} |
function onError(err) { |
_.defer(function() { |
deferred.reject(err); |
}); |
} |
}; |
} |
}(); |
var SearchIndex = function() { |
function SearchIndex(o) { |
o = o || {}; |
if (!o.datumTokenizer || !o.queryTokenizer) { |
$.error("datumTokenizer and queryTokenizer are both required"); |
} |
this.datumTokenizer = o.datumTokenizer; |
this.queryTokenizer = o.queryTokenizer; |
this.datums = []; |
this.trie = newNode(); |
} |
_.mixin(SearchIndex.prototype, { |
bootstrap: function bootstrap(o) { |
this.datums = o.datums; |
this.trie = o.trie; |
}, |
add: function(data) { |
var that = this; |
data = _.isArray(data) ? data : [ data ]; |
_.each(data, function(datum) { |
var id, tokens; |
id = that.datums.push(datum) - 1; |
tokens = normalizeTokens(that.datumTokenizer(datum)); |
_.each(tokens, function(token) { |
var node, chars, ch, ids; |
node = that.trie; |
chars = token.split(""); |
while (ch = chars.shift()) { |
node = node.children[ch] || (node.children[ch] = newNode()); |
node.ids.push(id); |
} |
}); |
}); |
}, |
get: function get(query) { |
var that = this, tokens, matches; |
tokens = normalizeTokens(this.queryTokenizer(query)); |
_.each(tokens, function(token) { |
var node, chars, ch, ids; |
if (matches && matches.length === 0) { |
return false; |
} |
node = that.trie; |
chars = token.split(""); |
while (node && (ch = chars.shift())) { |
node = node.children[ch]; |
} |
if (node && chars.length === 0) { |
ids = node.ids.slice(0); |
matches = matches ? getIntersection(matches, ids) : ids; |
} else { |
matches = []; |
return false; |
} |
}); |
return matches ? _.map(unique(matches), function(id) { |
return that.datums[id]; |
}) : []; |
}, |
serialize: function serialize() { |
return { |
datums: this.datums, |
trie: this.trie |
}; |
} |
}); |
return SearchIndex; |
function normalizeTokens(tokens) { |
tokens = _.filter(tokens, function(token) { |
return !!token; |
}); |
tokens = _.map(tokens, function(token) { |
return token.toLowerCase(); |
}); |
return tokens; |
} |
function newNode() { |
return { |
ids: [], |
children: {} |
}; |
} |
function unique(array) { |
var seen = {}, uniques = []; |
for (var i = 0; i < array.length; i++) { |
if (!seen[array[i]]) { |
seen[array[i]] = true; |
uniques.push(array[i]); |
} |
} |
return uniques; |
} |
function getIntersection(arrayA, arrayB) { |
var ai = 0, bi = 0, intersection = []; |
arrayA = arrayA.sort(compare); |
arrayB = arrayB.sort(compare); |
while (ai < arrayA.length && bi < arrayB.length) { |
if (arrayA[ai] < arrayB[bi]) { |
ai++; |
} else if (arrayA[ai] > arrayB[bi]) { |
bi++; |
} else { |
intersection.push(arrayA[ai]); |
ai++; |
bi++; |
} |
} |
return intersection; |
function compare(a, b) { |
return a - b; |
} |
} |
}(); |
var oParser = function() { |
return { |
local: getLocal, |
prefetch: getPrefetch, |
remote: getRemote |
}; |
function getLocal(o) { |
var local = o.local || null; |
if (_.isFunction(local)) { |
local = local.call(null); |
} |
return local; |
} |
function getPrefetch(o) { |
var prefetch, defaults; |
defaults = { |
url: null, |
thumbprint: "", |
ttl: 24 * 60 * 60 * 1e3, |
filter: null, |
ajax: {} |
}; |
if (prefetch = o.prefetch || null) { |
prefetch = _.isString(prefetch) ? { |
url: prefetch |
} : prefetch; |
prefetch = _.mixin(defaults, prefetch); |
prefetch.thumbprint = VERSION + prefetch.thumbprint; |
prefetch.ajax.type = prefetch.ajax.type || "GET"; |
prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; |
!prefetch.url && $.error("prefetch requires url to be set"); |
} |
return prefetch; |
} |
function getRemote(o) { |
var remote, defaults; |
defaults = { |
url: null, |
wildcard: "%QUERY", |
replace: null, |
rateLimitBy: "debounce", |
rateLimitWait: 300, |
send: null, |
filter: null, |
ajax: {} |
}; |
if (remote = o.remote || null) { |
remote = _.isString(remote) ? { |
url: remote |
} : remote; |
remote = _.mixin(defaults, remote); |
remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); |
remote.ajax.type = remote.ajax.type || "GET"; |
remote.ajax.dataType = remote.ajax.dataType || "json"; |
delete remote.rateLimitBy; |
delete remote.rateLimitWait; |
!remote.url && $.error("remote requires url to be set"); |
} |
return remote; |
function byDebounce(wait) { |
return function(fn) { |
return _.debounce(fn, wait); |
}; |
} |
function byThrottle(wait) { |
return function(fn) { |
return _.throttle(fn, wait); |
}; |
} |
} |
}(); |
var Bloodhound = window.Bloodhound = function() { |
var keys; |
keys = { |
data: "data", |
protocol: "protocol", |
thumbprint: "thumbprint" |
}; |
function Bloodhound(o) { |
if (!o || !o.local && !o.prefetch && !o.remote) { |
$.error("one of local, prefetch, or remote is required"); |
} |
this.limit = o.limit || 5; |
this.sorter = getSorter(o.sorter); |
this.dupDetector = o.dupDetector || ignoreDuplicates; |
this.local = oParser.local(o); |
this.prefetch = oParser.prefetch(o); |
this.remote = oParser.remote(o); |
this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; |
this.index = new SearchIndex({ |
datumTokenizer: o.datumTokenizer, |
queryTokenizer: o.queryTokenizer |
}); |
this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; |
} |
Bloodhound.tokenizers = { |
whitespace: function whitespaceTokenizer(s) { |
return s.split(/\s+/); |
}, |
nonword: function nonwordTokenizer(s) { |
return s.split(/\W+/); |
} |
}; |
_.mixin(Bloodhound.prototype, { |
_loadPrefetch: function loadPrefetch(o) { |
var that = this, serialized, deferred; |
if (serialized = this._readFromStorage(o.thumbprint)) { |
this.index.bootstrap(serialized); |
deferred = $.Deferred().resolve(); |
} else { |
deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); |
} |
return deferred; |
function handlePrefetchResponse(resp) { |
var filtered; |
filtered = o.filter ? o.filter(resp) : resp; |
that.add(filtered); |
that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); |
} |
}, |
_getFromRemote: function getFromRemote(query, cb) { |
var that = this, url, uriEncodedQuery; |
query = query || ""; |
uriEncodedQuery = encodeURIComponent(query); |
url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); |
return this.transport.get(url, this.remote.ajax, handleRemoteResponse); |
function handleRemoteResponse(resp) { |
var filtered = that.remote.filter ? that.remote.filter(resp) : resp; |
cb(filtered); |
} |
}, |
_saveToStorage: function saveToStorage(data, thumbprint, ttl) { |
if (this.storage) { |
this.storage.set(keys.data, data, ttl); |
this.storage.set(keys.protocol, location.protocol, ttl); |
this.storage.set(keys.thumbprint, thumbprint, ttl); |
} |
}, |
_readFromStorage: function readFromStorage(thumbprint) { |
var stored = {}, isExpired; |
if (this.storage) { |
stored.data = this.storage.get(keys.data); |
stored.protocol = this.storage.get(keys.protocol); |
stored.thumbprint = this.storage.get(keys.thumbprint); |
} |
isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; |
return stored.data && !isExpired ? stored.data : null; |
}, |
initialize: function initialize() { |
var that = this, deferred; |
deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); |
this.local && deferred.done(addLocalToIndex); |
this.transport = this.remote ? new Transport(this.remote) : null; |
this.initialize = function initialize() { |
return deferred.promise(); |
}; |
return deferred.promise(); |
function addLocalToIndex() { |
that.add(that.local); |
} |
}, |
add: function add(data) { |
this.index.add(data); |
}, |
get: function get(query, cb) { |
var that = this, matches, cacheHit = false; |
matches = this.index.get(query); |
matches = this.sorter(matches).slice(0, this.limit); |
if (matches.length < this.limit && this.transport) { |
cacheHit = this._getFromRemote(query, returnRemoteMatches); |
} |
!cacheHit && cb && cb(matches); |
function returnRemoteMatches(remoteMatches) { |
var matchesWithBackfill = matches.slice(0); |
_.each(remoteMatches, function(remoteMatch) { |
var isDuplicate; |
isDuplicate = _.some(matchesWithBackfill, function(match) { |
return that.dupDetector(remoteMatch, match); |
}); |
!isDuplicate && matchesWithBackfill.push(remoteMatch); |
return matchesWithBackfill.length < that.limit; |
}); |
cb && cb(that.sorter(matchesWithBackfill)); |
} |
}, |
ttAdapter: function ttAdapter() { |
return _.bind(this.get, this); |
} |
}); |
return Bloodhound; |
function getSorter(sortFn) { |
return _.isFunction(sortFn) ? sort : noSort; |
function sort(array) { |
return array.sort(sortFn); |
} |
function noSort(array) { |
return array; |
} |
} |
function ignoreDuplicates() { |
return false; |
} |
}(); |
var html = { |
wrapper: '<span class="twitter-typeahead"></span>', |
dropdown: '<span class="tt-dropdown-menu"></span>', |
dataset: '<div class="tt-dataset-%CLASS%"></div>', |
suggestions: '<span class="tt-suggestions"></span>', |
suggestion: '<div class="tt-suggestion">%BODY%</div>' |
}; |
var css = { |
wrapper: { |
position: "relative", |
display: "inline-block" |
}, |
hint: { |
position: "absolute", |
top: "0", |
left: "0", |
borderColor: "transparent", |
boxShadow: "none" |
}, |
input: { |
position: "relative", |
verticalAlign: "top", |
backgroundColor: "transparent" |
}, |
inputWithNoHint: { |
position: "relative", |
verticalAlign: "top" |
}, |
dropdown: { |
position: "absolute", |
top: "100%", |
left: "0", |
zIndex: "100", |
display: "none" |
}, |
suggestions: { |
display: "block" |
}, |
suggestion: { |
whiteSpace: "nowrap", |
cursor: "pointer" |
}, |
suggestionChild: { |
whiteSpace: "normal" |
}, |
ltr: { |
left: "0", |
right: "auto" |
}, |
rtl: { |
left: "auto", |
right: " 0" |
} |
}; |
if (_.isMsie()) { |
_.mixin(css.input, { |
backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" |
}); |
} |
if (_.isMsie() && _.isMsie() <= 7) { |
_.mixin(css.input, { |
marginTop: "-1px" |
}); |
} |
var EventBus = function() { |
var namespace = "typeahead:"; |
function EventBus(o) { |
if (!o || !o.el) { |
$.error("EventBus initialized without el"); |
} |
this.$el = $(o.el); |
} |
_.mixin(EventBus.prototype, { |
trigger: function(type) { |
var args = [].slice.call(arguments, 1); |
this.$el.trigger(namespace + type, args); |
} |
}); |
return EventBus; |
}(); |
var EventEmitter = function() { |
var splitter = /\s+/, nextTick = getNextTick(); |
return { |
onSync: onSync, |
onAsync: onAsync, |
off: off, |
trigger: trigger |
}; |
function on(method, types, cb, context) { |
var type; |
if (!cb) { |
return this; |
} |
types = types.split(splitter); |
cb = context ? bindContext(cb, context) : cb; |
this._callbacks = this._callbacks || {}; |
while (type = types.shift()) { |
this._callbacks[type] = this._callbacks[type] || { |
sync: [], |
async: [] |
}; |
this._callbacks[type][method].push(cb); |
} |
return this; |
} |
function onAsync(types, cb, context) { |
return on.call(this, "async", types, cb, context); |
} |
function onSync(types, cb, context) { |
return on.call(this, "sync", types, cb, context); |
} |
function off(types) { |
var type; |
if (!this._callbacks) { |
return this; |
} |
types = types.split(splitter); |
while (type = types.shift()) { |
delete this._callbacks[type]; |
} |
return this; |
} |
function trigger(types) { |
var that = this, type, callbacks, args, syncFlush, asyncFlush; |
if (!this._callbacks) { |
return this; |
} |
types = types.split(splitter); |
args = [].slice.call(arguments, 1); |
while ((type = types.shift()) && (callbacks = this._callbacks[type])) { |
syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); |
asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); |
syncFlush() && nextTick(asyncFlush); |
} |
return this; |
} |
function getFlush(callbacks, context, args) { |
return flush; |
function flush() { |
var cancelled; |
for (var i = 0; !cancelled && i < callbacks.length; i += 1) { |
cancelled = callbacks[i].apply(context, args) === false; |
} |
return !cancelled; |
} |
} |
function getNextTick() { |
var nextTickFn, messageChannel; |
if (window.setImmediate) { |
nextTickFn = function nextTickSetImmediate(fn) { |
setImmediate(function() { |
fn(); |
}); |
}; |
} else { |
nextTickFn = function nextTickSetTimeout(fn) { |
setTimeout(function() { |
fn(); |
}, 0); |
}; |
} |
return nextTickFn; |
} |
function bindContext(fn, context) { |
return fn.bind ? fn.bind(context) : function() { |
fn.apply(context, [].slice.call(arguments, 0)); |
}; |
} |
}(); |
var highlight = function(doc) { |
var defaults = { |
node: null, |
pattern: null, |
tagName: "strong", |
className: null, |
wordsOnly: false, |
caseSensitive: false |
}; |
return function hightlight(o) { |
var regex; |
o = _.mixin({}, defaults, o); |
if (!o.node || !o.pattern) { |
return; |
} |
o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; |
regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); |
traverse(o.node, hightlightTextNode); |
function hightlightTextNode(textNode) { |
var match, patternNode; |
if (match = regex.exec(textNode.data)) { |
wrapperNode = doc.createElement(o.tagName); |
o.className && (wrapperNode.className = o.className); |
patternNode = textNode.splitText(match.index); |
patternNode.splitText(match[0].length); |
wrapperNode.appendChild(patternNode.cloneNode(true)); |
textNode.parentNode.replaceChild(wrapperNode, patternNode); |
} |
return !!match; |
} |
function traverse(el, hightlightTextNode) { |
var childNode, TEXT_NODE_TYPE = 3; |
for (var i = 0; i < el.childNodes.length; i++) { |
childNode = el.childNodes[i]; |
if (childNode.nodeType === TEXT_NODE_TYPE) { |
i += hightlightTextNode(childNode) ? 1 : 0; |
} else { |
traverse(childNode, hightlightTextNode); |
} |
} |
} |
}; |
function getRegex(patterns, caseSensitive, wordsOnly) { |
var escapedPatterns = [], regexStr; |
for (var i = 0; i < patterns.length; i++) { |
escapedPatterns.push(_.escapeRegExChars(patterns[i])); |
} |
regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; |
return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); |
} |
}(window.document); |
var Input = function() { |
var specialKeyCodeMap; |
specialKeyCodeMap = { |
9: "tab", |
27: "esc", |
37: "left", |
39: "right", |
13: "enter", |
38: "up", |
40: "down" |
}; |
function Input(o) { |
var that = this, onBlur, onFocus, onKeydown, onInput; |
o = o || {}; |
if (!o.input) { |
$.error("input is missing"); |
} |
onBlur = _.bind(this._onBlur, this); |
onFocus = _.bind(this._onFocus, this); |
onKeydown = _.bind(this._onKeydown, this); |
onInput = _.bind(this._onInput, this); |
this.$hint = $(o.hint); |
this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); |
if (this.$hint.length === 0) { |
this.setHintValue = this.getHintValue = this.clearHint = _.noop; |
} |
if (!_.isMsie()) { |
this.$input.on("input.tt", onInput); |
} else { |
this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { |
if (specialKeyCodeMap[$e.which || $e.keyCode]) { |
return; |
} |
_.defer(_.bind(that._onInput, that, $e)); |
}); |
} |
this.query = this.$input.val(); |
this.$overflowHelper = buildOverflowHelper(this.$input); |
} |
Input.normalizeQuery = function(str) { |
return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); |
}; |
_.mixin(Input.prototype, EventEmitter, { |
_onBlur: function onBlur($e) { |
this.resetInputValue(); |
this.trigger("blurred"); |
}, |
_onFocus: function onFocus($e) { |
this.trigger("focused"); |
}, |
_onKeydown: function onKeydown($e) { |
var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; |
this._managePreventDefault(keyName, $e); |
if (keyName && this._shouldTrigger(keyName, $e)) { |
this.trigger(keyName + "Keyed", $e); |
} |
}, |
_onInput: function onInput($e) { |
this._checkInputValue(); |
}, |
_managePreventDefault: function managePreventDefault(keyName, $e) { |
var preventDefault, hintValue, inputValue; |
switch (keyName) { |
case "tab": |
hintValue = this.getHintValue(); |
inputValue = this.getInputValue(); |
preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); |
break; |
|
case "up": |
case "down": |
preventDefault = !withModifier($e); |
break; |
|
default: |
preventDefault = false; |
} |
preventDefault && $e.preventDefault(); |
}, |
_shouldTrigger: function shouldTrigger(keyName, $e) { |
var trigger; |
switch (keyName) { |
case "tab": |
trigger = !withModifier($e); |
break; |
|
default: |
trigger = true; |
} |
return trigger; |
}, |
_checkInputValue: function checkInputValue() { |
var inputValue, areEquivalent, hasDifferentWhitespace; |
inputValue = this.getInputValue(); |
areEquivalent = areQueriesEquivalent(inputValue, this.query); |
hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; |
if (!areEquivalent) { |
this.trigger("queryChanged", this.query = inputValue); |
} else if (hasDifferentWhitespace) { |
this.trigger("whitespaceChanged", this.query); |
} |
}, |
focus: function focus() { |
this.$input.focus(); |
}, |
blur: function blur() { |
this.$input.blur(); |
}, |
getQuery: function getQuery() { |
return this.query; |
}, |
setQuery: function setQuery(query) { |
this.query = query; |
}, |
getInputValue: function getInputValue() { |
return this.$input.val(); |
}, |
setInputValue: function setInputValue(value, silent) { |
this.$input.val(value); |
!silent && this._checkInputValue(); |
}, |
getHintValue: function getHintValue() { |
return this.$hint.val(); |
}, |
setHintValue: function setHintValue(value) { |
this.$hint.val(value); |
}, |
resetInputValue: function resetInputValue() { |
this.$input.val(this.query); |
}, |
clearHint: function clearHint() { |
this.$hint.val(""); |
}, |
getLanguageDirection: function getLanguageDirection() { |
return (this.$input.css("direction") || "ltr").toLowerCase(); |
}, |
hasOverflow: function hasOverflow() { |
var constraint = this.$input.width() - 2; |
this.$overflowHelper.text(this.getInputValue()); |
return this.$overflowHelper.width() >= constraint; |
}, |
isCursorAtEnd: function() { |
var valueLength, selectionStart, range; |
valueLength = this.$input.val().length; |
selectionStart = this.$input[0].selectionStart; |
if (_.isNumber(selectionStart)) { |
return selectionStart === valueLength; |
} else if (document.selection) { |
range = document.selection.createRange(); |
range.moveStart("character", -valueLength); |
return valueLength === range.text.length; |
} |
return true; |
}, |
destroy: function destroy() { |
this.$hint.off(".tt"); |
this.$input.off(".tt"); |
this.$hint = this.$input = this.$overflowHelper = null; |
} |
}); |
return Input; |
function buildOverflowHelper($input) { |
return $('<pre aria-hidden="true"></pre>').css({ |
position: "absolute", |
visibility: "hidden", |
whiteSpace: "nowrap", |
fontFamily: $input.css("font-family"), |
fontSize: $input.css("font-size"), |
fontStyle: $input.css("font-style"), |
fontVariant: $input.css("font-variant"), |
fontWeight: $input.css("font-weight"), |
wordSpacing: $input.css("word-spacing"), |
letterSpacing: $input.css("letter-spacing"), |
textIndent: $input.css("text-indent"), |
textRendering: $input.css("text-rendering"), |
textTransform: $input.css("text-transform") |
}).insertAfter($input); |
} |
function areQueriesEquivalent(a, b) { |
return Input.normalizeQuery(a) === Input.normalizeQuery(b); |
} |
function withModifier($e) { |
return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; |
} |
}(); |
var Dataset = function() { |
var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum"; |
function Dataset(o) { |
o = o || {}; |
o.templates = o.templates || {}; |
if (!o.source) { |
$.error("missing source"); |
} |
if (o.name && !isValidName(o.name)) { |
$.error("invalid dataset name: " + o.name); |
} |
this.query = null; |
this.highlight = !!o.highlight; |
this.name = o.name || _.getUniqueId(); |
this.source = o.source; |
this.displayFn = getDisplayFn(o.display || o.displayKey); |
this.templates = getTemplates(o.templates, this.displayFn); |
this.$el = $(html.dataset.replace("%CLASS%", this.name)); |
} |
Dataset.extractDatasetName = function extractDatasetName(el) { |
return $(el).data(datasetKey); |
}; |
Dataset.extractValue = function extractDatum(el) { |
return $(el).data(valueKey); |
}; |
Dataset.extractDatum = function extractDatum(el) { |
return $(el).data(datumKey); |
}; |
_.mixin(Dataset.prototype, EventEmitter, { |
_render: function render(query, suggestions) { |
if (!this.$el) { |
return; |
} |
var that = this, hasSuggestions; |
this.$el.empty(); |
hasSuggestions = suggestions && suggestions.length; |
if (!hasSuggestions && this.templates.empty) { |
this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); |
} else if (hasSuggestions) { |
this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); |
} |
this.trigger("rendered"); |
function getEmptyHtml() { |
return that.templates.empty({ |
query: query, |
isEmpty: true |
}); |
} |
function getSuggestionsHtml() { |
var $suggestions, nodes; |
$suggestions = $(html.suggestions).css(css.suggestions); |
nodes = _.map(suggestions, getSuggestionNode); |
$suggestions.append.apply($suggestions, nodes); |
that.highlight && highlight({ |
node: $suggestions[0], |
pattern: query |
}); |
return $suggestions; |
function getSuggestionNode(suggestion) { |
var $el, innerHtml, outerHtml; |
innerHtml = that.templates.suggestion(suggestion); |
outerHtml = html.suggestion.replace("%BODY%", innerHtml); |
$el = $(outerHtml).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion); |
$el.children().each(function() { |
$(this).css(css.suggestionChild); |
}); |
return $el; |
} |
} |
function getHeaderHtml() { |
return that.templates.header({ |
query: query, |
isEmpty: !hasSuggestions |
}); |
} |
function getFooterHtml() { |
return that.templates.footer({ |
query: query, |
isEmpty: !hasSuggestions |
}); |
} |
}, |
getRoot: function getRoot() { |
return this.$el; |
}, |
update: function update(query) { |
var that = this; |
this.query = query; |
this.source(query, renderIfQueryIsSame); |
function renderIfQueryIsSame(suggestions) { |
query === that.query && that._render(query, suggestions); |
} |
}, |
clear: function clear() { |
this._render(this.query || ""); |
}, |
isEmpty: function isEmpty() { |
return this.$el.is(":empty"); |
}, |
destroy: function destroy() { |
this.$el = null; |
} |
}); |
return Dataset; |
function getDisplayFn(display) { |
display = display || "value"; |
return _.isFunction(display) ? display : displayFn; |
function displayFn(obj) { |
return obj[display]; |
} |
} |
function getTemplates(templates, displayFn) { |
return { |
empty: templates.empty && _.templatify(templates.empty), |
header: templates.header && _.templatify(templates.header), |
footer: templates.footer && _.templatify(templates.footer), |
suggestion: templates.suggestion || suggestionTemplate |
}; |
function suggestionTemplate(context) { |
return "<p>" + displayFn(context) + "</p>"; |
} |
} |
function isValidName(str) { |
return /^[_a-zA-Z0-9-]+$/.test(str); |
} |
}(); |
var Dropdown = function() { |
function Dropdown(o) { |
var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; |
o = o || {}; |
if (!o.menu) { |
$.error("menu is required"); |
} |
this.isOpen = false; |
this.isEmpty = true; |
this.datasets = _.map(o.datasets, initializeDataset); |
onSuggestionClick = _.bind(this._onSuggestionClick, this); |
onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); |
onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); |
this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); |
_.each(this.datasets, function(dataset) { |
that.$menu.append(dataset.getRoot()); |
dataset.onSync("rendered", that._onRendered, that); |
}); |
} |
_.mixin(Dropdown.prototype, EventEmitter, { |
_onSuggestionClick: function onSuggestionClick($e) { |
this.trigger("suggestionClicked", $($e.currentTarget)); |
}, |
_onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { |
this._removeCursor(); |
this._setCursor($($e.currentTarget), true); |
}, |
_onSuggestionMouseLeave: function onSuggestionMouseLeave($e) { |
this._removeCursor(); |
}, |
_onRendered: function onRendered() { |
this.isEmpty = _.every(this.datasets, isDatasetEmpty); |
this.isEmpty ? this._hide() : this.isOpen && this._show(); |
this.trigger("datasetRendered"); |
function isDatasetEmpty(dataset) { |
return dataset.isEmpty(); |
} |
}, |
_hide: function() { |
this.$menu.hide(); |
}, |
_show: function() { |
this.$menu.css("display", "block"); |
}, |
_getSuggestions: function getSuggestions() { |
return this.$menu.find(".tt-suggestion"); |
}, |
_getCursor: function getCursor() { |
return this.$menu.find(".tt-cursor").first(); |
}, |
_setCursor: function setCursor($el, silent) { |
$el.first().addClass("tt-cursor"); |
!silent && this.trigger("cursorMoved"); |
}, |
_removeCursor: function removeCursor() { |
this._getCursor().removeClass("tt-cursor"); |
}, |
_moveCursor: function moveCursor(increment) { |
var $suggestions, $oldCursor, newCursorIndex, $newCursor; |
if (!this.isOpen) { |
return; |
} |
$oldCursor = this._getCursor(); |
$suggestions = this._getSuggestions(); |
this._removeCursor(); |
newCursorIndex = $suggestions.index($oldCursor) + increment; |
newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; |
if (newCursorIndex === -1) { |
this.trigger("cursorRemoved"); |
return; |
} else if (newCursorIndex < -1) { |
newCursorIndex = $suggestions.length - 1; |
} |
this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); |
this._ensureVisible($newCursor); |
}, |
_ensureVisible: function ensureVisible($el) { |
var elTop, elBottom, menuScrollTop, menuHeight; |
elTop = $el.position().top; |
elBottom = elTop + $el.outerHeight(true); |
menuScrollTop = this.$menu.scrollTop(); |
menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); |
if (elTop < 0) { |
this.$menu.scrollTop(menuScrollTop + elTop); |
} else if (menuHeight < elBottom) { |
this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); |
} |
}, |
close: function close() { |
if (this.isOpen) { |
this.isOpen = false; |
this._removeCursor(); |
this._hide(); |
this.trigger("closed"); |
} |
}, |
open: function open() { |
if (!this.isOpen) { |
this.isOpen = true; |
!this.isEmpty && this._show(); |
this.trigger("opened"); |
} |
}, |
setLanguageDirection: function setLanguageDirection(dir) { |
this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); |
}, |
moveCursorUp: function moveCursorUp() { |
this._moveCursor(-1); |
}, |
moveCursorDown: function moveCursorDown() { |
this._moveCursor(+1); |
}, |
getDatumForSuggestion: function getDatumForSuggestion($el) { |
var datum = null; |
if ($el.length) { |
datum = { |
raw: Dataset.extractDatum($el), |
value: Dataset.extractValue($el), |
datasetName: Dataset.extractDatasetName($el) |
}; |
} |
return datum; |
}, |
getDatumForCursor: function getDatumForCursor() { |
return this.getDatumForSuggestion(this._getCursor().first()); |
}, |
getDatumForTopSuggestion: function getDatumForTopSuggestion() { |
return this.getDatumForSuggestion(this._getSuggestions().first()); |
}, |
update: function update(query) { |
_.each(this.datasets, updateDataset); |
function updateDataset(dataset) { |
dataset.update(query); |
} |
}, |
empty: function empty() { |
_.each(this.datasets, clearDataset); |
this.isEmpty = true; |
function clearDataset(dataset) { |
dataset.clear(); |
} |
}, |
isVisible: function isVisible() { |
return this.isOpen && !this.isEmpty; |
}, |
destroy: function destroy() { |
this.$menu.off(".tt"); |
this.$menu = null; |
_.each(this.datasets, destroyDataset); |
function destroyDataset(dataset) { |
dataset.destroy(); |
} |
} |
}); |
return Dropdown; |
function initializeDataset(oDataset) { |
return new Dataset(oDataset); |
} |
}(); |
var Typeahead = function() { |
var attrsKey = "ttAttrs"; |
function Typeahead(o) { |
var $menu, $input, $hint, datasets; |
o = o || {}; |
if (!o.input) { |
$.error("missing input"); |
} |
this.autoselect = !!o.autoselect; |
this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; |
this.$node = buildDomStructure(o.input, o.withHint); |
$menu = this.$node.find(".tt-dropdown-menu"); |
$input = this.$node.find(".tt-input"); |
$hint = this.$node.find(".tt-hint"); |
this.eventBus = o.eventBus || new EventBus({ |
el: $input |
}); |
this.dropdown = new Dropdown({ |
menu: $menu, |
datasets: o.datasets |
}).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); |
this.input = new Input({ |
input: $input, |
hint: $hint |
}).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); |
$menu.on("mousedown.tt", function($e) { |
if (_.isMsie() && _.isMsie() < 9) { |
$input[0].onbeforedeactivate = function() { |
window.event.returnValue = false; |
$input[0].onbeforedeactivate = null; |
}; |
} |
$e.preventDefault(); |
}); |
} |
_.mixin(Typeahead.prototype, { |
_onSuggestionClicked: function onSuggestionClicked(type, $el) { |
var datum; |
if (datum = this.dropdown.getDatumForSuggestion($el)) { |
this._select(datum); |
} |
}, |
_onCursorMoved: function onCursorMoved() { |
var datum = this.dropdown.getDatumForCursor(); |
this.input.clearHint(); |
this.input.setInputValue(datum.value, true); |
this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); |
}, |
_onCursorRemoved: function onCursorRemoved() { |
this.input.resetInputValue(); |
this._updateHint(); |
}, |
_onDatasetRendered: function onDatasetRendered() { |
this._updateHint(); |
}, |
_onOpened: function onOpened() { |
this._updateHint(); |
this.eventBus.trigger("opened"); |
}, |
_onClosed: function onClosed() { |
this.input.clearHint(); |
this.eventBus.trigger("closed"); |
}, |
_onFocused: function onFocused() { |
this.dropdown.empty(); |
this.dropdown.open(); |
}, |
_onBlurred: function onBlurred() { |
this.dropdown.close(); |
}, |
_onEnterKeyed: function onEnterKeyed(type, $e) { |
var cursorDatum, topSuggestionDatum; |
cursorDatum = this.dropdown.getDatumForCursor(); |
topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); |
if (cursorDatum) { |
this._select(cursorDatum); |
$e.preventDefault(); |
} else if (this.autoselect && topSuggestionDatum) { |
this._select(topSuggestionDatum); |
$e.preventDefault(); |
} |
}, |
_onTabKeyed: function onTabKeyed(type, $e) { |
var datum; |
if (datum = this.dropdown.getDatumForCursor()) { |
this._select(datum); |
$e.preventDefault(); |
} else { |
this._autocomplete(); |
} |
}, |
_onEscKeyed: function onEscKeyed() { |
this.dropdown.close(); |
this.input.resetInputValue(); |
}, |
_onUpKeyed: function onUpKeyed() { |
var query = this.input.getQuery(); |
if (!this.dropdown.isOpen && query.length >= this.minLength) { |
this.dropdown.update(query); |
} |
this.dropdown.open(); |
this.dropdown.moveCursorUp(); |
}, |
_onDownKeyed: function onDownKeyed() { |
var query = this.input.getQuery(); |
if (!this.dropdown.isOpen && query.length >= this.minLength) { |
this.dropdown.update(query); |
} |
this.dropdown.open(); |
this.dropdown.moveCursorDown(); |
}, |
_onLeftKeyed: function onLeftKeyed() { |
this.dir === "rtl" && this._autocomplete(); |
}, |
_onRightKeyed: function onRightKeyed() { |
this.dir === "ltr" && this._autocomplete(); |
}, |
_onQueryChanged: function onQueryChanged(e, query) { |
this.input.clearHint(); |
this.dropdown.empty(); |
query.length >= this.minLength && this.dropdown.update(query); |
this.dropdown.open(); |
this._setLanguageDirection(); |
}, |
_onWhitespaceChanged: function onWhitespaceChanged() { |
this._updateHint(); |
this.dropdown.open(); |
}, |
_setLanguageDirection: function setLanguageDirection() { |
var dir; |
if (this.dir !== (dir = this.input.getLanguageDirection())) { |
this.dir = dir; |
this.$node.css("direction", dir); |
this.dropdown.setLanguageDirection(dir); |
} |
}, |
_updateHint: function updateHint() { |
var datum, inputValue, query, escapedQuery, frontMatchRegEx, match; |
datum = this.dropdown.getDatumForTopSuggestion(); |
if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { |
inputValue = this.input.getInputValue(); |
query = Input.normalizeQuery(inputValue); |
escapedQuery = _.escapeRegExChars(query); |
frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); |
match = frontMatchRegEx.exec(datum.value); |
this.input.setHintValue(inputValue + (match ? match[1] : "")); |
} |
}, |
_autocomplete: function autocomplete() { |
var hint, query, datum; |
hint = this.input.getHintValue(); |
query = this.input.getQuery(); |
if (hint && query !== hint && this.input.isCursorAtEnd()) { |
datum = this.dropdown.getDatumForTopSuggestion(); |
datum && this.input.setInputValue(datum.value); |
this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); |
} |
}, |
_select: function select(datum) { |
this.input.clearHint(); |
this.input.setQuery(datum.value); |
this.input.setInputValue(datum.value, true); |
this._setLanguageDirection(); |
this.eventBus.trigger("selected", datum.raw, datum.datasetName); |
this.dropdown.close(); |
_.defer(_.bind(this.dropdown.empty, this.dropdown)); |
}, |
open: function open() { |
this.dropdown.open(); |
}, |
close: function close() { |
this.dropdown.close(); |
}, |
getQuery: function getQuery() { |
return this.input.getQuery(); |
}, |
setQuery: function setQuery(val) { |
this.input.setInputValue(val); |
}, |
destroy: function destroy() { |
this.input.destroy(); |
this.dropdown.destroy(); |
destroyDomStructure(this.$node); |
this.$node = null; |
} |
}); |
return Typeahead; |
function buildDomStructure(input, withHint) { |
var $input, $wrapper, $dropdown, $hint; |
$input = $(input); |
$wrapper = $(html.wrapper).css(css.wrapper); |
$dropdown = $(html.dropdown).css(css.dropdown); |
$hint = $input.clone().css(css.hint).css(getBackgroundStyles($input)); |
$hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({ |
autocomplete: "off", |
spellcheck: "false" |
}); |
$input.data(attrsKey, { |
dir: $input.attr("dir"), |
autocomplete: $input.attr("autocomplete"), |
spellcheck: $input.attr("spellcheck"), |
style: $input.attr("style") |
}); |
$input.addClass("tt-input").attr({ |
autocomplete: "off", |
spellcheck: false |
}).css(withHint ? css.input : css.inputWithNoHint); |
try { |
!$input.attr("dir") && $input.attr("dir", "auto"); |
} catch (e) {} |
return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); |
} |
function getBackgroundStyles($el) { |
return { |
backgroundAttachment: $el.css("background-attachment"), |
backgroundClip: $el.css("background-clip"), |
backgroundColor: $el.css("background-color"), |
backgroundImage: $el.css("background-image"), |
backgroundOrigin: $el.css("background-origin"), |
backgroundPosition: $el.css("background-position"), |
backgroundRepeat: $el.css("background-repeat"), |
backgroundSize: $el.css("background-size") |
}; |
} |
function destroyDomStructure($node) { |
var $input = $node.find(".tt-input"); |
_.each($input.data(attrsKey), function(val, key) { |
_.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); |
}); |
$input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); |
$node.remove(); |
} |
}(); |
(function() { |
var old, typeaheadKey, methods; |
old = $.fn.typeahead; |
typeaheadKey = "ttTypeahead"; |
methods = { |
initialize: function initialize(o, datasets) { |
datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); |
o = o || {}; |
return this.each(attach); |
function attach() { |
var $input = $(this), eventBus, typeahead; |
_.each(datasets, function(d) { |
d.highlight = !!o.highlight; |
}); |
typeahead = new Typeahead({ |
input: $input, |
eventBus: eventBus = new EventBus({ |
el: $input |
}), |
withHint: _.isUndefined(o.hint) ? true : !!o.hint, |
minLength: o.minLength, |
autoselect: o.autoselect, |
datasets: datasets |
}); |
$input.data(typeaheadKey, typeahead); |
} |
}, |
open: function open() { |
return this.each(openTypeahead); |
function openTypeahead() { |
var $input = $(this), typeahead; |
if (typeahead = $input.data(typeaheadKey)) { |
typeahead.open(); |
} |
} |
}, |
close: function close() { |
return this.each(closeTypeahead); |
function closeTypeahead() { |
var $input = $(this), typeahead; |
if (typeahead = $input.data(typeaheadKey)) { |
typeahead.close(); |
} |
} |
}, |
val: function val(newVal) { |
return !arguments.length ? getQuery(this.first()) : this.each(setQuery); |
function setQuery() { |
var $input = $(this), typeahead; |
if (typeahead = $input.data(typeaheadKey)) { |
typeahead.setQuery(newVal); |
} |
} |
function getQuery($input) { |
var typeahead, query; |
if (typeahead = $input.data(typeaheadKey)) { |
query = typeahead.getQuery(); |
} |
return query; |
} |
}, |
destroy: function destroy() { |
return this.each(unattach); |
function unattach() { |
var $input = $(this), typeahead; |
if (typeahead = $input.data(typeaheadKey)) { |
typeahead.destroy(); |
$input.removeData(typeaheadKey); |
} |
} |
} |
}; |
$.fn.typeahead = function(method) { |
if (methods[method]) { |
return methods[method].apply(this, [].slice.call(arguments, 1)); |
} else { |
return methods.initialize.apply(this, arguments); |
} |
}; |
$.fn.typeahead.noConflict = function noConflict() { |
$.fn.typeahead = old; |
return this; |
}; |
})(); |
})(window.jQuery); |
/bower_components/bootstrap-tokenfield/docs-assets/js/typeahead.bundle.min.js |
@@ -0,0 +1,7 @@ |
/*! |
* typeahead.js 0.10.1 |
* https://github.com/twitter/typeahead.js |
* Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT |
*/ |
|
!function(a){var b={isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}},c="0.10.1",d=function(){function a(a){this.maxSize=a||100,this.size=0,this.hash={},this.list=new c}function c(){this.head=this.tail=null}function d(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(a.prototype,{set:function(a,b){var c,e=this.list.tail;this.size>=this.maxSize&&(this.list.remove(e),delete this.hash[e.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new d(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0}}),b.mixin(c.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),a}(this),e=function(){function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+this.prefix)}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),f=function(){function c(b){b=b||{},this._send=b.transport?e(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get}function e(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new d(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i=new d(10)},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(b),i.set(a,b)}function e(){f--,delete g[a],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var j,k=this;(j=g[a])?j.done(d):h>f?(f++,g[a]=this._send(a,b).done(d).always(e)):this.onDeckRequestArgs=[].slice.call(arguments,0)},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),(e=i.get(a))?b.defer(function(){d&&d(e)}):this._get(a,c,d),!!e}}),c}(),g=function(){function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.datums=[],this.trie=e()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0;d<a.length;d++)b[a[d]]||(b[a[d]]=!0,c.push(a[d]));return c}function g(a,b){function c(a,b){return a-b}var d=0,e=0,f=[];for(a=a.sort(c),b=b.sort(c);d<a.length&&e<b.length;)a[d]<b[e]?d++:a[d]>b[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),h=function(){function d(a){var c=a.local||null;return b.isFunction(c)&&(c=c.call(null)),c}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}(),i=(window.Bloodhound=function(){function c(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=d(b.sorter),this.dupDetector=b.dupDetector||i,this.local=h.local(b),this.prefetch=h.prefetch(b),this.remote=h.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new g({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new e(this.cacheKey):null}function d(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function i(){return!1}var j;return j={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.tokenizers={whitespace:function(a){return a.split(/\s+/)},nonword:function(a){return a.split(/\W+/)}},b.mixin(c.prototype,{_loadPrefetch:function(b){function c(a){var c;c=b.filter?b.filter(a):a,f.add(c),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a){var c=f.remote.filter?f.remote.filter(a):a;b(c)}var d,e,f=this;return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(j.data,a,c),this.storage.set(j.protocol,location.protocol,c),this.storage.set(j.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(j.data),c.protocol=this.storage.get(j.protocol),c.thumbprint=this.storage.get(j.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},initialize:function(){function b(){d.add(d.local)}var c,d=this;return c=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),this.local&&c.done(b),this.transport=this.remote?new f(this.remote):null,this.initialize=function(){return c.promise()},c.promise()},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=e.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return f.dupDetector(a,b)}),!c&&d.push(a),d.length<f.limit}),c&&c(f.sorter(d))}var e,f=this,g=!1;e=this.index.get(a),e=this.sorter(e).slice(0,this.limit),e.length<this.limit&&this.transport&&(g=this._getFromRemote(a,d)),!g&&c&&c(e)},ttAdapter:function(){return b.bind(this.get,this)}}),c}(),{wrapper:'<span class="twitter-typeahead"></span>',dropdown:'<span class="tt-dropdown-menu"></span>',dataset:'<div class="tt-dataset-%CLASS%"></div>',suggestions:'<span class="tt-suggestions"></span>',suggestion:'<div class="tt-suggestion">%BODY%</div>'}),j={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};b.isMsie()&&b.mixin(j.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),b.isMsie()&&b.isMsie()<=7&&b.mixin(j.input,{marginTop:"-1px"});var k=function(){function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return b.mixin(c.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),c}(),l=function(){function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0;!d&&e<a.length;e+=1)d=a[e].apply(b,c)===!1;return!d}return d}function g(){var a;return a=window.setImmediate?function(a){setImmediate(function(){a()})}:function(a){setTimeout(function(){a()},0)}}function h(a,b){return a.bind?a.bind(b):function(){a.apply(b,[].slice.call(arguments,0))}}var i=/\s+/,j=g();return{onSync:c,onAsync:b,off:d,trigger:e}}(),m=function(a){function c(a,c,d){for(var e,f=[],g=0;g<a.length;g++)f.push(b.escapeRegExChars(a[g]));return e=d?"\\b("+f.join("|")+")\\b":"("+f.join("|")+")",c?new RegExp(e):new RegExp(e,"i")}var d={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(e){function f(b){var c,d;return(c=h.exec(b.data))&&(wrapperNode=a.createElement(e.tagName),e.className&&(wrapperNode.className=e.className),d=b.splitText(c.index),d.splitText(c[0].length),wrapperNode.appendChild(d.cloneNode(!0)),b.parentNode.replaceChild(wrapperNode,d)),!!c}function g(a,b){for(var c,d=3,e=0;e<a.childNodes.length;e++)c=a.childNodes[e],c.nodeType===d?e+=b(c)?1:0:g(c,b)}var h;e=b.mixin({},d,e),e.node&&e.pattern&&(e.pattern=b.isArray(e.pattern)?e.pattern:[e.pattern],h=c(e.pattern,e.caseSensitive,e.wordsOnly),g(e.node,f))}}(window.document),n=function(){function c(c){var e,f,h,i,j=this;c=c||{},c.input||a.error("input is missing"),e=b.bind(this._onBlur,this),f=b.bind(this._onFocus,this),h=b.bind(this._onKeydown,this),i=b.bind(this._onInput,this),this.$hint=a(c.hint),this.$input=a(c.input).on("blur.tt",e).on("focus.tt",f).on("keydown.tt",h),0===this.$hint.length&&(this.setHintValue=this.getHintValue=this.clearHint=b.noop),b.isMsie()?this.$input.on("keydown.tt keypress.tt cut.tt paste.tt",function(a){g[a.which||a.keyCode]||b.defer(b.bind(j._onInput,j,a))}):this.$input.on("input.tt",i),this.query=this.$input.val(),this.$overflowHelper=d(this.$input)}function d(b){return a('<pre aria-hidden="true"></pre>').css({position:"absolute",visibility:"hidden",whiteSpace:"nowrap",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function e(a,b){return c.normalizeQuery(a)===c.normalizeQuery(b)}function f(a){return a.altKey||a.ctrlKey||a.metaKey||a.shiftKey}var g;return g={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},c.normalizeQuery=function(a){return(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," ")},b.mixin(c.prototype,l,{_onBlur:function(){this.resetInputValue(),this.trigger("blurred")},_onFocus:function(){this.trigger("focused")},_onKeydown:function(a){var b=g[a.which||a.keyCode];this._managePreventDefault(b,a),b&&this._shouldTrigger(b,a)&&this.trigger(b+"Keyed",a)},_onInput:function(){this._checkInputValue()},_managePreventDefault:function(a,b){var c,d,e;switch(a){case"tab":d=this.getHintValue(),e=this.getInputValue(),c=d&&d!==e&&!f(b);break;case"up":case"down":c=!f(b);break;default:c=!1}c&&b.preventDefault()},_shouldTrigger:function(a,b){var c;switch(a){case"tab":c=!f(b);break;default:c=!0}return c},_checkInputValue:function(){var a,b,c;a=this.getInputValue(),b=e(a,this.query),c=b?this.query.length!==a.length:!1,b?c&&this.trigger("whitespaceChanged",this.query):this.trigger("queryChanged",this.query=a)},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),!b&&this._checkInputValue()},getHintValue:function(){return this.$hint.val()},setHintValue:function(a){this.$hint.val(a)},resetInputValue:function(){this.$input.val(this.query)},clearHint:function(){this.$hint.val("")},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},hasOverflow:function(){var a=this.$input.width()-2;return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>=a},isCursorAtEnd:function(){var a,c,d;return a=this.$input.val().length,c=this.$input[0].selectionStart,b.isNumber(c)?c===a:document.selection?(d=document.selection.createRange(),d.moveStart("character",-a),a===d.text.length):!0},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null}}),c}(),o=function(){function c(c){c=c||{},c.templates=c.templates||{},c.source||a.error("missing source"),c.name&&!f(c.name)&&a.error("invalid dataset name: "+c.name),this.query=null,this.highlight=!!c.highlight,this.name=c.name||b.getUniqueId(),this.source=c.source,this.displayFn=d(c.display||c.displayKey),this.templates=e(c.templates,this.displayFn),this.$el=a(i.dataset.replace("%CLASS%",this.name))}function d(a){function c(b){return b[a]}return a=a||"value",b.isFunction(a)?a:c}function e(a,c){function d(a){return"<p>"+c(a)+"</p>"}return{empty:a.empty&&b.templatify(a.empty),header:a.header&&b.templatify(a.header),footer:a.footer&&b.templatify(a.footer),suggestion:a.suggestion||d}}function f(a){return/^[_a-zA-Z0-9-]+$/.test(a)}var g="ttDataset",h="ttValue",k="ttDatum";return c.extractDatasetName=function(b){return a(b).data(g)},c.extractValue=function(b){return a(b).data(h)},c.extractDatum=function(b){return a(b).data(k)},b.mixin(c.prototype,l,{_render:function(c,d){function e(){return p.templates.empty({query:c,isEmpty:!0})}function f(){function e(b){var c,d,e;return d=p.templates.suggestion(b),e=i.suggestion.replace("%BODY%",d),c=a(e).data(g,p.name).data(h,p.displayFn(b)).data(k,b),c.children().each(function(){a(this).css(j.suggestionChild)}),c}var f,l;return f=a(i.suggestions).css(j.suggestions),l=b.map(d,e),f.append.apply(f,l),p.highlight&&m({node:f[0],pattern:c}),f}function l(){return p.templates.header({query:c,isEmpty:!o})}function n(){return p.templates.footer({query:c,isEmpty:!o})}if(this.$el){var o,p=this;this.$el.empty(),o=d&&d.length,!o&&this.templates.empty?this.$el.html(e()).prepend(p.templates.header?l():null).append(p.templates.footer?n():null):o&&this.$el.html(f()).prepend(p.templates.header?l():null).append(p.templates.footer?n():null),this.trigger("rendered")}},getRoot:function(){return this.$el},update:function(a){function b(b){a===c.query&&c._render(a,b)}var c=this;this.query=a,this.source(a,b)},clear:function(){this._render(this.query||"")},isEmpty:function(){return this.$el.is(":empty")},destroy:function(){this.$el=null}}),c}(),p=function(){function c(c){var e,f,g,h=this;c=c||{},c.menu||a.error("menu is required"),this.isOpen=!1,this.isEmpty=!0,this.datasets=b.map(c.datasets,d),e=b.bind(this._onSuggestionClick,this),f=b.bind(this._onSuggestionMouseEnter,this),g=b.bind(this._onSuggestionMouseLeave,this),this.$menu=a(c.menu).on("click.tt",".tt-suggestion",e).on("mouseenter.tt",".tt-suggestion",f).on("mouseleave.tt",".tt-suggestion",g),b.each(this.datasets,function(a){h.$menu.append(a.getRoot()),a.onSync("rendered",h._onRendered,h)})}function d(a){return new o(a)}return b.mixin(c.prototype,l,{_onSuggestionClick:function(b){this.trigger("suggestionClicked",a(b.currentTarget))},_onSuggestionMouseEnter:function(b){this._removeCursor(),this._setCursor(a(b.currentTarget),!0)},_onSuggestionMouseLeave:function(){this._removeCursor()},_onRendered:function(){function a(a){return a.isEmpty()}this.isEmpty=b.every(this.datasets,a),this.isEmpty?this._hide():this.isOpen&&this._show(),this.trigger("datasetRendered")},_hide:function(){this.$menu.hide()},_show:function(){this.$menu.css("display","block")},_getSuggestions:function(){return this.$menu.find(".tt-suggestion")},_getCursor:function(){return this.$menu.find(".tt-cursor").first()},_setCursor:function(a,b){a.first().addClass("tt-cursor"),!b&&this.trigger("cursorMoved")},_removeCursor:function(){this._getCursor().removeClass("tt-cursor")},_moveCursor:function(a){var b,c,d,e;if(this.isOpen){if(c=this._getCursor(),b=this._getSuggestions(),this._removeCursor(),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return void this.trigger("cursorRemoved");-1>d&&(d=b.length-1),this._setCursor(e=b.eq(d)),this._ensureVisible(e)}},_ensureVisible:function(a){var b,c,d,e;b=a.position().top,c=b+a.outerHeight(!0),d=this.$menu.scrollTop(),e=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),0>b?this.$menu.scrollTop(d+b):c>e&&this.$menu.scrollTop(d+(c-e))},close:function(){this.isOpen&&(this.isOpen=!1,this._removeCursor(),this._hide(),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){this.$menu.css("ltr"===a?j.ltr:j.rtl)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getDatumForSuggestion:function(a){var b=null;return a.length&&(b={raw:o.extractDatum(a),value:o.extractValue(a),datasetName:o.extractDatasetName(a)}),b},getDatumForCursor:function(){return this.getDatumForSuggestion(this._getCursor().first())},getDatumForTopSuggestion:function(){return this.getDatumForSuggestion(this._getSuggestions().first())},update:function(a){function c(b){b.update(a)}b.each(this.datasets,c)},empty:function(){function a(a){a.clear()}b.each(this.datasets,a),this.isEmpty=!0},isVisible:function(){return this.isOpen&&!this.isEmpty},destroy:function(){function a(a){a.destroy()}this.$menu.off(".tt"),this.$menu=null,b.each(this.datasets,a)}}),c}(),q=function(){function c(c){var e,f,g;c=c||{},c.input||a.error("missing input"),this.autoselect=!!c.autoselect,this.minLength=b.isNumber(c.minLength)?c.minLength:1,this.$node=d(c.input,c.withHint),e=this.$node.find(".tt-dropdown-menu"),f=this.$node.find(".tt-input"),g=this.$node.find(".tt-hint"),this.eventBus=c.eventBus||new k({el:f}),this.dropdown=new p({menu:e,datasets:c.datasets}).onSync("suggestionClicked",this._onSuggestionClicked,this).onSync("cursorMoved",this._onCursorMoved,this).onSync("cursorRemoved",this._onCursorRemoved,this).onSync("opened",this._onOpened,this).onSync("closed",this._onClosed,this).onAsync("datasetRendered",this._onDatasetRendered,this),this.input=new n({input:f,hint:g}).onSync("focused",this._onFocused,this).onSync("blurred",this._onBlurred,this).onSync("enterKeyed",this._onEnterKeyed,this).onSync("tabKeyed",this._onTabKeyed,this).onSync("escKeyed",this._onEscKeyed,this).onSync("upKeyed",this._onUpKeyed,this).onSync("downKeyed",this._onDownKeyed,this).onSync("leftKeyed",this._onLeftKeyed,this).onSync("rightKeyed",this._onRightKeyed,this).onSync("queryChanged",this._onQueryChanged,this).onSync("whitespaceChanged",this._onWhitespaceChanged,this),e.on("mousedown.tt",function(a){b.isMsie()&&b.isMsie()<9&&(f[0].onbeforedeactivate=function(){window.event.returnValue=!1,f[0].onbeforedeactivate=null}),a.preventDefault()})}function d(b,c){var d,f,h,k;d=a(b),f=a(i.wrapper).css(j.wrapper),h=a(i.dropdown).css(j.dropdown),k=d.clone().css(j.hint).css(e(d)),k.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled",!0).attr({autocomplete:"off",spellcheck:"false"}),d.data(g,{dir:d.attr("dir"),autocomplete:d.attr("autocomplete"),spellcheck:d.attr("spellcheck"),style:d.attr("style")}),d.addClass("tt-input").attr({autocomplete:"off",spellcheck:!1}).css(c?j.input:j.inputWithNoHint);try{!d.attr("dir")&&d.attr("dir","auto")}catch(l){}return d.wrap(f).parent().prepend(c?k:null).append(h)}function e(a){return{backgroundAttachment:a.css("background-attachment"),backgroundClip:a.css("background-clip"),backgroundColor:a.css("background-color"),backgroundImage:a.css("background-image"),backgroundOrigin:a.css("background-origin"),backgroundPosition:a.css("background-position"),backgroundRepeat:a.css("background-repeat"),backgroundSize:a.css("background-size")}}function f(a){var c=a.find(".tt-input");b.each(c.data(g),function(a,d){b.isUndefined(a)?c.removeAttr(d):c.attr(d,a)}),c.detach().removeData(g).removeClass("tt-input").insertAfter(a),a.remove()}var g="ttAttrs";return b.mixin(c.prototype,{_onSuggestionClicked:function(a,b){var c;(c=this.dropdown.getDatumForSuggestion(b))&&this._select(c)},_onCursorMoved:function(){var a=this.dropdown.getDatumForCursor();this.input.clearHint(),this.input.setInputValue(a.value,!0),this.eventBus.trigger("cursorchanged",a.raw,a.datasetName)},_onCursorRemoved:function(){this.input.resetInputValue(),this._updateHint()},_onDatasetRendered:function(){this._updateHint()},_onOpened:function(){this._updateHint(),this.eventBus.trigger("opened")},_onClosed:function(){this.input.clearHint(),this.eventBus.trigger("closed")},_onFocused:function(){this.dropdown.empty(),this.dropdown.open()},_onBlurred:function(){this.dropdown.close()},_onEnterKeyed:function(a,b){var c,d;c=this.dropdown.getDatumForCursor(),d=this.dropdown.getDatumForTopSuggestion(),c?(this._select(c),b.preventDefault()):this.autoselect&&d&&(this._select(d),b.preventDefault())},_onTabKeyed:function(a,b){var c;(c=this.dropdown.getDatumForCursor())?(this._select(c),b.preventDefault()):this._autocomplete()},_onEscKeyed:function(){this.dropdown.close(),this.input.resetInputValue()},_onUpKeyed:function(){var a=this.input.getQuery();!this.dropdown.isOpen&&a.length>=this.minLength&&this.dropdown.update(a),this.dropdown.open(),this.dropdown.moveCursorUp()},_onDownKeyed:function(){var a=this.input.getQuery();!this.dropdown.isOpen&&a.length>=this.minLength&&this.dropdown.update(a),this.dropdown.open(),this.dropdown.moveCursorDown()},_onLeftKeyed:function(){"rtl"===this.dir&&this._autocomplete()},_onRightKeyed:function(){"ltr"===this.dir&&this._autocomplete()},_onQueryChanged:function(a,b){this.input.clearHint(),this.dropdown.empty(),b.length>=this.minLength&&this.dropdown.update(b),this.dropdown.open(),this._setLanguageDirection()},_onWhitespaceChanged:function(){this._updateHint(),this.dropdown.open()},_setLanguageDirection:function(){var a;this.dir!==(a=this.input.getLanguageDirection())&&(this.dir=a,this.$node.css("direction",a),this.dropdown.setLanguageDirection(a))},_updateHint:function(){var a,c,d,e,f,g;a=this.dropdown.getDatumForTopSuggestion(),a&&this.dropdown.isVisible()&&!this.input.hasOverflow()&&(c=this.input.getInputValue(),d=n.normalizeQuery(c),e=b.escapeRegExChars(d),f=new RegExp("^(?:"+e+")(.*$)","i"),g=f.exec(a.value),this.input.setHintValue(c+(g?g[1]:"")))},_autocomplete:function(){var a,b,c;a=this.input.getHintValue(),b=this.input.getQuery(),a&&b!==a&&this.input.isCursorAtEnd()&&(c=this.dropdown.getDatumForTopSuggestion(),c&&this.input.setInputValue(c.value),this.eventBus.trigger("autocompleted",c.raw,c.datasetName))},_select:function(a){this.input.clearHint(),this.input.setQuery(a.value),this.input.setInputValue(a.value,!0),this._setLanguageDirection(),this.eventBus.trigger("selected",a.raw,a.datasetName),this.dropdown.close(),b.defer(b.bind(this.dropdown.empty,this.dropdown))},open:function(){this.dropdown.open()},close:function(){this.dropdown.close()},getQuery:function(){return this.input.getQuery()},setQuery:function(a){this.input.setInputValue(a)},destroy:function(){this.input.destroy(),this.dropdown.destroy(),f(this.$node),this.$node=null}}),c}();!function(){var c,d,e;c=a.fn.typeahead,d="ttTypeahead",e={initialize:function(c,e){function f(){var f,g,h=a(this);b.each(e,function(a){a.highlight=!!c.highlight}),g=new q({input:h,eventBus:f=new k({el:h}),withHint:b.isUndefined(c.hint)?!0:!!c.hint,minLength:c.minLength,autoselect:c.autoselect,datasets:e}),h.data(d,g)}return e=b.isArray(e)?e:[].slice.call(arguments,1),c=c||{},this.each(f)},open:function(){function b(){var b,c=a(this);(b=c.data(d))&&b.open()}return this.each(b)},close:function(){function b(){var b,c=a(this);(b=c.data(d))&&b.close()}return this.each(b)},val:function(b){function c(){var c,e=a(this);(c=e.data(d))&&c.setQuery(b)}function e(a){var b,c;return(b=a.data(d))&&(c=b.getQuery()),c}return arguments.length?this.each(c):e(this.first())},destroy:function(){function b(){var b,c=a(this);(b=c.data(d))&&(b.destroy(),c.removeData(d))}return this.each(b)}},a.fn.typeahead=function(a){return e[a]?e[a].apply(this,[].slice.call(arguments,1)):e.initialize.apply(this,arguments)},a.fn.typeahead.noConflict=function(){return a.fn.typeahead=c,this}}()}(window.jQuery); |
/bower_components/bootstrap-tokenfield/index.html |
@@ -0,0 +1,661 @@ |
--- |
title: Tokenfield for Bootstrap |
--- |
|
<!DOCTYPE html> |
<html lang="en"> |
<head> |
<!-- Meta, title, CSS, favicons, etc. --> |
<meta charset="utf-8"> |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
<meta name="description" content=""> |
<meta name="author" content=""> |
|
<title>Tokenfield for Bootstrap</title> |
|
<!-- Bootstrap core CSS --> |
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"> |
<!-- jQuery UI CSS --> |
<link href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" type="text/css" rel="stylesheet"> |
<!-- Bootstrap styling for Typeahead --> |
<link href="dist/css/tokenfield-typeahead.css" type="text/css" rel="stylesheet"> |
<!-- Tokenfield CSS --> |
<link href="dist/css/bootstrap-tokenfield.css" type="text/css" rel="stylesheet"> |
<!-- Docs CSS --> |
<link href="docs-assets/css/pygments-manni.css" type="text/css" rel="stylesheet"> |
<link href="docs-assets/css/docs.css" type="text/css" rel="stylesheet"> |
|
<!--[if lt IE 9]> |
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> |
<script src="http://cdnjs.cloudflare.com/ajax/libs/respond.js/1.2.0/respond.min.js"></script> |
<![endif]--> |
|
</head> |
<body> |
|
<div class="bs-header" id="content"> |
<div class="container"> |
<h1>Tokenfield for Bootstrap</h1> |
<p>Advanced tagging/tokenizing plugin for jQuery and Twitter Bootstrap with a focus on keyboard and copy-paste support.</p> |
<ul id="feature-highlights"> |
<li>Copy & paste support</li> |
<li>Keyboard navigation</li> |
<li>Edit existing tokens</li> |
<li>Multiple lines of tokens</li> |
<li>Responsive</li> |
</ul> |
<div id="main-links"> |
<a id="download-master" href="https://github.com/sliptree/bootstrap-tokenfield/zipball/master" class="btn btn-primary">Download Development</a> |
</div> |
</div> |
</div> |
|
<div class="container bs-docs-container"> |
<div class="row"> |
|
<div class="col-md-3"> |
<div class="bs-sidebar hidden-print affix-top" role="complementary"> |
<ul class="nav bs-sidenav"> |
<li><a href="#examples">Examples</a></li> |
<li><a href="#usage">Usage</a></li> |
<li><a href="#options">Options</a></li> |
<li><a href="#methods">Methods</a></li> |
<li><a href="#events">Events</a></li> |
<li><a href="#keyboard">Keyboard support</a></li> |
<li><a href="#copy-paste">Copy & paste support</a></li> |
<li><a href="#validation-states">Validation states</a></li> |
<li><a href="#various-examples">Various examples</a></li> |
</ul> |
</div> |
</div> |
|
<div class="col-md-9" role="main"> |
<!-- Glyphicons |
================================================== --> |
<div class="bs-docs-section"> |
<div class="page-header"> |
<h1 id="tokenfield">Tokenfield <small>%VERSION%</small></h1> |
</div> |
|
<h2 id="examples">Examples</h2> |
<p>Create elegant taggable fields with copy/paste and keyboard support.</p> |
|
<p><strong>Using jQuery UI Autocomplete</strong></p> |
|
<div class="bs-example"> |
<div class="form-group"> |
<input type="text" class="form-control" id="tokenfield-1" value="red,green,blue" placeholder="Type something and hit enter" /> |
</div> |
</div> |
{% highlight html %} |
<input type="text" class="form-control" id="tokenfield" value="red,green,blue" /> |
{% endhighlight %} |
|
{% highlight js %} |
$('#tokenfield').tokenfield({ |
autocomplete: { |
source: ['red','blue','green','yellow','violet','brown','purple','black','white'], |
delay: 100 |
}, |
showAutocompleteOnFocus: true |
}) |
{% endhighlight %} |
|
<p><strong>Using Twitter Typeahead</strong></p> |
|
<div class="bs-example"> |
<div class="form-group"> |
<input type="text" class="form-control" id="tokenfield-typeahead" value="red,green,blue" placeholder="Type something and hit enter" /> |
</div> |
</div> |
{% highlight html %} |
<input type="text" class="form-control" id="tokenfield-typeahead" value="red,green,blue" /> |
{% endhighlight %} |
|
{% highlight js %} |
var engine = new Bloodhound({ |
local: [{value: 'red'}, {value: 'blue'}, {value: 'green'} , {value: 'yellow'}, {value: 'violet'}, {value: 'brown'}, {value: 'purple'}, {value: 'black'}, {value: 'white'}], |
datumTokenizer: function(d) { |
return Bloodhound.tokenizers.whitespace(d.value); |
}, |
queryTokenizer: Bloodhound.tokenizers.whitespace |
}); |
|
engine.initialize(); |
|
$('#tokenfield-typeahead').tokenfield({ |
typeahead: [null, { source: engine.ttAdapter() }] |
}); |
{% endhighlight %} |
|
<h2 id="usage">Usage</h2> |
|
<h3>Trigger tokenfield via JavaScript:</h3> |
{% highlight js %} |
$('.tokenfield').tokenfield() |
{% endhighlight %} |
|
<h3 id="options">Options</h3> |
|
<p>Options can be passed via data attributes or JavaScript. For data attributes, append the option name to <code>data-</code>, as in <code>data-minLength=""</code>.</p> |
|
<div class="table-responsive"> |
<table class="table table-bordered table-striped"> |
<thead> |
<tr> |
<th style="width: 100px;">Name</th> |
<th style="width: 50px;">type</th> |
<th style="width: 100px;">default</th> |
<th>description</th> |
</tr> |
</thead> |
<tbody> |
<tr> |
<td>tokens</td> |
<td>string, array</td> |
<td><code>[]</code></td> |
<td>Tokens (or tags). Can be a string with comma-separated values (<code>"one,two,three"</code>), an array of strings (<code>["one","two","three"]</code>), or an array of objects (<code>[{ value: "one", label: "Einz" }, { value: "two", label: "Zwei" }]</code>)</td> |
</tr> |
<tr> |
<td>limit</td> |
<td>int</td> |
<td><code>0</code></td> |
<td>Maximum number of tokens allowed. 0 = unlimited.</td> |
</tr> |
<tr> |
<td>minLength</td> |
<td>int</td> |
<td><code>0</code></td> |
<td>Minimum length required for token value.</td> |
</tr> |
<tr> |
<td>minWidth</td> |
<td>int</td> |
<td><code>60</code></td> |
<td>Minimum input field width. In pixels.</td> |
</tr> |
<tr> |
<td>autocomplete</td> |
<td>object</td> |
<td><code>{}</code></td> |
<td>jQuery UI Autocomplete options</td> |
</tr> |
<tr> |
<td>showAutocompleteOnFocus</td> |
<td>boolean</td> |
<td><code>false</code></td> |
<td>Whether to show autocomplete suggestions menu on focus or not. Works only for jQuery UI Autocomplete, as Typeahead has no support for this kind of behavior.</td> |
</tr> |
<tr> |
<td>typeahead</td> |
<td>array</td> |
<td><code>[]</code></td> |
<td>Arguments for Twitter Typeahead. The first argument should be an options hash (or <code>null</code> if you want to use the defaults). The second argument should be a dataset. You can add multiple datasets: <code>typeahead: [options, dataset1, dataset2]</code></td> |
</tr> |
<tr> |
<td>createTokensOnBlur</td> |
<td>boolean</td> |
<td><code>false</code></td> |
<td>Whether to turn input into tokens when tokenfield loses focus or not.</td> |
</tr> |
<tr> |
<td>delimiter</td> |
<td>string, array</td> |
<td><code>','</code></td> |
<td>A character or an array of characters that will trigger token creation on keypress event. Defaults to ',' (comma). Note - this does not affect Enter or Tab keys, as they are handled in the keydown event. The first delimiter will be used as a separator when getting the list of tokens or copy-pasting tokens.</td> |
</tr> |
<tr> |
<td>beautify</td> |
<td>boolean</td> |
<td><code>true</code></td> |
<td>Whether to insert spaces after each token when getting a comma-separated list of tokens. This affects both value returned by <code>getTokensList()<code> and the value of the original input field.</td> |
</tr> |
<tr> |
<td>inputType</td> |
<td>string</td> |
<td><code>'text'</code></td> |
<td>HTML type attribute for the token input. This is useful for specifying an HTML5 input type like <code>'email'</code>, <code>'url'</code> or <code>'tel'</code> which allows mobile browsers to show a specialized virtual keyboard optimized for different types of input. This only sets the type of the visible token input but does not touch the original input field. So you may set the original input to have <code>type="text"</code> but set this inputType option to <code>'email'</code> if you only want to take advantage of the email style keyboard on mobile, but don't want to enable HTML5 native email validation on the original hidden input.</td> |
</tr> |
</tbody> |
</table> |
</div> |
|
<div class="bs-callout bs-callout-info"> |
<h4>Data attributes for individual tokenfields</h4> |
<p>Options for individual tokenfields can alternatively be specified through the use of data attributes, as explained above.</p> |
</div> |
|
<div class="bs-callout bs-callout-info"> |
<h4>Styling Twitter Typeahead </h4> |
<p>Twitter Typeahead comes with no default styling. Make sure to include <a href="https://gist.github.com/ragulka/6388201">tokenfield-typeahead.css</a> on your page.</p> |
</div> |
|
<h3 id="methods">Methods</h3> |
<h4>.tokenfield(options)</h4> |
<p>Initializes an input with a tokenfield.</p> |
{% highlight js %} |
$('#myField').tokenfield(); |
{% endhighlight %} |
|
<h4>.tokenfield('setTokens', tokens)</h4> |
<p>Manually set the tokenfield content (replacing the old content)</p> |
{% highlight js %} |
$('#myField').tokenfield('setTokens', 'blue,red,white'); |
$('#myField').tokenfield('setTokens', ['blue','red','white']); |
$('#myField').tokenfield('setTokens', [{ value: "blue", label: "Blau" }, { value: "red", label: "Rot" }]); |
{% endhighlight %} |
|
<h4>.tokenfield('createToken', token)</h4> |
<p>Manually create a token and append it to the input</p> |
{% highlight js %} |
$('#myField').tokenfield('createToken', 'purple'); |
$('#myField').tokenfield('createToken', { value: 'violet', label: 'Violet' }); |
{% endhighlight %} |
|
<h4>.tokenfield('getTokens', active)</h4> |
<p>Get an array of tokens from the input. Set <code>active</code> to true to return only selected tokens.</p> |
{% highlight js %} |
$('#myField').tokenfield('getTokens'); |
{% endhighlight %} |
|
<h4>.tokenfield('getTokensList', delimiter, beautify, active)</h4> |
<p>Get a comma-separated list of the tokens from the input. You can use an alternative separator by supplying also a <code>delimiter</code> argument. Setting <code>beautify</code> to false will prevent adding a space after each token. Set <code>active</code> to true to return only selected tokens.</p> |
{% highlight js %} |
$('#myField').tokenfield('getTokensList'); |
$('#myField').tokenfield('getTokensList', '; '); |
{% endhighlight %} |
|
<h4>.tokenfield('disable')</h4> |
<p>Disable tokenfield (just like a normal input field)</p> |
{% highlight js %} |
$('#myField').tokenfield('disable'); |
{% endhighlight %} |
|
<h4>.tokenfield('enable')</h4> |
<p>Enable tokenfield (just like a normal input field)</p> |
{% highlight js %} |
$('#myField').tokenfield('enable'); |
{% endhighlight %} |
|
<h4>.tokenfield('readonly')</h4> |
<p>Make tokenfield a readonly field (just like a normal input field)</p> |
{% highlight js %} |
$('#myField').tokenfield('readonly'); |
{% endhighlight %} |
|
<h4>.tokenfield('writeable')</h4> |
<p>Make tokenfield writeable (just like a normal input field)</p> |
{% highlight js %} |
$('#myField').tokenfield('writeable'); |
{% endhighlight %} |
|
<h4>.tokenfield('destroy')</h4> |
<p>Destroy tokenfield and restore original input</p> |
{% highlight js %} |
$('#myField').tokenfield('destroy'); |
{% endhighlight %} |
|
<div class="bs-callout bs-callout-info"> |
<h4>Accessing original input</h4> |
<p>Though not recommended, you can access the original input field like so: <code>$('#tokenfield').data('bs.tokenfield').$input</code></p> |
<p>You can also set new options for the autocomplete or typehead objects from the original input above like so: <code>$('#tokenfield').data('bs.tokenfield').$input.autocomplete({source: new_array})</code></p> |
</div> |
|
<h3 id="events">Events</h3> |
<p>Tokenfield exposes a few events for hooking into it's functionality.</p> |
|
<div class="table-responsive"> |
<table class="table table-bordered table-striped"> |
<thead> |
<tr> |
<th style="width: 150px;">Event</th> |
<th>Description</th> |
</tr> |
</thead> |
<tbody> |
<tr> |
<td>tokenfield:initialize</td> |
<td>Fires after Tokenfield has been initialized.</td> |
</tr> |
<tr> |
<td>tokenfield:createtoken</td> |
<td>This event fires when a token is all set up to be created, but before it is inserted into the DOM and event listeners are attached. You can use this event to manipulate token value and label by changing the appropriate values of <code>attrs</code> property of the event. See below for an example. Calling <code>event.preventDefault()</code> or doing <code>return false</code> in the event handler will prevent the token from being created.</td> |
</tr> |
<tr> |
<td>tokenfield:createdtoken</td> |
<td>This event is fired after the token has been created. Here, <code>attrs</code> property of the event is also available, but is basically read-only. You can also get a direct reference to the token DOM object via <code>e.relatedTarget</code>. The example below uses this to set an 'invalid' class on the newly created token if it does not pass validation.</td> |
</tr> |
<tr> |
<td>tokenfield:edittoken</td> |
<td>This event is fired just before a token is about to be edited. This allows you to manipluate the input field value before it is created. Again, to do this, manipluate the <code>attrs</code> property of the event. Here you can also access the token DOM object with <code>e.relatedTarget</code>. Calling <code>event.preventDefault()</code> or doing <code>return false</code> in the event handler will prevent the token from being edited.</td> |
</tr> |
<tr> |
<td>tokenfield:editedtoken</td> |
<td>This event is fired when a token is ready for being edited. It means that the token has been replaced by an input field.</td> |
</tr> |
<tr> |
<td>tokenfield:removetoken</td> |
<td>This event is fired right before a token is removed. Here you can also access the token DOM object with <code>e.relatedTarget</code>. Calling <code>event.preventDefault()</code> or doing <code>return false</code> in the event handler will prevent the token from being removed. You can access token label and value by checking the <code>attrs</code> property of the event. Also, <code>e.relatedTarget</code> is a reference to the token DOM object.</td> |
</tr> |
<tr> |
<td>tokenfield:removedtoken</td> |
<td>This event is fired right after a token is removed from the DOM. You can access token label and value by checking the <code>attrs</code> property of the event.</td> |
</tr> |
</tbody> |
</table> |
</div> |
|
<p>The example below is pretty comprehensive. Here, we split user input into two parts: name and email. Then, we validate the email and if it is not valid, we add an <code>invalid</code> class to the token.</p> |
<p>When the user starts to edit the token, we merge token value and label together again.</p> |
|
<div class="bs-example"> |
<input type="text" class="form-control" id="tokenfield-2" data-tokens="me|me@example.com,you@example.com,not really an email" /> |
</div> |
|
{% highlight js %} |
$('#tokenfield') |
|
.on('tokenfield:createtoken', function (e) { |
var data = e.attrs.value.split('|') |
e.attrs.value = data[1] || data[0] |
e.attrs.label = data[1] ? data[0] + ' (' + data[1] + ')' : data[0] |
}) |
|
.on('tokenfield:createdtoken', function (e) { |
// Ãœber-simplistic e-mail validation |
var re = /\S+@\S+\.\S+/ |
var valid = re.test(e.attrs.value) |
if (!valid) { |
$(e.relatedTarget).addClass('invalid') |
} |
}) |
|
.on('tokenfield:edittoken', function (e) { |
if (e.attrs.label !== e.attrs.value) { |
var label = e.attrs.label.split(' (') |
e.attrs.value = label[0] + '|' + e.attrs.value |
} |
}) |
|
.on('tokenfield:removedtoken', function (e) { |
alert('Token removed! Token value was: ' + e.attrs.value) |
}) |
|
.tokenfield() |
{% endhighlight %} |
|
<h3 id="keyboard">Keyboard support</h3> |
<p>Tokenfield includes support for manipulating tokens via keyboard</p> |
<h4>left, right arrow keys</h4> |
<p>Arrow keys will move between active tokens. Try it out: click on one of the tokens and press left and right arrow keys</p> |
<h4>Backspace and delete</h4> |
<p>You can delete a selected token with backspace or delete keys. Try it out now:</p> |
<h4>Ctrl + A / Cmd + A, Ctrl + C / Cmd + C, Ctrl + V, Cmd + V</h4> |
<p>If You have one token selected, you can select all tokens with the keyboard. Then, you can copy the tokens using keyboard. You can also paste tokens to another field.</p> |
|
<div class="bs-example"> |
<input type="text" class="form-control token-example-field" value="try,pressing,backspace" /> |
</div> |
|
<h3 id="copy-paste">Copy & paste support</h3> |
<p>You can copy tokens from a tokenfield and paste them to any other field as comma-separated values. When you paste to another tokenfield, they will become tokens there, aswell!</p> |
<p>Try it out, copy the following to the field below: violet,yellow,brown</p> |
|
<div class="bs-example"> |
<input type="text" class="form-control token-example-field" value="copy,and,paste" /> |
</div> |
|
<h3 id="validation-states">Validation states</h3> |
<p>Tokenfield also supports all the default validation states from Bootstrap</p> |
|
<form class="bs-example"> |
|
<div class="form-group has-success"> |
<label class="control-label" for="inputSuccess">Input with success</label> |
<input type="text" class="form-control token-example-field" id="inputSuccess" value="red,green,blue" /> |
</div> |
|
<div class="form-group has-warning"> |
<label class="control-label" for="inputWarning">Input with warning</label> |
<input type="text" class="form-control token-example-field" id="inputWarning" value="red,green,blue"> |
</div> |
|
<div class="form-group has-error"> |
<label class="control-label" for="inputError">Input with error</label> |
<input type="text" class="form-control token-example-field" id="inputError" value="red,green,blue"> |
</div> |
|
</form> |
|
<h3 id="various-examples">Various examples of using tokenfield</h3> |
<p>Using tokenfield with input groups</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<div class="input-group"> |
<span class="input-group-addon">Tags:</span> |
<input type="text" class="form-control token-example-field" value="cool,nice,great" /> |
</div> |
<br> |
<div class="input-group"> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
<span class="input-group-addon"><span class="glyphicon glyphicon-star"></span></span> |
</div> |
<br> |
<div class="input-group"> |
<span class="input-group-addon">Tags:</span> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
<span class="input-group-addon"><span class="glyphicon glyphicon-star"></span></span> |
</div> |
</form> |
|
<p>Using tokenfield with input group checkboxes and radio buttons</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<div class="row"> |
<div class="col-lg-6"> |
<div class="input-group"> |
<span class="input-group-addon"> |
<input type="checkbox"> |
</span> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
</div><!-- /input-group --> |
</div><!-- /.col-lg-6 --> |
<div class="col-lg-6"> |
<div class="input-group"> |
<span class="input-group-addon"> |
<input type="radio"> |
</span> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
</div><!-- /input-group --> |
</div><!-- /.col-lg-6 --> |
</div> |
</form> |
|
<p>Using tokenfield with buttons in input groups</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<div class="input-group"> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
<span class="input-group-btn"> |
<button class="btn btn-default" type="button">Go!</button> |
</span> |
</div> |
<br> |
<div class="input-group"> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
<div class="input-group-btn"> |
<button type="button" class="btn btn-default" tabindex="-1">Action</button> |
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" tabindex="-1"> |
<span class="caret"></span> |
</button> |
<ul class="dropdown-menu pull-right" role="menu"> |
<li><a href="#">Action</a></li> |
<li><a href="#">Another action</a></li> |
<li><a href="#">Something else here</a></li> |
<li class="divider"></li> |
<li><a href="#">Separated link</a></li> |
</ul> |
</div> |
</div> |
</form> |
|
<p>Using tokenfield with different sizes</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<input type="text" class="form-control input-lg token-example-field" value="red,green,blue" /> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
<input type="text" class="form-control input-sm token-example-field" value="red,green,blue" /> |
<br> |
<div class="input-group input-group-lg"> |
<span class="input-group-addon">@</span> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
</div> |
<div class="input-group"> |
<span class="input-group-addon">@</span> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
</div> |
<div class="input-group input-group-sm"> |
<span class="input-group-addon">@</span> |
<input type="text" class="form-control token-example-field" value="red,green,blue" /> |
</div> |
</form> |
|
<p>Disabled tokenfield</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<input type="text" class="form-control token-example-field" value="red,green,blue" disabled="disabled" /> |
</form> |
|
<p>Disabled fieldset with tokenfield</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<fieldset disabled> |
<div class="form-group"> |
<label for="disabledTextInput">Disabled input</label> |
<input type="text" id="disabledTextInput" class="form-control" placeholder="Disabled input"> |
</div> |
<div class="form-group"> |
<label for="disabledTokenfield">Disabled tokenfield</label> |
<input type="text" id="disabledTokenfield" class="form-control token-example-field" value="red,green,blue" /> |
</div> |
</fieldset> |
</form> |
|
<p>Tokenfield in inline form</p> |
|
<form class="bs-example form-inline" role="form"> |
<div class="form-group"> |
<label class="sr-only" for="exampleInlineTags">Tags</label> |
<input type="text" id="exampleInlineTags" class="form-control token-example-field" value="red,green,blue" placeholder="Enter tags" /> |
</div> |
<button type="submit" class="btn btn-default">Search</button> |
</form> |
|
<p>Tokenfield in horizontal form</p> |
|
<form class="bs-example form-horizontal"> |
<div class="form-group"> |
<label for="inputEmail1" class="col-lg-2 control-label">Email</label> |
<div class="col-lg-10"> |
<input type="email" class="form-control" id="inputEmail1" placeholder="Email"> |
</div> |
</div> |
<div class="form-group"> |
<label for="inputPassword1" class="col-lg-2 control-label">Password</label> |
<div class="col-lg-10"> |
<input type="password" class="form-control" id="inputPassword1" placeholder="Password"> |
</div> |
</div> |
<div class="form-group"> |
<label for="inputTags1" class="col-lg-2 control-label">Tags</label> |
<div class="col-lg-10"> |
<input type="text" class="form-control token-example-field" id="inputTags1" placeholder="Tags" value="some,tags"> |
</div> |
</div> |
<div class="form-group"> |
<div class="col-lg-offset-2 col-lg-10"> |
<button type="submit" class="btn btn-default">Sign in</button> |
</div> |
</div> |
</form> |
|
<p>Tokenfield with fluid and fixed widths (50%, 300px, etc...)</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<div class="form-group"> |
<label for="example-fluid-tokenfield">Width: 50% (from stylesheet)</label> |
<input type="text" id="example-fluid-tokenfield" class="form-control token-example-field fluid-tokenfield" value="red,green,blue" /> |
</div> |
<div class="form-group"> |
<label for="example-fixed-tokenfield">Width: 300px (from stylesheet)</label> |
<input type="text" id="example-fixed-tokenfield" class="form-control token-example-field fixed-tokenfield" value="red,green,blue" /> |
</div> |
<div class="form-group"> |
<label for="example-fixed-tokenfield-2">Width: 400px (inline)</label> |
<input type="text" id="example-fixed-tokenfield-2" class="form-control token-example-field" value="red,green,blue" style="width:400px;" /> |
</div> |
</form> |
|
<p>Tokenfield with RTL direction</p> |
|
<form class="bs-example bs-example-form" role="form"> |
<input type="text" class="form-control token-example-field" value="red,green,blue" style="direction:rtl" /> |
</form> |
|
</div> |
|
</div><!-- // row --> |
|
</div><!-- //container --> |
|
<footer class="bs-footer" role="contentinfo"> |
<div class="container"> |
<div class="bs-social"> |
<ul class="bs-social-buttons"> |
<li> |
<iframe class="github-btn" src="http://ghbtns.com/github-btn.html?user=sliptree&repo=bootstrap-tokenfield&type=watch&count=true" width="100" height="20" title="Star on GitHub"></iframe> |
</li> |
<li> |
<iframe class="github-btn" src="http://ghbtns.com/github-btn.html?user=sliptree&repo=bootstrap-tokenfield&type=fork&count=true" width="102" height="20" title="Fork on GitHub"></iframe> |
</li> |
<li class="follow-btn"> |
<a href="https://twitter.com/sliptree" class="twitter-follow-button" data-show-count="false">Follow @sliptree</a> |
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script> |
</li> |
<li class="tweet-btn"> |
<a href="https://twitter.com/share" class="twitter-share-button" data-via="sliptree" data-hashtags="bootstrap-tokenfield">Tweet</a> |
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script> |
</li> |
</ul> |
</div> |
|
<p>Built by <a href="https://github.com/ragulka" target="_blank">@ragulka</a> from <a href="https://sliptree.com" target="_blank">Sliptree</a> |
<p>Sliptree 2013</p> |
<ul class="footer-links"> |
<li><a href="https://github.com/sliptree/bootstrap-tokenfield/issues?state=open">Issues</a></li> |
<li class="muted">·</li> |
<li><a href="https://github.com/sliptree/bootstrap-tokenfield">Fork</a></li> |
</ul> |
</div> |
</footer> |
|
<script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.js"></script> |
<script type="text/javascript" src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script> |
<script type="text/javascript" src="dist/bootstrap-tokenfield.js" charset="UTF-8"></script> |
<script type="text/javascript" src="docs-assets/js/scrollspy.js" charset="UTF-8"></script> |
<script type="text/javascript" src="docs-assets/js/affix.js" charset="UTF-8"></script> |
<script type="text/javascript" src="docs-assets/js/typeahead.bundle.min.js" charset="UTF-8"></script> |
<script type="text/javascript" src="docs-assets/js/docs.min.js" charset="UTF-8"></script> |
|
<script type="text/javascript"> |
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); |
|
ga('create', 'UA-22737379-5', 'sliptree.github.io'); |
ga('send', 'pageview'); |
</script> |
|
</body> |
</html> |
/bower_components/bootstrap-tokenfield/js/bootstrap-tokenfield.js |
@@ -0,0 +1,1029 @@ |
/*! |
* bootstrap-tokenfield |
* https://github.com/sliptree/bootstrap-tokenfield |
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT |
*/ |
|
(function (factory) { |
if (typeof define === 'function' && define.amd) { |
// AMD. Register as an anonymous module. |
define(['jquery'], factory); |
} else if (typeof exports === 'object') { |
// For CommonJS and CommonJS-like environments where a window with jQuery |
// is present, execute the factory with the jQuery instance from the window object |
// For environments that do not inherently posses a window with a document |
// (such as Node.js), expose a Tokenfield-making factory as module.exports |
// This accentuates the need for the creation of a real window or passing in a jQuery instance |
// e.g. require("bootstrap-tokenfield")(window); or require("bootstrap-tokenfield")($); |
module.exports = global.window && global.window.$ ? |
factory( global.window.$ ) : |
function( input ) { |
if ( !input.$ && !input.fn ) { |
throw new Error( "Tokenfield requires a window object with jQuery or a jQuery instance" ); |
} |
return factory( input.$ || input ); |
}; |
} else { |
// Browser globals |
factory(jQuery, window); |
} |
}(function ($, window) { |
|
"use strict"; // jshint ;_; |
|
/* TOKENFIELD PUBLIC CLASS DEFINITION |
* ============================== */ |
|
var Tokenfield = function (element, options) { |
var _self = this |
|
this.$element = $(element) |
this.textDirection = this.$element.css('direction'); |
|
// Extend options |
this.options = $.extend(true, {}, $.fn.tokenfield.defaults, { tokens: this.$element.val() }, this.$element.data(), options) |
|
// Setup delimiters and trigger keys |
this._delimiters = (typeof this.options.delimiter === 'string') ? [this.options.delimiter] : this.options.delimiter |
this._triggerKeys = $.map(this._delimiters, function (delimiter) { |
return delimiter.charCodeAt(0); |
}); |
this._firstDelimiter = this._delimiters[0]; |
|
// Check for whitespace, dash and special characters |
var whitespace = $.inArray(' ', this._delimiters) |
, dash = $.inArray('-', this._delimiters) |
|
if (whitespace >= 0) |
this._delimiters[whitespace] = '\\s' |
|
if (dash >= 0) { |
delete this._delimiters[dash] |
this._delimiters.unshift('-') |
} |
|
var specialCharacters = ['\\', '$', '[', '{', '^', '.', '|', '?', '*', '+', '(', ')'] |
$.each(this._delimiters, function (index, character) { |
var pos = $.inArray(character, specialCharacters) |
if (pos >= 0) _self._delimiters[index] = '\\' + character; |
}); |
|
// Store original input width |
var elRules = (window && typeof window.getMatchedCSSRules === 'function') ? window.getMatchedCSSRules( element ) : null |
, elStyleWidth = element.style.width |
, elCSSWidth |
, elWidth = this.$element.width() |
|
if (elRules) { |
$.each( elRules, function (i, rule) { |
if (rule.style.width) { |
elCSSWidth = rule.style.width; |
} |
}); |
} |
|
// Move original input out of the way |
var hidingPosition = $('body').css('direction') === 'rtl' ? 'right' : 'left', |
originalStyles = { position: this.$element.css('position') }; |
originalStyles[hidingPosition] = this.$element.css(hidingPosition); |
|
this.$element |
.data('original-styles', originalStyles) |
.data('original-tabindex', this.$element.prop('tabindex')) |
.css('position', 'absolute') |
.css(hidingPosition, '-10000px') |
.prop('tabindex', -1) |
|
// Create a wrapper |
this.$wrapper = $('<div class="tokenfield form-control" />') |
if (this.$element.hasClass('input-lg')) this.$wrapper.addClass('input-lg') |
if (this.$element.hasClass('input-sm')) this.$wrapper.addClass('input-sm') |
if (this.textDirection === 'rtl') this.$wrapper.addClass('rtl') |
|
// Create a new input |
var id = this.$element.prop('id') || new Date().getTime() + '' + Math.floor((1 + Math.random()) * 100) |
this.$input = $('<input type="'+this.options.inputType+'" class="token-input" autocomplete="off" />') |
.appendTo( this.$wrapper ) |
.prop( 'placeholder', this.$element.prop('placeholder') ) |
.prop( 'id', id + '-tokenfield' ) |
.prop( 'tabindex', this.$element.data('original-tabindex') ) |
|
// Re-route original input label to new input |
var $label = $( 'label[for="' + this.$element.prop('id') + '"]' ) |
if ( $label.length ) { |
$label.prop( 'for', this.$input.prop('id') ) |
} |
|
// Set up a copy helper to handle copy & paste |
this.$copyHelper = $('<input type="text" />').css('position', 'absolute').css(hidingPosition, '-10000px').prop('tabindex', -1).prependTo( this.$wrapper ) |
|
// Set wrapper width |
if (elStyleWidth) { |
this.$wrapper.css('width', elStyleWidth); |
} |
else if (elCSSWidth) { |
this.$wrapper.css('width', elCSSWidth); |
} |
// If input is inside inline-form with no width set, set fixed width |
else if (this.$element.parents('.form-inline').length) { |
this.$wrapper.width( elWidth ) |
} |
|
// Set tokenfield disabled, if original or fieldset input is disabled |
if (this.$element.prop('disabled') || this.$element.parents('fieldset[disabled]').length) { |
this.disable(); |
} |
|
// Set tokenfield readonly, if original input is readonly |
if (this.$element.prop('readonly')) { |
this.readonly(); |
} |
|
// Set up mirror for input auto-sizing |
this.$mirror = $('<span style="position:absolute; top:-999px; left:0; white-space:pre;"/>'); |
this.$input.css('min-width', this.options.minWidth + 'px') |
$.each([ |
'fontFamily', |
'fontSize', |
'fontWeight', |
'fontStyle', |
'letterSpacing', |
'textTransform', |
'wordSpacing', |
'textIndent' |
], function (i, val) { |
_self.$mirror[0].style[val] = _self.$input.css(val); |
}); |
this.$mirror.appendTo( 'body' ) |
|
// Insert tokenfield to HTML |
this.$wrapper.insertBefore( this.$element ) |
this.$element.prependTo( this.$wrapper ) |
|
// Calculate inner input width |
this.update() |
|
// Create initial tokens, if any |
this.setTokens(this.options.tokens, false, ! this.$element.val() && this.options.tokens ) |
|
// Start listening to events |
this.listen() |
|
// Initialize autocomplete, if necessary |
if ( ! $.isEmptyObject( this.options.autocomplete ) ) { |
var side = this.textDirection === 'rtl' ? 'right' : 'left' |
, autocompleteOptions = $.extend({ |
minLength: this.options.showAutocompleteOnFocus ? 0 : null, |
position: { my: side + " top", at: side + " bottom", of: this.$wrapper } |
}, this.options.autocomplete ) |
|
this.$input.autocomplete( autocompleteOptions ) |
} |
|
// Initialize typeahead, if necessary |
if ( ! $.isEmptyObject( this.options.typeahead ) ) { |
|
var typeaheadOptions = this.options.typeahead |
, defaults = { |
minLength: this.options.showAutocompleteOnFocus ? 0 : null |
} |
, args = $.isArray( typeaheadOptions ) ? typeaheadOptions : [typeaheadOptions, typeaheadOptions] |
|
args[0] = $.extend( {}, defaults, args[0] ) |
|
this.$input.typeahead.apply( this.$input, args ) |
this.typeahead = true |
} |
} |
|
Tokenfield.prototype = { |
|
constructor: Tokenfield |
|
, createToken: function (attrs, triggerChange) { |
var _self = this |
|
if (typeof attrs === 'string') { |
attrs = { value: attrs, label: attrs } |
} else { |
// Copy objects to prevent contamination of data sources. |
attrs = $.extend( {}, attrs ) |
} |
|
if (typeof triggerChange === 'undefined') { |
triggerChange = true |
} |
|
// Normalize label and value |
attrs.value = $.trim(attrs.value.toString()); |
attrs.label = attrs.label && attrs.label.length ? $.trim(attrs.label) : attrs.value |
|
// Bail out if has no value or label, or label is too short |
if (!attrs.value.length || !attrs.label.length || attrs.label.length <= this.options.minLength) return |
|
// Bail out if maximum number of tokens is reached |
if (this.options.limit && this.getTokens().length >= this.options.limit) return |
|
// Allow changing token data before creating it |
var createEvent = $.Event('tokenfield:createtoken', { attrs: attrs }) |
this.$element.trigger(createEvent) |
|
// Bail out if there if attributes are empty or event was defaultPrevented |
if (!createEvent.attrs || createEvent.isDefaultPrevented()) return |
|
var $token = $('<div class="token" />') |
.append('<span class="token-label" />') |
.append('<a href="#" class="close" tabindex="-1">×</a>') |
.data('attrs', attrs) |
|
// Insert token into HTML |
if (this.$input.hasClass('tt-input')) { |
// If the input has typeahead enabled, insert token before it's parent |
this.$input.parent().before( $token ) |
} else { |
this.$input.before( $token ) |
} |
|
// Temporarily set input width to minimum |
this.$input.css('width', this.options.minWidth + 'px') |
|
var $tokenLabel = $token.find('.token-label') |
, $closeButton = $token.find('.close') |
|
// Determine maximum possible token label width |
if (!this.maxTokenWidth) { |
this.maxTokenWidth = |
this.$wrapper.width() - $closeButton.outerWidth() - |
parseInt($closeButton.css('margin-left'), 10) - |
parseInt($closeButton.css('margin-right'), 10) - |
parseInt($token.css('border-left-width'), 10) - |
parseInt($token.css('border-right-width'), 10) - |
parseInt($token.css('padding-left'), 10) - |
parseInt($token.css('padding-right'), 10) |
parseInt($tokenLabel.css('border-left-width'), 10) - |
parseInt($tokenLabel.css('border-right-width'), 10) - |
parseInt($tokenLabel.css('padding-left'), 10) - |
parseInt($tokenLabel.css('padding-right'), 10) |
parseInt($tokenLabel.css('margin-left'), 10) - |
parseInt($tokenLabel.css('margin-right'), 10) |
} |
|
$tokenLabel |
.text(attrs.label) |
.css('max-width', this.maxTokenWidth) |
|
// Listen to events on token |
$token |
.on('mousedown', function (e) { |
if (_self._disabled || _self._readonly) return false |
_self.preventDeactivation = true |
}) |
.on('click', function (e) { |
if (_self._disabled || _self._readonly) return false |
_self.preventDeactivation = false |
|
if (e.ctrlKey || e.metaKey) { |
e.preventDefault() |
return _self.toggle( $token ) |
} |
|
_self.activate( $token, e.shiftKey, e.shiftKey ) |
}) |
.on('dblclick', function (e) { |
if (_self._disabled || _self._readonly || !_self.options.allowEditing ) return false |
_self.edit( $token ) |
}) |
|
$closeButton |
.on('click', $.proxy(this.remove, this)) |
|
// Trigger createdtoken event on the original field |
// indicating that the token is now in the DOM |
this.$element.trigger($.Event('tokenfield:createdtoken', { |
attrs: attrs, |
relatedTarget: $token.get(0) |
})) |
|
// Trigger change event on the original field |
if (triggerChange) { |
this.$element.val( this.getTokensList() ).trigger( $.Event('change', { initiator: 'tokenfield' }) ) |
} |
|
// Update tokenfield dimensions |
this.update() |
|
// Return original element |
return this.$element.get(0) |
} |
|
, setTokens: function (tokens, add, triggerChange) { |
if (!tokens) return |
|
if (!add) this.$wrapper.find('.token').remove() |
|
if (typeof triggerChange === 'undefined') { |
triggerChange = true |
} |
|
if (typeof tokens === 'string') { |
if (this._delimiters.length) { |
// Split based on delimiters |
tokens = tokens.split( new RegExp( '[' + this._delimiters.join('') + ']' ) ) |
} else { |
tokens = [tokens]; |
} |
} |
|
var _self = this |
$.each(tokens, function (i, attrs) { |
_self.createToken(attrs, triggerChange) |
}) |
|
return this.$element.get(0) |
} |
|
, getTokenData: function($token) { |
var data = $token.map(function() { |
var $token = $(this); |
return $token.data('attrs') |
}).get(); |
|
if (data.length == 1) { |
data = data[0]; |
} |
|
return data; |
} |
|
, getTokens: function(active) { |
var self = this |
, tokens = [] |
, activeClass = active ? '.active' : '' // get active tokens only |
this.$wrapper.find( '.token' + activeClass ).each( function() { |
tokens.push( self.getTokenData( $(this) ) ) |
}) |
return tokens |
} |
|
, getTokensList: function(delimiter, beautify, active) { |
delimiter = delimiter || this._firstDelimiter |
beautify = ( typeof beautify !== 'undefined' && beautify !== null ) ? beautify : this.options.beautify |
|
var separator = delimiter + ( beautify && delimiter !== ' ' ? ' ' : '') |
return $.map( this.getTokens(active), function (token) { |
return token.value |
}).join(separator) |
} |
|
, getInput: function() { |
return this.$input.val() |
} |
|
, listen: function () { |
var _self = this |
|
this.$element |
.on('change', $.proxy(this.change, this)) |
|
this.$wrapper |
.on('mousedown',$.proxy(this.focusInput, this)) |
|
this.$input |
.on('focus', $.proxy(this.focus, this)) |
.on('blur', $.proxy(this.blur, this)) |
.on('paste', $.proxy(this.paste, this)) |
.on('keydown', $.proxy(this.keydown, this)) |
.on('keypress', $.proxy(this.keypress, this)) |
.on('keyup', $.proxy(this.keyup, this)) |
|
this.$copyHelper |
.on('focus', $.proxy(this.focus, this)) |
.on('blur', $.proxy(this.blur, this)) |
.on('keydown', $.proxy(this.keydown, this)) |
.on('keyup', $.proxy(this.keyup, this)) |
|
// Secondary listeners for input width calculation |
this.$input |
.on('keypress', $.proxy(this.update, this)) |
.on('keyup', $.proxy(this.update, this)) |
|
this.$input |
.on('autocompletecreate', function() { |
// Set minimum autocomplete menu width |
var $_menuElement = $(this).data('ui-autocomplete').menu.element |
|
var minWidth = _self.$wrapper.outerWidth() - |
parseInt( $_menuElement.css('border-left-width'), 10 ) - |
parseInt( $_menuElement.css('border-right-width'), 10 ) |
|
$_menuElement.css( 'min-width', minWidth + 'px' ) |
}) |
.on('autocompleteselect', function (e, ui) { |
if (_self.createToken( ui.item )) { |
_self.$input.val('') |
if (_self.$input.data( 'edit' )) { |
_self.unedit(true) |
} |
} |
return false |
}) |
.on('typeahead:selected typeahead:autocompleted', function (e, datum, dataset) { |
// Create token |
if (_self.createToken( datum )) { |
_self.$input.typeahead('val', '') |
if (_self.$input.data( 'edit' )) { |
_self.unedit(true) |
} |
} |
}) |
|
// Listen to window resize |
$(window).on('resize', $.proxy(this.update, this )) |
|
} |
|
, keydown: function (e) { |
|
if (!this.focused) return |
|
var _self = this |
|
switch(e.keyCode) { |
case 8: // backspace |
if (!this.$input.is(document.activeElement)) break |
this.lastInputValue = this.$input.val() |
break |
|
case 37: // left arrow |
leftRight( this.textDirection === 'rtl' ? 'next': 'prev' ) |
break |
|
case 38: // up arrow |
upDown('prev') |
break |
|
case 39: // right arrow |
leftRight( this.textDirection === 'rtl' ? 'prev': 'next' ) |
break |
|
case 40: // down arrow |
upDown('next') |
break |
|
case 65: // a (to handle ctrl + a) |
if (this.$input.val().length > 0 || !(e.ctrlKey || e.metaKey)) break |
this.activateAll() |
e.preventDefault() |
break |
|
case 9: // tab |
case 13: // enter |
|
// We will handle creating tokens from autocomplete in autocomplete events |
if (this.$input.data('ui-autocomplete') && this.$input.data('ui-autocomplete').menu.element.find("li:has(a.ui-state-focus), li.ui-state-focus").length) break |
|
// We will handle creating tokens from typeahead in typeahead events |
if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-cursor').length ) break |
if (this.$input.hasClass('tt-input') && this.$wrapper.find('.tt-hint').val() && this.$wrapper.find('.tt-hint').val().length) break |
|
// Create token |
if (this.$input.is(document.activeElement) && this.$input.val().length || this.$input.data('edit')) { |
return this.createTokensFromInput(e, this.$input.data('edit')); |
} |
|
// Edit token |
if (e.keyCode === 13) { |
if (!this.$copyHelper.is(document.activeElement) || this.$wrapper.find('.token.active').length !== 1) break |
if (!_self.options.allowEditing) break |
this.edit( this.$wrapper.find('.token.active') ) |
} |
} |
|
function leftRight(direction) { |
if (_self.$input.is(document.activeElement)) { |
if (_self.$input.val().length > 0) return |
|
direction += 'All' |
var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction]('.token:first') : _self.$input[direction]('.token:first') |
if (!$token.length) return |
|
_self.preventInputFocus = true |
_self.preventDeactivation = true |
|
_self.activate( $token ) |
e.preventDefault() |
|
} else { |
_self[direction]( e.shiftKey ) |
e.preventDefault() |
} |
} |
|
function upDown(direction) { |
if (!e.shiftKey) return |
|
if (_self.$input.is(document.activeElement)) { |
if (_self.$input.val().length > 0) return |
|
var $token = _self.$input.hasClass('tt-input') ? _self.$input.parent()[direction + 'All']('.token:first') : _self.$input[direction + 'All']('.token:first') |
if (!$token.length) return |
|
_self.activate( $token ) |
} |
|
var opposite = direction === 'prev' ? 'next' : 'prev' |
, position = direction === 'prev' ? 'first' : 'last' |
|
_self.$firstActiveToken[opposite + 'All']('.token').each(function() { |
_self.deactivate( $(this) ) |
}) |
|
_self.activate( _self.$wrapper.find('.token:' + position), true, true ) |
e.preventDefault() |
} |
|
this.lastKeyDown = e.keyCode |
} |
|
, keypress: function(e) { |
|
// Comma |
if ($.inArray( e.which, this._triggerKeys) !== -1 && this.$input.is(document.activeElement)) { |
if (this.$input.val()) { |
this.createTokensFromInput(e) |
} |
return false; |
} |
} |
|
, keyup: function (e) { |
this.preventInputFocus = false |
|
if (!this.focused) return |
|
switch(e.keyCode) { |
case 8: // backspace |
if (this.$input.is(document.activeElement)) { |
if (this.$input.val().length || this.lastInputValue.length && this.lastKeyDown === 8) break |
|
this.preventDeactivation = true |
var $prevToken = this.$input.hasClass('tt-input') ? this.$input.parent().prevAll('.token:first') : this.$input.prevAll('.token:first') |
|
if (!$prevToken.length) break |
|
this.activate( $prevToken ) |
} else { |
this.remove(e) |
} |
break |
|
case 46: // delete |
this.remove(e, 'next') |
break |
} |
this.lastKeyUp = e.keyCode |
} |
|
, focus: function (e) { |
this.focused = true |
this.$wrapper.addClass('focus') |
|
if (this.$input.is(document.activeElement)) { |
this.$wrapper.find('.active').removeClass('active') |
this.$firstActiveToken = null |
|
if (this.options.showAutocompleteOnFocus) { |
this.search() |
} |
} |
} |
|
, blur: function (e) { |
|
this.focused = false |
this.$wrapper.removeClass('focus') |
|
if (!this.preventDeactivation && !this.$element.is(document.activeElement)) { |
this.$wrapper.find('.active').removeClass('active') |
this.$firstActiveToken = null |
} |
|
if (!this.preventCreateTokens && (this.$input.data('edit') && !this.$input.is(document.activeElement) || this.options.createTokensOnBlur )) { |
this.createTokensFromInput(e) |
} |
|
this.preventDeactivation = false |
this.preventCreateTokens = false |
} |
|
, paste: function (e) { |
var _self = this |
|
// Add tokens to existing ones |
if (_self.options.allowPasting) { |
setTimeout(function () { |
_self.createTokensFromInput(e) |
}, 1) |
} |
} |
|
, change: function (e) { |
if ( e.initiator === 'tokenfield' ) return // Prevent loops |
|
this.setTokens( this.$element.val() ) |
} |
|
, createTokensFromInput: function (e, focus) { |
if (this.$input.val().length < this.options.minLength) |
return // No input, simply return |
|
var tokensBefore = this.getTokensList() |
this.setTokens( this.$input.val(), true ) |
|
if (tokensBefore == this.getTokensList() && this.$input.val().length) |
return false // No tokens were added, do nothing (prevent form submit) |
|
if (this.$input.hasClass('tt-input')) { |
// Typeahead acts weird when simply setting input value to empty, |
// so we set the query to empty instead |
this.$input.typeahead('val', '') |
} else { |
this.$input.val('') |
} |
|
if (this.$input.data( 'edit' )) { |
this.unedit(focus) |
} |
|
return false // Prevent form being submitted |
} |
|
, next: function (add) { |
if (add) { |
var $firstActiveToken = this.$wrapper.find('.active:first') |
, deactivate = $firstActiveToken && this.$firstActiveToken ? $firstActiveToken.index() < this.$firstActiveToken.index() : false |
|
if (deactivate) return this.deactivate( $firstActiveToken ) |
} |
|
var $lastActiveToken = this.$wrapper.find('.active:last') |
, $nextToken = $lastActiveToken.nextAll('.token:first') |
|
if (!$nextToken.length) { |
this.$input.focus() |
return |
} |
|
this.activate($nextToken, add) |
} |
|
, prev: function (add) { |
|
if (add) { |
var $lastActiveToken = this.$wrapper.find('.active:last') |
, deactivate = $lastActiveToken && this.$firstActiveToken ? $lastActiveToken.index() > this.$firstActiveToken.index() : false |
|
if (deactivate) return this.deactivate( $lastActiveToken ) |
} |
|
var $firstActiveToken = this.$wrapper.find('.active:first') |
, $prevToken = $firstActiveToken.prevAll('.token:first') |
|
if (!$prevToken.length) { |
$prevToken = this.$wrapper.find('.token:first') |
} |
|
if (!$prevToken.length && !add) { |
this.$input.focus() |
return |
} |
|
this.activate( $prevToken, add ) |
} |
|
, activate: function ($token, add, multi, remember) { |
|
if (!$token) return |
|
if (typeof remember === 'undefined') var remember = true |
|
if (multi) var add = true |
|
this.$copyHelper.focus() |
|
if (!add) { |
this.$wrapper.find('.active').removeClass('active') |
if (remember) { |
this.$firstActiveToken = $token |
} else { |
delete this.$firstActiveToken |
} |
} |
|
if (multi && this.$firstActiveToken) { |
// Determine first active token and the current tokens indicies |
// Account for the 1 hidden textarea by subtracting 1 from both |
var i = this.$firstActiveToken.index() - 2 |
, a = $token.index() - 2 |
, _self = this |
|
this.$wrapper.find('.token').slice( Math.min(i, a) + 1, Math.max(i, a) ).each( function() { |
_self.activate( $(this), true ) |
}) |
} |
|
$token.addClass('active') |
this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() |
} |
|
, activateAll: function() { |
var _self = this |
|
this.$wrapper.find('.token').each( function (i) { |
_self.activate($(this), i !== 0, false, false) |
}) |
} |
|
, deactivate: function($token) { |
if (!$token) return |
|
$token.removeClass('active') |
this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() |
} |
|
, toggle: function($token) { |
if (!$token) return |
|
$token.toggleClass('active') |
this.$copyHelper.val( this.getTokensList( null, null, true ) ).select() |
} |
|
, edit: function ($token) { |
if (!$token) return |
|
var attrs = $token.data('attrs') |
|
// Allow changing input value before editing |
var options = { attrs: attrs, relatedTarget: $token.get(0) } |
var editEvent = $.Event('tokenfield:edittoken', options) |
this.$element.trigger( editEvent ) |
|
// Edit event can be cancelled if default is prevented |
if (editEvent.isDefaultPrevented()) return |
|
$token.find('.token-label').text(attrs.value) |
var tokenWidth = $token.outerWidth() |
|
var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input |
|
$token.replaceWith( $_input ) |
|
this.preventCreateTokens = true |
|
this.$input.val( attrs.value ) |
.select() |
.data( 'edit', true ) |
.width( tokenWidth ) |
|
this.update(); |
|
// Indicate that token is now being edited, and is replaced with an input field in the DOM |
this.$element.trigger($.Event('tokenfield:editedtoken', options )) |
} |
|
, unedit: function (focus) { |
var $_input = this.$input.hasClass('tt-input') ? this.$input.parent() : this.$input |
$_input.appendTo( this.$wrapper ) |
|
this.$input.data('edit', false) |
this.$mirror.text('') |
|
this.update() |
|
// Because moving the input element around in DOM |
// will cause it to lose focus, we provide an option |
// to re-focus the input after appending it to the wrapper |
if (focus) { |
var _self = this |
setTimeout(function () { |
_self.$input.focus() |
}, 1) |
} |
} |
|
, remove: function (e, direction) { |
if (this.$input.is(document.activeElement) || this._disabled || this._readonly) return |
|
var $token = (e.type === 'click') ? $(e.target).closest('.token') : this.$wrapper.find('.token.active') |
|
if (e.type !== 'click') { |
if (!direction) var direction = 'prev' |
this[direction]() |
|
// Was it the first token? |
if (direction === 'prev') var firstToken = $token.first().prevAll('.token:first').length === 0 |
} |
|
// Prepare events and their options |
var options = { attrs: this.getTokenData( $token ), relatedTarget: $token.get(0) } |
, removeEvent = $.Event('tokenfield:removetoken', options) |
|
this.$element.trigger(removeEvent); |
|
// Remove event can be intercepted and cancelled |
if (removeEvent.isDefaultPrevented()) return |
|
var removedEvent = $.Event('tokenfield:removedtoken', options) |
, changeEvent = $.Event('change', { initiator: 'tokenfield' }) |
|
// Remove token from DOM |
$token.remove() |
|
// Trigger events |
this.$element.val( this.getTokensList() ).trigger( removedEvent ).trigger( changeEvent ) |
|
// Focus, when necessary: |
// When there are no more tokens, or if this was the first token |
// and it was removed with backspace or it was clicked on |
if (!this.$wrapper.find('.token').length || e.type === 'click' || firstToken) this.$input.focus() |
|
// Adjust input width |
this.$input.css('width', this.options.minWidth + 'px') |
this.update() |
|
// Cancel original event handlers |
e.preventDefault() |
e.stopPropagation() |
} |
|
/** |
* Update tokenfield dimensions |
*/ |
, update: function (e) { |
var value = this.$input.val() |
, inputPaddingLeft = parseInt(this.$input.css('padding-left'), 10) |
, inputPaddingRight = parseInt(this.$input.css('padding-right'), 10) |
, inputPadding = inputPaddingLeft + inputPaddingRight |
|
if (this.$input.data('edit')) { |
|
if (!value) { |
value = this.$input.prop("placeholder") |
} |
if (value === this.$mirror.text()) return |
|
this.$mirror.text(value) |
|
var mirrorWidth = this.$mirror.width() + 10; |
if ( mirrorWidth > this.$wrapper.width() ) { |
return this.$input.width( this.$wrapper.width() ) |
} |
|
this.$input.width( mirrorWidth ) |
} |
else { |
var w = (this.textDirection === 'rtl') |
? this.$input.offset().left + this.$input.outerWidth() - this.$wrapper.offset().left - parseInt(this.$wrapper.css('padding-left'), 10) - inputPadding - 1 |
: this.$wrapper.offset().left + this.$wrapper.width() + parseInt(this.$wrapper.css('padding-left'), 10) - this.$input.offset().left - inputPadding; |
// |
// some usecases pre-render widget before attaching to DOM, |
// dimensions returned by jquery will be NaN -> we default to 100% |
// so placeholder won't be cut off. |
isNaN(w) ? this.$input.width('100%') : this.$input.width(w); |
} |
} |
|
, focusInput: function (e) { |
if ( $(e.target).closest('.token').length || $(e.target).closest('.token-input').length || $(e.target).closest('.tt-dropdown-menu').length ) return |
// Focus only after the current call stack has cleared, |
// otherwise has no effect. |
// Reason: mousedown is too early - input will lose focus |
// after mousedown. However, since the input may be moved |
// in DOM, there may be no click or mouseup event triggered. |
var _self = this |
setTimeout(function() { |
_self.$input.focus() |
}, 0) |
} |
|
, search: function () { |
if ( this.$input.data('ui-autocomplete') ) { |
this.$input.autocomplete('search') |
} |
} |
|
, disable: function () { |
this.setProperty('disabled', true); |
} |
|
, enable: function () { |
this.setProperty('disabled', false); |
} |
|
, readonly: function () { |
this.setProperty('readonly', true); |
} |
|
, writeable: function () { |
this.setProperty('readonly', false); |
} |
|
, setProperty: function(property, value) { |
this['_' + property] = value; |
this.$input.prop(property, value); |
this.$element.prop(property, value); |
this.$wrapper[ value ? 'addClass' : 'removeClass' ](property); |
} |
|
, destroy: function() { |
// Set field value |
this.$element.val( this.getTokensList() ); |
// Restore styles and properties |
this.$element.css( this.$element.data('original-styles') ); |
this.$element.prop( 'tabindex', this.$element.data('original-tabindex') ); |
|
// Re-route tokenfield label to original input |
var $label = $( 'label[for="' + this.$input.prop('id') + '"]' ) |
if ( $label.length ) { |
$label.prop( 'for', this.$element.prop('id') ) |
} |
|
// Move original element outside of tokenfield wrapper |
this.$element.insertBefore( this.$wrapper ); |
|
// Remove tokenfield-related data |
this.$element.removeData('original-styles') |
.removeData('original-tabindex') |
.removeData('bs.tokenfield'); |
|
// Remove tokenfield from DOM |
this.$wrapper.remove(); |
this.$mirror.remove(); |
|
var $_element = this.$element; |
|
return $_element; |
} |
|
} |
|
|
/* TOKENFIELD PLUGIN DEFINITION |
* ======================== */ |
|
var old = $.fn.tokenfield |
|
$.fn.tokenfield = function (option, param) { |
var value |
, args = [] |
|
Array.prototype.push.apply( args, arguments ); |
|
var elements = this.each(function () { |
var $this = $(this) |
, data = $this.data('bs.tokenfield') |
, options = typeof option == 'object' && option |
|
if (typeof option === 'string' && data && data[option]) { |
args.shift() |
value = data[option].apply(data, args) |
} else { |
if (!data && typeof option !== 'string' && !param) { |
$this.data('bs.tokenfield', (data = new Tokenfield(this, options))) |
$this.trigger('tokenfield:initialize') |
} |
} |
}) |
|
return typeof value !== 'undefined' ? value : elements; |
} |
|
$.fn.tokenfield.defaults = { |
minWidth: 60, |
minLength: 0, |
allowEditing: true, |
allowPasting: true, |
limit: 0, |
autocomplete: {}, |
typeahead: {}, |
showAutocompleteOnFocus: false, |
createTokensOnBlur: false, |
delimiter: ',', |
beautify: true, |
inputType: 'text' |
} |
|
$.fn.tokenfield.Constructor = Tokenfield |
|
|
/* TOKENFIELD NO CONFLICT |
* ================== */ |
|
$.fn.tokenfield.noConflict = function () { |
$.fn.tokenfield = old |
return this |
} |
|
return Tokenfield; |
|
})); |
/bower_components/bootstrap-tokenfield/test/test.tokenfield.1.unit.js |
@@ -0,0 +1,431 @@ |
describe('1. Unit tests:', function() { |
|
describe('initializing tokenfield', function() { |
describe('with an empty input', function() { |
|
it('must wrap the original input with the wrapper element', function() { |
this.$field.parents('.tokenfield').hasClass('form-control').must.be.true(); |
}); |
|
it('must create a new input element for token input', function() { |
this.$field.parents('.tokenfield').find('.token-input').length.must.equal(1); |
}); |
|
it('must move the original input out of the way', function() { |
this.$field.css('position').must.equal('absolute'); |
this.$field.css('left').must.equal('-10000px'); |
}); |
|
it('must not create any tokens', function() { |
this.$wrapper.find('.token').length.must.equal(0); |
}); |
|
}); |
|
describe('with an input with comma-separated values', function() { |
|
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must create tokens for each comma-separated value', function() { |
this.$wrapper.find('.token').length.must.equal(3); |
}); |
|
}); |
|
describe('with an input with data-tokens values', function() { |
|
before(function() { |
TFT.template = '<input type="text" class="tokenize" data-tokens="red, green, blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must create tokens for each comma-separated token', function() { |
this.$wrapper.find('.token').length.must.equal(3); |
}); |
|
}); |
|
describe('with RTL', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" style="direction:rtl" value="red,green, blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must set rtl class on tokenfield', function() { |
this.$wrapper.hasClass('rtl').must.equal(true); |
}); |
}) |
|
}); |
|
describe('destroying tokenfield', function() { |
before(function() { |
TFT.template = '<div id="destroy-test-container"><label for="destroy-test"></label><input type="text" id="destroy-test" class="tokenize" value="red,green, blue" /></div>'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must reset the original input to previous state', function() { |
var $field = this.$field.tokenfield('destroy'); |
$field.must.be.an.object(); |
$field.data().must.not.have.property('bs.tokenfield'); |
$field.parent().prop('id').must.equal('destroy-test-container'); |
$field.val().must.equal('red, green, blue'); |
}); |
}); |
|
describe('Tokenfield public methods', function() { |
|
describe('.createToken()', function() { |
|
describe('using an empty input', function() { |
|
beforeEach(function() { |
this.$field.tokenfield('createToken', 'awesome'); |
}); |
|
it('must create a new token', function() { |
this.$wrapper.find('.token').must.have.length(1); |
}); |
|
it('add the new token value to original input', function() { |
this.$field.val().must.equal('awesome'); |
}); |
|
}); |
|
describe('using a non-empty input', function() { |
|
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
beforeEach(function() { |
this.$field.tokenfield('createToken', 'awesome'); |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must append a new token to the end of existing tokens', function() { |
this.$field.val().must.equal('red, green, blue, awesome'); |
}); |
|
}); |
|
describe('given an object', function() { |
|
beforeEach(function() { |
this.$field.tokenfield('createToken', { value: 'purple', label: 'Violet' }); |
}); |
|
it('must create a new token', function() { |
this.$wrapper.find('.token').must.have.length(1); |
}); |
|
it('add the new token value to original input', function() { |
this.$field.val().must.equal('purple'); |
}); |
|
}); |
|
}); |
|
describe('.getTokens()', function() { |
|
describe('given no arguments', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must return an array of token key-value pairs', function() { |
var tokens = this.$field.tokenfield('getTokens'); |
tokens.must.be.an.array(); |
tokens.must.have.length(3); |
tokens[0].must.have.keys(['label', 'value']); |
tokens[0].label.must.equal('red'); |
tokens[0].value.must.equal('red'); |
}); |
}); |
|
describe('given arguments active = true', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must return an array of only active token key-value pairs', function() { |
// Mark green as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
var tokens = this.$field.tokenfield('getTokens', true); |
tokens.must.be.an.array(); |
tokens.must.have.length(1); |
tokens[0].must.have.keys(['label', 'value']); |
tokens[0].label.must.equal('green'); |
tokens[0].value.must.equal('green'); |
}); |
}); |
|
|
}); |
|
describe('getTokensList()', function() { |
|
describe('given no arguments', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must return a string with comma-separated token values', function() { |
var tokens = this.$field.tokenfield('getTokensList'); |
tokens.must.be.a.string(); |
tokens.must.equal('red, green, blue'); |
}); |
}); |
|
describe('given an alternative delimiter', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must return a string with semicolon-separated token values', function() { |
var tokens = this.$field.tokenfield('getTokensList', ';', false); |
tokens.must.be.a.string(); |
tokens.must.equal('red;green;blue'); |
}); |
}); |
|
describe('given an alternative delimiter and active = true', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow" />'; |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must return a string with semicolon-separated token values', function() { |
// Mark green and yellow as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
var tokens = this.$field.tokenfield('getTokensList', ';', false, true); |
tokens.must.be.a.string(); |
tokens.must.equal('green;yellow'); |
}); |
}); |
|
|
}); |
|
describe('setTokens()', function() { |
|
describe('using comma-separated string', function() { |
|
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
beforeEach(function(){ |
this.$field.tokenfield('setTokens', 'black,yellow,white'); |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must replace any existing tokens with new ones', function() { |
var tokens = this.$field.tokenfield('getTokens') |
, tokensList = this.$field.tokenfield('getTokensList'); |
|
tokens.must.have.length(3); |
tokens[0].must.have.keys(['label', 'value']); |
tokens[0].label.must.equal('black'); |
tokens[0].value.must.equal('black'); |
|
tokensList.must.not.contain('red'); |
|
}); |
|
it('must set the original input value to comma-separated list of token values', function() { |
this.$field.val().must.equal('black, yellow, white'); |
}); |
|
}); |
|
describe('using an array of string values', function() { |
|
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
beforeEach(function(){ |
this.$field.tokenfield('setTokens', 'black,yellow,white'); |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must replace any existing tokens with new ones', function() { |
var tokens = this.$field.tokenfield('getTokens') |
, tokensList = this.$field.tokenfield('getTokensList'); |
|
tokens.must.have.length(3); |
tokens[0].must.have.keys(['label', 'value']); |
tokens[0].label.must.equal('black'); |
tokens[0].value.must.equal('black'); |
|
tokensList.must.not.contain('red'); |
|
}); |
|
it('must set the original input value to comma-separated list of token values', function() { |
this.$field.val().must.equal('black, yellow, white'); |
}); |
|
}); |
|
describe('using an array of token objects', function() { |
|
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green, blue" />'; |
}); |
|
beforeEach(function(){ |
this.$field.tokenfield('setTokens', [{ value: "black", label: "Schwarz" }, { value: "yellow", label: "Gelb" }]); |
}); |
|
after(function() { |
TFT.template = null; |
}); |
|
it('must replace any existing tokens with new ones', function() { |
this.$field.tokenfield('setTokens', [{ value: "black", label: "Schwarz" }, { value: "yellow", label: "Gelb" }]); |
|
var tokens = this.$field.tokenfield('getTokens') |
, tokensList = this.$field.tokenfield('getTokensList'); |
|
tokens.must.have.length(2); |
tokens[0].must.have.keys(['label', 'value']); |
tokens[0].label.must.equal('Schwarz'); |
tokens[0].value.must.equal('black'); |
|
tokensList.must.not.contain('red'); |
|
}); |
|
it('must set the original input value to comma-separated list of token values', function() { |
this.$field.val().must.equal('black, yellow'); |
}); |
|
}); |
|
}); |
|
describe('disable()', function() { |
|
beforeEach(function() { |
this.$field.tokenfield('disable'); |
}); |
|
it('must disable both original and token input', function() { |
this.$field.prop('disabled').must.be.true(); |
this.$input.prop('disabled').must.be.true(); |
}); |
|
it('must add "disabled" class to tokenfield', function() { |
this.$wrapper.hasClass('disabled').must.be.true(); |
}); |
|
}); |
|
describe('enable()', function() { |
|
beforeEach(function() { |
this.$field.tokenfield('disable'); |
this.$field.tokenfield('enable'); |
}); |
|
it('must enable both original and token input', function() { |
this.$field.prop('disabled').must.be.false(); |
this.$input.prop('disabled').must.be.false(); |
}); |
|
it('must remove "disabled" class from tokenfield', function() { |
this.$wrapper.hasClass('disabled').must.be.false(); |
}); |
|
}); |
|
describe('readonly()', function() { |
|
beforeEach(function() { |
this.$field.tokenfield('readonly'); |
}); |
|
it('must make both original and token input readonly', function() { |
this.$field.prop('readonly').must.be.true(); |
this.$input.prop('readonly').must.be.true(); |
}); |
|
it('must add "readonly" class to tokenfield', function() { |
this.$wrapper.hasClass('readonly').must.be.true(); |
}); |
|
}); |
|
describe('writeable()', function() { |
|
beforeEach(function() { |
this.$field.tokenfield('readonly'); |
this.$field.tokenfield('writeable'); |
}); |
|
it('must make both original and token input writeable', function() { |
this.$field.prop('readonly').must.be.false(); |
this.$input.prop('readonly').must.be.false(); |
}); |
|
it('must remove "readonly" class from tokenfield', function() { |
this.$wrapper.hasClass('readonly').must.be.false(); |
}); |
|
}); |
}); |
|
}); |
/bower_components/bootstrap-tokenfield/test/test.tokenfield.2.integration.js |
@@ -0,0 +1,1028 @@ |
describe('2. Integration tests:', function() { |
|
before(function() { |
require('../node_modules/jquery-simulate-ext/libs/bililiteRange'); |
require('../node_modules/jquery-simulate-ext/libs/jquery.simulate'); |
|
// https://github.com/j-ulrich/jquery-simulate-ext/issues/9 |
// For us, it just saves a littlebit time |
global.$.simulate.ext_disableQuirkDetection = true; |
|
require('../node_modules/jquery-simulate-ext/src/jquery.simulate.ext'); |
require('../node_modules/jquery-simulate-ext/src/jquery.simulate.key-combo'); |
require('../node_modules/jquery-simulate-ext/src/jquery.simulate.key-sequence'); |
}); |
|
describe('Using an alternative delimiter', function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red;green;blue;yellow" />' |
TFT.options = { |
delimiter: ';' |
} |
}); |
|
after(function() { |
delete TFT.template; |
delete TFT.options; |
}); |
|
it('must create tokens by splitting the original value with delimiters', function() { |
this.$field.data('bs.tokenfield').$wrapper.find('.token').length.must.equal(4); |
}); |
|
it('must create a token when the delimiting key is pressed and use the first delimiter for setting original input value', function() { |
this.$input.focus().simulate("key-sequence", { sequence: "purple;olive;" }) |
this.$field.data('bs.tokenfield').$wrapper.find('.token').length.must.equal(6); |
this.$field.val().must.equal('red; green; blue; yellow; purple; olive'); |
}); |
}) |
|
describe('with multiple alternative delimiters', function() { |
describe("Delimiters: [' ', '.']", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red green blue.yellow" />' |
TFT.options = { |
delimiter: [' ', '.'] |
} |
}); |
|
after(function() { |
delete TFT.template; |
delete TFT.options; |
}); |
|
it('must create tokens by splitting the original value with delimiters', function() { |
this.$field.data('bs.tokenfield').$wrapper.find('.token').length.must.equal(4); |
}); |
|
it('must create a token when the delimiting key is pressed and use the first delimiter for setting original input value', function() { |
this.$input.focus().simulate("key-sequence", { sequence: "purple olive." }); |
this.$field.data('bs.tokenfield').$wrapper.find('.token').length.must.equal(6); |
this.$field.val().must.equal('red green blue yellow purple olive'); |
}); |
}); |
|
describe("Delimiters: [',', ' ', '-', '_'] (Regression test for #79)", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green blue-yellow_123" />' |
TFT.options = { |
delimiter: [',', ' ', '-', '_'] |
} |
}); |
|
after(function() { |
delete TFT.template; |
delete TFT.options; |
}); |
|
it('must create tokens by splitting the original value with delimiters', function() { |
this.$field.data('bs.tokenfield').$wrapper.find('.token').length.must.equal(5); |
}); |
}); |
|
describe("Using regexp special characters as delimiters", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red\\green$blue[yellow{orange^violet.purple|black?white*gray+silver(lime)navy" />' |
TFT.options = { |
delimiter: ['\\', '$', '[', '{', '^', '.', '|', '?', '*', '+', '(', ')'] |
} |
}); |
|
after(function() { |
delete TFT.template; |
delete TFT.options; |
}); |
|
it('must create tokens by splitting the original value with delimiters', function() { |
this.$field.data('bs.tokenfield').$wrapper.find('.token').length.must.equal(13); |
}); |
}); |
}); |
|
describe('Keyboard interaction', function() { |
|
describe("Pressing Ctrl+A", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must select all tokens", function() { |
this.$input.focus().simulate("key-combo", { combo: "ctrl+a" }); |
this.$field.tokenfield('getTokens', true).length.must.equal(3); |
}); |
}); |
|
describe("pressing Cmd+A", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must select all tokens", function() { |
this.$input.focus().simulate("key-combo", { combo: "meta+a" }); |
this.$field.tokenfield('getTokens', true).length.must.equal(3); |
}); |
}); |
|
describe("Pressing delete", function() { |
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
describe('and there are more tokens to the right of selected token', function() { |
it("must delete the selected token and move focus to the next token", function() { |
// Mark green as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{del}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList', null, null, true).must.equal('blue'); |
}); |
}) |
|
describe('and there are no more tokens to the right of the selected token', function() { |
it("must delete the selected token and move focus to input", function() { |
// Mark green as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{del}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList', null, null, true).must.equal(''); |
this.$input.is(document.activeElement).must.be.true(); |
}); |
}) |
}); |
|
describe("when multiple tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow,purple" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
describe('and there are more tokens to the right of selected tokens', function() { |
it("must delete the selected tokens and move focus to the next token of the rightmost selected token", function() { |
// Mark green and yellow as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{del}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList', null, null, true).must.equal('purple'); |
}); |
}); |
|
describe('and there are no more tokens to the right of selected tokens', function() { |
it("must delete the selected tokens and move focus input", function() { |
// Mark green and yellow as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(purple))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{del}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList').must.equal('red, blue, yellow'); |
this.$input.is(document.activeElement).must.be.true(); |
|
}); |
}); |
}); |
}); |
|
describe("Pressing backspace", function() { |
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
describe('and there are more tokens to the left of selected token', function() { |
it("must delete the selected token and move focus to the previous token", function() { |
// Mark green as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(blue))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{backspace}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList', null, null, true).must.equal('green'); |
}); |
}) |
|
describe('and there are no more tokens to the left of the selected token', function() { |
it("must delete the selected token and move focus to input", function() { |
// Mark green as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{backspace}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList', null, null, true).must.equal(''); |
this.$input.is(document.activeElement).must.be.true(); |
}); |
}) |
}); |
|
describe("when multiple tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow,purple" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
describe('and there are more tokens to the left of selected tokens', function() { |
it("must delete the selected tokens and move focus to the previous token of the leftmost selected token", function() { |
// Mark green and yellow as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{backspace}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList', null, null, true).must.equal('red'); |
}); |
}); |
|
describe('and there are no more tokens to the left of selected tokens', function() { |
it("must delete the selected tokens and move focus input", function() { |
// Mark green and yellow as active |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(purple))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{backspace}" }); |
this.$field.tokenfield('getTokens').length.must.equal(3); |
this.$field.tokenfield('getTokensList').must.equal('green, blue, yellow'); |
this.$input.is(document.activeElement).must.be.true(); |
}); |
}); |
}); |
|
describe("when focus is on input", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the last token", function() { |
this.$input.simulate("key-sequence", { sequence: "{backspace}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('blue'); |
}); |
}); |
}); |
|
describe("Pressing left arrow key", function() { |
describe("when no tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the last token", function() { |
this.$input.simulate("key-sequence", { sequence: "{leftarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('blue'); |
}); |
}); |
|
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the previous token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(blue))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{leftarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('green'); |
}); |
}); |
|
describe("when multiple tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the previous token of the leftmost selected token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{leftarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red'); |
}); |
}); |
|
describe("when the first token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must keep the focus on the first token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{leftarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red'); |
}); |
}); |
|
describe("when no tokens are selected and direction is RTL", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" style="direction:rtl" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must keep the focus on the input", function() { |
this.$input.simulate("key-sequence", { sequence: "{leftarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal(''); |
this.$input.is(document.activeElement).must.be.true(); |
}); |
}); |
}); |
|
describe("Pressing right arrow key", function() { |
describe("when no tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must keep the focus on the input", function() { |
this.$input.simulate("key-sequence", { sequence: "{rightarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal(''); |
this.$input.is(document.activeElement).must.be.true(); |
}); |
}); |
|
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the next token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{rightarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('green'); |
}); |
}); |
|
describe("when multiple tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the next token of the rightmost selected token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(blue))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{rightarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('yellow'); |
}); |
}); |
|
describe("when the last token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move the focus to the input", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(blue))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{rightarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal(''); |
this.$input.is(document.activeElement).must.be.true(); |
}); |
}); |
|
describe("when no tokens are selected and direction is RTL", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" style="direction:rtl" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the last token", function() { |
this.$input.simulate("key-sequence", { sequence: "{rightarrow}" }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('blue'); |
}); |
}); |
}); |
|
describe("Pressing Shift + left arrow key", function() { |
describe("when no tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move focus to the last token", function() { |
this.$input.focus().simulate("keydown", { keyCode: 37, charCode: 37, shiftKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('blue'); |
}); |
}); |
|
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must activate the previous token in addition to the already active token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(blue))').addClass('active'); |
|
this.$copyHelper.focus() |
.simulate("keydown", { keyCode: 37, shiftKey: true }) |
.simulate("keydown", { keyCode: 37, shiftKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red, green, blue'); |
}); |
}); |
|
describe("when multiple, non-adjacent tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow,purple" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move select the previous token of the leftmost selected token in addition to the already selected tokens", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
this.$copyHelper.focus().simulate("keydown", { keyCode: 37, shiftKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red, green, yellow'); |
}); |
}); |
}); |
|
describe("Pressing Shift + right arrow key", function() { |
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must activate the next token in addition to the already active token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.focus() |
.simulate("keydown", { keyCode: 39, shiftKey: true }) |
.simulate("keydown", { keyCode: 39, shiftKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red, green, blue'); |
}); |
}); |
|
describe("when multiple, non-adjacent tokens are selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue,yellow,purple" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must move select the next token of the rightmost selected token in addition to the already selected tokens", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(yellow))').addClass('active'); |
|
this.$copyHelper.focus().simulate("keydown", { keyCode: 39, shiftKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('green, yellow, purple'); |
}); |
}); |
}); |
|
describe("Pressing Enter key", function() { |
describe("when a token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must enter edit mode for the active token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{enter}" }); |
this.$input.data('edit').must.be.true(); |
this.$input.prev(':contains(red)').must.have.length(1); |
this.$input.next(':contains(blue)').must.have.length(1); |
}); |
}); |
|
describe("when a token is selected and allowEditing is false", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />'; |
TFT.options = { allowEditing: false } |
}); |
|
after(function() { |
delete TFT.template; |
delete TFT.options; |
}); |
|
it("must not enter edit mode for the active token [no data('edit') property]", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{enter}" }); |
this.$input.data().must.not.have.property('edit'); |
}); |
}); |
|
describe("when input has text", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must create a new token from the input", function() { |
|
this.$input.simulate("key-sequence", { sequence: "purple{enter}" }); |
this.$field.tokenfield('getTokens').must.have.length(4); |
}); |
}); |
}); |
|
describe("Pressing Tab key", function() { |
describe("when input has text", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must create a new token from the input", function() { |
|
this.$input.focus().simulate("key-sequence", { sequence: "purple" }); |
this.$input.simulate("keydown", { keyCode: 9 }); |
this.$field.tokenfield('getTokens').must.have.length(4); |
}); |
}); |
}); |
}); |
|
describe("Mouse interaction", function() { |
|
describe("Clicking on a token", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must select the token and deactivate any other active tokens", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
this.$wrapper.find('.token:contains(red)').click(); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red'); |
}); |
}); |
|
describe("Clicking on a token's remove icon", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must remove the token", function() { |
this.$wrapper.find('.token:contains(red)').find('.close').click(); |
this.$field.tokenfield('getTokensList' ).must.equal('green, blue'); |
}); |
}); |
|
describe("Double-clicking on a token", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must enter the edit mode of the token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
this.$wrapper.find('.token:contains(red)').dblclick(); |
this.$input.data('edit').must.be.true(); |
this.$input.next(':contains(green)').must.have.length(1); |
}); |
}); |
|
describe("must not enter the edit mode of the token when allowEditing false", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />'; |
TFT.options = { allowEditing: false } |
}); |
|
after(function() { |
delete TFT.template; |
delete TFT.options; |
}); |
|
it("must not enter the edit mode of the token", function() { |
this.$wrapper.find('.token:contains(red)').dblclick(); |
this.$input.data().must.not.have.property('edit'); |
}); |
}); |
|
describe("Ctrl-clicking on a token when another token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must activate the token in addition to the already active token", function() { |
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(green))').addClass('active'); |
|
this.$wrapper.find('.token:contains(red)').simulate('click', { ctrlKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red, green'); |
}); |
}); |
|
describe("Shift-clicking on a token when another token is selected", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must activate the token and all the tokens between the already active token", function() { |
this.$wrapper.find('.token:contains(blue)').simulate('click'); |
this.$wrapper.find('.token:contains(red)').simulate('click', { shiftKey: true }); |
this.$field.tokenfield('getTokensList', null, null, true ).must.equal('red, green, blue'); |
}); |
}); |
|
describe("Pressing enter when there is no input", function() { |
var submitted = false; |
|
before(function() { |
TFT.template = '<form method="post" action=""><input type="text" class="tokenize" value="red,green,blue" /><input type="submit"></form>'; |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
beforeEach(function() { |
this.$sandbox.find('form').on('submit', function(e) { |
submitted = true; |
e.preventDefault(); |
return false; |
}); |
// key-sequence does not trigger submit event on the form when pressing enter |
// so we need to trigger it manually. Not really a solid test-case, but oh well |
// https://github.com/j-ulrich/jquery-simulate-ext/pull/14 |
this.$input.focus().simulate("key-sequence", { sequence: "{enter}" }).trigger('submit'); |
}); |
|
it("must submit the underlying form", function() { |
submitted.must.equal(true); |
}); |
}); |
}); |
|
describe("Events", function() { |
|
describe("tokenfield:initialize", function() { |
it("must must be triggered when tokenfield is created", function (done) { |
$('<input type="text" />') |
.on('tokenfield:initialize', function() { |
done(); |
}) |
.tokenfield(); |
}); |
}); |
|
describe("tokenfield:createtoken", function() { |
it("must allow changing token label and value", function() { |
this.$field.on('tokenfield:createtoken', function (e) { |
e.attrs.value = 'one'; |
e.attrs.label = 'two'; |
}); |
this.$field.tokenfield('createToken', 'zero'); |
|
var results = this.$field.tokenfield('getTokens'); |
results[0].label.must.equal('two'); |
results[0].value.must.equal('one'); |
}); |
|
it("must allow setting token value to an empty string", function() { |
this.$field.on('tokenfield:createtoken', function (e) { |
e.attrs.value = ''; |
}); |
this.$field.tokenfield('createToken', 'zero'); |
|
var results = this.$field.tokenfield('getTokens'); |
results[0].label.must.equal('zero'); |
results[0].value.must.equal(''); |
}); |
|
it("must allow canceling createtoken by setting token to a falsy value", function() { |
this.$field.on('tokenfield:createtoken', function (e) { |
e.attrs = false; |
}); |
this.$field.tokenfield('createToken', 'yellow'); |
|
var results = this.$field.tokenfield('getTokens'); |
results.must.have.length(0); |
}); |
|
it("must allow canceling createtoken by calling event.preventDefault()", function() { |
this.$field.on('tokenfield:createtoken', function (e) { |
e.preventDefault(); |
}); |
this.$field.tokenfield('createToken', 'yellow'); |
|
var results = this.$field.tokenfield('getTokens'); |
results.must.have.length(0); |
}); |
|
it("must allow canceling createtoken by returning false in the event handler", function() { |
this.$field.on('tokenfield:createtoken', function (e) { |
return false; |
}); |
this.$field.tokenfield('createToken', 'yellow'); |
|
var results = this.$field.tokenfield('getTokens'); |
results.must.have.length(0); |
}); |
}); |
|
describe("tokenfield:createdtoken", function() { |
it("must be triggered when a token is created and in the DOM", function (done) { |
var self = this; |
|
this.$field.on('tokenfield:createdtoken', function (e) { |
e.attrs.must.be.an.object(); |
e.attrs.label.must.eql('red'); |
e.attrs.value.must.eql('red'); |
e.relatedTarget.must.be.an.object(); |
|
self.$wrapper.find(e.relatedTarget).must.have.length(1); |
|
done(); |
}); |
|
this.$field.tokenfield('createToken', 'red'); |
}); |
}); |
|
describe("tokenfield:edittoken", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must be triggered when a token is edited", function (done) { |
this.$field.on('tokenfield:edittoken', function (e) { |
e.attrs.must.be.an.object(); |
e.attrs.label.must.eql('red'); |
e.attrs.value.must.eql('red'); |
e.relatedTarget.must.be.an.object(); |
done(); |
}); |
|
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{enter}" }); |
}); |
|
it("must allow canceling the default event handler by calling event.preventDefault()", function() { |
this.$field.on('tokenfield:edittoken', function (e) { |
e.preventDefault(); |
}); |
|
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{enter}" }); |
|
this.$input.data().must.not.have.property('edit'); |
}); |
|
it("must allow canceling the default event handler by returning false in the event handler", function() { |
this.$field.on('tokenfield:edittoken', function (e) { |
return false; |
}); |
|
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{enter}" }); |
|
this.$input.data().must.not.have.property('edit'); |
}); |
}); |
|
describe("tokenfield:editedtoken", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must be triggered when a token is now being edited (replaced with an input in the DOM)", function (done) { |
var self = this; |
|
this.$field.on('tokenfield:editedtoken', function (e) { |
e.attrs.must.be.an.object(); |
e.attrs.label.must.eql('red'); |
e.attrs.value.must.eql('red'); |
e.relatedTarget.must.be.an.object(); |
|
self.$wrapper.find(e.relatedTarget).must.have.length(0); |
|
done(); |
}); |
|
this.$wrapper.find('.token') |
.filter(':has(.token-label:contains(red))').addClass('active'); |
|
this.$copyHelper.simulate("key-sequence", { sequence: "{enter}" }); |
}); |
}); |
|
describe("tokenfield:removetoken", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must be triggered when a token is about to be removed", function (done) { |
this.$field.on('tokenfield:removetoken', function (e) { |
e.attrs.must.be.an.object(); |
e.attrs.label.must.eql('red'); |
e.attrs.value.must.eql('red'); |
done(); |
}); |
|
this.$wrapper.find('.token:first').find('.close').click(); |
}); |
|
it("must allow canceling the default event handler by calling event.preventDefault()", function() { |
this.$field.on('tokenfield:removetoken', function (e) { |
e.preventDefault(); |
}); |
|
this.$wrapper.find('.token:first').find('.close').click(); |
|
this.$field.tokenfield('getTokens').must.have.length(3); |
}); |
|
it("must allow canceling the default event handler by returning false in the event handler", function() { |
this.$field.on('tokenfield:removetoken', function (e) { |
return false; |
}); |
|
this.$wrapper.find('.token:first').find('.close').click(); |
|
this.$field.tokenfield('getTokens').must.have.length(3); |
}); |
}); |
|
describe("tokenfield:removedtoken", function() { |
before(function() { |
TFT.template = '<input type="text" class="tokenize" value="red,green,blue" />' |
}); |
|
after(function() { |
delete TFT.template; |
}); |
|
it("must be triggered when a token is removed from the DOM", function (done) { |
var self = this; |
|
this.$field.on('tokenfield:removedtoken', function (e) { |
e.attrs.must.be.an.object(); |
e.attrs.label.must.eql('red'); |
e.attrs.value.must.eql('red'); |
|
self.$wrapper.find(e.relatedTarget).length.must.equal(0); |
|
done(); |
}); |
|
this.$wrapper.find('.token:first').find('.close').click(); |
}); |
}); |
|
}); |
|
}); |