Index: /branches/2.2/admin.php
===================================================================
--- /branches/2.2/admin.php	(revision 11055)
+++ /branches/2.2/admin.php	(revision 11056)
@@ -44,17 +44,4 @@
 // | Direct actions                                                        |
 // +-----------------------------------------------------------------------+
-
-// tags
-if (isset($_GET['fckb_tags']))
-{
-  $query = '
-SELECT
-    id AS tag_id,
-    name AS tag_name
-  FROM '.TAGS_TABLE.'
-;';
-  echo json_encode(get_fckb_taglist($query));
-  exit();
-}
 
 // theme changer
Index: /branches/2.2/admin/batch_manager.php
===================================================================
--- /branches/2.2/admin/batch_manager.php	(revision 11055)
+++ /branches/2.2/admin/batch_manager.php	(revision 11056)
@@ -362,4 +362,16 @@
 
 // +-----------------------------------------------------------------------+
+// |                              tags                                     |
+// +-----------------------------------------------------------------------+
+
+$query = '
+SELECT
+    id AS tag_id,
+    name AS tag_name
+  FROM '.TAGS_TABLE.'
+;';
+$template->assign('tags', get_taglist($query));
+
+// +-----------------------------------------------------------------------+
 // |                         open specific mode                            |
 // +-----------------------------------------------------------------------+
Index: /branches/2.2/admin/batch_manager_global.php
===================================================================
--- /branches/2.2/admin/batch_manager_global.php	(revision 11055)
+++ /branches/2.2/admin/batch_manager_global.php	(revision 11056)
@@ -124,5 +124,5 @@
     else
     {
-      $tag_ids = get_fckb_tag_ids($_POST['add_tags']);
+      $tag_ids = get_tag_ids($_POST['add_tags']);
       add_tags($tag_ids, $collection);
 
Index: /branches/2.2/admin/batch_manager_unit.php
===================================================================
--- /branches/2.2/admin/batch_manager_unit.php	(revision 11055)
+++ /branches/2.2/admin/batch_manager_unit.php	(revision 11056)
@@ -109,5 +109,5 @@
     if (isset($_POST[ 'tags-'.$row['id'] ]))
     {
-      $tag_ids = get_fckb_tag_ids($_POST[ 'tags-'.$row['id'] ]);
+      $tag_ids = get_tag_ids($_POST[ 'tags-'.$row['id'] ]);
       set_tags($tag_ids, $row['id']);
     }
@@ -257,5 +257,5 @@
   WHERE image_id = '.$row['id'].'
 ;';
-    $tag_selection = get_fckb_taglist($query);
+    $tag_selection = get_taglist($query);
 
     $template->append(
Index: /branches/2.2/admin/include/functions.php
===================================================================
--- /branches/2.2/admin/include/functions.php	(revision 11055)
+++ /branches/2.2/admin/include/functions.php	(revision 11056)
@@ -2101,7 +2101,8 @@
 }
 
-function get_fckb_taglist($query)
+function get_taglist($query)
 {
   $result = pwg_query($query);
+  
   $taglist = array();
   while ($row = pwg_db_fetch_assoc($result))
@@ -2110,14 +2111,17 @@
       $taglist,
       array(
-        'key' => $row['tag_name'],
-        'value' => '~~'.$row['tag_id'].'~~',
+        'name' => $row['tag_name'],
+        'id' => '~~'.$row['tag_id'].'~~',
         )
       );
   }
-
+  
+  $cmp = create_function('$a,$b', 'return strcasecmp($a["name"], $b["name"]);');
+  usort($taglist, $cmp);
+  
   return $taglist;
 }
 
