text: /* global jQuery:true utilities:true */
  
  /*
   * Fuel UX Pillbox
   * https://github.com/ExactTarget/fuelux
   *
   * Copyright (c) 2014 ExactTarget
   * Licensed under the BSD New license.
   */
  
  // -- BEGIN UMD WRAPPER PREFACE --
  
  // WARNING: Anything placed inside of the UMD Wrapper may be stripped out by dist task
  
  // For more information on UMD visit:
  // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
  (function umdFactory (factory) {
  	if (typeof define === 'function' && define.amd) {
  		// if AMD loader is available, register as an anonymous module.
  		define(['jquery', 'fuelux/utilities', 'fuelux/dropdown-autoflip'], factory);
  	} else if (typeof exports === 'object') {
  		// Node/CommonJS
  		module.exports = factory(require('jquery'), require('./utilities'), require('./dropdown-autoflip'));
  	} else {
  		// OR use browser globals if AMD is not present
  		factory(jQuery);
  	}
  }(function PillboxWrapper ($) {
  	if (!$.fn.dropdownautoflip) {
  		throw new Error('Fuel UX pillbox control requires dropdown-autoflip.');
  	}
  
  	if (!$.fn.utilities) {
  		throw new Error('Fuel UX pillbox control requires FuelUX utilities.');
  	}
  	// -- END UMD WRAPPER PREFACE --
  
  	// -- BEGIN MODULE CODE HERE --
  
  	var old = $.fn.pillbox;
  
  	var utilities = $.fn.utilities;
  	var CONST = $.fn.utilities.CONST;
  	var COMMA_KEYCODE = CONST.COMMA_KEYCODE;
  	var ENTER_KEYCODE = CONST.ENTER_KEYCODE;
  	var isBackspaceKey = utilities.isBackspaceKey;
  	var isDeleteKey = utilities.isDeleteKey;
  	var isTabKey = utilities.isTabKey;
  	var isUpArrow = utilities.isUpArrow;
  	var isDownArrow = utilities.isDownArrow;
  	var cleanInput = utilities.cleanInput;
  	var isShiftHeld = utilities.isShiftHeld;
  
  	// PILLBOX CONSTRUCTOR AND PROTOTYPE
  	var Pillbox = function Pillbox (element, options) {
  		this.$element = $(element);
  		this.$moreCount = this.$element.find('.pillbox-more-count');
  		this.$pillGroup = this.$element.find('.pill-group');
  		this.$addItem = this.$element.find('.pillbox-add-item');
  		this.$addItemWrap = this.$addItem.parent();
  		this.$suggest = this.$element.find('.suggest');
  		this.$pillHTML = '<li class="btn btn-default pill">' +
  		'	<span></span>' +
  		'	<span class="glyphicon glyphicon-close">' +
  		'		<span class="sr-only">Remove</span>' +
  		'	</span>' +
  		'</li>';
  
  		this.options = $.extend({}, $.fn.pillbox.defaults, options);
  
  		if (this.options.readonly === -1) {
  			if (this.$element.attr('data-readonly') !== undefined) {
  				this.readonly(true);
  			}
  		} else if (this.options.readonly) {
  			this.readonly(true);
  		}
  
  		// EVENTS
  		this.acceptKeyCodes = this._generateObject(this.options.acceptKeyCodes);
  		// Create an object out of the key code array, so we don't have to loop through it on every key stroke
  
  		this.$element.on('click.fu.pillbox', '.pill-group > .pill', $.proxy(this.itemClicked, this));
  		this.$element.on('click.fu.pillbox', $.proxy(this.inputFocus, this));
  		this.$element.on('keydown.fu.pillbox', '.pillbox-add-item', $.proxy(this.inputEvent, this));
  		if (this.options.onKeyDown) {
  			this.$element.on('mousedown.fu.pillbox', '.suggest > li', $.proxy(this.suggestionClick, this));
  		}
  
  		if (this.options.edit) {
  			this.$element.addClass('pills-editable');
  			this.$element.on('blur.fu.pillbox', '.pillbox-add-item', $.proxy(this.cancelEdit, this));
  		}
  		this.$element.on('blur.fu.pillbox', '.pillbox-add-item', $.proxy(this.inputEvent, this));
  	};
  
  	Pillbox.prototype = {
  		constructor: Pillbox,
  
  		destroy: function destroy () {
  			this.$element.remove();
  			// any external bindings
  			// [none]
  			// empty elements to return to original markup
  			// [none]
  			// returns string of markup
  			return this.$element[0].outerHTML;
  		},
  
  		items: function items () {
  			var self = this;
  
  			return this.$pillGroup.children('.pill').map(function getItemsData () {
  				return self.getItemData($(this));
  			}).get();
  		},
  
  		itemClicked: function itemClicked (e) {
  			var $target = $(e.target);
  			var $item;
  
  			e.preventDefault();
  			e.stopPropagation();
  			this._closeSuggestions();
  
  			if (!$target.hasClass('pill')) {
  				$item = $target.parent();
  				if (this.$element.attr('data-readonly') === undefined) {
  					if ($target.hasClass('glyphicon-close')) {
  						if (this.options.onRemove) {
  							this.options.onRemove(this.getItemData($item, {
  								el: $item
  							}), $.proxy(this._removeElement, this));
  						} else {
  							this._removeElement(this.getItemData($item, {
  								el: $item
  							}));
  						}
  
  						return false;
  					} else if (this.options.edit) {
  						if ($item.find('.pillbox-list-edit').length) {
  							return false;
  						}
  
  						this.openEdit($item);
  					}
  				}
  			} else {
  				$item = $target;
  			}
  
  			this.$element.trigger('clicked.fu.pillbox', this.getItemData($item));
  
  			return true;
  		},
  
  		readonly: function readonly (enable) {
  			if (enable) {
  				this.$element.attr('data-readonly', 'readonly');
  			} else {
  				this.$element.removeAttr('data-readonly');
  			}
  
  			if (this.options.truncate) {
  				this.truncate(enable);
  			}
  		},
  
  		suggestionClick: function suggestionClick (e) {
  			var $item = $(e.currentTarget);
  			var item = {
  				text: $item.html(),
  				value: $item.data('value')
  			};
  
  			e.preventDefault();
  			this.$addItem.val('');
  
  			if ($item.data('attr')) {
  				item.attr = JSON.parse($item.data('attr'));
  			}
  
  			item.data = $item.data('data');
  
  			this.addItems(item, true);
  
  			// needs to be after addItems for IE
  			this._closeSuggestions();
  		},
  
  		itemCount: function itemCount () {
  			return this.$pillGroup.children('.pill').length;
  		},
  
  		// First parameter is 1 based index (optional, if index is not passed all new items will be appended)
  		// Second parameter can be array of objects [{ ... }, { ... }] or you can pass n additional objects as args
  		// object structure is as follows (attr and value are optional): { text: '', value: '', attr: {}, data: {} }
  		addItems: function addItems () {
  			var self = this;
  			var items;
  			var index;
  			var isInternal;
  
  			if (isFinite(String(arguments[0])) && !(arguments[0] instanceof Array)) {
  				items = [].slice.call(arguments).slice(1);
  				index = arguments[0];
  			} else {
  				items = [].slice.call(arguments).slice(0);
  				isInternal = items[1] && !items[1].text;
  			}
  
  			// If first argument is an array, use that, otherwise they probably passed each thing through as a separate arg, so use items as-is
  			if (items[0] instanceof Array) {
  				items = items[0];
  			}
  
  			if (items.length) {
  				$.each(items, function normalizeItemsObject (i, value) {
  					var data = {
  						text: value.text,
  						value: (value.value ? value.value : value.text),
  						el: self.$pillHTML
  					};
  
  					if (value.attr) {
  						data.attr = value.attr;
  					}
  
  					if (value.data) {
  						data.data = value.data;
  					}
  
  					items[i] = data;
  				});
  
  				if (this.options.edit && this.currentEdit) {
  					items[0].el = this.currentEdit.wrap('<div></div>').parent().html();
  				}
  
  				if (isInternal) {
  					items.pop(1);
  				}
  
  				if (self.options.onAdd && isInternal) {
  					if (this.options.edit && this.currentEdit) {
  						self.options.onAdd(items[0], $.proxy(self.saveEdit, this));
  					} else {
  						self.options.onAdd(items[0], $.proxy(self.placeItems, this));
  					}
  				} else if (this.options.edit && this.currentEdit) {
  					self.saveEdit(items);
  				} else if (index) {
  					self.placeItems(index, items);
  				} else {
  					self.placeItems(items, isInternal);
  				}
  			}
  		},
  
  		// First parameter is the index (1 based) to start removing items
  		// Second parameter is the number of items to be removed
  		removeItems: function removeItems (index, howMany) {
  			var self = this;
  
  			if (!index) {
  				this.$pillGroup.find('.pill').remove();
  				this._removePillTrigger({
  					method: 'removeAll'
  				});
  			} else {
  				var itemsToRemove = howMany ? howMany : 1;
  
  				for (var item = 0; item < itemsToRemove; item++) {
  					var $currentItem = self.$pillGroup.find('> .pill:nth-child(' + index + ')');
  
  					if ($currentItem) {
  						$currentItem.remove();
  					} else {
  						break;
  					}
  				}
  			}
  		},
  
  		// First parameter is index (optional)
  		// Second parameter is new arguments
  		placeItems: function placeItems () {
  			var items;
  			var index;
  			var $neighbor;
  			var isInternal;
  
  			if (isFinite(String(arguments[0])) && !(arguments[0] instanceof Array)) {
  				items = [].slice.call(arguments).slice(1);
  				index = arguments[0];
  			} else {
  				items = [].slice.call(arguments).slice(0);
  				isInternal = items[1] && !items[1].text;
  			}
  
  			if (items[0] instanceof Array) {
  				items = items[0];
  			}
  
  			if (items.length) {
  				var newItems = [];
  				$.each(items, function prepareItemForAdd (i, item) {
  					var $item = $(item.el);
  
  					$item.attr('data-value', item.value);
  					$item.find('span:first').html(item.text);
  
  					// DOM attributes
  					if (item.attr) {
  						$.each(item.attr, function handleDOMAttributes (key, value) {
  							if (key === 'cssClass' || key === 'class') {
  								$item.addClass(value);
  							} else {
  								$item.attr(key, value);
  							}
  						});
  					}
  
  					if (item.data) {
  						$item.data('data', item.data);
  					}
  
  					newItems.push($item);
  				});
  
  				if (this.$pillGroup.children('.pill').length > 0) {
  					if (index) {
  						$neighbor = this.$pillGroup.find('.pill:nth-child(' + index + ')');
  
  						if ($neighbor.length) {
  							$neighbor.before(newItems);
  						} else {
  							this.$pillGroup.children('.pill:last').after(newItems);
  						}
  					} else {
  						this.$pillGroup.children('.pill:last').after(newItems);
  					}
  				} else {
  					this.$pillGroup.prepend(newItems);
  				}
  
  				if (isInternal) {
  					this.$element.trigger('added.fu.pillbox', {
  						text: items[0].text,
  						value: items[0].value
  					});
  				}
  			}
  		},
  
  		inputEvent: function inputEvent (e) {
  			var self = this;
  			var text = self.options.cleanInput(this.$addItem.val());
  			var isFocusOutEvent = e.type === 'focusout';
  			var blurredAfterInput = (isFocusOutEvent && text.length > 0);
  			// If we test for keycode only, it will match for `<` & `,` instead of just `,`
  			// This way users can type `<3` and `1 < 3`, etc...
  			var acceptKeyPressed = (this.acceptKeyCodes[e.keyCode] && !isShiftHeld(e));
  
  			if (acceptKeyPressed || blurredAfterInput) {
  				var attr;
  				var value;
  
  				if (this.options.onKeyDown && this._isSuggestionsOpen()) {
  					var $selection = this.$suggest.find('.pillbox-suggest-sel');
  
  					if ($selection.length) {
  						text = self.options.cleanInput($selection.html());
  						value = self.options.cleanInput($selection.data('value'));
  						attr = $selection.data('attr');
  					}
  				}
  
  				// ignore comma and make sure text that has been entered (protects against " ,". https://github.com/ExactTarget/fuelux/issues/593), unless allowEmptyPills is true.
  				if (text.replace(/[ ]*\,[ ]*/, '').match(/\S/) || (this.options.allowEmptyPills && text.length)) {
  					this._closeSuggestions();
  					this.$addItem.val('').hide();
  
  					if (attr) {
  						this.addItems({
  							text: text,
  							value: value,
  							attr: JSON.parse(attr)
  						}, true);
  					} else {
  						this.addItems({
  							text: text,
  							value: value
  						}, true);
  					}
  
  					setTimeout(function clearAddItemInput () {
  						self.$addItem.show().attr({
  							size: 10
  						}).focus();
  					}, 0);
  				}
  
  				e.preventDefault();
  				return true;
  			} else if (isBackspaceKey(e) || isDeleteKey(e)) {
  				if (!text.length) {
  					e.preventDefault();
  
  					if (this.options.edit && this.currentEdit) {
  						this.cancelEdit();
  						return true;
  					}
  
  					this._closeSuggestions();
  					var $lastItem = this.$pillGroup.children('.pill:last');
  
  					if ($lastItem.hasClass('pillbox-highlight')) {
  						this._removeElement(this.getItemData($lastItem, {
  							el: $lastItem
  						}));
  					} else {
  						$lastItem.addClass('pillbox-highlight');
  					}
  
  					return true;
  				}
  			} else if (text.length > 10) {
  				if (this.$addItem.width() < (this.$pillGroup.width() - 6)) {
  					this.$addItem.attr({
  						size: text.length + 3
  					});
  				}
  			}
  
  			this.$pillGroup.find('.pill').removeClass('pillbox-highlight');
  
  			if (this.options.onKeyDown && !isFocusOutEvent) {
  				if (
  					isTabKey(e) ||
  					isUpArrow(e) ||
  					isDownArrow(e)
  				) {
  					if (this._isSuggestionsOpen()) {
  						this._keySuggestions(e);
  					}
  
  					return true;
  				}
  
  				// only allowing most recent event callback to register
  				this.callbackId = e.timeStamp;
  				this.options.onKeyDown({
  					event: e,
  					value: text
  				}, function callOpenSuggestions (data) {
  					self._openSuggestions(e, data);
  				});
  			}
  
  			return true;
  		},
  
  		openEdit: function openEdit (el) {
  			var targetChildIndex = el.index() + 1;
  			var $addItemWrap = this.$addItemWrap.detach().hide();
  
  			this.$pillGroup.find('.pill:nth-child(' + targetChildIndex + ')').before($addItemWrap);
  			this.currentEdit = el.detach();
  
  			$addItemWrap.addClass('editing');
  			this.$addItem.val(el.find('span:first').html());
  			$addItemWrap.show();
  			this.$addItem.focus().select();
  		},
  
  		cancelEdit: function cancelEdit (e) {
  			var $addItemWrap;
  			if (!this.currentEdit) {
  				return false;
  			}
  
  			this._closeSuggestions();
  			if (e) {
  				this.$addItemWrap.before(this.currentEdit);
  			}
  
  			this.currentEdit = false;
  
  			$addItemWrap = this.$addItemWrap.detach();
  			$addItemWrap.removeClass('editing');
  			this.$addItem.val('');
  			this.$pillGroup.append($addItemWrap);
  
  			return true;
  		},
  
  		// Must match syntax of placeItem so addItem callback is called when an item is edited
  		// expecting to receive an array back from the callback containing edited items
  		saveEdit: function saveEdit () {
  			var item = arguments[0][0] ? arguments[0][0] : arguments[0];
  
  			this.currentEdit = $(item.el);
  			this.currentEdit.data('value', item.value);
  			this.currentEdit.find('span:first').html(item.text);
  
  			this.$addItemWrap.hide();
  			this.$addItemWrap.before(this.currentEdit);
  			this.currentEdit = false;
  
  			this.$addItem.val('');
  			this.$addItemWrap.removeClass('editing');
  			this.$pillGroup.append(this.$addItemWrap.detach().show());
  			this.$element.trigger('edited.fu.pillbox', {
  				value: item.value,
  				text: item.text
  			});
  		},
  
  		removeBySelector: function removeBySelector () {
  			var selectors = [].slice.call(arguments).slice(0);
  			var self = this;
  
  			$.each(selectors, function doRemove (i, sel) {
  				self.$pillGroup.find(sel).remove();
  			});
  
  			this._removePillTrigger({
  				method: 'removeBySelector',
  				removedSelectors: selectors
  			});
  		},
  
  		removeByValue: function removeByValue () {
  			var values = [].slice.call(arguments).slice(0);
  			var self = this;
  
  			$.each(values, function doRemove (i, val) {
  				self.$pillGroup.find('> .pill[data-value="' + val + '"]').remove();
  			});
  
  			this._removePillTrigger({
  				method: 'removeByValue',
  				removedValues: values
  			});
  		},
  
  		removeByText: function removeByText () {
  			var text = [].slice.call(arguments).slice(0);
  			var self = this;
  
  			$.each(text, function doRemove (i, matchingText) {
  				self.$pillGroup.find('> .pill:contains("' + matchingText + '")').remove();
  			});
  
  			this._removePillTrigger({
  				method: 'removeByText',
  				removedText: text
  			});
  		},
  
  		truncate: function truncate (enable) {
  			var self = this;
  
  			this.$element.removeClass('truncate');
  			this.$addItemWrap.removeClass('truncated');
  			this.$pillGroup.find('.pill').removeClass('truncated');
  
  			if (enable) {
  				this.$element.addClass('truncate');
  
  				var availableWidth = this.$element.width();
  				var containerFull = false;
  				var processedPills = 0;
  				var totalPills = this.$pillGroup.find('.pill').length;
  				var widthUsed = 0;
  
  				this.$pillGroup.find('.pill').each(function processPills () {
  					var pill = $(this);
  					if (!containerFull) {
  						processedPills++;
  						self.$moreCount.text(totalPills - processedPills);
  						if ((widthUsed + pill.outerWidth(true) + self.$addItemWrap.outerWidth(true)) <= availableWidth) {
  							widthUsed += pill.outerWidth(true);
  						} else {
  							self.$moreCount.text((totalPills - processedPills) + 1);
  							pill.addClass('truncated');
  							containerFull = true;
  						}
  					} else {
  						pill.addClass('truncated');
  					}
  				});
  				if (processedPills === totalPills) {
  					this.$addItemWrap.addClass('truncated');
  				}
  			}
  		},
  
  		inputFocus: function inputFocus () {
  			this.$element.find('.pillbox-add-item').focus();
  		},
  
  		getItemData: function getItemData (el, data) {
  			return $.extend({
  				text: el.find('span:first').html()
  			}, el.data(), data);
  		},
  
  		_removeElement: function _removeElement (data) {
  			data.el.remove();
  			delete data.el;
  			this.$element.trigger('removed.fu.pillbox', data);
  		},
  
  		_removePillTrigger: function _removePillTrigger (removedBy) {
  			this.$element.trigger('removed.fu.pillbox', removedBy);
  		},
  
  		_generateObject: function _generateObject (data) {
  			var obj = {};
  
  			$.each(data, function setObjectValue (index, value) {
  				obj[value] = true;
  			});
  
  			return obj;
  		},
  
  		_openSuggestions: function _openSuggestions (e, data) {
  			var $suggestionList = $('<ul>');
  
  			if (this.callbackId !== e.timeStamp) {
  				return false;
  			}
  
  			if (data.data && data.data.length) {
  				$.each(data.data, function appendSuggestions (index, value) {
  					var val = value.value ? value.value : value.text;
  
  					// markup concatentation is 10x faster, but does not allow data store
  					var $suggestion = $('<li data-value="' + val + '">' + value.text + '</li>');
  
  					if (value.attr) {
  						$suggestion.data('attr', JSON.stringify(value.attr));
  					}
  
  					if (value.data) {
  						$suggestion.data('data', value.data);
  					}
  
  					$suggestionList.append($suggestion);
  				});
  
  				// suggestion dropdown
  				this.$suggest.html('').append($suggestionList.children());
  				$(document).trigger('suggested.fu.pillbox', this.$suggest);
  			}
  
  			return true;
  		},
  
  		_closeSuggestions: function _closeSuggestions () {
  			this.$suggest.html('').parent().removeClass('open');
  		},
  
  		_isSuggestionsOpen: function _isSuggestionsOpen () {
  			return this.$suggest.parent().hasClass('open');
  		},
  
  		_keySuggestions: function _keySuggestions (e) {
  			var $first = this.$suggest.find('li.pillbox-suggest-sel');
  			var dir = isUpArrow(e);
  
  			e.preventDefault();
  
  			if (!$first.length) {
  				$first = this.$suggest.find('li:first');
  				$first.addClass('pillbox-suggest-sel');
  			} else {
  				var $next = dir ? $first.prev() : $first.next();
  
  				if (!$next.length) {
  					$next = dir ? this.$suggest.find('li:last') : this.$suggest.find('li:first');
  				}
  
  				if ($next) {
  					$next.addClass('pillbox-suggest-sel');
  					$first.removeClass('pillbox-suggest-sel');
  				}
  			}
  		}
  	};
  
  	Pillbox.prototype.getValue = Pillbox.prototype.items;
  
  	// PILLBOX PLUGIN DEFINITION
  
  	$.fn.pillbox = function pillbox (option) {
  		var args = Array.prototype.slice.call(arguments, 1);
  		var methodReturn;
  
  		var $set = this.each(function set () {
  			var $this = $(this);
  			var data = $this.data('fu.pillbox');
  			var options = typeof option === 'object' && option;
  
  			if (!data) {
  				$this.data('fu.pillbox', (data = new Pillbox(this, options)));
  			}
  
  			if (typeof option === 'string') {
  				methodReturn = data[option].apply(data, args);
  			}
  		});
  
  		return (methodReturn === undefined) ? $set : methodReturn;
  	};
  
  	$.fn.pillbox.defaults = {
  		edit: false,
  		readonly: -1, // can be true or false. -1 means it will check for data-readonly="readonly"
  		truncate: false,
  		acceptKeyCodes: [
  			ENTER_KEYCODE,
  			COMMA_KEYCODE
  		],
  		allowEmptyPills: false,
  		cleanInput: cleanInput
  
  		// example on remove
  		/* onRemove: function(data,callback){
  			console.log('onRemove');
  			callback(data);
  		} */
  
  		// example on key down
  		/* onKeyDown: function(event, data, callback ){
  			callback({data:[
  				{text: Math.random(),value:'sdfsdfsdf'},
  				{text: Math.random(),value:'sdfsdfsdf'}
  			]});
  		}
  		*/
  		// example onAdd
  		/* onAdd: function( data, callback ){
  			console.log(data, callback);
  			callback(data);
  		} */
  	};
  
  	$.fn.pillbox.Constructor = Pillbox;
  
  	$.fn.pillbox.noConflict = function noConflict () {
  		$.fn.pillbox = old;
  		return this;
  	};
  
  
  	// DATA-API
  
  	$(document).on('mousedown.fu.pillbox.data-api', '[data-initialize=pillbox]', function dataAPI (e) {
  		var $control = $(e.target).closest('.pillbox');
  		if (!$control.data('fu.pillbox')) {
  			$control.pillbox($control.data());
  		}
  	});
  
  	// Must be domReady for AMD compatibility
  	$(function DOMReady () {
  		$('[data-initialize=pillbox]').each(function init () {
  			var $this = $(this);
  			if ($this.data('fu.pillbox')) return;
  			$this.pillbox($this.data());
  		});
  	});
  
  	// -- BEGIN UMD WRAPPER AFTERWORD --
  }));
  // -- END UMD WRAPPER AFTERWORD --
  