Commits

Wez Furlong committed 6c8537a

use Manifest instead of tagit for the tag editor, as it has autocomplete
possibilities via Marco Polo. Style manifest the same way as Chosen for
visual consistency.

  • Participants
  • Parent commits d6ee222

Comments (0)

Files changed (9)

   '../inc/hyperlight/vibrant-ink.css',
   '../inc/hyperlight/zenburn.css',
   '../inc/hyperlight/wezterm.css',
-  'css/jquery.tagit.css',
+  'css/manifest.css',
 );
 
 foreach ($scripts as $name) {

web/css/chosen/chosen.css

   height: 13px;
   font-size: 1px;
   background: url(chosen-sprite.png) right top no-repeat;
+  border-bottom: none;
 }
 .chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover {
   background-position: right -11px;

web/css/jquery.tagit.css

-ul.tagit {
-	padding: 1px 5px;
-	overflow: auto;
-    margin-left: inherit; /* usually we don't want the regular ul margins. */
-    margin-right: inherit;
-}
-ul.tagit li {
-	display: block;
-	float: left;
-	margin: 2px 5px 2px 0;
-}
-ul.tagit li.tagit-choice {
-	padding: .2em 18px .2em .5em;
-    position: relative;
-    line-height: inherit;
-}
-ul.tagit li.tagit-new {
-	padding: .25em 4px .25em 0;
-}
-
-ul.tagit li.tagit-choice a.tagit-label {
-	cursor: pointer;
-	text-decoration: none;
-}
-ul.tagit li.tagit-choice .tagit-close {
-	cursor: pointer;
-    position: absolute;
-    right: .1em;
-    top: 50%;
-    margin-top: -8px;
-}
-
-/* used for some custom themes that don't need image icons */
-ul.tagit li.tagit-choice .tagit-close .text-icon {
-    display: none;
-}
-
-ul.tagit li.tagit-choice input {
-	display: block;
-	float: left;
-	margin: 2px 5px 2px 0;
-}
-ul.tagit input[type="text"] {
-    -moz-box-sizing:    border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing:         border-box;
-
-    border: none;
-	margin: 0;
-	padding: 0;
-	width: inherit;
-	background-color: inherit;
-    outline: none;
-}

web/css/manifest.css

+
+div.mf_container {
+	cursor: text;
+	background-color: #fff;
+  background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+  background-image: -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+  background-image: -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+  background-image: -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+  background-image: -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
+  background-image: linear-gradient(top, #ffffff 85%,#eeeeee 99%);
+  border: 1px solid #aaa;
+  margin: 0;
+  padding: 0;
+  cursor: text;
+  overflow: hidden;
+  height: auto !important;
+  height: 1%;
+  position: relative;
+
+}
+
+/* ordered list for displaying selected items */
+div.mf_container ol.mf_list {
+	display: inline;
+
+}
+
+/* selected item, regardless of state (highlighted, selected) */
+div.mf_container ol.mf_list li.mf_item {
+	position: relative;
+	cursor: pointer;
+	display: inline-block;
+
+  -webkit-border-radius: 3px;
+  -moz-border-radius   : 3px;
+  border-radius        : 3px;
+  -moz-background-clip   : padding;
+  -webkit-background-clip: padding-box;
+  background-clip        : padding-box;
+  background-color: #e4e4e4;
+  background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #e4e4e4), color-stop(0.7, #eeeeee));
+  background-image: -webkit-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%);
+  background-image: -moz-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%);
+  background-image: -o-linear-gradient(bottom, #e4e4e4 0%, #eeeeee 70%);
+  background-image: -ms-linear-gradient(top, #e4e4e4 0%,#eeeeee 70%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e4e4e4', endColorstr='#eeeeee',GradientType=0 );
+  background-image: linear-gradient(top, #e4e4e4 0%,#eeeeee 70%);
+  color: #333;
+  border: 1px solid #b4b4b4;
+  line-height: 13px;
+  padding: 3px 19px 3px 6px;
+  margin: 3px 0 3px 5px;
+  position: relative;
+
+}
+
+/* Selected item that's highlighted by mouseover. */
+div.mf_container ol.mf_list li.mf_item.mf_highlighted {
+  background-color: #E0E0E0;
+}
+
+/* Selected item that's selected by click or keyboard. */
+div.mf_container ol.mf_list li.mf_item.mf_selected {
+  background: #d4d4d4;
+}
+
+/* Remove link. */
+div.mf_container ol.mf_list li.mf_item a.mf_remove {
+  color: #E0E0E0;
+  margin-left: 10px;
+  text-decoration: none;
+  border-bottom: none;
+  background: url(chosen/chosen-sprite.png) right top no-repeat;
+  display: block;
+  position: absolute;
+  right: 3px;
+  top: 4px;
+  width: 12px;
+  height: 13px;
+  font-size: 1px;
+}
+
+div.mf_container ol.mf_list li.mf_item a.mf_remove:hover {
+  background-position: right -11px;
+}
+
+/* Remove link that's highlighted. */
+div.mf_container ol.mf_list li.mf_item.mf_highlighted a.mf_remove {
+  color: #FFFFFF;
+}
+
+/* Remove link that's selected. */
+div.mf_container ol.mf_list li.mf_item.mf_selected a.mf_remove {
+  color: #FFFFFF;
+  background-position: right -11px;
+}
+
+/* Actual input, styled to be invisible within the container. */
+div.mf_container input.mf_input {
+  border: 0;
+  font: inherit;
+  font-size: 100%;
+  margin: 2px;
+  outline: none;
+  padding: 4px;
+}
+
+/*** Marco Polo ***/
+
+/* Ordered list for display results. */
+ol.mp_list {
+  background-color: #FFFFFF;
+  border-left: 1px solid #C0C0C0;
+  border-right: 1px solid #C0C0C0;
+  overflow: hidden;
+  position: absolute;
+  width: 498px;
+  z-index: 99999;
+}
+
+/* Each list item, regardless of success, error, etc. */
+ol.mp_list li {
+  border-bottom: 1px solid #C0C0C0;
+  padding: 4px 4px 5px 9px;
+}
+
+/* Each list item from a successful request. */
+ol.mp_list li.mp_item {
+
+}
+
+/* Each list item that's selectable. */
+ol.mp_list li.mp_selectable {
+  cursor: pointer;
+}
+
+/* Currently highlighted list item. */
+ol.mp_list li.mp_highlighted {
+  background-color: #E0E0E0;
+}
+
+/* When a request is made that returns zero results. */
+ol.mp_list li.mp_no_results {
+
+}
+
+/* When a request is made that doesn't meet the 'minChars' length option. */
+ol.mp_list li.mp_min_chars {
+
+}
+
+/* When a request is made that fails during the ajax request. */
+ol.mp_list li.mp_error {
+
+}
   'jquery.markitup.js',
   'jquery.timeago.js',
   'json2.js',
-  'tag-it.js',
+  'jquery.marcopolo.min.js',
+  'jquery.manifest.js',
   'underscore-min.js',
   'backbone-min.js',
   'models.js',

web/js/jquery.manifest.js

+/**
+ * Manifest v@VERSION
+ *
+ * A jQuery plugin that adds delight to selecting multiple values for an input.
+ *
+ * https://github.com/jstayton/jquery-manifest
+ *
+ * Copyright 2011 by Justin Stayton
+ * Released under the MIT License
+ * http://en.wikipedia.org/wiki/MIT_License
+ */
+(function ($, undefined) {
+  'use strict';
+
+  // jQuery UI's Widget Factory provides an object-oriented plugin framework
+  // that handles the common plumbing tasks.
+  $.widget('mf.manifest', {
+    // Default options.
+    options: {
+      // Format the display of an item.
+      formatDisplay: function (data, $item, $mpItem) {
+        // If a Marco Polo item was selected, use the display HTML.
+        if ($mpItem) {
+          return $mpItem.html();
+        }
+        // Otherwise, assume the current input value is being added.
+        else {
+          return data;
+        }
+      },
+      // Format the display of the remove link included with each item.
+      formatRemove: function ($remove, $item) {
+        return 'X';
+      },
+      // Format the hidden value to be submitted for the item.
+      formatValue: function (data, $value, $item, $mpItem) {
+        // If a Marco Polo item was selected, use the display text.
+        if ($mpItem) {
+          return $mpItem.text();
+        }
+        // Otherwise, assume the current input value is being added.
+        else {
+          return data;
+        }
+      },
+      // Options to pass on to Marco Polo.
+      marcoPolo: false,
+      // Called when an item is added to the list. Return 'false' to prevent
+      // the item from being added.
+      onAdd: null,
+      // Called when an item is added or removed from the list.
+      onChange: null,
+      // Called when an item is highlighted via 'mouseover'.
+      onHighlight: null,
+      // Called when an item is no longer highlighted via 'mouseover'.
+      onHighlightRemove: null,
+      // Called when an item is removed from the list. Return 'false' to
+      // prevent the item from being removed.
+      onRemove: null,
+      // Called when an item is selected through keyboard navigation or click.
+      onSelect: null,
+      // Called when an item is no longer selected.
+      onSelectRemove: null,
+      // Whether to only allow items to be selected from the results list. If
+      // 'false', arbitrary, non-results-list values can be added when the
+      // 'separator' key character is pressed or the input is blurred.
+      required: false,
+      // Key character to separate arbitrary, non-results-list values if
+      // the 'required' option is 'false'. Pressing this will add the current
+      // input value to the list. Also used to split the initial input value
+      // for items.
+      separator: ',',
+      // One or more initial values (string or JSON) to add to the list.
+      values: null,
+      // Name of the hidden input value fields. Do not include '[]' at the end,
+      // as that will be added. If unset, the default is to add '_values[]' to
+      // the input name.
+      valuesName: null
+    },
+
+    // Marco Polo options required for this plugin to work.
+    _marcoPoloOptions: function () {
+      var self = this,
+          options = self.options;
+
+      return {
+        onFocus: function () {
+          // Allow for custom callback.
+          if (options.marcoPolo.onFocus) {
+            options.marcoPolo.onFocus.call(this);
+          }
+
+          self._resizeInput();
+        },
+        onRequestAfter: function () {
+          // Remove the "busy" indicator class on the container's parent.
+          self.$container.parent().removeClass('mf_busy');
+
+          // Allow for custom callback.
+          if (options.marcoPolo.onRequestAfter) {
+            options.marcoPolo.onRequestAfter.call(this);
+          }
+        },
+        onRequestBefore: function () {
+          // Add a class to the container's parent that can be hooked-into by
+          // the CSS to show a busy indicator.
+          self.$container.parent().addClass('mf_busy');
+
+          // Allow for custom callback.
+          if (options.marcoPolo.onRequestBefore) {
+            options.marcoPolo.onRequestBefore.call(this);
+          }
+        },
+        onSelect: function (mpData, $mpItem) {
+          // Allow for custom callback.
+          if (options.marcoPolo.onSelect) {
+            options.marcoPolo.onSelect.call(this, mpData, $mpItem);
+          }
+
+          // Add the selected Marco Polo item to the Manifest list.
+          self.add(mpData, $mpItem, true, true);
+        },
+        required: options.required
+      };
+    },
+
+    // Key code to key name mapping for easy reference.
+    keys: {
+      BACKSPACE: 8,
+      DELETE: 46,
+      ENTER: 13,
+      LEFT: 37,
+      RIGHT: 39
+    },
+
+    // Initialize the plugin on an input.
+    _create: function () {
+      var self = this,
+          options = self.options;
+
+      // Create a more appropriately named alias for the input.
+      self.$input = self.element.addClass('mf_input');
+
+      // Create a container to wrap together the input, list, and measure.
+      self.$container = $('<div class="mf_container" />');
+
+      // Create an empty list that items will be added to.
+      self.$list = $('<ol class="mf_list" />');
+
+      self.$measure = $('<span class="mf_measure" />');
+
+      // For keeping track of whether a 'mousedown' event has caused an input
+      // 'blur' event.
+      self.mousedown = false;
+      self.mpMousedown = false;
+
+      // Make note of the original input width in case 'destroy' is called.
+      self.originalWidth = self.$input.css('width');
+
+      if (options.marcoPolo) {
+        self._bindMarcoPolo();
+      }
+
+      self
+        ._bindInput()
+        ._bindList()
+        ._bindContainer()
+        ._bindDocument();
+
+      // Add the list and measure, and wrap them all in the container.
+      self.$input
+        .wrap(self.$container)
+        .before(self.$list)
+        .after(self.$measure);
+
+      // Because .wrap() only makes a copy of the wrapper, get the actual
+      // container that was inserted into the DOM.
+      self.$container = self.$input.parent();
+
+      // Add any initial values to the list.
+      if (options.values) {
+        self.add(options.values, null, false, false);
+      }
+
+      self
+        ._addInputValues()
+        ._styleMeasure()
+        ._resizeInput();
+    },
+
+    // Set an option.
+    _setOption: function (option, value) {
+      var self = this,
+          $input = self.$input;
+
+      switch (option) {
+        case 'marcoPolo':
+          // If Marco Polo is enabled, change the existing instance.
+          if (self.options.marcoPolo) {
+            if (value) {
+              // Pass changes on to Marco Polo, with the options required for
+              // this plugin to work overriding the custom options.
+              $input.marcoPolo('option', $.extend({}, value, self._marcoPoloOptions()));
+            }
+            else {
+              $input.marcoPolo('destroy');
+            }
+          }
+          // Otherwise, bind Marco Polo with the specified options.
+          else if (value) {
+            self._bindMarcoPolo($.extend({}, value, self._marcoPoloOptions()));
+
+            // Move the Marco Polo results list outside of and after the
+            // container. $input.parent() is used instead of self.$container
+            // due to the latter not knowing its location in the DOM.
+            $input.marcoPolo('list').insertAfter($input.parent());
+          }
+
+          break;
+
+        case 'required':
+          if (self.options.marcoPolo) {
+            $input.marcoPolo('option', 'required', value);
+          }
+
+          break;
+
+        case 'values':
+          // Although normally set on initialization, if this option is called
+          // later, append the values to the list just like the 'add' method.
+          self.add(value, null, false, true);
+
+          break;
+
+        case 'valuesName':
+          // Change the 'name' of all hidden input values currently added to
+          // the list.
+          self.$list
+            .find('input:hidden.mf_value')
+            .attr('name', value + '[]');
+
+          break;
+      }
+
+      // Required call to the parent where the new option value is saved.
+      $.Widget.prototype._setOption.apply(self, arguments);
+    },
+
+    // Bind the necessary events for Marco Polo.
+    _bindMarcoPolo: function (mpOptions) {
+      var self = this,
+          $input = self.$input,
+          options = self.options;
+
+      // Build the Marco Polo options from existing options if none are passed
+      // in. Options required for this plugin to work override custom options.
+      if (mpOptions === undefined) {
+        mpOptions = $.extend({}, options.marcoPolo, self._marcoPoloOptions());
+      }
+
+      $input.marcoPolo(mpOptions);
+
+      $input.marcoPolo('list').bind('mousedown.manifest', function () {
+        // For use in input 'blur' and document 'mouseup' to make sure the
+        // current input value is either added or removed.
+        self.mpMousedown = true;
+      });
+
+      return self;
+    },
+
+    // Bind the necessary events to the input.
+    _bindInput: function () {
+      var self = this,
+          $input = self.$input,
+          options = self.options;
+
+      $input
+        .bind('keydown.manifest', function (key) {
+          self._resizeInput();
+
+          // Prevent the form from submitting on enter.
+          if (key.which === self.keys.ENTER) {
+            key.preventDefault();
+          }
+
+          // Keyboard navigation only works without an input value.
+          if ($input.val()) {
+            return;
+          }
+
+          switch (key.which) {
+            // Remove the selected item.
+            case self.keys.BACKSPACE:
+            case self.keys.DELETE:
+              var $selected = self._selected();
+
+              if ($selected.length) {
+                self.remove($selected);
+              }
+              else {
+                self._selectPrev();
+              }
+
+              break;
+
+            // Select the previous item.
+            case self.keys.LEFT:
+              self._selectPrev();
+
+              break;
+
+            // Select the next item.
+            case self.keys.RIGHT:
+              self._selectNext();
+
+              break;
+
+            // Any other key removes the selected state from the current item.
+            default:
+              self._removeSelected();
+
+              break;
+          }
+        })
+        .bind('keypress.manifest', function (key) {
+          // If arbitrary values are allowed, add the current input value if
+          // the separator key is pressed.
+          if (!options.required && key.which === options.separator.charCodeAt()) {
+            // Prevent the separator key character from being added to the
+            // input value.
+            key.preventDefault();
+
+            // Add the current input value if there is any.
+            if ($input.val()) {
+              self.add($input.val(), null, true, true);
+            }
+          }
+        })
+        .bind('keyup.manifest', function (key) {
+          self._resizeInput();
+        })
+        .bind('blur.manifest', function () {
+          // 1ms timeout ensures that whatever 'mousedown' event caused this
+          // 'blur' event fires first.
+          setTimeout(function () {
+            // If a list item 'mousedown' event did not cause this 'blur'
+            // event, make sure nothing is left selected.
+            if (!self.mousedown) {
+              self._removeSelected();
+            }
+
+            // If a Marco Polo list 'mousedown' event did not cause this 'blur'
+            // event...
+            if (!self.mpMousedown) {
+              // Marco Polo will clear the input value, requiring a resize.
+              if (options.marcoPolo && options.required) {
+                self._resizeInput();
+              }
+              // Add the input value since arbitrary values are allowed.
+              else if ($input.val()) {
+                self.add($input.val(), null, true, true);
+              }
+            }
+          }, 1);
+        });
+
+      return self;
+    },
+
+    // Bind the necessary events to the list.
+    _bindList: function () {
+      var self = this;
+
+      self.$list
+        .delegate('li', 'mouseover', function () {
+          self._addHighlight($(this));
+        })
+        .delegate('li', 'mouseout', function () {
+          self._removeHighlight($(this));
+        })
+        .delegate('li', 'mousedown', function () {
+          // For use in input 'blur' and document 'mouseup' to make sure
+          // nothing is left selected if this 'mousedown' ends elsewhere.
+          self.mousedown = true;
+        })
+        .delegate('li', 'click', function () {
+          self._toggleSelect($(this));
+        })
+        .delegate('a.mf_remove', 'click', function (event) {
+          self.remove($(this).closest('li'));
+
+          event.preventDefault();
+        });
+
+      return self;
+    },
+
+    // Bind the necessary events to the container.
+    _bindContainer: function () {
+      var self = this;
+
+      // Focus on the input if a click happens anywhere on the container.
+      self.$container.bind('click.manifest', function () {
+        self.$input.focus();
+      });
+
+      return self;
+    },
+
+    // Bind the necessary events to the document.
+    _bindDocument: function () {
+      var self = this,
+          $input = self.$input;
+
+      $(document).bind('mouseup.manifest', function (event) {
+        // If a 'mousedown' event starts on a list item, but ends somewhere
+        // else, make sure nothing is left selected.
+        if (self.mousedown) {
+          self.mousedown = false;
+
+          if (!$(event.target).is('li.mf_item, li.mf_item *')) {
+            self._removeSelected();
+          }
+        }
+
+        // If a 'mousedown' event starts on a Marco Polo list item, but ends
+        // somewhere else, add the current input value if arbitrary values are
+        // allowed.
+        if (self.mpMousedown) {
+          self.mpMousedown = false;
+
+          if (self.options.required) {
+            self._resizeInput();
+          }
+          else if ($input.val()) {
+            self.add($input.val(), null, true, true);
+          }
+        }
+      });
+
+      return self;
+    },
+
+    // Get the container element.
+    container: function () {
+      return this.$container;
+    },
+
+    // Get the list element.
+    list: function () {
+      return this.$list;
+    },
+
+    // Add one or more items to the end of the list.
+    add: function (data, $mpItem, clearInputValue, triggerChange) {
+      var self = this,
+          $input = self.$input,
+          options = self.options,
+          // If only a single item is being added, normalize to an array.
+          values = $.isArray(data) ? data : [data],
+          value,
+          $item = $(),
+          $remove = $(),
+          $value = $(),
+          add = true;
+
+      for (var i = 0; i < values.length; i++) {
+        value = values[i];
+
+        // Trim extra spaces, tabs, and newlines from the beginning and end of
+        // the value if it's a string.
+        if (typeof value === 'string') {
+          value = $.trim(value);
+        }
+
+        // Don't add if the value is an empty string or object.
+        if (!value || ($.isPlainObject(value) && $.isEmptyObject(value))) {
+          continue;
+        }
+
+        $item = $('<li class="mf_item" />');
+        $remove = $('<a href="#" class="mf_remove" title="Remove" />');
+        $value = $('<input type="hidden" class="mf_value" />');
+
+        // Store the data with the item for easy access.
+        $item.data('manifest', value);
+
+        // Format the HTML display of the item.
+        $item.html(options.formatDisplay.call($input, value, $item, $mpItem));
+
+        // Format the HTML display of the remove link.
+        $remove.html(options.formatRemove.call($input, $remove, $item));
+
+        if (options.valuesName) {
+          $value.attr('name', options.valuesName + '[]');
+        }
+        // If no custom 'name' is set for the hidden input values, append
+        // '_values[]' to the input name as the default.
+        else {
+          $value.attr('name', $input.attr('name') + '_values[]');
+        }
+
+        // Format the hidden value to be submitted for the item.
+        $value.val(options.formatValue.call($input, value, $value, $item, $mpItem));
+
+        // Append the remove link and hidden values after the display elements of
+        // the item.
+        $item.append($remove, $value);
+
+        add = self._trigger('add', [value, $item]);
+
+        if (add !== false) {
+          $item.appendTo(self.$list);
+
+          // Sometimes an 'onChange' event shouldn't be fired, like when
+          // initial values are added.
+          if (triggerChange || triggerChange === undefined) {
+            self._trigger('change', ['add', value, $item]);
+          }
+        }
+      }
+
+      if (clearInputValue) {
+        self._clearInputValue();
+      }
+    },
+
+    // Add the input values specified in the 'data-values' attribute (JSON) or
+    // standard 'value' attribute (string split with 'separator').
+    _addInputValues: function () {
+      var self = this,
+          $input = self.$input,
+          data = $input.data('values'),
+          value = $input.val(),
+          values = [];
+
+      // First check if the input has a 'data-values' attribute with JSON.
+      if (data) {
+        values = $.isArray(data) ? data : [data];
+      }
+      // If not, split the current input value with the 'separator'.
+      else if (value) {
+        values = value.split(self.options.separator);
+        values = $.map(values, $.trim);
+      }
+
+      if (values.length) {
+        self.add(values, null, true, false);
+      }
+
+      return self;
+    },
+
+    // Remove one or more list items, specifying either jQuery objects or a
+    // selector that matches list children.
+    remove: function (selector) {
+      var self = this,
+          $input = self.$input,
+          $items = $();
+
+      // If the selector is already a jQuery object (or objects), use that.
+      if (selector instanceof jQuery) {
+        $items = selector;
+      }
+      // Otherwise, query for the items to remove based on the selector.
+      else {
+        $items = self.$list.children(selector);
+      }
+
+      $items.each(function () {
+        var $item = $(this),
+            data = $item.data('manifest'),
+            remove = true;
+
+        remove = self._trigger('remove', [data, $item]);
+
+        if (remove !== false) {
+          $item.remove();
+
+          self._trigger('change', ['remove', data, $item]);
+        }
+      });
+    },
+
+    // Get an array of the current values.
+    values: function () {
+      var self = this,
+          $list = self.$list,
+          values = [];
+
+      $list.find('input:hidden.mf_value').each(function () {
+        values.push($(this).val());
+      });
+
+      return values;
+    },
+
+    // Remove the elements, events, and functionality of this plugin and return
+    // the input to its original state.
+    destroy: function () {
+      var self = this,
+          valuesString = self.values().join(self.options.separator + ' ');
+
+      // Destroy Marco Polo.
+      if (self.options.marcoPolo) {
+        self.$input.marcoPolo('destroy');
+      }
+
+      self.$list.remove();
+      self.$measure.remove();
+      self.$input
+        .unwrap()
+        .removeClass('mf_input')
+        // Set the input back to its original width.
+        .width(self.originalWidth)
+        // Join the added item values together and set as the input value.
+        .val(valuesString);
+
+      $(document).unbind('.manifest');
+
+      // Parent destroy removes the input's data and events.
+      $.Widget.prototype.destroy.apply(self, arguments);
+    },
+
+    // Style the measure to match the text style of the input, so that text
+    // measurements are pixel precise.
+    _styleMeasure: function () {
+      var self = this,
+          $input = self.$input;
+
+      self.$measure.css({
+        fontFamily: $input.css('font-family'),
+        fontSize: $input.css('font-size'),
+        fontStyle: $input.css('font-style'),
+        fontVariant: $input.css('font-variant'),
+        fontWeight: $input.css('font-weight'),
+        left: -9999,
+        letterSpacing: $input.css('letter-spacing'),
+        position: 'absolute',
+        textTransform: $input.css('text-transform'),
+        top: -9999,
+        whiteSpace: 'nowrap',
+        width: 'auto',
+        wordSpacing: $input.css('word-spacing')
+      });
+
+      return self;
+    },
+
+    // Measure the width of a string of text in the style of the input.
+    _measureText: function (text) {
+      var $measure = this.$measure,
+          escapedText;
+
+      // Escape certain HTML special characters.
+      escapedText = text
+                      .replace(/&/g, '&amp;')
+                      .replace(/\s/g, '&nbsp;')
+                      .replace(/</g, '&lt;')
+                      .replace(/>/g, '&gt;');
+
+      $measure.html(escapedText);
+
+      return $measure.width();
+    },
+
+    // Get the maximum width the input can be resized to without overflowing
+    // the container. Takes into account the input's margin, border, padding.
+    _maxInputWidth: function () {
+      var self = this,
+          $container = self.$container,
+          $input = self.$input;
+
+      return $container.width() - ($input.outerWidth(true) - $input.width());
+    },
+
+    // Resize the input to fit the current value with space for the next
+    // character.
+    _resizeInput: function () {
+      var self = this,
+          $input = self.$input,
+          textWidth;
+
+      // '---' adds enough space for whatever the next character may be.
+      textWidth = self._measureText($input.val() + '---');
+
+      // Set the input's width to the size of the text, making sure not to set
+      // it wider than the container.
+      $input.width(Math.min(textWidth, self._maxInputWidth()));
+
+      return self;
+    },
+
+    // Clear the current value of the input.
+    _clearInputValue: function () {
+      var self = this,
+          $input = self.$input;
+
+      // If Marco Polo is enabled, use its method to change the input value.
+      if (self.options.marcoPolo) {
+        $input.marcoPolo('change', '');
+      }
+      else {
+        $input.val('');
+      }
+
+      self._resizeInput();
+
+      return self;
+    },
+
+    // Get the currently highlighted item.
+    _highlighted: function () {
+      return this.$list.children('li.mf_highlighted');
+    },
+
+    // Add the highlighted state to the specified item.
+    _addHighlight: function ($item) {
+      var self = this;
+
+      if (!$item.length) {
+        return self;
+      }
+
+      // The current highlight is removed to ensure that only one item is
+      // highlighted at a time.
+      self._removeHighlighted();
+
+      $item.addClass('mf_highlighted');
+
+      self._trigger('highlight', [$item.data('marcoPolo'), $item]);
+
+      return self;
+    },
+
+    // Remove the highlighted state from the specified item.
+    _removeHighlight: function ($item) {
+      var self = this;
+
+      if (!$item.length) {
+        return self;
+      }
+
+      $item.removeClass('mf_highlighted');
+
+      self._trigger('highlightRemove', [$item.data('marcoPolo'), $item]);
+
+      return self;
+    },
+
+    // Remove the highlighted state from the currently highlighted item.
+    _removeHighlighted: function () {
+      return this._removeHighlight(this._highlighted());
+    },
+
+    // Get the currently selected item.
+    _selected: function () {
+      return this.$list.children('li.mf_selected');
+    },
+
+    // Add the selected state to the specified item.
+    _addSelect: function ($item) {
+      var self = this;
+
+      if (!$item.length) {
+        return self;
+      }
+
+      // The current selection is removed to ensure that only one item is
+      // selected at a time.
+      self._removeSelected();
+
+      $item.addClass('mf_selected');
+
+      self._trigger('select', [$item.data('marcoPolo'), $item]);
+
+      return self;
+    },
+
+    // Remove the selected state from the specified item.
+    _removeSelect: function ($item) {
+      var self = this;
+
+      if (!$item.length) {
+        return self;
+      }
+
+      $item.removeClass('mf_selected');
+
+      self._trigger('selectRemove', [$item.data('marcoPolo'), $item]);
+
+      return self;
+    },
+
+    // Remove the selected state from the currently selected item.
+    _removeSelected: function () {
+      return this._removeSelect(this._selected());
+    },
+
+    // Toggle the selection of the specified item.
+    _toggleSelect: function ($item) {
+      if ($item.hasClass('mf_selected')) {
+        return this._removeSelect($item);
+      }
+      else {
+        return this._addSelect($item);
+      }
+    },
+
+    // Select the item before the currently selected item.
+    _selectPrev: function () {
+      var self = this,
+          $selected = self._selected(),
+          $prev = $();
+
+      // Select the previous item if there's a current selection.
+      if ($selected.length) {
+        $prev = $selected.prev();
+      }
+      // Select the last item added to the list if not.
+      else {
+        $prev = self.$list.children(':last');
+      }
+
+      // Only change the current selection if there's a previous item. If the
+      // first item in the list is the current selection, it remains selected.
+      if ($prev.length) {
+        self._addSelect($prev);
+      }
+
+      return self;
+    },
+
+    // Select the item after the currently selected item.
+    _selectNext: function () {
+      var self = this,
+          $selected = self._selected(),
+          $next = $selected.next();
+
+      if ($next.length) {
+        return self._addSelect($next);
+      }
+      // If there's nothing after the currently selected item, remove the
+      // current selection, leaving nothing selected.
+      else {
+        return self._removeSelect($selected);
+      }
+    },
+
+    // Trigger a callback subscribed to via an option or using .bind().
+    _trigger: function (name, args) {
+      var self = this,
+          callbackName = 'on' + name.charAt(0).toUpperCase() + name.slice(1),
+          triggerName = self.widgetEventPrefix.toLowerCase() + name.toLowerCase(),
+          callback = self.options[callbackName];
+
+      self.element.trigger(triggerName, args);
+
+      return callback && callback.apply(self.element, args);
+    }
+  });
+})(jQuery);

web/js/jquery.marcopolo.min.js

+/**
+ * Marco Polo v1.3.2
+ *
+ * A jQuery autocomplete plugin for the discerning developer.
+ *
+ * https://github.com/jstayton/jquery-marcopolo
+ *
+ * Copyright 2011 by Justin Stayton
+ * Released under the MIT License
+ * http://en.wikipedia.org/wiki/MIT_License
+ */
+(function(f){var j={};f.widget("mp.marcoPolo",{options:{cache:!0,compare:!1,data:{},delay:250,formatData:null,formatError:function(){return"<em>Your search could not be completed at this time.</em>"},formatItem:function(a){return a.title||a.name},formatMinChars:function(a){return"<em>Your search must be at least <strong>"+a+"</strong> characters.</em>"},formatNoResults:function(a){return"<em>No results for <strong>"+a+"</strong>.</em>"},hideOnSelect:!0,label:null,minChars:1,onChange:null,onError:null,
+onFocus:null,onMinChars:null,onNoResults:null,onRequestBefore:null,onRequestAfter:null,onResults:null,onSelect:function(a){this.val(a.title||a.name)},param:"q",required:!1,selectable:"*",selected:null,url:null},keys:{DOWN:40,ENTER:13,ESC:27,UP:38},_create:function(){this.$input=this.element.addClass("mp_input");this.$list=f('<ol class="mp_list" />').hide().insertAfter(this.$input);this.autocomplete=this.$input.attr("autocomplete");this.$input.attr("autocomplete","off");this.ajax=null;this.ajaxAborted=
+!1;this.documentMouseup=null;this.mousedown=this.focusReal=this.focusPseudo=!1;this.selected=null;this.selectedMouseup=!1;this.timer=null;this.value=this.$input.val();this._bindInput()._bindList()._bindDocument();this._initOptions()},_setOption:function(a,b){f.Widget.prototype._setOption.apply(this,arguments);this._initOptions(a,b)},_initOptions:function(a,b){var d=this,c={};typeof a==="undefined"?c=d.options:c[a]=b;f.each(c,function(a,b){switch(a){case "label":d.options.label=f(b).addClass("mp_label");
+d._toggleLabel();break;case "selected":d._select(b,null);break;case "url":if(!b)d.options.url=d.$input.closest("form").attr("action")}});return d},change:function(a){a!==this.value&&(this.$input.val(a),this._change(a),this.focusPseudo?this._cancelPendingRequest()._hideAndEmptyList():this._toggleLabel())},search:function(a){var b=this.$input;typeof a!=="undefined"&&b.val(a);b.focus()},destroy:function(){var a=this.options;this.$list.remove();this.autocomplete!=="off"&&this.$input.removeAttr("autocomplete");
+this.$input.removeClass("mp_input");a.label&&a.label.removeClass("mp_label");f(document).unbind("mouseup.marcoPolo",this.documentMouseup);f.Widget.prototype.destroy.apply(this,arguments)},list:function(){return this.$list},_bindInput:function(){var a=this,b=a.$input,d=a.$list;b.bind("focus.marcoPolo",function(){if(!a.focusReal)a.focusPseudo=!0,a.focusReal=!0,a._toggleLabel(),a.selectedMouseup?a.selectedMouseup=!1:(a._trigger("focus"),a._request(b.val()))}).bind("keydown.marcoPolo",function(b){var e=
+f();switch(b.which){case a.keys.UP:b.preventDefault();a._showList()._highlightPrev();break;case a.keys.DOWN:b.preventDefault();a._showList()._highlightNext();break;case a.keys.ENTER:b.preventDefault();if(!d.is(":visible"))break;e=a._highlighted();e.length&&a._select(e.data("marcoPolo"),e);break;case a.keys.ESC:a._cancelPendingRequest()._hideList()}}).bind("keyup.marcoPolo",function(){b.val()!==a.value&&a._request(b.val())}).bind("blur.marcoPolo",function(){a.focusReal=!1;setTimeout(function(){if(!a.mousedown)a.focusPseudo=
+!1,a._dismiss()},1)});return a},_bindList:function(){var a=this;a.$list.bind("mousedown.marcoPolo",function(){a.mousedown=!0}).delegate("li.mp_selectable","mouseover",function(){a._addHighlight(f(this))}).delegate("li.mp_selectable","mouseout",function(){a._removeHighlight(f(this))}).delegate("li.mp_selectable","mouseup",function(){var b=f(this);a._select(b.data("marcoPolo"),b);a.selectedMouseup=!0;a.$input.focus()});return a},_bindDocument:function(){var a=this;f(document).bind("mouseup.marcoPolo",
+a.documentMouseup=function(){a.mousedown=!1;if(!a.focusReal&&a.$list.is(":visible"))a.focusPseudo=!1,a._dismiss()});return a},_toggleLabel:function(){var a=this.options.label;a.length&&(this.focusPseudo||this.$input.val()?a.hide():a.show());return this},_firstSelectableItem:function(){return this.$list.children("li.mp_selectable:visible:first")},_lastSelectableItem:function(){return this.$list.children("li.mp_selectable:visible:last")},_highlighted:function(){return this.$list.children("li.mp_highlighted")},
+_removeHighlight:function(a){a.removeClass("mp_highlighted");return this},_addHighlight:function(a){this._removeHighlight(this._highlighted());a.addClass("mp_highlighted");return this},_highlightFirst:function(){this._addHighlight(this._firstSelectableItem());return this},_highlightPrev:function(){var a=this._highlighted().prevAll("li.mp_selectable:visible:first");a.length||(a=this._lastSelectableItem());this._addHighlight(a);return this},_highlightNext:function(){var a=this._highlighted().nextAll("li.mp_selectable:visible:first");
+a.length||(a=this._firstSelectableItem());this._addHighlight(a);return this},_showList:function(){var a=this.$list;a.children().length&&a.show();return this},_hideList:function(){this.$list.hide();return this},_hideAndEmptyList:function(){this.$list.hide().empty();return this},_buildNoResultsList:function(a){var b=this.$input,d=this.$list,c=this.options,e=f('<li class="mp_no_results" />');(b=c.formatNoResults&&c.formatNoResults.call(b,a,e))&&e.html(b);this._trigger("noResults",[a,e]);b?(e.appendTo(d),
+this._showList()):this._hideList();return this},_buildResultsList:function(a,b){for(var d=this.$input,c=this.$list,e=this.options,h=this.selected,g=e.compare&&h,i,k,m=!1,l=f(),j=0;b[j];j++)i=b[j],l=f('<li class="mp_item" />'),k=e.formatItem.call(d,i,l),l.data("marcoPolo",i),l.html(k).appendTo(c),g&&(e.compare===!0?k=h:(i=i[e.compare],k=h[e.compare]),i===k&&(this._addHighlight(l),g=!1,m=!0));c.children(e.selectable).addClass("mp_selectable");this._trigger("results",[b]);this._showList();m||this._highlightFirst();
+return this},_buildSuccessList:function(a,b){var d=this.$input,c=this.options;this.$list.empty();c.formatData&&(b=c.formatData.call(d,b));f.isEmptyObject(b)?this._buildNoResultsList(a):this._buildResultsList(a,b);return this},_buildErrorList:function(a,b,d){var c=this.$input,e=this.$list,h=this.options,g=f('<li class="mp_error" />');e.empty();(c=h.formatError&&h.formatError.call(c,g,a,b,d))&&g.html(c);this._trigger("error",[g,a,b,d]);c?(g.appendTo(e),this._showList()):this._hideList();return this},
+_buildMinCharsList:function(a){var b=this.$input,d=this.$list,c=this.options,e=f('<li class="mp_min_chars" />');if(!a.length)return this._hideAndEmptyList(),this;d.empty();(a=c.formatMinChars&&c.formatMinChars.call(b,c.minChars,e))&&e.html(a);this._trigger("minChars",[c.minChars,e]);a?(e.appendTo(d),this._showList()):this._hideList();return this},_cancelPendingRequest:function(){this.ajax?(this.ajaxAborted=!0,this.ajax.abort()):this.ajaxAborted=!1;clearTimeout(this.timer);return this},_change:function(a){this.selected=
+null;this.value=a;this._trigger("change",[a]);return this},_request:function(a){var b=this,d=b.$input,c=b.options;b._cancelPendingRequest();a!==b.value&&b._change(a);b.timer=setTimeout(function(){var e={},h={},g,i=f();if(a.length<c.minChars)return b._buildMinCharsList(a),b;e[c.param]=a;h=f.extend({},c.data,e);g=c.url+(c.url.indexOf("?")===-1?"?":"&")+f.param(h);c.cache&&j[g]?b._buildSuccessList(a,j[g]):(b._trigger("requestBefore"),i=d.parent().addClass("mp_busy"),b.ajax=f.ajax({url:c.url,dataType:"json",
+data:h,success:function(c){b._buildSuccessList(a,c);j[g]=c},error:function(a,c,d){b.ajaxAborted||b._buildErrorList(a,c,d)},complete:function(a,c){b.ajax=null;b.ajaxAborted=!1;i.removeClass("mp_busy");b._trigger("requestAfter",[a,c])}}))},c.delay);return b},_select:function(a,b){var d=this.$input,c=this.options.hideOnSelect;this.selected=a;if(!a)return this;c&&this._hideList();this._trigger("select",[a,b]);if(d.val()!==this.value)this.value=d.val(),this._hideAndEmptyList();return this},_dismiss:function(){var a=
+this.$input,b=this.options;this._cancelPendingRequest()._hideAndEmptyList();b.required&&!this.selected&&(a.val(""),this._change(""));this._toggleLabel();return this},_trigger:function(a,b){var d="on"+a.charAt(0).toUpperCase()+a.slice(1),c=this.widgetEventPrefix.toLowerCase()+a.toLowerCase(),d=this.options[d];this.element.trigger(c,b);return d&&d.apply(this.element,b)}})})(jQuery);

web/js/tag-it.js

-/*
-* jQuery UI Tag-it!
-*
-* @version v2.0 (06/2011)
-*
-* Copyright 2011, Levy Carneiro Jr.
-* Released under the MIT license.
-* http://aehlke.github.com/tag-it/LICENSE
-*
-* Homepage:
-*   http://aehlke.github.com/tag-it/
-*
-* Authors:
-*   Levy Carneiro Jr.
-*   Martin Rehfeld
-*   Tobias Schmidt
-*   Skylar Challand
-*   Alex Ehlke
-*
-* Maintainer:
-*   Alex Ehlke - Twitter: @aehlke
-*
-* Dependencies:
-*   jQuery v1.4+
-*   jQuery UI v1.8+
-*/
-(function($) {
-
-    $.widget('ui.tagit', {
-        options: {
-            itemName          : 'item',
-            fieldName         : 'tags',
-            availableTags     : [],
-            tagSource         : null,
-            removeConfirmation: false,
-            caseSensitive     : true,
-
-            // When enabled, quotes are not neccesary
-            // for inputting multi-word tags.
-            allowSpaces: false,
-
-            // Whether to animate tag removals or not.
-            animate: true,
-
-            // The below options are for using a single field instead of several
-            // for our form values.
-            //
-            // When enabled, will use a single hidden field for the form,
-            // rather than one per tag. It will delimit tags in the field
-            // with singleFieldDelimiter.
-            //
-            // The easiest way to use singleField is to just instantiate tag-it
-            // on an INPUT element, in which case singleField is automatically
-            // set to true, and singleFieldNode is set to that element. This 
-            // way, you don't need to fiddle with these options.
-            singleField: false,
-
-            singleFieldDelimiter: ',',
-
-            // Set this to an input DOM node to use an existing form field.
-            // Any text in it will be erased on init. But it will be
-            // populated with the text of tags as they are created,
-            // delimited by singleFieldDelimiter.
-            //
-            // If this is not set, we create an input node for it,
-            // with the name given in settings.fieldName, 
-            // ignoring settings.itemName.
-            singleFieldNode: null,
-
-            // Optionally set a tabindex attribute on the input that gets
-            // created for tag-it.
-            tabIndex: null,
-
-
-            // Event callbacks.
-            onTagAdded  : null,
-            onTagRemoved: null,
-            onTagClicked: null
-        },
-
-
-        _create: function() {
-            // for handling static scoping inside callbacks
-            var that = this;
-
-            // There are 2 kinds of DOM nodes this widget can be instantiated on:
-            //     1. UL, OL, or some element containing either of these.
-            //     2. INPUT, in which case 'singleField' is overridden to true,
-            //        a UL is created and the INPUT is hidden.
-            if (this.element.is('input')) {
-                this.tagList = $('<ul></ul>').insertAfter(this.element);
-                this.options.singleField = true;
-                this.options.singleFieldNode = this.element;
-                this.element.css('display', 'none');
-            } else {
-                this.tagList = this.element.find('ul, ol').andSelf().last();
-            }
-
-            this._tagInput = $('<input type="text" />').addClass('ui-widget-content');
-            if (this.options.tabIndex) {
-                this._tagInput.attr('tabindex', this.options.tabIndex);
-            }
-
-            this.options.tagSource = this.options.tagSource || function(search, showChoices) {
-                var filter = search.term.toLowerCase();
-                var choices = $.grep(this.options.availableTags, function(element) {
-                    // Only match autocomplete options that begin with the search term.
-                    // (Case insensitive.)
-                    return (element.toLowerCase().indexOf(filter) === 0);
-                });
-                showChoices(this._subtractArray(choices, this.assignedTags()));
-            };
-
-            // Bind tagSource callback functions to this context.
-            if ($.isFunction(this.options.tagSource)) {
-                this.options.tagSource = $.proxy(this.options.tagSource, this);
-            }
-
-            this.tagList
-                .addClass('tagit')
-                .addClass('ui-widget ui-widget-content ui-corner-all')
-                // Create the input field.
-                .append($('<li class="tagit-new"></li>').append(this._tagInput))
-                .click(function(e) {
-                    var target = $(e.target);
-                    if (target.hasClass('tagit-label')) {
-                        that._trigger('onTagClicked', e, target.closest('.tagit-choice'));
-                    } else {
-                        // Sets the focus() to the input field, if the user
-                        // clicks anywhere inside the UL. This is needed
-                        // because the input field needs to be of a small size.
-                        that._tagInput.focus();
-                    }
-                });
-
-            // Add existing tags from the list, if any.
-            this.tagList.children('li').each(function() {
-                if (!$(this).hasClass('tagit-new')) {
-                    that.createTag($(this).html(), $(this).attr('class'));
-                    $(this).remove();
-                }
-            });
-
-            // Single field support.
-            if (this.options.singleField) {
-                if (this.options.singleFieldNode) {
-                    // Add existing tags from the input field.
-                    var node = $(this.options.singleFieldNode);
-                    var tags = node.val().split(this.options.singleFieldDelimiter);
-                    node.val('');
-                    $.each(tags, function(index, tag) {
-                        that.createTag(tag);
-                    });
-                } else {
-                    // Create our single field input after our list.
-                    this.options.singleFieldNode = this.tagList.after('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />');
-                }
-            }
-
-            // Events.
-            this._tagInput
-                .keydown(function(event) {
-                    // Backspace is not detected within a keypress, so it must use keydown.
-                    if (event.which == $.ui.keyCode.BACKSPACE && that._tagInput.val() === '') {
-                        var tag = that._lastTag();
-                        if (!that.options.removeConfirmation || tag.hasClass('remove')) {
-                            // When backspace is pressed, the last tag is deleted.
-                            that.removeTag(tag);
-                        } else if (that.options.removeConfirmation) {
-                            tag.addClass('remove ui-state-highlight');
-                        }
-                    } else if (that.options.removeConfirmation) {
-                        that._lastTag().removeClass('remove ui-state-highlight');
-                    }
-
-                    // Comma/Space/Enter are all valid delimiters for new tags,
-                    // except when there is an open quote or if setting allowSpaces = true.
-                    // Tab will also create a tag, unless the tag input is empty, in which case it isn't caught.
-                    if (
-                        event.which == $.ui.keyCode.COMMA ||
-                        event.which == $.ui.keyCode.ENTER ||
-                        (
-                            event.which == $.ui.keyCode.TAB &&
-                            that._tagInput.val() !== ''
-                        ) ||
-                        (
-                            event.which == $.ui.keyCode.SPACE &&
-                            that.options.allowSpaces !== true &&
-                            (
-                                $.trim(that._tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
-                                (
-                                    $.trim(that._tagInput.val()).charAt(0) == '"' &&
-                                    $.trim(that._tagInput.val()).charAt($.trim(that._tagInput.val()).length - 1) == '"' &&
-                                    $.trim(that._tagInput.val()).length - 1 !== 0
-                                )
-                            )
-                        )
-                    ) {
-                        event.preventDefault();
-                        that.createTag(that._cleanedInput());
-
-                        // The autocomplete doesn't close automatically when TAB is pressed.
-                        // So let's ensure that it closes.
-                        that._tagInput.autocomplete('close');
-                    }
-                }).blur(function(e){
-                    // Create a tag when the element loses focus (unless it's empty).
-                    that.createTag(that._cleanedInput());
-                });
-                
-
-            // Autocomplete.
-            if (this.options.availableTags || this.options.tagSource) {
-                this._tagInput.autocomplete({
-                    source: this.options.tagSource,
-                    select: function(event, ui) {
-                        // Delete the last tag if we autocomplete something despite the input being empty
-                        // This happens because the input's blur event causes the tag to be created when
-                        // the user clicks an autocomplete item.
-                        // The only artifact of this is that while the user holds down the mouse button
-                        // on the selected autocomplete item, a tag is shown with the pre-autocompleted text,
-                        // and is changed to the autocompleted text upon mouseup.
-                        if (that._tagInput.val() === '') {
-                            that.removeTag(that._lastTag(), false);
-                        }
-                        that.createTag(ui.item.value);
-                        // Preventing the tag input to be updated with the chosen value.
-                        return false;
-                    }
-                });
-            }
-        },
-
-        _cleanedInput: function() {
-            // Returns the contents of the tag input, cleaned and ready to be passed to createTag
-            return $.trim(this._tagInput.val().replace(/^"(.*)"$/, '$1'));
-        },
-
-        _lastTag: function() {
-            return this.tagList.children('.tagit-choice:last');
-        },
-
-        assignedTags: function() {
-            // Returns an array of tag string values
-            var that = this;
-            var tags = [];
-            if (this.options.singleField) {
-                tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
-                if (tags[0] === '') {
-                    tags = [];
-                }
-            } else {
-                this.tagList.children('.tagit-choice').each(function() {
-                    tags.push(that.tagLabel(this));
-                });
-            }
-            return tags;
-        },
-
-        _updateSingleTagsField: function(tags) {
-            // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter
-            $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter));
-        },
-
-        _subtractArray: function(a1, a2) {
-            var result = [];
-            for (var i = 0; i < a1.length; i++) {
-                if ($.inArray(a1[i], a2) == -1) {
-                    result.push(a1[i]);
-                }
-            }
-            return result;
-        },
-
-        tagLabel: function(tag) {
-            // Returns the tag's string label.
-            if (this.options.singleField) {
-                return $(tag).children('.tagit-label').text();
-            } else {
-                return $(tag).children('input').val();
-            }
-        },
-
-        _isNew: function(value) {
-            var that = this;
-            var isNew = true;
-            this.tagList.children('.tagit-choice').each(function(i) {
-                if (that._formatStr(value) == that._formatStr(that.tagLabel(this))) {
-                    isNew = false;
-                    return false;
-                }
-            });
-            return isNew;
-        },
-
-        _formatStr: function(str) {
-            if (this.options.caseSensitive) {
-                return str;
-            }
-            return $.trim(str.toLowerCase());
-        },
-
-        createTag: function(value, additionalClass) {
-            var that = this;
-            // Automatically trims the value of leading and trailing whitespace.
-            value = $.trim(value);
-
-            if (!this._isNew(value) || value === '') {
-                return false;
-            }
-
-            var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);
-
-            // Create tag.
-            var tag = $('<li></li>')
-                .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
-                .addClass(additionalClass)
-                .append(label);
-
-            // Button for removing the tag.
-            var removeTagIcon = $('<span></span>')
-                .addClass('ui-icon ui-icon-close');
-            var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X
-                .addClass('tagit-close')
-                .append(removeTagIcon)
-                .click(function(e) {
-                    // Removes a tag when the little 'x' is clicked.
-                    that.removeTag(tag);
-                });
-            tag.append(removeTag);
-
-            // Unless options.singleField is set, each tag has a hidden input field inline.
-            if (this.options.singleField) {
-                var tags = this.assignedTags();
-                tags.push(value);
-                this._updateSingleTagsField(tags);
-            } else {
-                var escapedValue = label.html();
-                tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.itemName + '[' + this.options.fieldName + '][]" />');
-            }
-
-            this._trigger('onTagAdded', null, tag);
-
-            // Cleaning the input.
-            this._tagInput.val('');
-
-            // insert tag
-            this._tagInput.parent().before(tag);
-        },
-        
-        removeTag: function(tag, animate) {
-            animate = animate || this.options.animate;
-
-            tag = $(tag);
-
-            this._trigger('onTagRemoved', null, tag);
-
-            if (this.options.singleField) {
-                var tags = this.assignedTags();
-                var removedTagLabel = this.tagLabel(tag);
-                tags = $.grep(tags, function(el){
-                    return el != removedTagLabel;
-                });
-                this._updateSingleTagsField(tags);
-            }
-            // Animate the removal.
-            if (animate) {
-                tag.fadeOut('fast').hide('blind', {direction: 'horizontal'}, 'fast', function(){
-                    tag.remove();
-                }).dequeue();
-            } else {
-                tag.remove();
-            }
-        },
-
-        removeAll: function() {
-            // Removes all tags.
-            var that = this;
-            this.tagList.children('.tagit-choice').each(function(index, tag) {
-                that.removeTag(tag, false);
-            });
-        }
-
-    });
-
-})(jQuery);
-
-
     }
 
     el.empty();
-    var ul = $("<ul/>");
-    _.each(tags, function (t) {
-      var li = $("<li/>");
-      li.text(t);
-      ul.append(li);
+    var m = $("<input/>");
+    el.append(m);
+
+    m.manifest({
+      values: tags,
+      formatRemove: function ($remove, $item) {
+        return '';
+      }
     });
-
-    el.append(ul);
-    ul.tagit({
-      /* this is called to render existing tags too, so it is not just
-       * notification that the user typed a new tag */
-      onTagAdded: function (evt, tag) {
-        tag = $('input', tag).val();
-        if (!_.include(tags, tag)) {
-          tags.push(tag);
-          view.tagschange(tags);
-        }
-      },
-      onTagRemoved: function (evt, tag) {
-        tag = $('input', tag).val();
-        tags = _.reject(tags, function (t) { return t === tag; });
+    m.bind('manifestadd', function (evt, tag, $item) {
+      if (!_.include(tags, tag)) {
+        tags.push(tag);
         view.tagschange(tags);
       }
     });
+    m.bind('manifestremove', function (evt, tag, $item) {
+      tags = _.reject(tags, function (t) { return t === tag; });
+      view.tagschange(tags);
+    });
+
     return this;
   },
   tagschange: function(tags) {