text: /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($, undefined) {
  	/**
  	 * TextExt is the main core class which by itself doesn't provide any functionality
  	 * that is user facing, however it has the underlying mechanics to bring all the
  	 * plugins together under one roof and make them work with each other or on their
  	 * own.
  	 */
  	function TextExt() {};
  
  	/**
  	 * ItemManager is used to seamlessly convert between string that come from the user input to whatever 
  	 * the format the item data is being passed around in. It's used by all plugins that in one way or 
  	 * another operate with items, such as Tags, Filter, Autocomplete and Suggestions. Default implementation 
  	 * works with `String` type. 
  	 *
  	 * Each instance of `TextExt` creates a new instance of default implementation of `ItemManager`
  	 * unless `itemManager` option was set to another implementation.
  	 *
  	 * To satisfy requirements of managing items of type other than a `String`, different implementation
  	 * if `ItemManager` should be supplied.
  	 *
  	 * If you wish to bring your own implementation, you need to create a new class and implement all the 
  	 * methods that `ItemManager` has. After, you need to supply your pass via the `itemManager` option during
  	 * initialization like so:
  	 *
  	 *     $('#input').textext({
  	 *         itemManager : CustomItemManager
  	 *     })
  	 */
  	function ItemManager() {};
  
  	/**
  	 * TextExtPlugin is a base class for all plugins. It provides common methods which are reused
  	 * by majority of plugins.
  	 *
  	 * All plugins must register themselves by calling the `$.fn.textext.addPlugin(name, constructor)`
  	 * function while providing plugin name and constructor. The plugin name is the same name that user
  	 * will identify the plugin in the `plugins` option when initializing TextExt component and constructor
  	 * function will create a new instance of the plugin. *Without registering, the core won't
  	 * be able to see the plugin.*
  	 *
  	 * <span class="new label version">new in 1.2.0</span> You can get instance of each plugin from the core 
  	 * via associated function with the same name as the plugin. For example:
  	 *
  	 *     $('#input').textext()[0].tags()
  	 *     $('#input').textext()[0].autocomplete()
  	 *     ...
  	 */
  	function TextExtPlugin() {};
  
  	var stringify = (JSON || {}).stringify,
  		slice     = Array.prototype.slice,
  		p,
  		UNDEFINED = 'undefined',
  
  		/**
  		 * TextExt provides a way to pass in the options to configure the core as well as
  		 * each plugin that is being currently used. The jQuery exposed plugin `$().textext()` 
  		 * function takes a hash object with key/value set of options. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         enabled: true
  		 *     })
  		 *
  		 * There are multiple ways of passing in the options:
  		 *
  		 * 1. Options could be nested multiple levels deep and accessed using all lowercased, dot
  		 * separated style, eg `foo.bar.world`. The manual is using this style for clarity and
  		 * consistency. For example:
  		 *
  		 *        {
  		 *            item: {
  		 *                manager: ...
  		 *            },
  		 *
  		 *            html: {
  		 *                wrap: ...
  		 *            },
  		 *
  		 *            autocomplete: {
  		 *                enabled: ...,
  		 *                dropdown: {
  		 *                   position: ...
  		 *                }
  		 *            }
  		 *        }
  		 *
  		 * 2. Options could be specified using camel cased names in a flat key/value fashion like so:
  		 *
  		 *        {
  		 *            itemManager: ...,
  		 *            htmlWrap: ...,
  		 *            autocompleteEnabled: ...,
  		 *            autocompleteDropdownPosition: ...
  		 *        }
  		 *
  		 * 3. Finally, options could be specified in mixed style. It's important to understand that
  		 * for each dot separated name, its alternative in camel case is also checked for, eg for 
  		 * `foo.bar.world` it's alternatives could be `fooBarWorld`, `foo.barWorld` or `fooBar.world`, 
  		 * which translates to `{ foo: { bar: { world: ... } } }`, `{ fooBarWorld: ... }`, 
  		 * `{ foo : { barWorld : ... } }` or `{ fooBar: { world: ... } }` respectively. For example:
  		 *
  		 *        {
  		 *            itemManager : ...,
  		 *            htmlWrap: ...,
  		 *            autocomplete: {
  		 *                enabled: ...,
  		 *                dropdownPosition: ...
  		 *            }
  		 *        }
  		 *
  		 * Mixed case is used through out the code, wherever it seems appropriate. However in the code, all option
  		 * names are specified in the dot notation because it works both ways where as camel case is not
  		 * being converted to its alternative dot notation.
  		 */
  
  		/**
  		 * Default instance of `ItemManager` which takes `String` type as default for tags.
  		 *
  		 * @name item.manager
  		 * @default ItemManager
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.options.item.manager
  		 */
  		OPT_ITEM_MANAGER = 'item.manager',
  		
  		/**
  		 * List of plugins that should be used with the current instance of TextExt. The list could be
  		 * specified as array of strings or as comma or space separated string.
  		 *
  		 * @name plugins
  		 * @default []
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.options.plugins
  		 */
  		OPT_PLUGINS = 'plugins',
  		
  		/**
  		 * TextExt allows for overriding of virtually any method that the core or any of its plugins
  		 * use. This could be accomplished through the use of the `ext` option.
  		 *
  		 * It's possible to specifically target the core or any plugin, as well as overwrite all the
  		 * desired methods everywhere.
  		 *
  		 * 1. Targeting the core:
  		 *
  		 *        ext: {
  		 *            core: {
  		 *                trigger: function()
  		 *                {
  		 *                    console.log('TextExt.trigger', arguments);
  		 *                    $.fn.textext.TextExt.prototype.trigger.apply(this, arguments);
  		 *                }
  		 *            }
  		 *        }
  		 *
  		 * 2. Targeting individual plugins:
  		 *
  		 *        ext: {
  		 *            tags: {
  		 *                addTags: function(tags)
  		 *                {
  		 *                    console.log('TextExtTags.addTags', tags);
  		 *                    $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
  		 *                }
  		 *            }
  		 *        }
  		 *
  		 * 3. Targeting `ItemManager` instance:
  		 *
  		 *        ext: {
  		 *            itemManager: {
  		 *                stringToItem: function(str)
  		 *                {
  		 *                    console.log('ItemManager.stringToItem', str);
  		 *                    return $.fn.textext.ItemManager.prototype.stringToItem.apply(this, arguments);
  		 *                }
  		 *            }
  		 *        }
  		 *
  		 * 4. And finally, in edge cases you can extend everything at once:
  		 *
  		 *        ext: {
  		 *            '*': {
  		 *                fooBar: function() {}
  		 *            }
  		 *        }
  		 *
  		 * @name ext
  		 * @default {}
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.options.ext
  		 */
  		OPT_EXT = 'ext',
  		
  		/**
  		 * HTML source that is used to generate elements necessary for the core and all other
  		 * plugins to function.
  		 *
  		 * @name html.wrap
  		 * @default '<div class="text-core"><div class="text-wrap"/></div>'
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.options.html.wrap
  		 */
  		OPT_HTML_WRAP = 'html.wrap',
  
  		/**
  		 * HTML source that is used to generate hidden input value of which will be submitted 
  		 * with the HTML form.
  		 *
  		 * @name html.hidden
  		 * @default '<input type="hidden" />'
  		 * @author agorbatchev
  		 * @date 2011/08/20
  		 * @id TextExt.options.html.hidden
  		 */
  		OPT_HTML_HIDDEN = 'html.hidden',
  		
  		/**
  		 * Hash table of key codes and key names for which special events will be created
  		 * by the core. For each entry a `[name]KeyDown`, `[name]KeyUp` and `[name]KeyPress` events 
  		 * will be triggered along side with `anyKeyUp` and `anyKeyDown` events for every 
  		 * key stroke.
  		 *
  		 * Here's a list of default keys:
  		 *
  		 *     {
  		 *         8   : 'backspace',
  		 *         9   : 'tab',
  		 *         13  : 'enter!',
  		 *         27  : 'escape!',
  		 *         37  : 'left',
  		 *         38  : 'up!',
  		 *         39  : 'right',
  		 *         40  : 'down!',
  		 *         46  : 'delete',
  		 *         108 : 'numpadEnter'
  		 *     }
  		 *
  		 * Please note the `!` at the end of some keys. This tells the core that by default
  		 * this keypress will be trapped and not passed on to the text input.
  		 *
  		 * @name keys
  		 * @default { ... }
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.options.keys
  		 */
  		OPT_KEYS = 'keys',
  
  		/**
  		 * The core triggers or reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExt.events
  		 */
  
  		/**
  		 * Core triggers `preInvalidate` event before the dimensions of padding on the text input
  		 * are set.
  		 *
  		 * @name preInvalidate
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.preInvalidate
  		 */
  		EVENT_PRE_INVALIDATE = 'preInvalidate',
  
  		/**
  		 * Core triggers `postInvalidate` event after the dimensions of padding on the text input
  		 * are set.
  		 *
  		 * @name postInvalidate
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.postInvalidate
  		 */
  		EVENT_POST_INVALIDATE = 'postInvalidate',
  		
  		/**
  		 * Core triggers `getFormData` on every key press to collect data that will be populated
  		 * into the hidden input that will be submitted with the HTML form and data that will
  		 * be displayed in the input field that user is currently interacting with.
  		 *
  		 * All plugins that wish to affect how the data is presented or sent must react to 
  		 * `getFormData` and populate the data in the following format:
  		 *
  		 *     {
  		 *         input : {String},
  		 *         form  : {Object}
  		 *     }
  		 *
  		 * The data key must be a numeric weight which will be used to determine which data
  		 * ends up being used. Data with the highest numerical weight gets the priority. This
  		 * allows plugins to set the final data regardless of their initialization order, which
  		 * otherwise would be impossible.
  		 *
  		 * For example, the Tags and Autocomplete plugins have to work side by side and Tags
  		 * plugin must get priority on setting the data. Therefore the Tags plugin sets data
  		 * with the weight 200 where as the Autocomplete plugin sets data with the weight 100.
  		 *
  		 * Here's an example of a typical `getFormData` handler:
  		 * 
  		 *     TextExtPlugin.prototype.onGetFormData = function(e, data, keyCode)
  		 *     {
  		 *         data[100] = self.formDataObject('input value', 'form value');
  		 *     };
  		 *
  		 * Core also reacts to the `getFormData` and updates hidden input with data which will be
  		 * submitted with the HTML form.
  		 *
  		 * @name getFormData
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.getFormData
  		 */
  		EVENT_GET_FORM_DATA = 'getFormData',
  
  		/**
  		 * Core triggers and reacts to the `setFormData` event to update the actual value in the
  		 * hidden input that will be submitted with the HTML form. Second argument can be value
  		 * of any type and by default it will be JSON serialized with `TextExt.serializeData()`
  		 * function.
  		 *
  		 * @name setFormData
  		 * @author agorbatchev
  		 * @date 2011/08/22
  		 * @id TextExt.events.setFormData
  		 */
  		EVENT_SET_FORM_DATA = 'setFormData',
  
  		/**
  		 * Core triggers and reacts to the `setInputData` event to update the actual value in the
  		 * text input that user is interacting with. Second argument must be of a `String` type
  		 * the value of which will be set into the text input.
  		 *
  		 * @name setInputData
  		 * @author agorbatchev
  		 * @date 2011/08/22
  		 * @id TextExt.events.setInputData
  		 */
  		EVENT_SET_INPUT_DATA = 'setInputData',
  		
  		/**
  		 * Core triggers `postInit` event to let plugins run code after all plugins have been 
  		 * created and initialized. This is a good place to set some kind of global values before 
  		 * somebody gets to use them. This is not the right place to expect all plugins to finish
  		 * their initialization.
  		 *
  		 * @name postInit
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.postInit
  		 */
  		EVENT_POST_INIT = 'postInit',
  
  		/**
  		 * Core triggers `ready` event after all global configuration and prepearation has been
  		 * done and the TextExt component is ready for use. Event handlers should expect all 
  		 * values to be set and the plugins to be in the final state.
  		 *
  		 * @name ready
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.ready
  		 */
  		EVENT_READY = 'ready',
  
  		/**
  		 * Core triggers `anyKeyUp` event for every key up event triggered within the component.
  		 *
  		 * @name anyKeyUp
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.anyKeyUp
  		 */
  
  		/**
  		 * Core triggers `anyKeyDown` event for every key down event triggered within the component.
  		 *
  		 * @name anyKeyDown
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.anyKeyDown
  		 */
  
  		/**
  		 * Core triggers `[name]KeyUp` event for every key specifid in the `keys` option that is 
  		 * triggered within the component.
  		 *
  		 * @name [name]KeyUp
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.[name]KeyUp
  		 */
  
  		/**
  		 * Core triggers `[name]KeyDown` event for every key specified in the `keys` option that is 
  		 * triggered within the component.
  		 *
  		 * @name [name]KeyDown
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.[name]KeyDown
  		 */
  
  		/**
  		 * Core triggers `[name]KeyPress` event for every key specified in the `keys` option that is 
  		 * triggered within the component.
  		 *
  		 * @name [name]KeyPress
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExt.events.[name]KeyPress
  		 */
  
  		DEFAULT_OPTS = {
  			itemManager : ItemManager,
  
  			plugins : [],
  			ext : {},
  
  			html : {
  				wrap   : '<div class="text-core"><div class="text-wrap"/></div>',
  				hidden : '<input type="hidden" />'
  			},
  
  			keys : {
  				8   : 'backspace',
  				9   : 'tab',
  				13  : 'enter!',
  				27  : 'escape!',
  				37  : 'left',
  				38  : 'up!',
  				39  : 'right',
  				40  : 'down!',
  				46  : 'delete',
  				108 : 'numpadEnter'
  			}
  		}
  		;
  
  	// Freak out if there's no JSON.stringify function found
  	if(!stringify)
  		throw new Error('JSON.stringify() not found');
  
  	/**
  	 * Returns object property by name where name is dot-separated and object is multiple levels deep.
  	 * @param target Object Source object.
  	 * @param name String Dot separated property name, ie `foo.bar.world`
  	 * @id core.getProperty
  	 */
  	function getProperty(source, name)
  	{
  		if(typeof(name) === 'string')
  			name = name.split('.');
  
  		var fullCamelCaseName = name.join('.').replace(/\.(\w)/g, function(match, letter) { return letter.toUpperCase() }),
  			nestedName        = name.shift(),
  			result
  			;
  
  		if(typeof(result = source[fullCamelCaseName]) != UNDEFINED)
  			result = result;
  
  		else if(typeof(result = source[nestedName]) != UNDEFINED && name.length > 0)
  			result = getProperty(result, name);
  
  		// name.length here should be zero
  		return result;
  	};
  
  	/**
  	 * Hooks up specified events in the scope of the current object.
  	 * @author agorbatchev
  	 * @date 2011/08/09
  	 */
  	function hookupEvents()
  	{
  		var args   = slice.apply(arguments),
  			self   = this,
  			target = args.length === 1 ? self : args.shift(),
  			event
  			;
  
  		args = args[0] || {};
  
  		function bind(event, handler)
  		{
  			target.bind(event, function()
  			{
  				// apply handler to our PLUGIN object, not the target
  				return handler.apply(self, arguments);
  			});
  		}
  
  		for(event in args)
  			bind(event, args[event]);
  	};
  
  	function formDataObject(input, form)
  	{
  		return { 'input' : input, 'form' : form };
  	};
  
  	//--------------------------------------------------------------------------------
  	// ItemManager core component
  	
  	p = ItemManager.prototype;
  
  	/**
  	 * Initialization method called by the core during instantiation.
  	 *
  	 * @signature ItemManager.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id ItemManager.init
  	 */
  	p.init = function(core)
  	{
  	};
  
  	/**
  	 * Filters out items from the list that don't match the query and returns remaining items. Default 
  	 * implementation checks if the item starts with the query.
  	 *
  	 * @signature ItemManager.filter(list, query)
  	 *
  	 * @param list {Array} List of items. Default implementation works with strings.
  	 * @param query {String} Query string.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id ItemManager.filter
  	 */
  	p.filter = function(list, query)
  	{
  		var result = [],
  			i, item
  			;
  
  		for(i = 0; i < list.length; i++)
  		{
  			item = list[i];
  			if(this.itemContains(item, query))
  				result.push(item);
  		}
  
  		return result;
  	};
  
  	/**
  	 * Returns `true` if specified item contains another string, `false` otherwise. In the default implementation 
  	 * `String.indexOf()` is used to check if item string begins with the needle string.
  	 *
  	 * @signature ItemManager.itemContains(item, needle)
  	 *
  	 * @param item {Object} Item to check. Default implementation works with strings.
  	 * @param needle {String} Search string to be found within the item.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id ItemManager.itemContains
  	 */
  	p.itemContains = function(item, needle)
  	{
  		return this.itemToString(item).toLowerCase().indexOf(needle.toLowerCase()) == 0;
  	};
  
  	/**
  	 * Converts specified string to item. Because default implemenation works with string, input string
  	 * is simply returned back. To use custom objects, different implementation of this method could
  	 * return something like `{ name : {String} }`.
  	 *
  	 * @signature ItemManager.stringToItem(str)
  	 *
  	 * @param str {String} Input string.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id ItemManager.stringToItem
  	 */
  	p.stringToItem = function(str)
  	{
  		return str;
  	};
  
  	/**
  	 * Converts specified item to string. Because default implemenation works with string, input string
  	 * is simply returned back. To use custom objects, different implementation of this method could
  	 * for example return `name` field of `{ name : {String} }`.
  	 *
  	 * @signature ItemManager.itemToString(item)
  	 *
  	 * @param item {Object} Input item to be converted to string.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id ItemManager.itemToString
  	 */
  	p.itemToString = function(item)
  	{
  		return item;
  	};
  
  	/**
  	 * Returns `true` if both items are equal, `false` otherwise. Because default implemenation works with 
  	 * string, input items are compared as strings. To use custom objects, different implementation of this 
  	 * method could for example compare `name` fields of `{ name : {String} }` type object.
  	 *
  	 * @signature ItemManager.compareItems(item1, item2)
  	 *
  	 * @param item1 {Object} First item.
  	 * @param item2 {Object} Second item.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id ItemManager.compareItems
  	 */
  	p.compareItems = function(item1, item2)
  	{
  		return item1 == item2;
  	};
  
  	//--------------------------------------------------------------------------------
  	// TextExt core component
  
  	p = TextExt.prototype;
  		
  	/**
  	 * Initializes current component instance with work with the supplied text input and options.
  	 *
  	 * @signature TextExt.init(input, opts)
  	 *
  	 * @param input {HTMLElement} Text input.
  	 * @param opts {Object} Options.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.init
  	 */
  	p.init = function(input, opts)
  	{
  		var self = this,
  			hiddenInput,
  			itemManager,
  			container
  			;
  
  		self._defaults    = $.extend({}, DEFAULT_OPTS);
  		self._opts        = opts || {};
  		self._plugins     = {};
  		self._itemManager = itemManager = new (self.opts(OPT_ITEM_MANAGER))();
  		input             = $(input);
  		container         = $(self.opts(OPT_HTML_WRAP));
  		hiddenInput       = $(self.opts(OPT_HTML_HIDDEN));
  
  		input
  			.wrap(container)
  			.keydown(function(e) { return self.onKeyDown(e) })
  			.keyup(function(e) { return self.onKeyUp(e) })
  			.data('textext', self)
  			;
  
  		// keep references to html elements using jQuery.data() to avoid circular references
  		$(self).data({
  			'hiddenInput'   : hiddenInput,
  			'wrapElement' : input.parents('.text-wrap').first(),
  			'input'         : input
  		});
  
  		// set the name of the hidden input to the text input's name
  		hiddenInput.attr('name', input.attr('name'));
  		// remove name attribute from the text input
  		input.attr('name', null);
  		// add hidden input to the DOM
  		hiddenInput.insertAfter(input);
  
  		$.extend(true, itemManager, self.opts(OPT_EXT + '.item.manager'));
  		$.extend(true, self, self.opts(OPT_EXT + '.*'), self.opts(OPT_EXT + '.core'));
  		
  		self.originalWidth = input.outerWidth();
  
  		self.invalidateBounds();
  
  		itemManager.init(self);
  
  		self.initPatches();
  		self.initPlugins(self.opts(OPT_PLUGINS), $.fn.textext.plugins);
  
  		self.on({
  			setFormData  : self.onSetFormData,
  			getFormData  : self.onGetFormData,
  			setInputData : self.onSetInputData,
  			anyKeyUp     : self.onAnyKeyUp
  		});
  
  		self.trigger(EVENT_POST_INIT);
  		self.trigger(EVENT_READY);
  
  		self.getFormData(0);
  	};
  
  	/**
  	 * Initialized all installed patches against current instance. The patches are initialized based on their
  	 * initialization priority which is returned by each patch's `initPriority()` method. Priority
  	 * is a `Number` where patches with higher value gets their `init()` method called before patches
  	 * with lower priority value.
  	 *
  	 * This facilitates initializing of patches in certain order to insure proper dependencies
  	 * regardless of which order they are loaded.
  	 *
  	 * By default all patches have the same priority - zero, which means they will be initialized
  	 * in rorder they are loaded, that is unless `initPriority()` is overriden.
  	 *
  	 * @signature TextExt.initPatches()
  	 *
  	 */
  	p.initPatches = function()
  	{
  		var list   = [],
  			source = $.fn.textext.patches,
  			name
  			;
  
  		for(name in source)
  			list.push(name);
  
  		this.initPlugins(list, source);
  	};
  
  	/**
  	 * Creates and initializes all specified plugins. The plugins are initialized based on their
  	 * initialization priority which is returned by each plugin's `initPriority()` method. Priority
  	 * is a `Number` where plugins with higher value gets their `init()` method called before plugins
  	 * with lower priority value.
  	 *
  	 * This facilitates initializing of plugins in certain order to insure proper dependencies
  	 * regardless of which order user enters them in the `plugins` option field.
  	 *
  	 * By default all plugins have the same priority - zero, which means they will be initialized
  	 * in the same order as entered by the user.
  	 *
  	 * @signature TextExt.initPlugins(plugins)
  	 *
  	 * @param plugins {Array} List of plugin names to initialize.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.initPlugins
  	 */
  	p.initPlugins = function(plugins, source)
  	{
  		var self = this,
  			ext, name, plugin, initList = [], i
  			;
  
  		if(typeof(plugins) == 'string')
  			plugins = plugins.split(/\s*,\s*|\s+/g);
  
  		for(i = 0; i < plugins.length; i++)
  		{
  			name   = plugins[i];
  			plugin = source[name];
  
  			if(plugin)
  			{
  				self._plugins[name] = plugin = new plugin();
  				self[name] = (function(plugin) { 
  				  return function(){ return plugin; } 
  				})(plugin);
  				initList.push(plugin);
  				$.extend(true, plugin, self.opts(OPT_EXT + '.*'), self.opts(OPT_EXT + '.' + name));
  			}
  		}
  
  		// sort plugins based on their priority values
  		initList.sort(function(p1, p2)
  		{
  			p1 = p1.initPriority();
  			p2 = p2.initPriority();
  
  			return p1 === p2
  				? 0
  				: p1 < p2 ? 1 : -1
  				;
  		});
  
  		for(i = 0; i < initList.length; i++)
  			initList[i].init(self);
  	};
  
  	/**
  	 * Returns true if specified plugin is was instantiated for the current instance of core.
  	 *
  	 * @signature TextExt.hasPlugin(name)
  	 *
  	 * @param name {String} Name of the plugin to check.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/28
  	 * @id TextExt.hasPlugin
  	 * @version 1.1
  	 */
  	p.hasPlugin = function(name)
  	{
  		return !!this._plugins[name];
  	};
  
  	/**
  	 * Allows to add multiple event handlers which will be execued in the scope of the current object.
  	 * 
  	 * @signature TextExt.on([target], handlers)
  	 *
  	 * @param target {Object} **Optional**. Target object which has traditional `bind(event, handler)` method.
  	 *                        Handler function will still be executed in the current object's scope.
  	 * @param handlers {Object} Key/value pairs of event names and handlers, eg `{ event: handler }`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.on
  	 */
  	p.on = hookupEvents;
  
  	/**
  	 * Binds an event handler to the input box that user interacts with.
  	 *
  	 * @signature TextExt.bind(event, handler)
  	 *
  	 * @param event {String} Event name.
  	 * @param handler {Function} Event handler.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.bind
  	 */
  	p.bind = function(event, handler)
  	{
  		this.input().bind(event, handler);
  	};
  
  	/**
  	 * Triggers an event on the input box that user interacts with. All core events are originated here.
  	 * 
  	 * @signature TextExt.trigger(event, ...args)
  	 *
  	 * @param event {String} Name of the event to trigger.
  	 * @param ...args All remaining arguments will be passed to the event handler.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.trigger
  	 */
  	p.trigger = function()
  	{
  		var args = arguments;
  		this.input().trigger(args[0], slice.call(args, 1));
  	};
  
  	/**
  	 * Returns instance of `itemManager` that is used by the component.
  	 *
  	 * @signature TextExt.itemManager()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.itemManager
  	 */
  	p.itemManager = function()
  	{
  		return this._itemManager;
  	};
  
  	/**
  	 * Returns jQuery input element with which user is interacting with.
  	 *
  	 * @signature TextExt.input()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/10
  	 * @id TextExt.input
  	 */
  	p.input = function()
  	{
  		return $(this).data('input');
  	};
  
  	/**
  	 * Returns option value for the specified option by name. If the value isn't found in the user
  	 * provided options, it will try looking for default value.
  	 *
  	 * @signature TextExt.opts(name)
  	 *
  	 * @param name {String} Option name as described in the options.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.opts
  	 */
  	p.opts = function(name)
  	{
  		var result = getProperty(this._opts, name);
  		return typeof(result) == 'undefined' ? getProperty(this._defaults, name) : result;
  	};
  
  	/**
  	 * Returns HTML element that was created from the `html.wrap` option. This is the top level HTML
  	 * container for the text input with which user is interacting with.
  	 *
  	 * @signature TextExt.wrapElement()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.wrapElement
  	 */
  	p.wrapElement = function()
  	{
  		return $(this).data('wrapElement');
  	};
  
  	/**
  	 * Updates container to match dimensions of the text input. Triggers `preInvalidate` and `postInvalidate`
  	 * events.
  	 *
  	 * @signature TextExt.invalidateBounds()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.invalidateBounds
  	 */
  	p.invalidateBounds = function()
  	{
  		var self      = this,
  			input     = self.input(),
  			wrap      = self.wrapElement(),
  			container = wrap.parent(),
  			width     = self.originalWidth + 'px',
  			height
  			;
  
  		self.trigger(EVENT_PRE_INVALIDATE);
  
  		height = input.outerHeight() + 'px';
  
  		// using css() method instead of width() and height() here because they don't seem to do the right thing in jQuery 1.8.x
  		// https://github.com/alexgorbatchev/jquery-textext/issues/74
  		input.css({ 'width' : width });
  		wrap.css({ 'width' : width, 'height' : height });
  		container.css({ 'height' : height }); 
  
  		self.trigger(EVENT_POST_INVALIDATE);
  	};
  
  	/**
  	 * Focuses user input on the text box.
  	 *
  	 * @signature TextExt.focusInput()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.focusInput
  	 */
  	p.focusInput = function()
  	{
  		this.input()[0].focus();
  	};
  
  	/**
  	 * Serializes data for to be set into the hidden input field and which will be submitted 
  	 * with the HTML form.
  	 *
  	 * By default simple JSON serialization is used. It's expected that `JSON.stringify`
  	 * method would be available either through built in class in most modern browsers
  	 * or through JSON2 library.
  	 *
  	 * @signature TextExt.serializeData(data)
  	 *
  	 * @param data {Object} Data to serialize.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/09
  	 * @id TextExt.serializeData
  	 */
  	p.serializeData = stringify;
  
  	/**
  	 * Returns the hidden input HTML element which will be submitted with the HTML form.
  	 *
  	 * @signature TextExt.hiddenInput()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/09
  	 * @id TextExt.hiddenInput
  	 */
  	p.hiddenInput = function(value)
  	{
  		return $(this).data('hiddenInput');
  	};
  
  	/**
  	 * Abstracted functionality to trigger an event and get the data with maximum weight set by all
  	 * the event handlers. This functionality is used for the `getFormData` event.
  	 *
  	 * @signature TextExt.getWeightedEventResponse(event, args)
  	 *
  	 * @param event {String} Event name.
  	 * @param args {Object} Argument to be passed with the event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExt.getWeightedEventResponse
  	 */
  	p.getWeightedEventResponse = function(event, args)
  	{
  		var self      = this,
  			data      = {},
  			maxWeight = 0
  			;
  
  		self.trigger(event, data, args);
  
  		for(var weight in data)
  			maxWeight = Math.max(maxWeight, weight);
  
  		return data[maxWeight];
  	};
  
  	/**
  	 * Triggers the `getFormData` event to get all the plugins to return their data.
  	 *
  	 * After the data is returned, triggers `setFormData` and `setInputData` to update appopriate values.
  	 *
  	 * @signature TextExt.getFormData(keyCode)
  	 *
  	 * @param keyCode {Number} Key code number which has triggered this update. It's impotant to pass
  	 * this value to the plugins because they might return different values based on the key that was 
  	 * pressed. For example, the Tags plugin returns an empty string for the `input` value if the enter
  	 * key was pressed, otherwise it returns whatever is currently in the text input.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExt.getFormData
  	 */
  	p.getFormData = function(keyCode)
  	{
  		var self = this,
  			data = self.getWeightedEventResponse(EVENT_GET_FORM_DATA, keyCode || 0)
  			;
  
  		self.trigger(EVENT_SET_FORM_DATA  , data['form']);
  		self.trigger(EVENT_SET_INPUT_DATA , data['input']);
  	};
  
  	//--------------------------------------------------------------------------------
  	// Event handlers
  
  	/**
  	 * Reacts to the `anyKeyUp` event and triggers the `getFormData` to change data that will be submitted
  	 * with the form. Default behaviour is that everything that is typed in will be JSON serialized, so
  	 * the end result will be a JSON string.
  	 *
  	 * @signature TextExt.onAnyKeyUp(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.onAnyKeyUp
  	 */
  	p.onAnyKeyUp = function(e, keyCode)
  	{
  		this.getFormData(keyCode);
  	};
  
  	/**
  	 * Reacts to the `setInputData` event and populates the input text field that user is currently
  	 * interacting with.
  	 *
  	 * @signature TextExt.onSetInputData(e, data)
  	 *
  	 * @param e {Event} jQuery event.
  	 * @param data {String} Value to be set.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExt.onSetInputData
  	 */
  	p.onSetInputData = function(e, data)
  	{
  		this.input().val(data);
  	};
  
  	/**
  	 * Reacts to the `setFormData` event and populates the hidden input with will be submitted with
  	 * the HTML form. The value will be serialized with `serializeData()` method.
  	 *
  	 * @signature TextExt.onSetFormData(e, data)
  	 *
  	 * @param e {Event} jQuery event.
  	 * @param data {Object} Data that will be set.
  	 * 
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExt.onSetFormData
  	 */
  	p.onSetFormData = function(e, data)
  	{
  		var self = this;
  		self.hiddenInput().val(self.serializeData(data));
  	};
  
  	/**
  	 * Reacts to `getFormData` event triggered by the core. At the bare minimum the core will tell
  	 * itself to use the current value in the text input as the data to be submitted with the HTML
  	 * form.
  	 *
  	 * @signature TextExt.onGetFormData(e, data)
  	 *
  	 * @param e {Event} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/09
  	 * @id TextExt.onGetFormData
  	 */
  	p.onGetFormData = function(e, data)
  	{
  		var val = this.input().val();
  		data[0] = formDataObject(val, val);
  	};
  
  	//--------------------------------------------------------------------------------
  	// User mouse/keyboard input
  
  	/**
  	 * Triggers `[name]KeyUp` and `[name]KeyPress` for every keystroke as described in the events.
  	 *
  	 * @signature TextExt.onKeyUp(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.onKeyUp
  	 */
  
  	/**
  	 * Triggers `[name]KeyDown` for every keystroke as described in the events.
  	 *
  	 * @signature TextExt.onKeyDown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.onKeyDown
  	 */
  	
  	$(['Down', 'Up']).each(function()
  	{
  		var type = this.toString();
  
  		p['onKey' + type] = function(e)
  		{
  			var self          = this,
  				keyName       = self.opts(OPT_KEYS)[e.keyCode],
  				defaultResult = true
  				;
  
  			if(keyName)
  			{
  				defaultResult = keyName.substr(-1) != '!';
  				keyName       = keyName.replace('!', '');
  
  				self.trigger(keyName + 'Key' + type);
  
  				// manual *KeyPress event fimplementation for the function keys like Enter, Backspace, etc.
  				if(type == 'Up' && self._lastKeyDown == e.keyCode)
  				{
  					self._lastKeyDown = null;
  					self.trigger(keyName + 'KeyPress');
  				}
  
  				if(type == 'Down')
  					self._lastKeyDown = e.keyCode;
  			}
  
  			self.trigger('anyKey' + type, e.keyCode);
  
  			return defaultResult;
  		};
  	});
  
  	//--------------------------------------------------------------------------------
  	// Plugin Base
  	
  	p = TextExtPlugin.prototype;
  
  	/**
  	 * Allows to add multiple event handlers which will be execued in the scope of the current object.
  	 * 
  	 * @signature TextExt.on([target], handlers)
  	 *
  	 * @param target {Object} **Optional**. Target object which has traditional `bind(event, handler)` method.
  	 *                        Handler function will still be executed in the current object's scope.
  	 * @param handlers {Object} Key/value pairs of event names and handlers, eg `{ event: handler }`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.on
  	 */
  	p.on = hookupEvents;
  
  	/**
  	 * Returns the hash object that `getFormData` triggered by the core expects.
  	 *
  	 * @signature TextExtPlugin.formDataObject(input, form)
  	 *
  	 * @param input {String} Value that will go into the text input that user is interacting with.
  	 * @param form {Object} Value that will be serialized and put into the hidden that will be submitted
  	 * with the HTML form.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtPlugin.formDataObject
  	 */
  	p.formDataObject = formDataObject;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation. This method must be implemented
  	 * by each plugin individually.
  	 *
  	 * @signature TextExtPlugin.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.init
  	 */
  	p.init = function(core) { throw new Error('Not implemented') };
  
  	/**
  	 * Initialization method wich should be called by the plugin during the `init()` call.
  	 *
  	 * @signature TextExtPlugin.baseInit(core, defaults)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 * @param defaults {Object} Default plugin options. These will be checked if desired value wasn't
  	 * found in the options supplied by the user.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.baseInit
  	 */
  	p.baseInit = function(core, defaults)
  	{
  		var self = this;
  
  		core._defaults = $.extend(true, core._defaults, defaults);
  		self._core     = core;
  		self._timers   = {};
  	};
  
  	/**
  	 * Allows starting of multiple timeout calls. Each time this method is called with the same
  	 * timer name, the timer is reset. This functionality is useful in cases where an action needs
  	 * to occur only after a certain period of inactivity. For example, making an AJAX call after 
  	 * user stoped typing for 1 second.
  	 *
  	 * @signature TextExtPlugin.startTimer(name, delay, callback)
  	 *
  	 * @param name {String} Timer name.
  	 * @param delay {Number} Delay in seconds.
  	 * @param callback {Function} Callback function.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/25
  	 * @id TextExtPlugin.startTimer
  	 */
  	p.startTimer = function(name, delay, callback)
  	{
  		var self = this;
  
  		self.stopTimer(name);
  
  		self._timers[name] = setTimeout(
  			function()
  			{
  				delete self._timers[name];
  				callback.apply(self);
  			},
  			delay * 1000
  		);
  	};
  
  	/**
  	 * Stops the timer by name without resetting it.
  	 *
  	 * @signature TextExtPlugin.stopTimer(name)
  	 *
  	 * @param name {String} Timer name.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/25
  	 * @id TextExtPlugin.stopTimer
  	 */
  	p.stopTimer = function(name)
  	{
  		clearTimeout(this._timers[name]);
  	};
  
  	/**
  	 * Returns instance of the `TextExt` to which current instance of the plugin is attached to.
  	 *
  	 * @signature TextExtPlugin.core()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.core
  	 */
  	p.core = function()
  	{
  		return this._core;
  	};
  
  	/**
  	 * Shortcut to the core's `opts()` method. Returns option value.
  	 *
  	 * @signature TextExtPlugin.opts(name)
  	 * 
  	 * @param name {String} Option name as described in the options.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.opts
  	 */
  	p.opts = function(name)
  	{
  		return this.core().opts(name);
  	};
  
  	/**
  	 * Shortcut to the core's `itemManager()` method. Returns instance of the `ItemManger` that is
  	 * currently in use.
  	 *
  	 * @signature TextExtPlugin.itemManager()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.itemManager
  	 */
  	p.itemManager = function()
  	{
  		return this.core().itemManager();
  	};
  
  	/**
  	 * Shortcut to the core's `input()` method. Returns instance of the HTML element that represents
  	 * current text input.
  	 *
  	 * @signature TextExtPlugin.input()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.input
  	 */
  	p.input = function()
  	{
  		return this.core().input();
  	};
  
  	/**
  	 * Shortcut to the commonly used `this.input().val()` call to get or set value of the text input.
  	 *
  	 * @signature TextExtPlugin.val(value)
  	 *
  	 * @param value {String} Optional value. If specified, the value will be set, otherwise it will be
  	 * returned.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/20
  	 * @id TextExtPlugin.val
  	 */
  	p.val = function(value)
  	{
  		var input = this.input();
  
  		if(typeof(value) === UNDEFINED)
  			return input.val();
  		else
  			input.val(value);
  	};
  
  	/**
  	 * Shortcut to the core's `trigger()` method. Triggers specified event with arguments on the
  	 * component core.
  	 *
  	 * @signature TextExtPlugin.trigger(event, ...args)
  	 *
  	 * @param event {String} Name of the event to trigger.
  	 * @param ...args All remaining arguments will be passed to the event handler.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtPlugin.trigger
  	 */
  	p.trigger = function()
  	{
  		var core = this.core();
  		core.trigger.apply(core, arguments);
  	};
  
  	/**
  	 * Shortcut to the core's `bind()` method. Binds specified handler to the event.
  	 *
  	 * @signature TextExtPlugin.bind(event, handler)
  	 *
  	 * @param event {String} Event name.
  	 * @param handler {Function} Event handler.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/20
  	 * @id TextExtPlugin.bind
  	 */
  	p.bind = function(event, handler)
  	{
  		this.core().bind(event, handler);
  	};
  
  	/**
  	 * Returns initialization priority for this plugin. If current plugin depends upon some other plugin
  	 * to be initialized before or after, priority needs to be adjusted accordingly. Plugins with higher
  	 * priority initialize before plugins with lower priority.
  	 *
  	 * Default initialization priority is `0`.
  	 *
  	 * @signature TextExtPlugin.initPriority()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtPlugin.initPriority
  	 */
  	p.initPriority = function()
  	{
  		return 0;
  	};
  
  	//--------------------------------------------------------------------------------
  	// jQuery Integration
  	
  	/**
  	 * TextExt integrates as a jQuery plugin available through the `$(selector).textext(opts)` call. If
  	 * `opts` argument is passed, then a new instance of `TextExt` will be created for all the inputs
  	 * that match the `selector`. If `opts` wasn't passed and TextExt was already intantiated for 
  	 * inputs that match the `selector`, array of `TextExt` instances will be returned instead.
  	 *
  	 *     // will create a new instance of `TextExt` for all elements that match `.sample`
  	 *     $('.sample').textext({ ... });
  	 *
  	 *     // will return array of all `TextExt` instances
  	 *     var list = $('.sample').textext();
  	 *
  	 * The following properties are also exposed through the jQuery `$.fn.textext`:
  	 *
  	 * * `TextExt` -- `TextExt` class.
  	 * * `TextExtPlugin` -- `TextExtPlugin` class.
  	 * * `ItemManager` -- `ItemManager` class.
  	 * * `plugins` -- Key/value table of all registered plugins.
  	 * * `addPlugin(name, constructor)` -- All plugins should register themselves using this function.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExt.jquery
  	 */
  
  	var cssInjected = false;
  
  	var textext = $.fn.textext = function(opts)
  	{
  		var css;
  		
  		if(!cssInjected && (css = $.fn.textext.css) != null)
  		{
  			$('head').append('<style>' + css + '</style>');
  			cssInjected = true;
  		}
  
  		return this.map(function()
  		{
  			var self = $(this);
  
  			if(opts == null)
  				return self.data('textext');
  
  			var instance = new TextExt();
  
  			instance.init(self, opts);
  			self.data('textext', instance);
  
  			return instance.input()[0];
  		});
  	};
  
  	/**
  	 * This static function registers a new plugin which makes it available through the `plugins` option
  	 * to the end user. The name specified here is the name the end user would put in the `plugins` option
  	 * to add this plugin to a new instance of TextExt.
  	 * 
  	 * @signature $.fn.textext.addPlugin(name, constructor)
  	 *
  	 * @param name {String} Name of the plugin.
  	 * @param constructor {Function} Plugin constructor.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/10/11
  	 * @id TextExt.addPlugin
  	 */
  	textext.addPlugin = function(name, constructor)
  	{
  		textext.plugins[name] = constructor;
  		constructor.prototype = new textext.TextExtPlugin();
  	};
  
  	/**
  	 * This static function registers a new patch which is added to each instance of TextExt. If you are
  	 * adding a new patch, make sure to call this method.
  	 * 
  	 * @signature $.fn.textext.addPatch(name, constructor)
  	 *
  	 * @param name {String} Name of the patch.
  	 * @param constructor {Function} Patch constructor.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/10/11
  	 * @id TextExt.addPatch
  	 */
  	textext.addPatch = function(name, constructor)
  	{
  		textext.patches[name] = constructor;
  		constructor.prototype = new textext.TextExtPlugin();
  	};
  
  	textext.TextExt       = TextExt;
  	textext.TextExtPlugin = TextExtPlugin;
  	textext.ItemManager   = ItemManager;
  	textext.plugins       = {};
  	textext.patches       = {};
  })(jQuery);
  
  (function($)
  {
  	function TextExtIE9Patches() {};
  
  	$.fn.textext.TextExtIE9Patches = TextExtIE9Patches;
  	$.fn.textext.addPatch('ie9',TextExtIE9Patches);
  
  	var p = TextExtIE9Patches.prototype;
  
  	p.init = function(core)
  	{
  		if(navigator.userAgent.indexOf('MSIE 9') == -1)
  			return;
  
  		var self = this;
  
  		core.on({ postInvalidate : self.onPostInvalidate });
  	};
  
  	p.onPostInvalidate = function()
  	{
  		var self  = this,
  			input = self.input(),
  			val   = input.val()
  			;
  
  		// agorbatchev :: IE9 doesn't seem to update the padding if box-sizing is on until the
  		// text box value changes, so forcing this change seems to do the trick of updating
  		// IE's padding visually.
  		input.val(Math.random());
  		input.val(val);
  	};
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($)
  {
  	/**
  	 * AJAX plugin is very useful if you want to load list of items from a data point and pass it
  	 * to the Autocomplete or Filter plugins.
  	 *
  	 * Because it meant to be as a helper method for either Autocomplete or Filter plugin, without
  	 * either of these two present AJAX plugin won't do anything.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/16
  	 * @id TextExtAjax
  	 */
  	function TextExtAjax() {};
  
  	$.fn.textext.TextExtAjax = TextExtAjax;
  	$.fn.textext.addPlugin('ajax', TextExtAjax);
  
  	var p = TextExtAjax.prototype,
  
  		/**
  		 * AJAX plugin options are grouped under `ajax` when passed to the `$().textext()` function. Be
  		 * mindful that the whole `ajax` object is also passed to jQuery `$.ajax` call which means that
  		 * you can change all jQuery options as well. Please refer to the jQuery documentation on how
  		 * to set url and all other parameters. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'ajax',
  		 *         ajax: {
  		 *             url: 'http://...'
  		 *         }
  		 *     })
  		 *
  		 * **Important**: Because it's necessary to pass options to `jQuery.ajax()` in a single object,
  		 * all jQuery related AJAX options like `url`, `dataType`, etc **must** be within the `ajax` object.
  		 * This is the exception to general rule that TextExt options can be specified in dot or camel case 
  		 * notation.
  		 * 
  		 * @author agorbatchev
  		 * @date 2011/08/16
  		 * @id TextExtAjax.options
  		 */
  
  		/**
  		 * By default, when user starts typing into the text input, AJAX plugin will start making requests
  		 * to the `url` that you have specified and will pass whatever user has typed so far as a parameter
  		 * named `q`, eg `?q=foo`.
  		 *
  		 * If you wish to change this behaviour, you can pass a function as a value for this option which
  		 * takes one argument (the user input) and should return a key/value object that will be converted
  		 * to the request parameters. For example:
  		 *
  		 *     'dataCallback' : function(query)
  		 *     {
  		 *         return { 'search' : query };
  		 *     } 
  		 *
  		 * @name ajax.data.callback
  		 * @default null
  		 * @author agorbatchev
  		 * @date 2011/08/16
  		 * @id TextExtAjax.options.data.callback
  		 */
  		OPT_DATA_CALLBACK = 'ajax.data.callback',
  		
  		/**
  		 * By default, the server end point is constantly being reloaded whenever user changes the value
  		 * in the text input. If you'd rather have the client do result filtering, you can return all
  		 * possible results from the server and cache them on the client by setting this option to `true`.
  		 *
  		 * In such a case, only one call to the server will be made and filtering will be performed on
  		 * the client side using `ItemManager` attached to the core.
  		 *
  		 * @name ajax.data.results
  		 * @default false
  		 * @author agorbatchev
  		 * @date 2011/08/16
  		 * @id TextExtAjax.options.cache.results
  		 */
  		OPT_CACHE_RESULTS = 'ajax.cache.results',
  		
  		/**
  		 * The loading message delay is set in seconds and will specify how long it would take before
  		 * user sees the message. If you don't want user to ever see this message, set the option value
  		 * to `Number.MAX_VALUE`.
  		 *
  		 * @name ajax.loading.delay
  		 * @default 0.5
  		 * @author agorbatchev
  		 * @date 2011/08/16
  		 * @id TextExtAjax.options.loading.delay
  		 */
  		OPT_LOADING_DELAY = 'ajax.loading.delay',
  
  		/**
  		 * Whenever an AJAX request is made and the server takes more than the number of seconds specified
  		 * in `ajax.loading.delay` to respond, the message specified in this option will appear in the drop
  		 * down.
  		 *
  		 * @name ajax.loading.message
  		 * @default "Loading..."
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAjax.options.loading.message
  		 */
  		OPT_LOADING_MESSAGE = 'ajax.loading.message',
  
  		/**
  		 * When user is typing in or otherwise changing the value of the text input, it's undesirable to make
  		 * an AJAX request for every keystroke. Instead it's more conservative to send a request every number
  		 * of seconds while user is typing the value. This number of seconds is specified by the `ajax.type.delay`
  		 * option.
  		 *
  		 * @name ajax.type.delay
  		 * @default 0.5
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAjax.options.type.delay
  		 */
  		OPT_TYPE_DELAY = 'ajax.type.delay',
  
  		/**
  		 * AJAX plugin dispatches or reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAjax.events
  		 */
  
  		/**
  		 * AJAX plugin reacts to the `getSuggestions` event dispatched by the Autocomplete plugin.
  		 *
  		 * @name getSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAjax.events.getSuggestions
  		 */
  
  		/**
  		 * In the event of successful AJAX request, the AJAX coponent dispatches the `setSuggestions`
  		 * event meant to be recieved by the Autocomplete plugin.
  		 *
  		 * @name setSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAjax.events.setSuggestions
  		 */
  		EVENT_SET_SUGGESTION = 'setSuggestions',
  
  		/**
  		 * AJAX plugin dispatches the `showDropdown` event which Autocomplete plugin is expecting.
  		 * This is used to temporarily show the loading message if the AJAX request is taking longer
  		 * than expected.
  		 *
  		 * @name showDropdown
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAjax.events.showDropdown
  		 */
  		EVENT_SHOW_DROPDOWN = 'showDropdown',
  
  		TIMER_LOADING = 'loading',
  
  		DEFAULT_OPTS = {
  			ajax : {
  				typeDelay      : 0.5,
  				loadingMessage : 'Loading...',
  				loadingDelay   : 0.5,
  				cacheResults   : false,
  				dataCallback   : null
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtAjax.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAjax.init
  	 */
  	p.init = function(core) {
  		var self = this;
  
  		self.baseInit(core, DEFAULT_OPTS);
  
  		self.on({
  			getSuggestions : self.onGetSuggestions
  		});
  
  		self._suggestions = null;
  	};
  
  	/**
  	 * Performas an async AJAX with specified options.
  	 *
  	 * @signature TextExtAjax.load(query)
  	 *
  	 * @param query {String} Value that user has typed into the text area which is
  	 * presumably the query.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/14
  	 * @id TextExtAjax.load
  	 */
  	p.load = function(query) {
  		var self         = this,
  			dataCallback = self.opts(OPT_DATA_CALLBACK) || function(query) { return { q : query } },
  			opts
  			;
  
  		opts = $.extend(true,
  			{
  				data    : dataCallback(query),
  				success : function(data) { self.onComplete(data, query) },
  				error   : function(jqXHR, message) { console.error(message, query) }
  			}, 
  			self.opts('ajax')
  		);
  
  		$.ajax(opts);
  	};
  
  	/**
  	 * Successful call AJAX handler. Takes the data that came back from AJAX and the
  	 * original query that was used to make the call.
  	 *
  	 * @signature TextExtAjax.onComplete(data, query)
  	 *
  	 * @param data {Object} Data loaded from the server, should be an Array of strings
  	 * by default or whatever data structure your custom `ItemManager` implements.
  	 *
  	 * @param query {String} Query string, ie whatever user has typed in.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/14
  	 * @id TextExtAjax.onComplete
  	 */
  	p.onComplete = function(data, query) {
  		var self   = this,
  			result = data
  			;
  		
  		self.dontShowLoading();
  
  		// If results are expected to be cached, then we store the original
  		// data set and return the filtered one based on the original query.
  		// That means we do filtering on the client side, instead of the
  		// server side.
  		if(self.opts(OPT_CACHE_RESULTS) == true)
  		{
  			self._suggestions = data;
  			result = self.itemManager().filter(data, query);
  		}
  
  		self.trigger(EVENT_SET_SUGGESTION, { result : result });
  	};
  
  	/**
  	 * If show loading message timer was started, calling this function disables it,
  	 * otherwise nothing else happens.
  	 *
  	 * @signature TextExtAjax.dontShowLoading()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/16
  	 * @id TextExtAjax.dontShowLoading
  	 */
  	p.dontShowLoading = function() {
  		this.stopTimer(TIMER_LOADING);
  	};
  
  	/**
  	 * Shows message specified in `ajax.loading.message` if loading data takes more than
  	 * number of seconds specified in `ajax.loading.delay`.
  	 *
  	 * @signature TextExtAjax.showLoading()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/15
  	 * @id TextExtAjax.showLoading
  	 */
  	p.showLoading = function() {
  		var self = this;
  
  		self.dontShowLoading();
  		self.startTimer(
  			TIMER_LOADING,
  			self.opts(OPT_LOADING_DELAY),
  			function()
  			{
  				self.trigger(EVENT_SHOW_DROPDOWN, function(autocomplete)
  				{
  					autocomplete.clearItems();
  					var node = autocomplete.addDropdownItem(self.opts(OPT_LOADING_MESSAGE));
  					node.addClass('text-loading');
  				});
  			}
  		);
  	};
  
  	/**
  	 * Reacts to the `getSuggestions` event and begin loading suggestions. If
  	 * `ajax.cache.results` is specified, all calls after the first one will use
  	 * cached data and filter it with the `core.itemManager.filter()`.
  	 *
  	 * @signature TextExtAjax.onGetSuggestions(e, data)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Data structure passed with the `getSuggestions` event
  	 * which contains the user query, eg `{ query : "..." }`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/15
  	 * @id TextExtAjax.onGetSuggestions
  	 */
  	p.onGetSuggestions = function(e, data) {
  		var self        = this,
  			suggestions = self._suggestions,
  			query       = (data || {}).query || ''
  			;
  
  		if(suggestions && self.opts(OPT_CACHE_RESULTS) === true)
  			return self.onComplete(suggestions, query);
  		
  		self.startTimer(
  			'ajax',
  			self.opts(OPT_TYPE_DELAY),
  			function()
  			{
  				self.showLoading();
  				self.load(query);
  			}
  		);
  	};
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($)
  {
  	/**
  	 * Autocomplete plugin brings the classic autocomplete functionality to the TextExt ecosystem.
  	 * The gist of functionality is when user starts typing in, for example a term or a tag, a
  	 * dropdown would be presented with possible suggestions to complete the input quicker.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete
  	 */
  	function TextExtAutocomplete() {};
  
  	$.fn.textext.TextExtAutocomplete = TextExtAutocomplete;
  	$.fn.textext.addPlugin('autocomplete', TextExtAutocomplete);
  
  	var p = TextExtAutocomplete.prototype,
  		
  		CSS_DOT            = '.',
  		CSS_SELECTED       = 'text-selected',
  		CSS_DOT_SELECTED   = CSS_DOT + CSS_SELECTED,
  		CSS_SUGGESTION     = 'text-suggestion',
  		CSS_DOT_SUGGESTION = CSS_DOT + CSS_SUGGESTION,
  		CSS_LABEL          = 'text-label',
  		CSS_DOT_LABEL      = CSS_DOT + CSS_LABEL,
  
  		/**
  		 * Autocomplete plugin options are grouped under `autocomplete` when passed to the 
  		 * `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'autocomplete',
  		 *         autocomplete: {
  		 *             dropdownPosition: 'above'
  		 *         }
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.options
  		 */
  
  		/**
  		 * This is a toggle switch to enable or disable the Autucomplete plugin. The value is checked
  		 * each time at the top level which allows you to toggle this setting on the fly.
  		 *
  		 * @name autocomplete.enabled
  		 * @default true
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.options.autocomplete.enabled
  		 */
  		OPT_ENABLED = 'autocomplete.enabled',
  
  		/**
  		 * This option allows to specify position of the dropdown. The two possible values
  		 * are `above` and `below`.
  		 *
  		 * @name autocomplete.dropdown.position
  		 * @default "below"
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.options.autocomplete.dropdown.position
  		 */
  		OPT_POSITION = 'autocomplete.dropdown.position',
  
  		/**
  		 * This option allows to specify maximum height of the dropdown. Value is taken directly, so
  		 * if desired height is 200 pixels, value must be `200px`.
  		 *
  		 * @name autocomplete.dropdown.maxHeight
  		 * @default "100px"
  		 * @author agorbatchev
  		 * @date 2011/12/29
  		 * @id TextExtAutocomplete.options.autocomplete.dropdown.maxHeight
  		 * @version 1.1
  		 */
  		OPT_MAX_HEIGHT = 'autocomplete.dropdown.maxHeight',
  
  		/**
  		 * This option allows to override how a suggestion item is rendered. The value should be
  		 * a function, the first argument of which is suggestion to be rendered and `this` context
  		 * is the current instance of `TextExtAutocomplete`. 
  		 *
  		 * [Click here](/manual/examples/autocomplete-with-custom-render.html) to see a demo.
  		 *
  		 * For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'autocomplete',
  		 *         autocomplete: {
  		 *             render: function(suggestion)
  		 *             {
  		 *                 return '<b>' + suggestion + '</b>';
  		 *             }
  		 *         }
  		 *     })
  		 *
  		 * @name autocomplete.render
  		 * @default null
  		 * @author agorbatchev
  		 * @date 2011/12/23
  		 * @id TextExtAutocomplete.options.autocomplete.render
  		 * @version 1.1
  		 */
  		OPT_RENDER = 'autocomplete.render',
  
  		/**
  		 * HTML source that is used to generate the dropdown.
  		 *
  		 * @name html.dropdown
  		 * @default '<div class="text-dropdown"><div class="text-list"/></div>'
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.options.html.dropdown
  		 */
  		OPT_HTML_DROPDOWN = 'html.dropdown',
  
  		/**
  		 * HTML source that is used to generate each suggestion.
  		 *
  		 * @name html.suggestion
  		 * @default '<div class="text-suggestion"><span class="text-label"/></div>'
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.options.html.suggestion
  		 */
  		OPT_HTML_SUGGESTION = 'html.suggestion',
  
  		/**
  		 * Autocomplete plugin triggers or reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.events
  		 */
  	
  		/**
  		 * Autocomplete plugin triggers and reacts to the `hideDropdown` to hide the dropdown if it's 
  		 * already visible.
  		 *
  		 * @name hideDropdown
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.events.hideDropdown
  		 */
  		EVENT_HIDE_DROPDOWN = 'hideDropdown',
  
  		/**
  		 * Autocomplete plugin triggers and reacts to the `showDropdown` to show the dropdown if it's 
  		 * not already visible.
  		 *
  		 * It's possible to pass a render callback function which will be called instead of the
  		 * default `TextExtAutocomplete.renderSuggestions()`. 
  		 *
  		 * Here's how another plugin should trigger this event with the optional render callback:
  		 *
  		 *     this.trigger('showDropdown', function(autocomplete)
  		 *     {
  		 *         autocomplete.clearItems();
  		 *         var node = autocomplete.addDropdownItem('<b>Item</b>');
  		 *         node.addClass('new-look');
  		 *     });
  		 *
  		 * @name showDropdown
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.events.showDropdown
  		 */
  		EVENT_SHOW_DROPDOWN = 'showDropdown',
  
  		/**
  		 * Autocomplete plugin reacts to the `setSuggestions` event triggered by other plugins which
  		 * wish to populate the suggestion items. Suggestions should be passed as event argument in the 
  		 * following format: `{ data : [ ... ] }`. 
  		 *
  		 * Here's how another plugin should trigger this event:
  		 *
  		 *     this.trigger('setSuggestions', { data : [ "item1", "item2" ] });
  		 *
  		 * @name setSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.events.setSuggestions
  		 */
  
  		/**
  		 * Autocomplete plugin triggers the `getSuggestions` event and expects to get results by listening for
  		 * the `setSuggestions` event.
  		 *
  		 * @name getSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtAutocomplete.events.getSuggestions
  		 */
  		EVENT_GET_SUGGESTIONS = 'getSuggestions',
  
  		/**
  		 * Autocomplete plugin triggers `getFormData` event with the current suggestion so that the the core
  		 * will be updated with serialized data to be submitted with the HTML form.
  		 * 
  		 * @name getFormData
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtAutocomplete.events.getFormData
  		 */
  		EVENT_GET_FORM_DATA = 'getFormData',
  
  		/**
  		 * Autocomplete plugin reacts to `toggleDropdown` event and either shows or hides the dropdown
  		 * depending if it's currently hidden or visible.
  		 * 
  		 * @name toggleDropdown
  		 * @author agorbatchev
  		 * @date 2011/12/27
  		 * @id TextExtAutocomplete.events.toggleDropdown
  		 * @version 1.1
  		 */
  		EVENT_TOGGLE_DROPDOWN = 'toggleDropdown',
  
  		POSITION_ABOVE = 'above',
  		POSITION_BELOW = 'below',
  		
  		DATA_MOUSEDOWN_ON_AUTOCOMPLETE = 'mousedownOnAutocomplete',
  
  		DEFAULT_OPTS = {
  			autocomplete : {
  				enabled : true,
  				dropdown : {
  					position : POSITION_BELOW,
  					maxHeight : '100px'
  				}
  			},
  
  			html : {
  				dropdown   : '<div class="text-dropdown"><div class="text-list"/></div>',
  				suggestion : '<div class="text-suggestion"><span class="text-label"/></div>'
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtAutocomplete.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.init
  	 */
  	p.init = function(core) {
  		var self = this;
  
  		self.baseInit(core, DEFAULT_OPTS);
  
  		var input = self.input(),
  			container
  			;
  
  		if(self.opts(OPT_ENABLED) === true)
  		{
  			self.on({
  				blur              : self.onBlur,
  				anyKeyUp          : self.onAnyKeyUp,
  				deleteKeyUp       : self.onAnyKeyUp,
  				backspaceKeyPress : self.onBackspaceKeyPress,
  				enterKeyPress     : self.onEnterKeyPress,
  				escapeKeyPress    : self.onEscapeKeyPress,
  				setSuggestions    : self.onSetSuggestions,
  				showDropdown      : self.onShowDropdown,
  				hideDropdown      : self.onHideDropdown,
  				toggleDropdown    : self.onToggleDropdown,
  				postInvalidate    : self.positionDropdown,
  				getFormData       : self.onGetFormData,
  
  				// using keyDown for up/down keys so that repeat events are
  				// captured and user can scroll up/down by holding the keys
  				downKeyDown : self.onDownKeyDown,
  				upKeyDown   : self.onUpKeyDown
  			});
  
  			container = $(self.opts(OPT_HTML_DROPDOWN));
  			container.insertAfter(input);
  
  			self.on(container, {
  				mouseover : self.onMouseOver,
  				mousedown : self.onMouseDown,
  				click     : self.onClick
  			});
  
  			container
  				.css('maxHeight', self.opts(OPT_MAX_HEIGHT))
  				.addClass('text-position-' + self.opts(OPT_POSITION))
  				;
  
  			$(self).data('container', container);
  			
  			$(document.body).click(function(e) 
  			{
  				if (self.isDropdownVisible() && !self.withinWrapElement(e.target))
  					self.trigger(EVENT_HIDE_DROPDOWN);
  			});
  
  			self.positionDropdown();
  		}
  	};
  
  	/**
  	 * Returns top level dropdown container HTML element.
  	 * 
  	 * @signature TextExtAutocomplete.containerElement()
  	 * 
  	 * @author agorbatchev
  	 * @date 2011/08/15
  	 * @id TextExtAutocomplete.containerElement
  	 */
  	p.containerElement = function()	{
  		return $(this).data('container');
  	};
  
  	//--------------------------------------------------------------------------------
  	// User mouse/keyboard input
  	
  	/**
  	 * Reacts to the `mouseOver` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onMouseOver(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onMouseOver
  	 */
  	p.onMouseOver = function(e)	{
  		var self   = this,
  			target = $(e.target)
  			;
  
  		if(target.is(CSS_DOT_SUGGESTION))
  		{
  			self.clearSelected();
  			target.addClass(CSS_SELECTED);
  		}
  	};
  	
  	/**
  	 * Reacts to the `mouseDown` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onMouseDown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author adamayres
  	 * @date 2012/01/13
  	 * @id TextExtAutocomplete.onMouseDown
  	 */
  	p.onMouseDown = function(e)	{
  		this.containerElement().data(DATA_MOUSEDOWN_ON_AUTOCOMPLETE, true);
  	};
  	
  	/**
  	 * Reacts to the `click` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onClick(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onClick
  	 */
  	p.onClick = function(e) {
  		var self   = this,
  			target = $(e.target)
  			;
  
  		if(target.is(CSS_DOT_SUGGESTION) || target.is(CSS_DOT_LABEL))
  			self.trigger('enterKeyPress');
  		
  		if (self.core().hasPlugin('tags'))
  			self.val('');
  	};
  
  	/**
  	 * Reacts to the `blur` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onBlur(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onBlur
  	 */
  	p.onBlur = function(e) {
  		var self              = this,
  			container         = self.containerElement(),
  			isBlurByMousedown = container.data(DATA_MOUSEDOWN_ON_AUTOCOMPLETE) === true
  			;
  
  		// only trigger a close event if the blur event was 
  		// not triggered by a mousedown event on the autocomplete
  		// otherwise set focus back back on the input
  		if(self.isDropdownVisible())
  			isBlurByMousedown ? self.core().focusInput() : self.trigger(EVENT_HIDE_DROPDOWN);
  				
  		container.removeData(DATA_MOUSEDOWN_ON_AUTOCOMPLETE);
  	};
  
  	/**
  	 * Reacts to the `backspaceKeyPress` event triggered by the TextExt core. 
  	 *
  	 * @signature TextExtAutocomplete.onBackspaceKeyPress(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onBackspaceKeyPress
  	 */
  	p.onBackspaceKeyPress = function(e)	{
  		var self    = this,
  			isEmpty = self.val().length > 0
  			;
  
  		if(isEmpty || self.isDropdownVisible())
  			self.getSuggestions();
  	};
  
  	/**
  	 * Reacts to the `anyKeyUp` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onAnyKeyUp(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onAnyKeyUp
  	 */
  	p.onAnyKeyUp = function(e, keyCode) {
  		var self          = this,
  			isFunctionKey = self.opts('keys.' + keyCode) != null
  			;
  
  		if(self.val().length > 0 && !isFunctionKey)
  			self.getSuggestions();
  	};
  
  	/**
  	 * Reacts to the `downKeyDown` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onDownKeyDown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onDownKeyDown
  	 */
  	p.onDownKeyDown = function(e) {
  		var self = this;
  
  		self.isDropdownVisible()
  			? self.toggleNextSuggestion() 
  			: self.getSuggestions()
  			;
  	};
  
  	/**
  	 * Reacts to the `upKeyDown` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onUpKeyDown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onUpKeyDown
  	 */
  	p.onUpKeyDown = function(e) {
  		this.togglePreviousSuggestion();
  	};
  
  	/**
  	 * Reacts to the `enterKeyPress` event triggered by the TextExt core.
  	 *
  	 * @signature TextExtAutocomplete.onEnterKeyPress(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onEnterKeyPress
  	 */
  	p.onEnterKeyPress = function(e) {
  		var self = this;
  
  		if(self.isDropdownVisible())
  			self.selectFromDropdown();
  	};
  
  	/**
  	 * Reacts to the `escapeKeyPress` event triggered by the TextExt core. Hides the dropdown
  	 * if it's currently visible.
  	 *
  	 * @signature TextExtAutocomplete.onEscapeKeyPress(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onEscapeKeyPress
  	 */
  	p.onEscapeKeyPress = function(e) {
  		var self = this;
  
  		if(self.isDropdownVisible())
  			self.trigger(EVENT_HIDE_DROPDOWN);
  	};
  
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  	/**
  	 * Positions dropdown either below or above the input based on the `autocomplete.dropdown.position`
  	 * option specified, which could be either `above` or `below`.
  	 *
  	 * @signature TextExtAutocomplete.positionDropdown()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/15
  	 * @id TextExtAutocomplete.positionDropdown
  	 */
  	p.positionDropdown = function()	{
  		var self      = this,
  			container = self.containerElement(),
  			direction = self.opts(OPT_POSITION),
  			height    = self.core().wrapElement().outerHeight(),
  			css       = {}
  			;
  
  		css[direction === POSITION_ABOVE ? 'bottom' : 'top'] = height + 'px';
  		container.css(css);
  	};
  
  	/**
  	 * Returns list of all the suggestion HTML elements in the dropdown.
  	 *
  	 * @signature TextExtAutocomplete.suggestionElements()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.suggestionElements
  	 */
  	p.suggestionElements = function() {
  		return this.containerElement().find(CSS_DOT_SUGGESTION);
  	};
  
  
  	/**
  	 * Highlights specified suggestion as selected in the dropdown.
  	 *
  	 * @signature TextExtAutocomplete.setSelectedSuggestion(suggestion)
  	 *
  	 * @param suggestion {Object} Suggestion object. With the default `ItemManager` this
  	 * is expected to be a string, anything else with custom implementations.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.setSelectedSuggestion
  	 */
  	p.setSelectedSuggestion = function(suggestion) {
  		if(!suggestion)
  			return;
  
  		var self   = this,
  			all    = self.suggestionElements(),
  			target = all.first(),
  			item, i
  			;
  
  		self.clearSelected();
  
  		for(i = 0; i < all.length; i++)
  		{
  			item = $(all[i]);
  
  			if(self.itemManager().compareItems(item.data(CSS_SUGGESTION), suggestion))
  			{
  				target = item.addClass(CSS_SELECTED);
  				break;
  			}
  		}
  
  		target.addClass(CSS_SELECTED);
  		self.scrollSuggestionIntoView(target);
  	};
  
  	/**
  	 * Returns the first suggestion HTML element from the dropdown that is highlighted as selected.
  	 *
  	 * @signature TextExtAutocomplete.selectedSuggestionElement()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.selectedSuggestionElement
  	 */
  	p.selectedSuggestionElement = function() {
  		return this.suggestionElements().filter(CSS_DOT_SELECTED).first();
  	};
  
  	/**
  	 * Returns `true` if dropdown is currently visible, `false` otherwise.
  	 *
  	 * @signature TextExtAutocomplete.isDropdownVisible()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.isDropdownVisible
  	 */
  	p.isDropdownVisible = function() {
  		return this.containerElement().is(':visible') === true;
  	};
  
  	/**
  	 * Reacts to the `getFormData` event triggered by the core. Returns data with the
  	 * weight of 100 to be *less than the Tags plugin* data weight. The weights system is
  	 * covered in greater detail in the [`getFormData`][1] event documentation.
  	 *
  	 * [1]: /manual/textext.html#getformdata
  	 *
  	 * @signature TextExtAutocomplete.onGetFormData(e, data, keyCode)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Data object to be populated.
  	 * @param keyCode {Number} Key code that triggered the original update request.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtAutocomplete.onGetFormData
  	 */
  	p.onGetFormData = function(e, data, keyCode) {
  		var self       = this,
  			val        = self.val(),
  			inputValue = val,
  			formValue  = val
  			;
  		data[100] = self.formDataObject(inputValue, formValue);
  	};
  
  	/**
  	 * Returns initialization priority of the Autocomplete plugin which is expected to be
  	 * *greater than the Tags plugin* because of the dependencies. The value is 200.
  	 *
  	 * @signature TextExtAutocomplete.initPriority()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtAutocomplete.initPriority
  	 */
  	p.initPriority = function() {
  		return 200;
  	};
  
  	/**
  	 * Reacts to the `hideDropdown` event and hides the dropdown if it's already visible.
  	 *
  	 * @signature TextExtAutocomplete.onHideDropdown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onHideDropdown
  	 */
  	p.onHideDropdown = function(e) {
  		this.hideDropdown();
  	};
  
  	/**
  	 * Reacts to the 'toggleDropdown` event and shows or hides the dropdown depending if
  	 * it's currently hidden or visible.
  	 *
  	 * @signature TextExtAutocomplete.onToggleDropdown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/27
  	 * @id TextExtAutocomplete.onToggleDropdown
  	 * @version 1.1.0
  	 */
  	p.onToggleDropdown = function(e) {
  		var self = this;
  		self.trigger(self.containerElement().is(':visible') ? EVENT_HIDE_DROPDOWN : EVENT_SHOW_DROPDOWN);
  	};
  
  	/**
  	 * Reacts to the `showDropdown` event and shows the dropdown if it's not already visible.
  	 * It's possible to pass a render callback function which will be called instead of the
  	 * default `TextExtAutocomplete.renderSuggestions()`.
  	 *
  	 * If no suggestion were previously loaded, it will fire `getSuggestions` event and exit.
  	 *
  	 * Here's how another plugin should trigger this event with the optional render callback:
  	 *
  	 *     this.trigger('showDropdown', function(autocomplete)
  	 *     {
  	 *         autocomplete.clearItems();
  	 *         var node = autocomplete.addDropdownItem('<b>Item</b>');
  	 *         node.addClass('new-look');
  	 *     });
  	 *
  	 * @signature TextExtAutocomplete.onShowDropdown(e, renderCallback)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param renderCallback {Function} Optional callback function which would be used to 
  	 * render dropdown items. As a first argument, reference to the current instance of 
  	 * Autocomplete plugin will be supplied. It's assumed, that if this callback is provided
  	 * rendering will be handled completely manually.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onShowDropdown
  	 */
  	p.onShowDropdown = function(e, renderCallback) {
  		var self        = this,
  			current     = self.selectedSuggestionElement().data(CSS_SUGGESTION),
  			suggestions = self._suggestions
  			;
  
  		if(!suggestions)
  			return self.trigger(EVENT_GET_SUGGESTIONS);
  
  		if($.isFunction(renderCallback))
  		{
  			renderCallback(self);
  		}
  		else
  		{
  			self.renderSuggestions(self._suggestions);
  			self.toggleNextSuggestion();
  		}
  		
  		self.showDropdown(self.containerElement());
  		self.setSelectedSuggestion(current);
  	};
  
  	/**
  	 * Reacts to the `setSuggestions` event. Expects to recieve the payload as the second argument
  	 * in the following structure:
  	 *
  	 *     {
  	 *         result : [ "item1", "item2" ],
  	 *         showHideDropdown : false
  	 *     }
  	 *
  	 * Notice the optional `showHideDropdown` option. By default, ie without the `showHideDropdown` 
  	 * value the method will trigger either `showDropdown` or `hideDropdown` depending if there are
  	 * suggestions. If set to `false`, no event is triggered.
  	 *
  	 * @signature TextExtAutocomplete.onSetSuggestions(e, data)
  	 *
  	 * @param data {Object} Data payload.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.onSetSuggestions
  	 */
  	p.onSetSuggestions = function(e, data) {
  		var self        = this,
  			suggestions = self._suggestions = data.result
  			;
  
  		if(data.showHideDropdown !== false)
  			self.trigger(suggestions === null || suggestions.length === 0 ? EVENT_HIDE_DROPDOWN : EVENT_SHOW_DROPDOWN);
  	};
  
  	/**
  	 * Prepears for and triggers the `getSuggestions` event with the `{ query : {String} }` as second
  	 * argument.
  	 *
  	 * @signature TextExtAutocomplete.getSuggestions()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.getSuggestions
  	 */
  	p.getSuggestions = function() {
  		var self = this,
  			val  = self.val()
  			;
  
  		if(self._previousInputValue == val)
  			return;
  
  		// if user clears input, then we want to select first suggestion
  		// instead of the last one
  		if(val == '')
  			current = null;
  
  		self._previousInputValue = val;
  		self.trigger(EVENT_GET_SUGGESTIONS, { query : val });
  	};
  
  	/**
  	 * Removes all HTML suggestion items from the dropdown.
  	 *
  	 * @signature TextExtAutocomplete.clearItems()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.clearItems
  	 */
  	p.clearItems = function() {
  		this.containerElement().find('.text-list').children().remove();
  	};
  
  	/**
  	 * Clears all and renders passed suggestions.
  	 *
  	 * @signature TextExtAutocomplete.renderSuggestions(suggestions)
  	 *
  	 * @param suggestions {Array} List of suggestions to render.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.renderSuggestions
  	 */
  	p.renderSuggestions = function(suggestions) {
  		var self = this;
  
  		self.clearItems();
  
  		$.each(suggestions || [], function(index, item)
  		{
  			self.addSuggestion(item);
  		});
  	};
  
  	/**
  	 * Shows the dropdown.
  	 *
  	 * @signature TextExtAutocomplete.showDropdown()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.showDropdown
  	 */
  	p.showDropdown = function() {
  		this.containerElement().show();
  	};
  
  	/**
  	 * Hides the dropdown.
  	 *
  	 * @signature TextExtAutocomplete.hideDropdown()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.hideDropdown
  	 */
  	p.hideDropdown = function() {
  		var self     = this,
  			dropdown = self.containerElement()
  			;
  
  		self._previousInputValue = null;
  		dropdown.hide();
  	};
  
  	/**
  	 * Adds single suggestion to the bottom of the dropdown. Uses `ItemManager.itemToString()` to
  	 * serialize provided suggestion to string.
  	 *
  	 * @signature TextExtAutocomplete.addSuggestion(suggestion)
  	 *
  	 * @param suggestion {Object} Suggestion item. By default expected to be a string.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.addSuggestion
  	 */
  	p.addSuggestion = function(suggestion) {
  		var self     = this,
  			renderer = self.opts(OPT_RENDER),
  			node     = self.addDropdownItem(renderer ? renderer.call(self, suggestion) : self.itemManager().itemToString(suggestion))
  			;
  
  		node.data(CSS_SUGGESTION, suggestion);
  	};
  
  	/**
  	 * Adds and returns HTML node to the bottom of the dropdown.
  	 *
  	 * @signature TextExtAutocomplete.addDropdownItem(html)
  	 *
  	 * @param html {String} HTML to be inserted into the item.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.addDropdownItem
  	 */
  	p.addDropdownItem = function(html) {
  		var self      = this,
  			container = self.containerElement().find('.text-list'),
  			node      = $(self.opts(OPT_HTML_SUGGESTION))
  			;
  
  		node.find('.text-label').html(html);
  		container.append(node);
  		return node;
  	};
  
  	/**
  	 * Removes selection highlight from all suggestion elements.
  	 *
  	 * @signature TextExtAutocomplete.clearSelected()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/02
  	 * @id TextExtAutocomplete.clearSelected
  	 */
  	p.clearSelected = function() {
  		this.suggestionElements().removeClass(CSS_SELECTED);
  	};
  
  	/**
  	 * Selects next suggestion relative to the current one. If there's no
  	 * currently selected suggestion, it will select the first one. Selected
  	 * suggestion will always be scrolled into view.
  	 *
  	 * @signature TextExtAutocomplete.toggleNextSuggestion()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/02
  	 * @id TextExtAutocomplete.toggleNextSuggestion
  	 */
  	p.toggleNextSuggestion = function() {
  		var self     = this,
  			selected = self.selectedSuggestionElement(),
  			next
  			;
  
  		if(selected.length > 0)
  		{
  			next = selected.next();
  
  			if(next.length > 0)
  				selected.removeClass(CSS_SELECTED);
  		}
  		else
  		{
  			next = self.suggestionElements().first();
  		}
  
  		next.addClass(CSS_SELECTED);
  		self.scrollSuggestionIntoView(next);
  	};
  
  	/**
  	 * Selects previous suggestion relative to the current one. Selected
  	 * suggestion will always be scrolled into view.
  	 *
  	 * @signature TextExtAutocomplete.togglePreviousSuggestion()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/02
  	 * @id TextExtAutocomplete.togglePreviousSuggestion
  	 */
  	p.togglePreviousSuggestion = function() {
  		var self     = this,
  			selected = self.selectedSuggestionElement(),
  			prev     = selected.prev()
  			;
  
  		if(prev.length == 0)
  			return;
  
  		self.clearSelected();
  		prev.addClass(CSS_SELECTED);
  		self.scrollSuggestionIntoView(prev);
  	};
  
  	/**
  	 * Scrolls specified HTML suggestion element into the view.
  	 *
  	 * @signature TextExtAutocomplete.scrollSuggestionIntoView(item)
  	 *
  	 * @param item {HTMLElement} jQuery HTML suggestion element which needs to
  	 * scrolled into view.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.scrollSuggestionIntoView
  	 */
  	p.scrollSuggestionIntoView = function(item) {
  		var itemHeight     = item.outerHeight(),
  			dropdown       = this.containerElement(),
  			dropdownHeight = dropdown.innerHeight(),
  			scrollPos      = dropdown.scrollTop(),
  			itemTop        = (item.position() || {}).top,
  			scrollTo       = null,
  			paddingTop     = parseInt(dropdown.css('paddingTop'))
  			;
  
  		if(itemTop == null)
  			return;
  
  		// if scrolling down and item is below the bottom fold
  		if(itemTop + itemHeight > dropdownHeight)
  			scrollTo = itemTop + scrollPos + itemHeight - dropdownHeight + paddingTop;
  
  		// if scrolling up and item is above the top fold
  		if(itemTop < 0)
  			scrollTo = itemTop + scrollPos - paddingTop;
  
  		if(scrollTo != null)
  			dropdown.scrollTop(scrollTo);
  	};
  
  	/**
  	 * Uses the value from the text input to finish autocomplete action. Currently selected
  	 * suggestion from the dropdown will be used to complete the action. Triggers `hideDropdown`
  	 * event.
  	 *
  	 * @signature TextExtAutocomplete.selectFromDropdown()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/17
  	 * @id TextExtAutocomplete.selectFromDropdown
  	 */
  	p.selectFromDropdown = function() {
  		var self       = this,
  			suggestion = self.selectedSuggestionElement().data(CSS_SUGGESTION)
  			;
  
  		if(suggestion)
  		{
  			self.val(self.itemManager().itemToString(suggestion));
  			self.core().getFormData();	
  		}
  
  		self.trigger(EVENT_HIDE_DROPDOWN);
  	};
  	
  	/**
  	 * Determines if the specified HTML element is within the TextExt core wrap HTML element.
  	 *
  	 * @signature TextExtAutocomplete.withinWrapElement(element)
  	 *
  	 * @param element {HTMLElement} element to check if contained by wrap element
  	 *
  	 * @author adamayres
  	 * @version 1.3.0
  	 * @date 2012/01/15
  	 * @id TextExtAutocomplete.withinWrapElement
  	 */
  	p.withinWrapElement = function(element) {
  		return this.core().wrapElement().find(element).size() > 0;
  	}
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($) {
  	/**
  	 * Displays a dropdown style arrow button. The `TextExtArrow` works together with the
  	 * `TextExtAutocomplete` plugin and whenever clicked tells the autocomplete plugin to
  	 * display its suggestions.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/27
  	 * @id TextExtArrow
  	 */
  	function TextExtArrow() {};
  
  	$.fn.textext.TextExtArrow = TextExtArrow;
  	$.fn.textext.addPlugin('arrow', TextExtArrow);
  
  	var p = TextExtArrow.prototype,
  		/**
  		 * Arrow plugin only has one option and that is its HTML template. It could be 
  		 * changed when passed to the `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'arrow',
  		 *         html: {
  		 *             arrow: "<span/>"
  		 *         }
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/12/27
  		 * @id TextExtArrow.options
  		 */
  		
  		/**
  		 * HTML source that is used to generate markup required for the arrow.
  		 *
  		 * @name html.arrow
  		 * @default '<div class="text-arrow"/>'
  		 * @author agorbatchev
  		 * @date 2011/12/27
  		 * @id TextExtArrow.options.html.arrow
  		 */
  		OPT_HTML_ARROW = 'html.arrow',
  
  		DEFAULT_OPTS = {
  			html : {
  				arrow : '<div class="text-arrow"/>'
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtArrow.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/27
  	 * @id TextExtArrow.init
  	 */
  	p.init = function(core) {
  		var self = this,
  			arrow
  			;
  
  		self.baseInit(core, DEFAULT_OPTS);
  
  		self._arrow = arrow = $(self.opts(OPT_HTML_ARROW));
  		self.core().wrapElement().append(arrow);
  		arrow.bind('click', function(e) { self.onArrowClick(e); });
  	};
  
  	//--------------------------------------------------------------------------------
  	// Event handlers
  	
  	/**
  	 * Reacts to the `click` event whenever user clicks the arrow.
  	 *
  	 * @signature TextExtArrow.onArrowClick(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @author agorbatchev
  	 * @date 2011/12/27
  	 * @id TextExtArrow.onArrowClick
  	 */
  	p.onArrowClick = function(e) {
  		this.trigger('toggleDropdown');
  		this.core().focusInput();
  	};
  	
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://alexgorbatchev.com/textext
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($)
  {
  	/**
  	 * Displays a clear search button.
  	 *
  	 * @author mreinstein
  	 * @date 2012/02/19
  	 * @id TextExtClear
  	 */
  	function TextExtClear() {};
  
  	$.fn.textext.TextExtClear = TextExtClear;
  	$.fn.textext.addPlugin('clear', TextExtClear);
  
  	var p = TextExtClear.prototype,
  		/**
  		 * Clear plugin only has one option and that is its HTML template. It could be 
  		 * changed when passed to the `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'clear',
  		 *         html: {
  		 *             clear: "<span/>"
  		 *         }
  		 *     })
  		 *
  		 * @author mreinstein
  		 * @date 2012/02/19
  		 * @id TextExtClear.options
  		 */
  
  		/**
  		 * HTML source that is used to generate markup required for the clear.
  		 *
  		 * @name html.clear
  		 * @default '<div class="text-clear"/>'
  		 * @author mreinstein
  		 * @date 2012/02/19
  		 * @id TextExtClear.options.html.clear
  		 */
  		OPT_HTML_CLEAR = 'html.clear',
  
  		DEFAULT_OPTS = {
  			html : {
  				clear : '<div class="text-clear"/>'
  			}
  		};
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtClear.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/27
  	 * @id TextExtClear.init
  	 */
  	p.init = function(core) {
  		var self = this,
  			clear
  			;
  
  		self.baseInit(core, DEFAULT_OPTS);
  
  		self._clear = clear = $(self.opts(OPT_HTML_CLEAR));
  		self.core().wrapElement().append(clear);
  		clear.bind('click', function(e) { self.onClearClick(e); });
  	};
  
  	//--------------------------------------------------------------------------------
  	// Event handlers
  
  	/**
  	 * Reacts to the `click` event whenever user clicks the clear.
  	 *
  	 * @signature TextExtClear.onClearClick(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @author agorbatchev
  	 * @date 2011/12/27
  	 * @id TextExtClear.onClearClick
  	 */
  	p.onClearClick = function(e) {
  		var self = this;
  
  		// check if the tags plugin is present
  		if(typeof self.core()._plugins.tags != 'undefined')
  		{
  			// it is! remove all tags 
  			var elems = self.core()._plugins.tags.tagElements();
  			for(var i =0; i < elems.length;i++)
  			{
  				self.core()._plugins.tags.removeTag($(elems[i]));
  			}
  		}
  		// clear the text from the search area
  		self.val('');
  		self.core().getFormData();
  	};
  
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($) {
  	/**
  	 * The Filter plugin introduces ability to limit input that the text field
  	 * will accept. If the Tags plugin is used, Filter plugin will limit which
  	 * tags it's possible to add.
  	 *
  	 * The list of allowed items can be either specified through the
  	 * options, can come from the Suggestions plugin or be loaded by the Ajax 
  	 * plugin. All these plugins have one thing in common -- they 
  	 * trigger `setSuggestions` event which the Filter plugin is expecting.
  	 * 
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtFilter
  	 */
  	function TextExtFilter() {};
  
  	$.fn.textext.TextExtFilter = TextExtFilter;
  	$.fn.textext.addPlugin('filter', TextExtFilter);
  
  	var p = TextExtFilter.prototype,
  
  		/**
  		 * Filter plugin options are grouped under `filter` when passed to the 
  		 * `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'filter',
  		 *         filter: {
  		 *             items: [ "item1", "item2" ]
  		 *         }
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFilter.options
  		 */
  		
  		/**
  		 * This is a toggle switch to enable or disable the Filter plugin. The value is checked
  		 * each time at the top level which allows you to toggle this setting on the fly.
  		 *
  		 * @name filter.enabled
  		 * @default true
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFilter.options.enabled
  		 */
  		OPT_ENABLED = 'filter.enabled',
  
  		/**
  		 * Arra of items that the Filter plugin will allow the Tag plugin to add to the list of
  		 * its resut tags. Each item by default is expected to be a string which default `ItemManager`
  		 * can work with. You can change the item type by supplying custom `ItemManager`.
  		 *
  		 * @name filter.items
  		 * @default null
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFilter.options.items
  		 */
  		OPT_ITEMS = 'filter.items',
  
  		/**
  		 * Filter plugin dispatches and reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFilter.events
  		 */
  
  		/**
  		 * Filter plugin reacts to the `isTagAllowed` event triggered by the Tags plugin before
  		 * adding a new tag to the list. If the new tag is among the `items` specified in options,
  		 * then the new tag will be allowed.
  		 *
  		 * @name isTagAllowed
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFilter.events.isTagAllowed
  		 */
  
  		/**
  		 * Filter plugin reacts to the `setSuggestions` event triggered by other plugins like 
  		 * Suggestions and Ajax.
  		 *
  		 * However, event if this event is handled and items are passed with it and stored, if `items`
  		 * option was supplied, it will always take precedense.
  		 *
  		 * @name setSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFilter.events.setSuggestions
  		 */
  
  		DEFAULT_OPTS = {
  			filter : {
  				enabled : true,
  				items : null
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtFilter.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtFilter.init
  	 */
  	p.init = function(core) {
  		var self = this;
  		self.baseInit(core, DEFAULT_OPTS);
  
  		self.on({
  			getFormData    : self.onGetFormData,
  			isTagAllowed   : self.onIsTagAllowed,
  			setSuggestions : self.onSetSuggestions
  		});
  
  		self._suggestions = null;
  	};
  
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  	/**
  	 * Reacts to the [`getFormData`][1] event triggered by the core. Returns data with the
  	 * weight of 200 to be *greater than the Autocomplete plugins* data weights. 
  	 * The weights system is covered in greater detail in the [`getFormData`][1] event 
  	 * documentation.
  	 *
  	 * This method does nothing if Tags tag is also present.
  	 *
  	 * [1]: /manual/textext.html#getformdata
  	 *
  	 * @signature TextExtFilter.onGetFormData(e, data, keyCode)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Data object to be populated.
  	 * @param keyCode {Number} Key code that triggered the original update request.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/28
  	 * @id TextExtFilter.onGetFormData
  	 * @version 1.1
  	 */
  	p.onGetFormData = function(e, data, keyCode) {
  		var self       = this,
  			val        = self.val(),
  			inputValue = val,
  			formValue  = ''
  			;
  
  		if(!self.core().hasPlugin('tags'))
  		{
  			if(self.isValueAllowed(inputValue))
  				formValue = val;
  
  			data[300] = self.formDataObject(inputValue, formValue);
  		}
  	};
  
  	/**
  	 * Checks given value if it's present in `filterItems` or was loaded for the Autocomplete
  	 * or by the Suggestions plugins. `value` is compared to each item using `ItemManager.compareItems`
  	 * method which is currently attached to the core. Returns `true` if value is known or
  	 * Filter plugin is disabled.
  	 *
  	 * @signature TextExtFilter.isValueAllowed(value)
  	 *
  	 * @param value {Object} Value to check.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/12/28
  	 * @id TextExtFilter.isValueAllowed
  	 * @version 1.1
  	 */
  	p.isValueAllowed = function(value) {
  		var self        = this,
  			list        = self.opts('filterItems') || self._suggestions || [],
  			itemManager = self.itemManager(),
  			result      = !self.opts(OPT_ENABLED), // if disabled, should just return true
  			i
  			;
  
  		for(i = 0; i < list.length && !result; i++)
  			if(itemManager.compareItems(value, list[i]))
  				result = true;
  
  		return result;
  	};
  
  	/**
  	 * Handles `isTagAllowed` event dispatched by the Tags plugin. If supplied tag is not
  	 * in the `items` list, method sets `result` on the `data` argument to `false`.
  	 *
  	 * @signature TextExtFilter.onIsTagAllowed(e, data)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Payload in the following format : `{ tag : {Object}, result : {Boolean} }`.
  	 * @author agorbatchev
  	 * @date 2011/08/04
  	 * @id TextExtFilter.onIsTagAllowed
  	 */
  	p.onIsTagAllowed = function(e, data) {
  		data.result = this.isValueAllowed(data.tag);
  	};
  
  	/**
  	 * Reacts to the `setSuggestions` events and stores supplied suggestions for future use.
  	 * 
  	 * @signature TextExtFilter.onSetSuggestions(e, data)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Payload in the following format : `{ result : {Array} } }`.
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtFilter.onSetSuggestions
  	 */
  	p.onSetSuggestions = function(e, data) {
  		this._suggestions = data.result;
  	};
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($) {
  	/**
  	 * Focus plugin displays a visual effect whenever user sets focus
  	 * into the text area.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtFocus
  	 */
  	function TextExtFocus() {};
  
  	$.fn.textext.TextExtFocus = TextExtFocus;
  	$.fn.textext.addPlugin('focus', TextExtFocus);
  
  	var p = TextExtFocus.prototype,
  		/**
  		 * Focus plugin only has one option and that is its HTML template. It could be 
  		 * changed when passed to the `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'focus',
  		 *         html: {
  		 *             focus: "<span/>"
  		 *         }
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFocus.options
  		 */
  		
  		/**
  		 * HTML source that is used to generate markup required for the focus effect.
  		 *
  		 * @name html.focus
  		 * @default '<div class="text-focus"/>'
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFocus.options.html.focus
  		 */
  		OPT_HTML_FOCUS = 'html.focus',
  
  		/**
  		 * Focus plugin dispatches or reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtFocus.events
  		 */
  
  		/**
  		 * Focus plugin reacts to the `focus` event and shows the markup generated from
  		 * the `html.focus` option.
  		 *
  		 * @name focus
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFocus.events.focus
  		 */
  
  		/**
  		 * Focus plugin reacts to the `blur` event and hides the effect.
  		 *
  		 * @name blur
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtFocus.events.blur
  		 */
  
  		DEFAULT_OPTS = {
  			html : {
  				focus : '<div class="text-focus"/>'
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtFocus.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtFocus.init
  	 */
  	p.init = function(core) {
  		var self = this;
  
  		self.baseInit(core, DEFAULT_OPTS);
  		self.core().wrapElement().append(self.opts(OPT_HTML_FOCUS));
  		self.on({
  			blur  : self.onBlur,
  			focus : self.onFocus
  		});
  
  		self._timeoutId = 0;
  	};
  
  	//--------------------------------------------------------------------------------
  	// Event handlers
  	
  	/**
  	 * Reacts to the `blur` event and hides the focus effect with a slight delay which 
  	 * allows quick refocusing without effect blinking in and out.
  	 *
  	 * @signature TextExtFocus.onBlur(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtFocus.onBlur
  	 */
  	p.onBlur = function(e) {
  		var self = this;
  
  		clearTimeout(self._timeoutId);
  
  		self._timeoutId = setTimeout(function()
  		{
  			self.getFocus().hide();
  		},
  		100);
  	};
  
  	/**
  	 * Reacts to the `focus` event and shows the focus effect.
  	 *
  	 * @signature TextExtFocus.onFocus
  	 *
  	 * @param e {Object} jQuery event.
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtFocus.onFocus
  	 */
  	p.onFocus = function(e) {
  		var self = this;
  
  		clearTimeout(self._timeoutId);
  		
  		self.getFocus().show();
  	};
  	
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  	/**
  	 * Returns focus effect HTML element.
  	 *
  	 * @signature TextExtFocus.getFocus()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtFocus.getFocus
  	 */
  	p.getFocus = function() {
  		return this.core().wrapElement().find('.text-focus');
  	};
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($) {
  	/**
  	 * Prompt plugin displays a visual user propmpt in the text input area. If user focuses
  	 * on the input, the propt is hidden and only shown again when user focuses on another
  	 * element and text input doesn't have a value.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtPrompt
  	 */
  	function TextExtPrompt() {};
  
  	$.fn.textext.TextExtPrompt = TextExtPrompt;
  	$.fn.textext.addPlugin('prompt', TextExtPrompt);
  
  	var p = TextExtPrompt.prototype,
  
  		CSS_HIDE_PROMPT = 'text-hide-prompt',
  
  		/**
  		 * Prompt plugin has options to change the prompt label and its HTML template. The options
  		 * could be changed when passed to the `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'prompt',
  		 *         prompt: 'Your email address'
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtPrompt.options
  		 */
  
  		/**
  		 * Prompt message that is displayed to the user whenever there's no value in the input.
  		 *
  		 * @name prompt
  		 * @default 'Awaiting input...'
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtPrompt.options.prompt
  		 */
  		OPT_PROMPT = 'prompt',
  
  		/**
  		 * HTML source that is used to generate markup required for the prompt effect.
  		 *
  		 * @name html.prompt
  		 * @default '<div class="text-prompt"/>'
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtPrompt.options.html.prompt
  		 */
  		OPT_HTML_PROMPT = 'html.prompt',
  
  		/**
  		 * Prompt plugin dispatches or reacts to the following events.
  		 * @id TextExtPrompt.events
  		 */
  
  		/**
  		 * Prompt plugin reacts to the `focus` event and hides the markup generated from
  		 * the `html.prompt` option.
  		 *
  		 * @name focus
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtPrompt.events.focus
  		 */
  
  		/**
  		 * Prompt plugin reacts to the `blur` event and shows the prompt back if user
  		 * hasn't entered any value.
  		 *
  		 * @name blur
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtPrompt.events.blur
  		 */
  	
  		DEFAULT_OPTS = {
  			prompt : 'Awaiting input...',
  
  			html : {
  				prompt : '<div class="text-prompt"/>'
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtPrompt.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtPrompt.init
  	 */
  	p.init = function(core) {
  		var self           = this,
  			placeholderKey = 'placeholder',
  			container,
  			prompt
  			;
  
  		self.baseInit(core, DEFAULT_OPTS);
  		
  		container = $(self.opts(OPT_HTML_PROMPT));
  		$(self).data('container', container);
  
  		self.core().wrapElement().append(container);
  		self.setPrompt(self.opts(OPT_PROMPT));
  		
  		prompt = core.input().attr(placeholderKey);
  
  		if(!prompt)
  			prompt = self.opts(OPT_PROMPT);
  
  		// clear placeholder attribute if set
  		core.input().attr(placeholderKey, '');
  
  		if(prompt)
  			self.setPrompt(prompt);
  
  		if($.trim(self.val()).length > 0)
  			self.hidePrompt();
  
  		self.on({
  			blur           : self.onBlur,
  			focus          : self.onFocus,
  			postInvalidate : self.onPostInvalidate,
  			postInit       : self.onPostInit
  		});
  	};
  
  	//--------------------------------------------------------------------------------
  	// Event handlers
  	
  	/**
  	 * Reacts to the `postInit` and configures the plugin for initial display.
  	 *
  	 * @signature TextExtPrompt.onPostInit(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/24
  	 * @id TextExtPrompt.onPostInit
  	 */
  	p.onPostInit = function(e) {
  		this.invalidateBounds();
  	};
  
  	/**
  	 * Reacts to the `postInvalidate` and insures that prompt display remains correct.
  	 *
  	 * @signature TextExtPrompt.onPostInvalidate(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/24
  	 * @id TextExtPrompt.onPostInvalidate
  	 */
  	p.onPostInvalidate = function(e) {
  		this.invalidateBounds();
  	};
  
  	/**
  	 * Repositions the prompt to make sure it's always at the same place as in the text input carret.
  	 *
  	 * @signature TextExtPrompt.invalidateBounds()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/24
  	 * @id TextExtPrompt.invalidateBounds
  	 */
  	p.invalidateBounds = function() {
  		var self  = this,
  			input = self.input()
  			;
  
  		self.containerElement().css({
  			paddingLeft : input.css('paddingLeft'),
  			paddingTop  : input.css('paddingTop')
  		});
  	};
  
  	/**
  	 * Reacts to the `blur` event and shows the prompt effect with a slight delay which 
  	 * allows quick refocusing without effect blinking in and out.
  	 *
  	 * The prompt is restored if the text box has no value.
  	 *
  	 * @signature TextExtPrompt.onBlur(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtPrompt.onBlur
  	 */
  	p.onBlur = function(e) {
  		var self = this;
  
  		self.startTimer('prompt', 0.1, function()
  		{
  			self.showPrompt();
  		});
  	};
  
  	/**
  	 * Shows prompt HTML element.
  	 *
  	 * @signature TextExtPrompt.showPrompt()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtPrompt.showPrompt
  	 */
  	p.showPrompt = function() {
  		var self     = this,
  			input    = self.input()
  			;
  		
  		if($.trim(self.val()).length === 0 && !input.is(':focus'))
  			self.containerElement().removeClass(CSS_HIDE_PROMPT);
  	};
  
  	/**
  	 * Hides prompt HTML element.
  	 *
  	 * @signature TextExtPrompt.hidePrompt()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtPrompt.hidePrompt
  	 */
  	p.hidePrompt = function() {
  		this.stopTimer('prompt');
  		this.containerElement().addClass(CSS_HIDE_PROMPT);
  	};
  
  	/**
  	 * Reacts to the `focus` event and hides the prompt effect.
  	 *
  	 * @signature TextExtPrompt.onFocus
  	 *
  	 * @param e {Object} jQuery event.
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtPrompt.onFocus
  	 */
  	p.onFocus = function(e) {
  		this.hidePrompt();
  	};
  	
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  	/**
  	 * Sets the prompt display to the specified string.
  	 *
  	 * @signature TextExtPrompt.setPrompt(str)
  	 *
  	 * @oaram str {String} String that will be displayed in the prompt.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtPrompt.setPrompt
  	 */
  	p.setPrompt = function(str) {
  		this.containerElement().text(str);
  	};
  
  	/**
  	 * Returns prompt effect HTML element.
  	 *
  	 * @signature TextExtPrompt.containerElement()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtPrompt.containerElement
  	 */
  	p.containerElement = function() {
  		return $(this).data('container');
  	};
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($) {
  	/**
  	 * Suggestions plugin allows to easily specify the list of suggestion items that the
  	 * Autocomplete plugin would present to the user.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtSuggestions
  	 */
  	function TextExtSuggestions() {};
  
  	$.fn.textext.TextExtSuggestions = TextExtSuggestions;
  	$.fn.textext.addPlugin('suggestions', TextExtSuggestions);
  
  	var p = TextExtSuggestions.prototype,
  		/**
  		 * Suggestions plugin only has one option and that is to set suggestion items. It could be 
  		 * changed when passed to the `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'suggestions',
  		 *         suggestions: [ "item1", "item2" ]
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtSuggestions.options
  		 */
  
  		/**
  		 * List of items that Autocomplete plugin would display in the dropdown.
  		 *
  		 * @name suggestions
  		 * @default null
  		 * @author agorbatchev
  		 * @date 2011/08/18
  		 * @id TextExtSuggestions.options.suggestions
  		 */
  		OPT_SUGGESTIONS = 'suggestions',
  
  		/**
  		 * Suggestions plugin dispatches or reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtSuggestions.events
  		 */
  
  		/**
  		 * Suggestions plugin reacts to the `getSuggestions` event and returns `suggestions` items
  		 * from the options.
  		 *
  		 * @name getSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtSuggestions.events.getSuggestions
  		 */
  
  		/**
  		 * Suggestions plugin triggers the `setSuggestions` event to pass its own list of `Suggestions`
  		 * to the Autocomplete plugin.
  		 *
  		 * @name setSuggestions
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtSuggestions.events.setSuggestions
  		 */
  
  		/**
  		 * Suggestions plugin reacts to the `postInit` event to pass its list of `suggestions` to the
  		 * Autocomplete right away.
  		 *
  		 * @name postInit
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtSuggestions.events.postInit
  		 */
  
  		DEFAULT_OPTS = {
  			suggestions : null
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtSuggestions.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/18
  	 * @id TextExtSuggestions.init
  	 */
  	p.init = function(core) {
  		var self = this;
  
  		self.baseInit(core, DEFAULT_OPTS);
  
  		self.on({
  			getSuggestions : self.onGetSuggestions,
  			postInit       : self.onPostInit
  		});
  	};
  
  	/**
  	 * Triggers `setSuggestions` and passes supplied suggestions to the Autocomplete plugin.
  	 *
  	 * @signature TextExtSuggestions.setSuggestions(suggestions, showHideDropdown)
  	 *
  	 * @param suggestions {Array} List of suggestions. With the default `ItemManager` it should
  	 * be a list of strings.
  	 * @param showHideDropdown {Boolean} If it's undesirable to show the dropdown right after
  	 * suggestions are set, `false` should be passed for this argument.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtSuggestions.setSuggestions
  	 */
  	p.setSuggestions = function(suggestions, showHideDropdown) {
  		this.trigger('setSuggestions', { result : suggestions, showHideDropdown : showHideDropdown != false });
  	};
  
  	/**
  	 * Reacts to the `postInit` event and triggers `setSuggestions` event to set suggestions list 
  	 * right after initialization.
  	 *
  	 * @signature TextExtSuggestions.onPostInit(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtSuggestions.onPostInit
  	 */
  	p.onPostInit = function(e) {
  		var self = this;
  		self.setSuggestions(self.opts(OPT_SUGGESTIONS), false);
  	};
  
  	/**
  	 * Reacts to the `getSuggestions` event and triggers `setSuggestions` event with the list
  	 * of `suggestions` specified in the options.
  	 *
  	 * @signature TextExtSuggestions.onGetSuggestions(e, data)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Payload from the `getSuggestions` event with the user query, eg `{ query: {String} }`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtSuggestions.onGetSuggestions
  	 */
  	p.onGetSuggestions = function(e, data) {
  		var self        = this,
  			suggestions = self.opts(OPT_SUGGESTIONS)
  			;
  
  		suggestions.sort();
  		self.setSuggestions(self.itemManager().filter(suggestions, data.query));
  	};
  
  })(jQuery);
  
  /**
   * jQuery TextExt Plugin
   * http://textextjs.com
   *
   * @version 1.3.1
   * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
   * @license MIT License
   */
  (function($) {
  	/**
  	 * Tags plugin brings in the traditional tag functionality where user can assemble and
  	 * edit list of tags. Tags plugin works especially well together with Autocomplete, Filter,
  	 * Suggestions and Ajax plugins to provide full spectrum of features. It can also work on
  	 * its own and just do one thing -- tags.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags
  	 */
  	function TextExtTags() {};
  
  	$.fn.textext.TextExtTags = TextExtTags;
  	$.fn.textext.addPlugin('tags', TextExtTags);
  
  	var p = TextExtTags.prototype,
  
  		CSS_DOT             = '.',
  		CSS_TAGS_ON_TOP     = 'text-tags-on-top',
  		CSS_DOT_TAGS_ON_TOP = CSS_DOT + CSS_TAGS_ON_TOP,
  		CSS_TAG             = 'text-tag',
  		CSS_DOT_TAG         = CSS_DOT + CSS_TAG,
  		CSS_TAGS            = 'text-tags',
  		CSS_DOT_TAGS        = CSS_DOT + CSS_TAGS,
  		CSS_LABEL           = 'text-label',
  		CSS_DOT_LABEL       = CSS_DOT + CSS_LABEL,
  		CSS_REMOVE          = 'text-remove',
  		CSS_DOT_REMOVE      = CSS_DOT + CSS_REMOVE,
  
  		/**
  		 * Tags plugin options are grouped under `tags` when passed to the
  		 * `$().textext()` function. For example:
  		 *
  		 *     $('textarea').textext({
  		 *         plugins: 'tags',
  		 *         tags: {
  		 *             items: [ "tag1", "tag2" ]
  		 *         }
  		 *     })
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtTags.options
  		 */
  
  		/**
  		 * This is a toggle switch to enable or disable the Tags plugin. The value is checked
  		 * each time at the top level which allows you to toggle this setting on the fly.
  		 *
  		 * @name tags.enabled
  		 * @default true
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtTags.options.tags.enabled
  		 */
  		OPT_ENABLED = 'tags.enabled',
  
  		/**
  		 * Allows to specify tags which will be added to the input by default upon initialization.
  		 * Each item in the array must be of the type that current `ItemManager` can understand.
  		 * Default type is `String`.
  		 *
  		 * @name tags.items
  		 * @default null
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtTags.options.tags.items
  		 */
  		OPT_ITEMS = 'tags.items',
  
  		/**
  		 * HTML source that is used to generate a single tag.
  		 *
  		 * @name html.tag
  		 * @default '<div class="text-tags"/>'
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtTags.options.html.tag
  		 */
  		OPT_HTML_TAG  = 'html.tag',
  
  		/**
  		 * HTML source that is used to generate container for the tags.
  		 *
  		 * @name html.tags
  		 * @default '<div class="text-tag"><div class="text-button"><span class="text-label"/><a class="text-remove"/></div></div>'
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtTags.options.html.tags
  		 */
  		OPT_HTML_TAGS = 'html.tags',
  
  		/**
  		 * Tags plugin dispatches or reacts to the following events.
  		 *
  		 * @author agorbatchev
  		 * @date 2011/08/17
  		 * @id TextExtTags.events
  		 */
  
  		/**
  		 * Tags plugin triggers the `isTagAllowed` event before adding each tag to the tag list. Other plugins have
  		 * an opportunity to interrupt this by setting `result` of the second argument to `false`. For example:
  		 *
  		 *     $('textarea').textext({...}).bind('isTagAllowed', function(e, data)
  		 *     {
  		 *         if(data.tag === 'foo')
  		 *             data.result = false;
  		 *     })
  		 *
  		 * The second argument `data` has the following format: `{ tag : {Object}, result : {Boolean} }`. `tag`
  		 * property is in the format that the current `ItemManager` can understand.
  		 *
  		 * @name isTagAllowed
  		 * @author agorbatchev
  		 * @date 2011/08/19
  		 * @id TextExtTags.events.isTagAllowed
  		 */
  		EVENT_IS_TAG_ALLOWED = 'isTagAllowed',
  
  		/**
  		 * Tags plugin triggers the `tagClick` event when user clicks on one of the tags. This allows to process
  		 * the click and potentially change the value of the tag (for example in case of user feedback).
  		 *
  		 *     $('textarea').textext({...}).bind('tagClick', function(e, tag, value, callback)
  		 *     {
  		 *         var newValue = window.prompt('New value', value);
  
  		 *         if(newValue)
  		 *             callback(newValue, true);
  		 *     })
  		 *
  		 *  Callback argument has the following signature:
  		 *
  		 *     function(newValue, refocus)
  		 *     {
  		 *         ...
  		 *     }
  		 *
  		 * Please check out [example](/manual/examples/tags-changing.html).
  		 *
  		 * @name tagClick
  		 * @version 1.3.0
  		 * @author s.stok
  		 * @date 2011/01/23
  		 * @id TextExtTags.events.tagClick
  		 */
  		EVENT_TAG_CLICK = 'tagClick',
  
  		DEFAULT_OPTS = {
  			tags : {
  				enabled : true,
  				items   : null
  			},
  
  			html : {
  				tags : '<div class="text-tags"/>',
  				tag  : '<div class="text-tag"><div class="text-button"><span class="text-label"/><a class="text-remove"/></div></div>'
  			}
  		}
  		;
  
  	/**
  	 * Initialization method called by the core during plugin instantiation.
  	 *
  	 * @signature TextExtTags.init(core)
  	 *
  	 * @param core {TextExt} Instance of the TextExt core class.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.init
  	 */
  	p.init = function(core)	{
  		this.baseInit(core, DEFAULT_OPTS);
  		var self  = this,
  			input = self.input(),
  			container
  			;
  
  		if(self.opts(OPT_ENABLED))
  		{
  			container = $(self.opts(OPT_HTML_TAGS));
  			input.after(container);
  
  			$(self).data('container', container);
  
  			self.on({
  				enterKeyPress    : self.onEnterKeyPress,
  				backspaceKeyDown : self.onBackspaceKeyDown,
  				preInvalidate    : self.onPreInvalidate,
  				postInit         : self.onPostInit,
  				getFormData      : self.onGetFormData
  			});
  
  			self.on(container, {
  				click     : self.onClick,
  				mousemove : self.onContainerMouseMove
  			});
  
  			self.on(input, {
  				mousemove : self.onInputMouseMove
  			});
  		}
  
  		self._originalPadding = {
  			left : parseInt(input.css('paddingLeft') || 0),
  			top  : parseInt(input.css('paddingTop') || 0)
  		};
  
  		self._paddingBox = {
  			left : 0,
  			top  : 0
  		};
  
  		self.updateFormCache();
  	};
  
  	/**
  	 * Returns HTML element in which all tag HTML elements are residing.
  	 *
  	 * @signature TextExtTags.containerElement()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/15
  	 * @id TextExtTags.containerElement
  	 */
  	p.containerElement = function()	{
  		return $(this).data('container');
  	};
  
  	//--------------------------------------------------------------------------------
  	// Event handlers
  
  	/**
  	 * Reacts to the `postInit` event triggered by the core and sets default tags
  	 * if any were specified.
  	 *
  	 * @signature TextExtTags.onPostInit(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/09
  	 * @id TextExtTags.onPostInit
  	 */
  	p.onPostInit = function(e) {
  		var self = this;
  		self.addTags(self.opts(OPT_ITEMS));
  	};
  
  	/**
  	 * Reacts to the [`getFormData`][1] event triggered by the core. Returns data with the
  	 * weight of 200 to be *greater than the Autocomplete plugin* data weight. The weights
  	 * system is covered in greater detail in the [`getFormData`][1] event documentation.
  	 *
  	 * [1]: /manual/textext.html#getformdata
  	 *
  	 * @signature TextExtTags.onGetFormData(e, data, keyCode)
  	 *
  	 * @param e {Object} jQuery event.
  	 * @param data {Object} Data object to be populated.
  	 * @param keyCode {Number} Key code that triggered the original update request.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtTags.onGetFormData
  	 */
  	p.onGetFormData = function(e, data, keyCode) {
  		var self       = this,
  			inputValue = keyCode === 13 ? '' : self.val(),
  			formValue  = self._formData
  			;
  
  		data[200] = self.formDataObject(inputValue, formValue);
  	};
  
  	/**
  	 * Returns initialization priority of the Tags plugin which is expected to be
  	 * *less than the Autocomplete plugin* because of the dependencies. The value is
  	 * 100.
  	 *
  	 * @signature TextExtTags.initPriority()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/22
  	 * @id TextExtTags.initPriority
  	 */
  	p.initPriority = function()	{
  		return 100;
  	};
  
  	/**
  	 * Reacts to user moving mouse over the text area when cursor is over the text
  	 * and not over the tags. Whenever mouse cursor is over the area covered by
  	 * tags, the tags container is flipped to be on top of the text area which
  	 * makes all tags functional with the mouse.
  	 *
  	 * @signature TextExtTags.onInputMouseMove(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtTags.onInputMouseMove
  	 */
  	p.onInputMouseMove = function(e) {
  		this.toggleZIndex(e);
  	};
  
  	/**
  	 * Reacts to user moving mouse over the tags. Whenever the cursor moves out
  	 * of the tags and back into where the text input is happening visually,
  	 * the tags container is sent back under the text area which allows user
  	 * to interact with the text using mouse cursor as expected.
  	 *
  	 * @signature TextExtTags.onContainerMouseMove(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtTags.onContainerMouseMove
  	 */
  	p.onContainerMouseMove = function(e) {
  		this.toggleZIndex(e);
  	};
  
  	/**
  	 * Reacts to the `backspaceKeyDown` event. When backspace key is pressed in an empty text field,
  	 * deletes last tag from the list.
  	 *
  	 * @signature TextExtTags.onBackspaceKeyDown(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/02
  	 * @id TextExtTags.onBackspaceKeyDown
  	 */
  	p.onBackspaceKeyDown = function(e) {
  		var self    = this,
  			lastTag = self.tagElements().last()
  			;
  
  		if(self.val().length == 0)
  			self.removeTag(lastTag);
  	};
  
  	/**
  	 * Reacts to the `preInvalidate` event and updates the input box to look like the tags are
  	 * positioned inside it.
  	 *
  	 * @signature TextExtTags.onPreInvalidate(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.onPreInvalidate
  	 */
  	p.onPreInvalidate = function(e)	{
  		var self    = this,
  			lastTag = self.tagElements().last(),
  			pos     = lastTag.position()
  			;
  
  		if(lastTag.length > 0)
  			pos.left += lastTag.innerWidth();
  		else
  			pos = self._originalPadding;
  
  		self._paddingBox = pos;
  
  		self.input().css({
  			paddingLeft : pos.left,
  			paddingTop  : pos.top
  		});
  	};
  
  	/**
  	 * Reacts to the mouse `click` event.
  	 *
  	 * @signature TextExtTags.onClick(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.onClick
  	 */
  	p.onClick = function(e)	{
  		var self   = this,
  			core   = self.core(),
  			source = $(e.target),
  			focus  = 0,
  			tag
  			;
  
  		if(source.is(CSS_DOT_TAGS))
  		{
  			focus = 1;
  		}
  		else if(source.is(CSS_DOT_REMOVE))
  		{
  			self.removeTag(source.parents(CSS_DOT_TAG + ':first'));
  			focus = 1;
  		}
  		else if(source.is(CSS_DOT_LABEL))
  		{
  			tag = source.parents(CSS_DOT_TAG + ':first');
  			self.trigger(EVENT_TAG_CLICK, tag, tag.data(CSS_TAG), tagClickCallback);
  		}
  
  		function tagClickCallback(newValue, refocus)
  		{
  			tag.data(CSS_TAG, newValue);
  			tag.find(CSS_DOT_LABEL).text(self.itemManager().itemToString(newValue));
  
  			self.updateFormCache();
  			core.getFormData();
  			core.invalidateBounds();
  
  			if(refocus)
  				core.focusInput();
  		}
  
  		if(focus)
  			core.focusInput();
  	};
  
  	/**
  	 * Reacts to the `enterKeyPress` event and adds whatever is currently in the text input
  	 * as a new tag. Triggers `isTagAllowed` to check if the tag could be added first.
  	 *
  	 * @signature TextExtTags.onEnterKeyPress(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.onEnterKeyPress
  	 */
  	p.onEnterKeyPress = function(e)	{
  		var self = this,
  			val  = self.val(),
  			tag  = self.itemManager().stringToItem(val)
  			;
  
  		if(self.isTagAllowed(tag))
  		{
  			self.addTags([ tag ]);
  			// refocus the textarea just in case it lost the focus
  			self.core().focusInput();
  		}
  	};
  
  	//--------------------------------------------------------------------------------
  	// Core functionality
  
  	/**
  	 * Creates a cache object with all the tags currently added which will be returned
  	 * in the `onGetFormData` handler.
  	 *
  	 * @signature TextExtTags.updateFormCache()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/09
  	 * @id TextExtTags.updateFormCache
  	 */
  	p.updateFormCache = function()	{
  		var self   = this,
  			result = []
  			;
  
  		self.tagElements().each(function()
  		{
  			result.push($(this).data(CSS_TAG));
  		});
  
  		// cache the results to be used in the onGetFormData
  		self._formData = result;
  	};
  
  	/**
  	 * Toggles tag container to be on top of the text area or under based on where
  	 * the mouse cursor is located. When cursor is above the text input and out of
  	 * any of the tags, the tags container is sent under the text area. If cursor
  	 * is over any of the tags, the tag container is brought to be over the text
  	 * area.
  	 *
  	 * @signature TextExtTags.toggleZIndex(e)
  	 *
  	 * @param e {Object} jQuery event.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/08
  	 * @id TextExtTags.toggleZIndex
  	 */
  	p.toggleZIndex = function(e) {
  		var self            = this,
  			offset          = self.input().offset(),
  			mouseX          = e.clientX - offset.left,
  			mouseY          = e.clientY - offset.top,
  			box             = self._paddingBox,
  			container       = self.containerElement(),
  			isOnTop         = container.is(CSS_DOT_TAGS_ON_TOP),
  			isMouseOverText = mouseX > box.left && mouseY > box.top
  			;
  
  		if(!isOnTop && !isMouseOverText || isOnTop && isMouseOverText)
  			container[(!isOnTop ? 'add' : 'remove') + 'Class'](CSS_TAGS_ON_TOP);
  	};
  
  	/**
  	 * Returns all tag HTML elements.
  	 *
  	 * @signature TextExtTags.tagElements()
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.tagElements
  	 */
  	p.tagElements = function()	{
  		return this.containerElement().find(CSS_DOT_TAG);
  	};
  
  	/**
  	 * Wrapper around the `isTagAllowed` event which triggers it and returns `true`
  	 * if `result` property of the second argument remains `true`.
  	 *
  	 * @signature TextExtTags.isTagAllowed(tag)
  	 *
  	 * @param tag {Object} Tag object that the current `ItemManager` can understand.
  	 * Default is `String`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.isTagAllowed
  	 */
  	p.isTagAllowed = function(tag)	{
  		var opts = { tag : tag, result : true };
  		this.trigger(EVENT_IS_TAG_ALLOWED, opts);
  		return opts.result === true;
  	};
  
  	/**
  	 * Adds specified tags to the tag list. Triggers `isTagAllowed` event for each tag
  	 * to insure that it could be added. Calls `TextExt.getFormData()` to refresh the data.
  	 *
  	 * @signature TextExtTags.addTags(tags)
  	 *
  	 * @param tags {Array} List of tags that current `ItemManager` can understand. Default
  	 * is `String`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.addTags
  	 */
  	p.addTags = function(tags)	{
  		if(!tags || tags.length == 0)
  			return;
  
  		var self      = this,
  			core      = self.core(),
  			container = self.containerElement(),
  			i, tag
  			;
  
  		for(i = 0; i < tags.length; i++)
  		{
  			tag = tags[i];
  
  			if(tag && self.isTagAllowed(tag))
  				container.append(self.renderTag(tag));
  		}
  
  		self.updateFormCache();
  		core.getFormData();
  		core.invalidateBounds();
  	};
  
  	/**
  	 * Returns HTML element for the specified tag.
  	 *
  	 * @signature TextExtTags.getTagElement(tag)
  	 *
  	 * @param tag {Object} Tag object in the format that current `ItemManager` can understand.
  	 * Default is `String`.
  
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.getTagElement
  	 */
  	p.getTagElement = function(tag)	{
  		var self = this,
  			list = self.tagElements(),
  			i, item
  			;
  
  		for(i = 0; i < list.length; i++) {
  			item = $(list[i]);
  			if(self.itemManager().compareItems(item.data(CSS_TAG), tag))
  				return item;
  		}
  		
  		return null;
  	};
  
  	/**
  	 * Removes specified tag from the list. Calls `TextExt.getFormData()` to refresh the data.
  	 *
  	 * @signature TextExtTags.removeTag(tag)
  	 *
  	 * @param tag {Object} Tag object in the format that current `ItemManager` can understand.
  	 * Default is `String`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.removeTag
  	 */
  	p.removeTag = function(tag)	{
  		var self = this,
  			core = self.core(),
  			element
  			;
  
  		if(tag instanceof $)
  		{
  			element = tag;
  			tag = tag.data(CSS_TAG);
  		}
  		else
  		{
  			element = self.getTagElement(tag);
  			if (element === null) {
  				//Tag does not exist
  				return;
  			}
  		}
  
  		element.remove();
  		self.updateFormCache();
  		core.getFormData();
  		core.invalidateBounds();
  	};
  
  	/**
  	 * Creates and returns new HTML element from the source code specified in the `html.tag` option.
  	 *
  	 * @signature TextExtTags.renderTag(tag)
  	 *
  	 * @param tag {Object} Tag object in the format that current `ItemManager` can understand.
  	 * Default is `String`.
  	 *
  	 * @author agorbatchev
  	 * @date 2011/08/19
  	 * @id TextExtTags.renderTag
  	 */
  	p.renderTag = function(tag)	{
  		var self = this,
  			node = $(self.opts(OPT_HTML_TAG))
  			;
  
  		node.find('.text-label').text(self.itemManager().itemToString(tag));
  		node.data(CSS_TAG, tag);
  		return node;
  	};
  
  })(jQuery);
  
  
  
  