-function get_fckb_tag_ids($raw_tags)
+function get_tag_ids($raw_tags)
 {
   // In $raw_tags we receive something like array('~~6~~', '~~59~~', 'New
@@ -2127,4 +2131,5 @@
 
   $tag_ids = array();
+  $raw_tags = explode(',',$raw_tags);
 
   foreach ($raw_tags as $raw_tag)
Index: /branches/2.2/admin/picture_modify.php
===================================================================
--- /branches/2.2/admin/picture_modify.php	(revision 11055)
+++ /branches/2.2/admin/picture_modify.php	(revision 11056)
@@ -167,5 +167,5 @@
   if (isset($_POST['tags']))
   {
-    $tag_ids = get_fckb_tag_ids($_POST['tags']);
+    $tag_ids = get_tag_ids($_POST['tags']);
   }
   set_tags($tag_ids, $_GET['image_id']);
@@ -234,5 +234,13 @@
   WHERE image_id = '.$_GET['image_id'].'
 ;';
-$tags = get_fckb_taglist($query);
+$tag_selection = get_taglist($query);
+
+$query = '
+SELECT
+    id AS tag_id,
+    name AS tag_name
+  FROM '.TAGS_TABLE.'
+;';
+$tags = get_taglist($query);
 
 // retrieving direct information about picture
@@ -268,4 +276,5 @@
 $template->assign(
   array(
+    'tag_selection' => $tag_selection,
     'tags' => $tags,
     'U_SYNC' => $admin_url_start.'&amp;sync_metadata=1',
Index: /branches/2.2/admin/themes/default/template/batch_manager_global.tpl
===================================================================
--- /branches/2.2/admin/themes/default/template/batch_manager_global.tpl	(revision 11055)
+++ /branches/2.2/admin/themes/default/template/batch_manager_global.tpl	(revision 11056)
@@ -6,20 +6,22 @@
 {/literal}{/footer_script}
 
-{combine_script id='jquery.fcbkcomplete' load='footer' require='jquery' path='themes/default/js/plugins/jquery.fcbkcomplete.js'}
-
-{footer_script require='jquery.fcbkcomplete'}{literal}
-jQuery(document).ready(function() {
-  jQuery("#tags").fcbkcomplete({
-    json_url: "admin.php?fckb_tags=1",
-    cache: false,
-    filter_case: false,
-    filter_hide: true,
-    firstselected: true,
-    filter_selected: true,
-    maxitems: 100,
-    newel: true
-  });
+{combine_script id='jquery.tokeninput' load='footer' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'}
+
+{footer_script require='jquery.tokeninput'}
+jQuery(document).ready(function() {ldelim}
+  jQuery("#tags").tokenInput(
+    [{foreach from=$tags item=tag name=tags}{ldelim}"name":"{$tag.name}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}],
+    {ldelim}
+      hintText: '{'Type in a search term'|@translate}',
+      noResultsText: '{'No results'|@translate}',
+      searchingText: '{'Searching...'|@translate}',
+      newText: ' ({'new'|@translate})',
+      animateDropdown: false,
+      preventDuplicates: true,
+      allowCreation: true
+    }
+  );
 });
-{/literal}{/footer_script}
+{/footer_script}
 
 {footer_script}
Index: /branches/2.2/admin/themes/default/template/batch_manager_unit.tpl
===================================================================
--- /branches/2.2/admin/themes/default/template/batch_manager_unit.tpl	(revision 11055)
+++ /branches/2.2/admin/themes/default/template/batch_manager_unit.tpl	(revision 11056)
@@ -3,6 +3,6 @@
 {include file='include/colorbox.inc.tpl'}
 
-{combine_script id='jquery.fcbkcomplete' load='async' require='jquery' path='themes/default/js/plugins/jquery.fcbkcomplete.js'}
-{footer_script require='jquery.fcbkcomplete'}
+{combine_script id='jquery.tokeninput' load='async' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'}
+{footer_script require='jquery.tokeninput'}
 var tag_boxes_selector = "";
 {foreach from=$elements item=element name=element}
@@ -14,20 +14,22 @@
 	tag_boxes_selector = tag_boxes_selector + prefix + "#tags-" + {$element.ID};
 {/foreach}
-{literal}
-jQuery(document).ready(function() {
-	$(tag_boxes_selector).fcbkcomplete({
-		json_url: "admin.php?fckb_tags=1",
-		cache: false,
-		filter_case: false,
-		filter_hide: true,
-		firstselected: true,
-		filter_selected: true,
-		maxitems: 100,
-		newel: true
-	});
-  
-  $("a.preview-box").colorbox();
+
+jQuery(document).ready(function() {ldelim}
+  jQuery(tag_boxes_selector).tokenInput(
+    [{foreach from=$tags item=tag name=tags}{ldelim}"name":"{$tag.name}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}],
+    {ldelim}
+      hintText: '{'Type in a search term'|@translate}',
+      noResultsText: '{'No results'|@translate}',
+      searchingText: '{'Searching...'|@translate}',
+      newText: ' ({'new'|@translate})',
+      animateDropdown: false,
+      preventDuplicates: true,
+      allowCreation: true
+    }
+  );
+
+  jQuery("a.preview-box").colorbox();
 });
-{/literal}{/footer_script}
+{/footer_script}
 
 <h2>{'Batch Manager'|@translate}</h2>
@@ -113,5 +115,5 @@
 <select id="tags-{$element.ID}" name="tags-{$element.ID}">
 {foreach from=$element.TAGS item=tag}
-  <option value="{$tag.value}" class="selected">{$tag.key}</option>
+  <option value="{$tag.id}" class="selected">{$tag.name}</option>
 {/foreach}
 </select>
Index: /branches/2.2/admin/themes/default/template/picture_modify.tpl
===================================================================
--- /branches/2.2/admin/themes/default/template/picture_modify.tpl	(revision 11055)
+++ /branches/2.2/admin/themes/default/template/picture_modify.tpl	(revision 11056)
@@ -3,19 +3,21 @@
 {include file='include/datepicker.inc.tpl'}
 
-{combine_script id='jquery.fcbkcomplete' load='async' require='jquery' path='themes/default/js/plugins/jquery.fcbkcomplete.js'}
-{footer_script require='jquery.fcbkcomplete'}{literal}
-jQuery(document).ready(function() {
-	jQuery("#tags").fcbkcomplete({
-		json_url: "admin.php?fckb_tags=1",
-		cache: false,
-		filter_case: false,
-		filter_hide: true,
-		firstselected: true,
-		filter_selected: true,
-		maxitems: 100,
-		newel: true
-	});
+{combine_script id='jquery.tokeninput' load='async' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'}
+{footer_script require='jquery.tokeninput'}
+jQuery(document).ready(function() {ldelim}
+  jQuery("#tags").tokenInput(
+    [{foreach from=$tags item=tag name=tags}{ldelim}"name":"{$tag.name}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}],
+    {ldelim}
+      hintText: '{'Type in a search term'|@translate}',
+      noResultsText: '{'No results'|@translate}',
+      searchingText: '{'Searching...'|@translate}',
+      newText: ' ({'new'|@translate})',
+      animateDropdown: false,
+      preventDuplicates: true,
+      allowCreation: true
+    }
+  );
 });
-{/literal}{/footer_script}
+{/footer_script}
 
 {footer_script}
@@ -137,6 +139,6 @@
         <td>
 <select id="tags" name="tags">
-{foreach from=$tags item=tag}
-  <option value="{$tag.value}" class="selected">{$tag.key}</option>
+{foreach from=$tag_selection item=tag}
+  <option value="{$tag.id}" class="selected">{$tag.name}</option>
 {/foreach}
 </select>
Index: /branches/2.2/admin/themes/default/theme.css
===================================================================
--- /branches/2.2/admin/themes/default/theme.css	(revision 11055)
+++ /branches/2.2/admin/themes/default/theme.css	(revision 11056)
@@ -586,48 +586,4 @@
 }
 
-/* jQuery FCBKcomplete */
-/* TextboxList sample CSS */
-ul.holder { margin: 0; border: 1px solid #999; overflow: hidden; height: auto !important; height: 1%; padding: 4px 5px 0; }
-*:first-child+html ul.holder { padding-bottom: 2px; } * html ul.holder { padding-bottom: 2px; } /* ie7 and below */
-ul.holder li { float: left; list-style-type: none; margin: 0 5px 4px 0; white-space:nowrap;}
-ul.holder li.bit-box, ul.holder li.bit-input input { font: 11px "Lucida Grande", "Verdana"; }
-ul.holder li.bit-box { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; border: 1px solid #CAD8F3; background: #DEE7F8; padding: 1px 5px 2px; }
-ul.holder li.bit-box-focus { border-color: #598BEC; background: #598BEC; color: #fff; }
-ul.holder li.bit-input input { width: auto; overflow:visible; margin: 0; border: 0px; outline: 0; padding: 3px 0px 2px; } /* no left/right padding here please */
-ul.holder li.bit-input input.smallinput { width: 20px; }
-
-/* Facebook demo CSS */      
-#add { border: 1px solid #999; width: 550px; margin: 50px; padding: 20px 30px 10px; }
-form ol li { list-style-type: none; }
-form ol { font: 11px "Lucida Grande", "Verdana"; margin: 0; padding: 0; }
-form ol li.input-text { margin-bottom: 10px; list-style-type: none; padding-bottom: 10px; }
-form ol li.input-text label { font-weight: bold; cursor: pointer; display: block; font-size: 13px; margin-bottom: 10px; }
-form ol li.input-text input { width: 500px; padding: 5px 5px 6px; font: 11px "Lucida Grande", "Verdana"; border: 1px solid #999; }
-form ul.holder { width: 500px; }
-form ul { margin: 0 !important }
-ul.holder li.bit-box, #apple-list ul.holder li.bit-box { padding-right: 15px; position: relative; z-index:1000;}
-#apple-list ul.holder li.bit-input { margin: 0; }
-#apple-list ul.holder li.bit-input input.smallinput { width: 5px; }
-ul.holder li.bit-hover { background: #BBCEF1; border: 1px solid #6D95E0; }
-ul.holder li.bit-box-focus { border-color: #598BEC; background: #598BEC; color: #fff; }
-ul.holder li.bit-box a.closebutton { position: absolute; right: 4px; top: 5px; display: block; width: 7px; height: 7px; font-size: 1px; background: url(icon/fcbkcomplete_close.gif); }
-ul.holder li.bit-box a.closebutton:hover { background-position: 7px; }
-ul.holder li.bit-box-focus a.closebutton, ul.holder li.bit-box-focus a.closebutton:hover { background-position: bottom; }
-
-/* Autocompleter */
-
-.facebook-auto { display: none; position: absolute; width: 512px; background: #eee; }
-.facebook-auto .default { padding: 5px 7px; border: 1px solid #ccc; border-width: 0 1px 1px;font-family:"Lucida Grande","Verdana"; font-size:11px; }
-.facebook-auto ul { display: none; margin: 0; padding: 0; overflow: auto; position:absolute; z-index:9999}
-.facebook-auto ul li { padding: 5px 12px; z-index: 1000; cursor: pointer; margin: 0; list-style-type: none; border: 1px solid #ccc; border-width: 0 1px 1px; font: 11px "Lucida Grande", "Verdana"; background-color: #eee }
-.facebook-auto ul li em { font-weight: bold; font-style: normal; background: #ccc; }
-.facebook-auto ul li.auto-focus { background: #4173CC; color: #fff; }
-.facebook-auto ul li.auto-focus em { background: none; }
-.deleted { background-color:#4173CC !important; color:#ffffff !important;}
-.hidden { display:none;}
-
-#demo ul.holder li.bit-input input { padding: 2px 0 1px; border: 1px solid #999; }
-.ie6fix {height:1px;width:1px; position:absolute;top:0px;left:0px;z-index:1;}
-
 /* Add photos, direct mode */
 #uploadBoxes P {
@@ -1046,4 +1002,22 @@
 #batchManagerGlobal .useFilterCheckbox {display:none}
 
+
+/* TokenInput (with Facebook style) */
+ul.token-input-list {overflow: hidden; height: auto !important; height: 1%;width: 400px;border: 1px solid #8496ba;cursor: text;font-size: 12px;font-family: Verdana;min-height: 1px;z-index: 999;margin: 0;padding: 0;background-color: #fff;list-style-type: none;clear: left;}
+ul.token-input-list li input {border: 0;width: 100px;padding: 3px 8px;background-color: white;margin: 2px 0;-webkit-appearance: caret;}
+li.token-input-token {overflow: hidden; height: auto !important; height: 15px;margin: 3px;padding: 1px 3px;background-color: #eff2f7;color: #000;cursor: default;border: 1px solid #ccd5e4;font-size: 11px;border-radius: 5px;-moz-border-radius: 5px;-webkit-border-radius: 5px;float: left;white-space: nowrap;}
+li.token-input-token p {display: inline;padding: 0;margin: 0;}
+li.token-input-token span {color: #a6b3cf;margin-left: 5px;font-weight: bold;cursor: pointer;}
+li.token-input-selected-token {background-color: #5670a6;border: 1px solid #3b5998;color: #fff;}
+li.token-input-input-token {float: left;margin: 0;padding: 0;list-style-type: none;width:10px;}
+div.token-input-dropdown {position: absolute;width: 400px;background-color: #fff;overflow: hidden;border-left: 1px solid #ccc;border-right: 1px solid #ccc;border-bottom: 1px solid #ccc;cursor: default;font-size: 11px;font-family: Verdana;z-index: 1;}
+div.token-input-dropdown p {margin: 0;padding: 5px;font-weight: bold;color: #777;}
+div.token-input-dropdown ul {margin: 0;padding: 0;}
+div.token-input-dropdown ul li {background-color: #fff;padding: 3px;margin: 0;list-style-type: none;}
+div.token-input-dropdown ul li.token-input-dropdown-item {background-color: #fff;}
+div.token-input-dropdown ul li.token-input-dropdown-item2 {background-color: #fff;}
+div.token-input-dropdown ul li em {font-weight: bold;font-style: normal;}
+div.token-input-dropdown ul li.token-input-selected-dropdown-item {background-color: #3b5998;color: #fff;}
+
 .warning {
   background:url(icon/warning.png) no-repeat top left;
Index: /branches/2.2/admin/themes/roma/theme.css
===================================================================
--- /branches/2.2/admin/themes/roma/theme.css	(revision 11055)
+++ /branches/2.2/admin/themes/roma/theme.css	(revision 11056)
@@ -246,2 +246,9 @@
 #batchManagerGlobal #selectedMessage {background-color:#555; color:#ddd;}
 
+/* TokenInput (with Facebook style for ROMA) */
+ul.token-input-list {border-color:#666;background-color:#444;}
+ul.token-input-list li input {background-color:#444;}
+li.token-input-token span {color:#878787;}
+div.token-input-dropdown {background-color:#eee;border-color:#666;}
+div.token-input-dropdown ul li {background-color:#eee;}
+div.token-input-dropdown ul li.token-input-selected-dropdown-item {background-color:#FF7800;}
Index: /branches/2.2/language/en_UK/admin.lang.php
===================================================================
--- /branches/2.2/language/en_UK/admin.lang.php	(revision 11055)
+++ /branches/2.2/language/en_UK/admin.lang.php	(revision 11056)
@@ -430,4 +430,5 @@
 $lang['Move'] = "Move";
 $lang['Name'] = "Name";
+$lang['new'] = "new";
 $lang['New name'] = "New name";
 $lang['New parent album'] = "New parent album";
@@ -444,4 +445,5 @@
 $lang['No photo selected, %d photos in current set'] = 'No photo selected, %d photos in current set';
 $lang['No photo selected, no action possible.'] = 'No photo selected, no action possible.';
+$lang['No results'] = "No results";
 $lang['No user to send notifications by mail.'] = "No user to be notified by mail.";
 $lang['no write access'] = "no write access";
@@ -577,4 +579,5 @@
 $lang['Save Settings'] = 'Save Settings';
 $lang['Save to permalink history'] = "Save to permalinks history";
+$lang['Searching...'] = "Searching...";
 $lang['Search for new images in the directories'] = "Search for new images in the directories";
 $lang['Section'] = "Section";
@@ -692,4 +695,5 @@
 $lang['Type here the author name'] = 'Type here the author name';
 $lang['Type here the title'] = 'Type here the title';
+$lang['Type in a search term'] = "Type in a search term";
 $lang['Unable to check for upgrade.'] = "Unable to check for upgrade.";
 $lang['Uncheck all'] = "Uncheck all";
Index: /branches/2.2/language/fr_FR/admin.lang.php
===================================================================
--- /branches/2.2/language/fr_FR/admin.lang.php	(revision 11055)
+++ /branches/2.2/language/fr_FR/admin.lang.php	(revision 11056)
@@ -795,3 +795,7 @@
 $lang['ERROR: THIS PLUGIN IS MISSING BUT IT IS INSTALLED! UNINSTALL IT NOW.'] = 'ERREUR: CE PLUGIN EST MANQUANT MAIS TOUJOURS INSTALLÉ! DÉSINSTALLEZ-LE.';
 $lang['display'] = 'Afficher';
+$lang['No results'] = "Pas de résultat";
+$lang['Type in a search term'] = "Entrez un terme de recherche";
+$lang['Searching...'] = "Recherche...";
+$lang['new'] = "nouveau";
 ?>
Index: anches/2.2/themes/default/js/plugins/jquery.fcbkcomplete.js
===================================================================
--- /branches/2.2/themes/default/js/plugins/jquery.fcbkcomplete.js	(revision 11055)
+++ 	(revision )
@@ -1,645 +1,0 @@
-/**
- FCBKcomplete 2.7.5
- - Jquery version required: 1.2.x, 1.3.x, 1.4.x
- 
- Based on TextboxList by Guillermo Rauch http://devthought.com/
- 
- Changelog:
- - 2.00 new version of fcbkcomplete
- 
- - 2.01 fixed bugs & added features
-    fixed filter bug for preadded items
-    focus on the input after selecting tag
-    the element removed pressing backspace when the element is selected
-    input tag in the control has a border in IE7
-    added iterate over each match and apply the plugin separately
-    set focus on the input after selecting tag
- 
- - 2.02 fixed fist element selected bug
-    fixed defaultfilter error bug
- 
- - 2.5  removed selected="selected" attribute due ie bug
-    element search algorithm changed
-    better performance fix added
-    fixed many small bugs
-    onselect event added
-    onremove event added
- 
- - 2.6  ie6/7 support fix added
-    added new public method addItem due request
-    added new options "firstselected" that you can set true/false to select first element on dropdown list
-    autoexpand input element added
-    removeItem bug fixed
-    and many more bug fixed
-    fixed public method to use it $("elem").trigger("addItem",[{"title": "test", "value": "test"}]);
-    
-- 2.7   jquery 1.4 compability
-    item lock possability added by adding locked class to preadded option <option value="value" class="selected locked">text</option>
-    maximum item that can be added
-
-- 2.7.1 bug fixed
-    ajax delay added thanks to http://github.com/dolorian
-
-- 2.7.2 some minor bug fixed
-    minified version recompacted due some problems
-    
-- 2.7.3 event call fixed thanks to William Parry <williamparry!at!gmail.com>
-
-- 2.7.4 standart event change call added on addItem, removeItem
-    preSet also check if item have "selected" attribute
-    addItem minor fix
-    
-- 2.7.5  event call removeItem fixed
-         new public method destroy added needed to remove fcbkcomplete element from dome
-
- */
-/* Coded by: emposha <admin@emposha.com> */
-/* Copyright: Emposha.com <http://www.emposha.com> - Distributed under MIT - Keep this message! */
-
-/**
- * json_url         - url to fetch json object
- * cache            - use cache
- * height           - maximum number of element shown before scroll will apear
- * newel            - show typed text like a element
- * firstselected    - automaticly select first element from dropdown
- * filter_case      - case sensitive filter
- * filter_selected  - filter selected items from list
- * complete_text    - text for complete page
- * maxshownitems    - maximum numbers that will be shown at dropdown list (less better performance)
- * onselect         - fire event on item select
- * onremove         - fire event on item remove
- * maxitimes        - maximum items that can be added
- * delay            - delay between ajax request (bigger delay, lower server time request)
- * addontab         - add first visible element on tab or enter hit
- * attachto         - after this element fcbkcomplete insert own elements
- */
-jQuery(function($) {
-    $.fn.fcbkcomplete = function(opt) {
-        return this.each(function() {
-            function init() {
-                createFCBK();
-                preSet();
-                addInput(0);
-            }
-
-            function createFCBK() {
-                element.hide();
-                element.attr("multiple", "multiple");
-                if (element.attr("name").indexOf("[]") == -1) {
-                    element.attr("name", element.attr("name") + "[]");
-                }
-
-                holder = $(document.createElement("ul"));
-                holder.attr("class", "holder");
-                
-                if (options.attachto) {
-                  if (typeof(options.attachto) == "object") {
-                    options.attachto.append(holder);
-                  }
-                  else {
-                    $(options.attachto).append(holder);
-                  }
-                  
-                }
-                else {
-                  element.after(holder);
-                }
-
-                complete = $(document.createElement("div"));
-                complete.addClass("facebook-auto");
-                complete.append('<div class="default">' + options.complete_text + "</div>");
-                complete.hover(function() {options.complete_hover = 0;}, function() {options.complete_hover = 1;});
-                
-                feed = $(document.createElement("ul"));
-                feed.attr("id", elemid + "_feed");
-
-                complete.prepend(feed);
-                holder.after(complete);
-                feed.css("width", complete.width());
-            }
-
-            function preSet() {
-                element.children("option").each(function(i, option) {
-                    option = $(option);                    
-                    if (option.hasClass("selected")) {
-                        addItem(option.text(), option.val(), true, option.hasClass("locked"));
-                        option.attr("selected", "selected");
-                    }
-                    cache.push({
-                        key: option.text(),
-                        value: option.val()
-                    });
-                    search_string += "" + (cache.length - 1) + ":" + option.text() + ";";
-                });
-            }
-
-            //public method to add new item
-            $(this).bind("addItem",
-            function(event, data) {
-                addItem(data.title, data.value, 0, 0, 0);
-            });
-
-            //public method to remove item
-            $(this).bind("removeItem",
-            function(event, data) {
-                var item = holder.children('li[rel=' + data.value + ']');
-                if (item.length) {
-                    removeItem(item);
-                }
-            });
-            
-            //public method to remove item
-            $(this).bind("destroy",
-            function(event, data) {
-                holder.remove();
-                complete.remove();
-                element.show();
-            });
-
-            function addItem(title, value, preadded, locked, focusme) {
-                if (!maxItems()) {
-                    return false;
-                }
-                var li = document.createElement("li");
-                var txt = document.createTextNode(title);
-                var aclose = document.createElement("a");
-                var liclass = "bit-box" + (locked ? " locked": "");
-                $(li).attr({
-                    "class": liclass,
-                    "rel": value
-                });
-                $(li).prepend(txt);
-                $(aclose).attr({
-                    "class": "closebutton",
-                    "href": "#"
-                });
-
-                li.appendChild(aclose);
-                holder.append(li);
-
-                $(aclose).click(function() {
-                    removeItem($(this).parent("li"));
-                    return false;
-                });
-
-                if (!preadded) {
-                    $("#" + elemid + "_annoninput").remove();
-                    var _item;
-                    addInput(focusme);
-                    if (element.children("option[value=" + value + "]").length) {
-                        _item = element.children("option[value=" + value + "]");
-                        _item.get(0).setAttribute("selected", "selected");
-                        _item.attr("selected", "selected");
-                        if (!_item.hasClass("selected")) {
-                            _item.addClass("selected");
-                        }
-                    }
-                    else{
-                        var _item = $(document.createElement("option"));
-                        _item.attr("value", value).get(0).setAttribute("selected", "selected");
-                        _item.attr("value", value).attr("selected", "selected");
-                        _item.attr("value", value).addClass("selected");
-                        _item.text(title);
-                        element.append(_item);
-                    }
-                    if (options.onselect) {
-                        funCall(options.onselect, _item)
-                    }
-                    element.change();
-                }
-                holder.children("li.bit-box.deleted").removeClass("deleted");
-                feed.hide();
-            }
-
-            function removeItem(item) {
-
-                if (!item.hasClass('locked')) {                    
-                    item.fadeOut("fast");
-                    if (options.onremove) {
-                        var _item = element.children("option[value=" + item.attr("rel") + "]");
-                        funCall(options.onremove, _item)
-                    }
-                    element.children('option[value="' + item.attr("rel") + '"]').removeAttr("selected").removeClass("selected");
-                    item.remove();
-                    element.change();
-                    deleting = 0;
-                }
-            }
-
-            function addInput(focusme) {
-                var li = $(document.createElement("li"));
-                var input = $(document.createElement("input"));
-                var getBoxTimeout = 0;
-
-                li.attr({
-                    "class": "bit-input",
-                    "id": elemid + "_annoninput"
-                });
-                input.attr({
-                    "type": "text",
-                    "class": "maininput",
-                    "size": "1"
-                });
-                holder.append(li.append(input));
-
-                input.focus(function() {
-                    complete.fadeIn("fast");
-                });
-
-                input.blur(function() {
-                    if (options.complete_hover) {
-                      complete.fadeOut("fast");
-                    }
-                    else {
-                      input.focus();
-                    }
-                });              
-
-                holder.click(function() {
-                    input.focus();
-                    if (feed.length && input.val().length) {
-                        feed.show();
-                    }
-                    else{
-                        feed.hide();
-                        complete.children(".default").show();
-                    }
-                });
-
-                input.keypress(function(event) {
-                    if (event.keyCode == 13) {
-                        return false;
-                    }
-                    //auto expand input             
-                    input.attr("size", input.val().length + 1);
-                });
-
-                input.keydown(function(event) {
-                    //prevent to enter some bad chars when input is empty
-                    if (event.keyCode == 191) {
-                        event.preventDefault();
-                        return false;
-                    }
-                });
-
-                input.keyup(function(event) {
-                    var etext = xssPrevent(input.val());
-
-                    if (event.keyCode == 8 && etext.length == 0) {
-                        feed.hide();
-                        if (!holder.children("li.bit-box:last").hasClass('locked')) {
-                            if (holder.children("li.bit-box.deleted").length == 0) {
-                                holder.children("li.bit-box:last").addClass("deleted");
-                                return false;
-                            }
-                            else{
-                                if (deleting) {
-                                    return;
-                                }
-                                deleting = 1;
-                                holder.children("li.bit-box.deleted").fadeOut("fast",
-                                function() {
-                                    removeItem($(this));
-                                    return false;
-                                });
-                            }
-                        }
-                    }
-
-                    if (event.keyCode != 40 && event.keyCode != 38 && event.keyCode!=37 && event.keyCode!=39 && etext.length != 0) {
-                        counter = 0;
-
-                        if (options.json_url) {
-                            if (options.cache && json_cache) {
-                                addMembers(etext);
-                                bindEvents();
-                            }
-                            else{
-                                getBoxTimeout++;
-                                var getBoxTimeoutValue = getBoxTimeout;
-                                setTimeout(function() {
-                                    if (getBoxTimeoutValue != getBoxTimeout) return;
-                                    $.getJSON(options.json_url, {tag: etext},
-                                    function(data) {
-                                        addMembers(etext, data);
-                                        json_cache = true;
-                                        bindEvents();
-                                    });
-                                },
-                                options.delay);
-                            }
-                        }
-                        else{
-                            addMembers(etext);
-                            bindEvents();
-                        }
-                        complete.children(".default").hide();
-                        feed.show();
-                    }
-                });
-                if (focusme) {
-                    setTimeout(function() {
-                        input.focus();
-                        complete.children(".default").show();
-                    },
-                    1);
-                }
-            }
-
-            function addMembers(etext, data) {
-                feed.html('');
-
-                if (!options.cache && data != null) {
-                    cache = new Array();
-                    search_string = "";
-                }
-
-                addTextItem(etext);
-
-                if (data != null && data.length) {
-                    $.each(data,
-                    function(i, val) {
-                        cache.push({
-                            key: val.key,
-                            value: val.value
-                        });
-                        search_string += "" + (cache.length - 1) + ":" + val.key + ";";
-                    });
-                }
-
-                var maximum = options.maxshownitems < cache.length ? options.maxshownitems: cache.length;
-                var filter = "i";
-                if (options.filter_case) {
-                    filter = "";
-                }
-
-                var myregexp,
-                match;
-                try{
-                    myregexp = eval('/(?:^|;)\\s*(\\d+)\\s*:[^;]*?' + etext + '[^;]*/g' + filter);
-                    match = myregexp.exec(search_string);
-                }
-                catch(ex) {
-                    };
-
-                var content = '';
-                while (match != null && maximum > 0) {
-                    var id = match[1];
-                    var object = cache[id];
-                    if (options.filter_selected && element.children("option[value=" + object.value + "]").hasClass("selected")) {
-                        //nothing here...
-                        }
-                    else{
-                        content += '<li rel="' + object.value + '">' + itemIllumination(object.key, etext) + '</li>';
-                        counter++;
-                        maximum--;
-                    }
-                    match = myregexp.exec(search_string);
-                }
-                feed.append(content);
-
-                if (options.firstselected) {
-                    focuson = feed.children("li:visible:first");
-                    focuson.addClass("auto-focus");
-                }
-
-                if (counter > options.height) {
-                    feed.css({
-                        "height": (options.height * 24) + "px",
-                        "overflow": "auto"
-                    });
-                }
-                else{
-                    feed.css("height", "auto");
-                }
-            }
-
-            function itemIllumination(text, etext) {
-                if (options.filter_case) {
-                    try{
-                        eval("var text = text.replace(/(.*)(" + etext + ")(.*)/gi,'$1<em>$2</em>$3');");
-                    }
-                    catch(ex) {
-                        };
-                }
-                else{
-                    try{
-                        eval("var text = text.replace(/(.*)(" + etext.toLowerCase() + ")(.*)/gi,'$1<em>$2</em>$3');");
-                    }
-                    catch(ex) {
-                        };
-                }
-                return text;
-            }
-
-            function bindFeedEvent() {
-                feed.children("li").mouseover(function() {
-                    feed.children("li").removeClass("auto-focus");
-                    $(this).addClass("auto-focus");
-                    focuson = $(this);
-                });
-
-                feed.children("li").mouseout(function() {
-                    $(this).removeClass("auto-focus");
-                    focuson = null;
-                });
-            }
-
-            function removeFeedEvent() {
-                feed.children("li").unbind("mouseover");
-                feed.children("li").unbind("mouseout");
-                feed.mousemove(function() {
-                    bindFeedEvent();
-                    feed.unbind("mousemove");
-                });
-            }
-
-            function bindEvents() {
-                var maininput = $("#" + elemid + "_annoninput").children(".maininput");
-                bindFeedEvent();
-                feed.children("li").unbind("mousedown");
-                feed.children("li").mousedown(function() {
-                    var option = $(this);
-                    addItem(option.text(), option.attr("rel"), 0, 0, 1);
-                    feed.hide();
-                    complete.hide();
-                });
-
-                maininput.unbind("keydown");
-                maininput.keydown(function(event) {
-                    if (event.keyCode == 191) {
-                        event.preventDefault();
-                        return false;
-                    }
-
-                    if (event.keyCode != 8) {
-                        holder.children("li.bit-box.deleted").removeClass("deleted");
-                    }
-
-                    if ((event.keyCode == 13 || event.keyCode == 9) && checkFocusOn()) {
-                        var option = focuson;
-                        addItem(option.text(), option.attr("rel"), 0, 0, 1);
-                        complete.hide();
-                        event.preventDefault();
-                        focuson = null;
-                        return false;
-                    }
-
-                    if ((event.keyCode == 13 || event.keyCode == 9) && !checkFocusOn()) {
-                        if (options.newel) {
-                            var value = xssPrevent($(this).val());
-                            addItem(value, value, 0, 0, 1);
-                            complete.hide();
-                            event.preventDefault();
-                            focuson = null;
-                            return false;
-                        }
-                        
-                        if (options.addontab) {
-                          focuson = feed.children("li:visible:first");
-                          var option = focuson;
-                          addItem(option.text(), option.attr("rel"), 0, 0, 1);
-                          complete.hide();
-                          event.preventDefault();
-                          focuson = null;
-                          return false;
-                        }                        
-                    }
-
-                    if (event.keyCode == 40) {
-                        removeFeedEvent();
-                        if (focuson == null || focuson.length == 0) {
-                            focuson = feed.children("li:visible:first");
-                            feed.get(0).scrollTop = 0;
-                        }
-                        else{
-                            focuson.removeClass("auto-focus");
-                            focuson = focuson.nextAll("li:visible:first");
-                            var prev = parseInt(focuson.prevAll("li:visible").length, 10);
-                            var next = parseInt(focuson.nextAll("li:visible").length, 10);
-                            if ((prev > Math.round(options.height / 2) || next <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") {
-                                feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2));
-                            }
-                        }
-                        feed.children("li").removeClass("auto-focus");
-                        focuson.addClass("auto-focus");
-                    }
-                    if (event.keyCode == 38) {
-                        removeFeedEvent();
-                        if (focuson == null || focuson.length == 0) {
-                            focuson = feed.children("li:visible:last");
-                            feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (parseInt(feed.children("li:visible").length, 10) - Math.round(options.height / 2));
-                        }
-                        else{
-                            focuson.removeClass("auto-focus");
-                            focuson = focuson.prevAll("li:visible:first");
-                            var prev = parseInt(focuson.prevAll("li:visible").length, 10);
-                            var next = parseInt(focuson.nextAll("li:visible").length, 10);
-                            if ((next > Math.round(options.height / 2) || prev <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") {
-                                feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2));
-                            }
-                        }
-                        feed.children("li").removeClass("auto-focus");
-                        focuson.addClass("auto-focus");
-                    }
-                });
-            }
-
-            function maxItems() {
-                if (options.maxitems != 0) {
-                    if (holder.children("li.bit-box").length < options.maxitems) {
-                        return true;
-                    }
-                    else{
-                        return false;
-                    }
-                }
-            }
-
-            function addTextItem(value) {
-                if (options.newel && maxItems()) {
-                    feed.children("li[fckb=1]").remove();
-                    if (value.length == 0) {
-                        return;
-                    }
-                    var li = $(document.createElement("li"));
-                    li.attr({
-                        "rel": value,
-                        "fckb": "1"
-                    }).html(value);
-                    feed.prepend(li);
-                    counter++;
-                }
-                else{
-                    return;
-                }
-            }
-
-            function funCall(func, item) {
-                var _object = "";
-                for (i = 0; i < item.get(0).attributes.length; i++) {
-                    if (item.get(0).attributes[i].nodeValue != null) {
-                        _object += "\"_" + item.get(0).attributes[i].nodeName + "\": \"" + item.get(0).attributes[i].nodeValue + "\",";
-                    }
-                }
-                _object = "{" + _object + " notinuse: 0}";
-                func.call(func, _object);
-            }
-
-            function checkFocusOn() {
-                if (focuson == null) {
-                    return false;
-                }
-                if (focuson.length == 0) {
-                    return false;
-                }
-                return true;
-            }
-
-            function xssPrevent(string) {
-                string = string.replace(/[\"\'][\s]*javascript:(.*)[\"\']/g, "\"\"");
-                string = string.replace(/script(.*)/g, "");
-                string = string.replace(/eval\((.*)\)/g, "");
-                string = string.replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '');
-                return string;
-            }
-
-            var options = $.extend({
-                json_url: null,
-                cache: false,
-                height: "10",
-                newel: false,
-                addontab: false,
-                firstselected: false,
-                filter_case: false,
-                filter_selected: false,
-                complete_text: "Start to type...",
-                maxshownitems: 30,
-                maxitems: 10,
-                onselect: null,
-                onremove: null,
-                attachto: null,
-                delay: 350
-            },
-            opt);
-
-            //system variables
-            var holder = null;
-            var feed = null;
-            var complete = null;
-            var counter = 0;
-            var cache = new Array();
-            var json_cache = false;
-            var search_string = "";
-            var focuson = null;
-            var deleting = 0;
-            var complete_hover = 1;
-
-            var element = $(this);
-            var elemid = element.attr("id");
-            init();
-
-            return this;
-        });
-    };
-});
Index: /branches/2.2/themes/default/js/plugins/jquery.tokeninput.js
===================================================================
--- /branches/2.2/themes/default/js/plugins/jquery.tokeninput.js	(revision 11056)
+++ /branches/2.2/themes/default/js/plugins/jquery.tokeninput.js	(revision 11056)
@@ -0,0 +1,777 @@
+/**
+  DON'T MAKE AUTOMATIC UPGRADE
+  This is a merged version of :
+  + https://github.com/gr2m/jquery-tokeninput/
+  + https://github.com/mistic100/jquery-tokeninput/
+*/
+
+/*
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
+ * Version 1.4.2
+ *
+ * Copyright (c) 2009 James Smith (http://loopj.com)
+ * Licensed jointly under the GPL and MIT licenses,
+ * choose which one suits your project best!
+ *
+ */
+
+(function ($) {
+// Default settings
+var DEFAULT_SETTINGS = {
+    hintText: "Type in a search term",
+    noResultsText: "No results",
+    searchingText: "Searching...",
+    newText: "(new)",
+    deleteText: "&times;",
+    searchDelay: 300,
+    minChars: 1,
+    tokenLimit: null,
+    jsonContainer: null,
+    method: "GET",
+    contentType: "json",
+    queryParam: "q",
+    tokenDelimiter: ",",
+    preventDuplicates: false,
+    prePopulate: null,
+    processPrePopulate: false,
+    animateDropdown: true,
+    onResult: null,
+    onAdd: null,
+    onDelete: null,
+    allowCreation: false
+};
+
+// Default classes to use when theming
+var DEFAULT_CLASSES = {
+    tokenList: "token-input-list",
+    token: "token-input-token",
+    tokenDelete: "token-input-delete-token",
+    selectedToken: "token-input-selected-token",
+    highlightedToken: "token-input-highlighted-token",
+    dropdown: "token-input-dropdown",
+    dropdownItem: "token-input-dropdown-item",
+    dropdownItem2: "token-input-dropdown-item2",
+    selectedDropdownItem: "token-input-selected-dropdown-item",
+    inputToken: "token-input-input-token"
+};
+
+// Input box position "enum"
+var POSITION = {
+    BEFORE: 0,
+    AFTER: 1,
+    END: 2
+};
+
+// Keys "enum"
+var KEY = {
+    BACKSPACE: 8,
+    TAB: 9,
+    ENTER: 13,
+    ESCAPE: 27,
+    SPACE: 32,
+    PAGE_UP: 33,
+    PAGE_DOWN: 34,
+    END: 35,
+    HOME: 36,
+    LEFT: 37,
+    UP: 38,
+    RIGHT: 39,
+    DOWN: 40,
+    NUMPAD_ENTER: 108,
+    COMMA: 188
+};
+
+
+// Expose the .tokenInput function to jQuery as a plugin
+$.fn.tokenInput = function (url_or_data, options) {
+    var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
+
+    return this.each(function () {
+        new $.TokenList(this, url_or_data, settings);
+    });
+};
+
+
+// TokenList class for each input
+$.TokenList = function (input, url_or_data, settings) {
+    //
+    // Initialization
+    //
+
+    // Configure the data source
+    if(typeof(url_or_data) === "string") {
+        // Set the url to query against
+        settings.url = url_or_data;
+
+        // Make a smart guess about cross-domain if it wasn't explicitly specified
+        if(settings.crossDomain === undefined) {
+            if(settings.url.indexOf("://") === -1) {
+                settings.crossDomain = false;
+            } else {
+                settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
+            }
+        }
+    } else if(typeof(url_or_data) === "object") {
+        // Set the local data to search through
+        settings.local_data = url_or_data;
+    }
+
+    // Build class names
+    if(settings.classes) {
+        // Use custom class names
+        settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
+    } else if(settings.theme) {
+        // Use theme-suffixed default class names
+        settings.classes = {};
+        $.each(DEFAULT_CLASSES, function(key, value) {
+            settings.classes[key] = value + "-" + settings.theme;
+        });
+    } else {
+        settings.classes = DEFAULT_CLASSES;
+    }
+
+
+    // Save the tokens
+    var saved_tokens = [];
+
+    // Keep track of the number of tokens in the list
+    var token_count = 0;
+
+    // Basic cache to save on db hits
+    var cache = new $.TokenList.Cache();
+
+    // Keep track of the timeout, old vals
+    var timeout;
+    var input_val;
+
+    // Create a new text input an attach keyup events
+    var input_box = $("<input type=\"text\"  autocomplete=\"off\">")
+        .css({
+            outline: "none"
+        })
+        .focus(function () {
+            if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
+                show_dropdown_hint();
+            }
+        })
+        .blur(function () {
+            hide_dropdown();
+        })
+        .bind("keyup keydown blur update", resize_input)
+        .keydown(function (event) {
+            var previous_token;
+            var next_token;
+
+            switch(event.keyCode) {
+                case KEY.LEFT:
+                case KEY.RIGHT:
+                case KEY.UP:
+                case KEY.DOWN:
+                    if(!$(this).val()) {
+                        previous_token = input_token.prev();
+                        next_token = input_token.next();
+
+                        if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
+                            // Check if there is a previous/next token and it is selected
+                            if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
+                                deselect_token($(selected_token), POSITION.BEFORE);
+                            } else {
+                                deselect_token($(selected_token), POSITION.AFTER);
+                            }
+                        } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
+                            // We are moving left, select the previous token if it exists
+                            select_token($(previous_token.get(0)));
+                        } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
+                            // We are moving right, select the next token if it exists
+                            select_token($(next_token.get(0)));
+                        }
+                    } else {
+                        var dropdown_item = null;
+
+                        if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
+                            dropdown_item = $(selected_dropdown_item).next();
+                        } else {
+                            dropdown_item = $(selected_dropdown_item).prev();
+                        }
+
+                        if(dropdown_item.length) {
+                            select_dropdown_item(dropdown_item);
+                        }
+                        return false;
+                    }
+                    break;
+
+                case KEY.BACKSPACE:
+                    previous_token = input_token.prev();
+
+                    if(!$(this).val().length) {
+                        if(selected_token) {
+                            delete_token($(selected_token));
+                        } else if(previous_token.length) {
+                            select_token($(previous_token.get(0)));
+                        }
+
+                        return false;
+                    } else if($(this).val().length === 1) {
+                        hide_dropdown();
+                    } else {
+                        // set a timeout just long enough to let this function finish.
+                        setTimeout(function(){do_search();}, 5);
+                    }
+                    break;
+
+                case KEY.TAB:
+                case KEY.ENTER:
+                case KEY.NUMPAD_ENTER:
+                case KEY.COMMA:
+                  if(selected_dropdown_item) {
+                    add_token($(selected_dropdown_item));
+                    return false;
+                  }
+                  break;
+
+                case KEY.ESCAPE:
+                  hide_dropdown();
+                  return true;
+
+                default:
+                    if(String.fromCharCode(event.which)) {
+                        // set a timeout just long enough to let this function finish.
+                        setTimeout(function(){do_search();}, 5);
+                    }
+                    break;
+            }
+        });
+
+    if ($(input).get(0).tagName == 'SELECT') {
+    // Create a new input to store selected tokens, original will be delete later
+      var hidden_input = $("<input type=\"text\"  name=\"" + $(input).attr('name') + "\" autocomplete=\"off\">")
+                             .hide()
+                             .val("")
+                             .focus(function () {
+                                 input_box.focus();
+                             })
+                             .blur(function () {
+                                 input_box.blur();
+                             })
+                             .insertBefore(input);
+    } else {
+    // Keep a reference to the original input box
+      var hidden_input = $(input)
+                             .hide()
+                             .val("")
+                             .focus(function () {
+                                 input_box.focus();
+                             })
+                             .blur(function () {
+                                 input_box.blur();
+                             });
+    }
+
+    // Keep a reference to the selected token and dropdown item
+    var selected_token = null;
+    var selected_token_index = 0;
+    var selected_dropdown_item = null;
+
+    // The list to store the token items in
+    var token_list = $("<ul />")
+        .addClass(settings.classes.tokenList)
+        .click(function (event) {
+            var li = $(event.target).closest("li");
+            if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
+                toggle_select_token(li);
+            } else {
+                // Deselect selected token
+                if(selected_token) {
+                    deselect_token($(selected_token), POSITION.END);
+                }
+
+                // Focus input box
+                input_box.focus();
+            }
+        })
+        .mouseover(function (event) {
+            var li = $(event.target).closest("li");
+            if(li && selected_token !== this) {
+                li.addClass(settings.classes.highlightedToken);
+            }
+        })
+        .mouseout(function (event) {
+            var li = $(event.target).closest("li");
+            if(li && selected_token !== this) {
+                li.removeClass(settings.classes.highlightedToken);
+            }
+        })
+        .insertBefore(hidden_input);
+
+    // The token holding the input box
+    var input_token = $("<li />")
+        .addClass(settings.classes.inputToken)
+        .appendTo(token_list)
+        .append(input_box);
+
+    // The list to store the dropdown items in
+    var dropdown = $("<div>")
+        .addClass(settings.classes.dropdown)
+        .appendTo("body")
+        .hide();
+
+    // Magic element to help us resize the text input
+    var input_resizer = $("<tester/>")
+        .insertAfter(input_box)
+        .css({
+            position: "absolute",
+            top: -9999,
+            left: -9999,
+            width: "auto",
+            fontSize: input_box.css("fontSize"),
+            fontFamily: input_box.css("fontFamily"),
+            fontWeight: input_box.css("fontWeight"),
+            letterSpacing: input_box.css("letterSpacing"),
+            whiteSpace: "nowrap"
+        });
+
+    // Pre-populate list if items exist
+    hidden_input.val("");
+    var li_data = settings.prePopulate || hidden_input.data("pre");
+    if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
+        li_data = settings.onResult.call(hidden_input, li_data);
+    }    
+    if(li_data && li_data.length) {
+        $.each(li_data, function (index, value) {
+            insert_token(value.id, value.name);
+        });
+    }
+    
+    // Pre-populate from SELECT options
+    if ($(input).get(0).tagName == 'SELECT') {
+      $(input).children('option').each(function () {
+        insert_token($(this).attr('value'), $(this).text());
+      });
+    }
+
+
+
+    //
+    // Private functions
+    //
+
+    function resize_input() {
+        if(input_val === (input_val = input_box.val())) {return;}
+
+        // Enter new content into resizer and resize input accordingly
+        var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+        input_resizer.html(escaped);
+        input_box.width(input_resizer.width() + 30);
+    }
+
+    function is_printable_character(keycode) {
+        return ((keycode >= 48 && keycode <= 90) ||     // 0-1a-z
+                (keycode >= 96 && keycode <= 111) ||    // numpad 0-9 + - / * .
+                (keycode >= 186 && keycode <= 192) ||   // ; = , - . / ^
+                (keycode >= 219 && keycode <= 222));    // ( \ ) '
+    }
+
+    // Inner function to a token to the list
+    function insert_token(id, value) {
+        var this_token = $("<li><p>"+ value +"</p></li>")
+          .addClass(settings.classes.token)
+          .insertBefore(input_token);
+
+        // The 'delete token' button
+        $("<span>" + settings.deleteText + "</span>")
+            .addClass(settings.classes.tokenDelete)
+            .appendTo(this_token)
+            .click(function () {
+                delete_token($(this).parent());
+                return false;
+            });
+
+        // Store data on the token
+        var token_data = {"id": id, "name": value};
+        $.data(this_token.get(0), "tokeninput", token_data);
+
+        // Save this token for duplicate checking
+        saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
+        selected_token_index++;
+
+        // Update the hidden input
+        var token_ids = $.map(saved_tokens, function (el) {
+            return el.id;
+        });
+        hidden_input.val(token_ids.join(settings.tokenDelimiter));
+
+        token_count += 1;
+
+        return this_token;
+    }
+
+    // Add a token to the token list based on user input
+    function add_token (item) {
+        var li_data = $.data(item.get(0), "tokeninput");
+        var callback = settings.onAdd;
+
+        // See if the token already exists and select it if we don't want duplicates
+        if(token_count > 0 && settings.preventDuplicates) {
+            var found_existing_token = null;
+            token_list.children().each(function () {
+                var existing_token = $(this);
+                var existing_data = $.data(existing_token.get(0), "tokeninput");
+                if(existing_data && existing_data.id === li_data.id) {
+                    found_existing_token = existing_token;
+                    return false;
+                }
+            });
+
+            if(found_existing_token) {
+                select_token(found_existing_token);
+                input_token.insertAfter(found_existing_token);
+                input_box.focus();
+                return;
+            }
+        }
+
+        // Insert the new tokens
+        insert_token(li_data.id, li_data.name);
+
+        // Check the token limit
+        if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
+            input_box.hide();
+            hide_dropdown();
+            return;
+        } else {
+            input_box.focus();
+        }
+
+        // Clear input box
+        input_box.val("");
+
+        // Don't show the help dropdown, they've got the idea
+        hide_dropdown();
+
+        // Execute the onAdd callback if defined
+        if($.isFunction(callback)) {
+            callback.call(hidden_input,li_data);
+        }
+    }
+
+    // Select a token in the token list
+    function select_token (token) {
+        token.addClass(settings.classes.selectedToken);
+        selected_token = token.get(0);
+
+        // Hide input box
+        input_box.val("");
+
+        // Hide dropdown if it is visible (eg if we clicked to select token)
+        hide_dropdown();
+    }
+
+    // Deselect a token in the token list
+    function deselect_token (token, position) {
+        token.removeClass(settings.classes.selectedToken);
+        selected_token = null;
+
+        if(position === POSITION.BEFORE) {
+            input_token.insertBefore(token);
+            selected_token_index--;
+        } else if(position === POSITION.AFTER) {
+            input_token.insertAfter(token);
+            selected_token_index++;
+        } else {
+            input_token.appendTo(token_list);
+            selected_token_index = token_count;
+        }
+
+        // Show the input box and give it focus again
+        input_box.focus();
+    }
+
+    // Toggle selection of a token in the token list
+    function toggle_select_token(token) {
+        var previous_selected_token = selected_token;
+
+        if(selected_token) {
+            deselect_token($(selected_token), POSITION.END);
+        }
+
+        if(previous_selected_token === token.get(0)) {
+            deselect_token(token, POSITION.END);
+        } else {
+            select_token(token);
+        }
+    }
+
+    // Delete a token from the token list
+    function delete_token (token) {
+        // Remove the id from the saved list
+        var token_data = $.data(token.get(0), "tokeninput");
+        var callback = settings.onDelete;
+
+        var index = token.prevAll().length;
+        if(index > selected_token_index) index--;
+
+        // Delete the token
+        token.remove();
+        selected_token = null;
+
+        // Show the input box and give it focus again
+        input_box.focus();
+
+        // Remove this token from the saved list
+        saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
+        if(index < selected_token_index) selected_token_index--;
+
+        // Update the hidden input
+        var token_ids = $.map(saved_tokens, function (el) {
+            return el.id;
+        });
+        hidden_input.val(token_ids.join(settings.tokenDelimiter));
+
+        token_count -= 1;
+
+        if(settings.tokenLimit !== null) {
+            input_box
+                .show()
+                .val("")
+                .focus();
+        }
+
+        // Execute the onDelete callback if defined
+        if($.isFunction(callback)) {
+            callback.call(hidden_input,token_data);
+        }
+    }
+
+    // Hide and clear the results dropdown
+    function hide_dropdown () {
+        dropdown.hide().empty();
+        selected_dropdown_item = null;
+    }
+
+    function show_dropdown() {
+        dropdown
+            .css({
+                position: "absolute",
+                top: $(token_list).offset().top + $(token_list).outerHeight(),
+                left: $(token_list).offset().left,
+                zindex: 999
+            })
+            .show();
+    }
+
+    function show_dropdown_searching () {
+        if(settings.searchingText) {
+            dropdown.html("<p>"+settings.searchingText+"</p>");
+            show_dropdown();
+        }
+    }
+
+    function show_dropdown_hint () {
+        if(settings.hintText) {
+            dropdown.html("<p>"+settings.hintText+"</p>");
+            show_dropdown();
+        }
+    }
+
+    // Highlight the query part of the search term
+    function highlight_term(value, term) {
+        return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
+    }
+
+    // Populate the results dropdown with some results
+    function populate_dropdown (query, results) {
+        if(results && results.length) {
+            dropdown.empty();
+            var dropdown_ul = $("<ul>")
+                .appendTo(dropdown)
+                .mouseover(function (event) {
+                    select_dropdown_item($(event.target).closest("li"));
+                })
+                .mousedown(function (event) {
+                    add_token($(event.target).closest("li"));
+                    return false;
+                })
+                .hide();
+
+            $.each(results, function(index, value) {
+                var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
+                                  .appendTo(dropdown_ul);
+
+                if(index % 2) {
+                    this_li.addClass(settings.classes.dropdownItem);
+                } else {
+                    this_li.addClass(settings.classes.dropdownItem2);
+                }
+
+                if(index === 0) {
+                    select_dropdown_item(this_li);
+                }
+
+                $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name});
+            });
+
+            show_dropdown();
+
+            if(settings.animateDropdown) {
+                dropdown_ul.slideDown("fast");
+            } else {
+                dropdown_ul.show();
+            }
+        } else {
+            if(settings.noResultsText) {
+                dropdown.html("<p>"+settings.noResultsText+"</p>");
+                show_dropdown();
+            }
+        }
+    }
+
+    // Highlight an item in the results dropdown
+    function select_dropdown_item (item) {
+        if(item) {
+            if(selected_dropdown_item) {
+                deselect_dropdown_item($(selected_dropdown_item));
+            }
+
+            item.addClass(settings.classes.selectedDropdownItem);
+            selected_dropdown_item = item.get(0);
+        }
+    }
+
+    // Remove highlighting from an item in the results dropdown
+    function deselect_dropdown_item (item) {
+        item.removeClass(settings.classes.selectedDropdownItem);
+        selected_dropdown_item = null;
+    }
+
+    // Do a search and show the "searching" dropdown if the input is longer
+    // than settings.minChars
+    function do_search() {
+        var query = input_box.val().toLowerCase();
+
+        if(query && query.length) {
+            if(selected_token) {
+                deselect_token($(selected_token), POSITION.AFTER);
+            }
+
+            if(query.length >= settings.minChars) {
+                show_dropdown_searching();
+                clearTimeout(timeout);
+
+                timeout = setTimeout(function(){
+                    run_search(query);
+                }, settings.searchDelay);
+            } else {
+                hide_dropdown();
+            }
+        }
+    }
+
+    // Do the actual search
+    function run_search(query) {
+        var cached_results = cache.get(query);
+        if(cached_results) {
+            populate_dropdown(query, cached_results);
+        } else {
+            // Are we doing an ajax search or local data search?
+            if(settings.url) {
+                // Extract exisiting get params
+                var ajax_params = {};
+                ajax_params.data = {};
+                if(settings.url.indexOf("?") > -1) {
+                    var parts = settings.url.split("?");
+                    ajax_params.url = parts[0];
+
+                    var param_array = parts[1].split("&");
+                    $.each(param_array, function (index, value) {
+                        var kv = value.split("=");
+                        ajax_params.data[kv[0]] = kv[1];
+                    });
+                } else {
+                    ajax_params.url = settings.url;
+                }
+
+                // Prepare the request
+                ajax_params.data[settings.queryParam] = query;
+                ajax_params.type = settings.method;
+                ajax_params.dataType = settings.contentType;
+                if(settings.crossDomain) {
+                    ajax_params.dataType = "jsonp";
+                }
+
+                // Attach the success callback
+                ajax_params.success = function(results) {
+                  if($.isFunction(settings.onResult)) {
+                      results = settings.onResult.call(hidden_input, results);
+                  }
+                  
+                  if(settings.allowCreation) {
+                      results.push({name: input_box.val() + settings.newText, id: input_box.val()});
+                  }
+                  cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
+
+                  // only populate the dropdown if the results are associated with the active search query
+                  if(input_box.val().toLowerCase() === query) {
+                      populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
+                  }
+                };
+
+                // Make the request
+                $.ajax(ajax_params);
+            } else if(settings.local_data) {
+                // Do the search through local data
+                var results = $.grep(settings.local_data, function (row) {
+                    return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
+                });
+
+                if($.isFunction(settings.onResult)) {
+                    results = settings.onResult.call(hidden_input, results);
+                }
+                
+                if(settings.allowCreation) {
+                    results.push({name: input_box.val() + settings.newText, id: input_box.val()});
+                }
+                
+                cache.add(query, results);                
+                
+                populate_dropdown(query, results);
+            }
+        }
+    }
+    
+    if ($(input).get(0).tagName == 'SELECT') {
+      $(input).remove();
+    }
+};
+
+// Really basic cache for the results
+$.TokenList.Cache = function (options) {
+    var settings = $.extend({
+        max_size: 500
+    }, options);
+
+    var data = {};
+    var size = 0;
+
+    var flush = function () {
+        data = {};
+        size = 0;
+    };
+
+    this.add = function (query, results) {
+        if(size > settings.max_size) {
+            flush();
+        }
+
+        if(!data[query]) {
+            size += 1;
+        }
+
+        data[query] = results;
+    };
+
+    this.get = function (query) {
+        return data[query];
+    };
+};
+}(jQuery));
