incubator-blur-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cr...@apache.org
Subject [02/17] git commit: Swapped out tagsManager with selectivity for licensing purposes
Date Wed, 29 Jul 2015 23:39:19 GMT
http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/d5805c42/blur-console/src/main/webapp/libs/selectivity/selectivity-full.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/libs/selectivity/selectivity-full.js b/blur-console/src/main/webapp/libs/selectivity/selectivity-full.js
new file mode 100644
index 0000000..f4f6cb6
--- /dev/null
+++ b/blur-console/src/main/webapp/libs/selectivity/selectivity-full.js
@@ -0,0 +1,4694 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.selectivity=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+_dereq_(5);_dereq_(6);_dereq_(7);_dereq_(9);_dereq_(10);_dereq_(11);_dereq_(12);_dereq_(13);_dereq_(14);_dereq_(15);_dereq_(16);_dereq_(17);_dereq_(18);_dereq_(19);module.exports=_dereq_(8);
+},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"5":5,"6":6,"7":7,"8":8,"9":9}],2:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+/**
+ * Event Delegator Constructor.
+ */
+function EventDelegator() {
+
+    this._events = [];
+
+    this.delegateEvents();
+}
+
+/**
+ * Methods.
+ */
+$.extend(EventDelegator.prototype, {
+
+    /**
+     * Attaches all listeners from the events map to the instance's element.
+     *
+     * Normally, you should not have to call this method yourself as it's called automatically in
+     * the constructor.
+     */
+    delegateEvents: function() {
+
+        this.undelegateEvents();
+
+        $.each(this.events, function(event, listener) {
+            var selector, index = event.indexOf(' ');
+            if (index > -1) {
+                selector = event.slice(index + 1);
+                event = event.slice(0, index);
+            }
+
+            if ($.type(listener) === 'string') {
+                listener = this[listener];
+            }
+
+            listener = listener.bind(this);
+
+            if (selector) {
+                this.$el.on(event, selector, listener);
+            } else {
+                this.$el.on(event, listener);
+            }
+
+            this._events.push({ event: event, selector: selector, listener: listener });
+        }.bind(this));
+    },
+
+    /**
+     * Detaches all listeners from the events map from the instance's element.
+     */
+    undelegateEvents: function() {
+
+        this._events.forEach(function(event) {
+            if (event.selector) {
+                this.$el.off(event.event, event.selector, event.listener);
+            } else {
+                this.$el.off(event.event, event.listener);
+            }
+        }, this);
+
+        this._events = [];
+    }
+
+});
+
+module.exports = EventDelegator;
+
+},{"jquery":"jquery"}],3:[function(_dereq_,module,exports){
+'use strict';
+
+/**
+ * @license
+ * lodash 3.3.1 (Custom Build) <https://lodash.com/>
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+
+/**
+ * Gets the number of milliseconds that have elapsed since the Unix epoch
+ *  (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @category Date
+ * @example
+ *
+ * _.defer(function(stamp) {
+ *   console.log(_.now() - stamp);
+ * }, _.now());
+ * // => logs the number of milliseconds it took for the deferred function to be invoked
+ */
+var now = Date.now;
+
+/**
+ * Creates a function that delays invoking `func` until after `wait` milliseconds
+ * have elapsed since the last time it was invoked.
+ *
+ * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
+ * for details over the differences between `_.debounce` and `_.throttle`.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to debounce.
+ * @param {number} [wait=0] The number of milliseconds to delay.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // avoid costly calculations while the window size is in flux
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+ */
+function debounce(func, wait) {
+    var args,
+        result,
+        stamp,
+        timeoutId,
+        trailingCall,
+        lastCalled = 0;
+
+    wait = wait < 0 ? 0 : (+wait || 0);
+
+    function delayed() {
+        var remaining = wait - (now() - stamp);
+        if (remaining <= 0 || remaining > wait) {
+            var isCalled = trailingCall;
+            timeoutId = trailingCall = undefined;
+            if (isCalled) {
+                lastCalled = now();
+                result = func.apply(null, args);
+                if (!timeoutId) {
+                    args = null;
+                }
+            }
+        } else {
+            timeoutId = setTimeout(delayed, remaining);
+        }
+    }
+
+    function debounced() {
+        args = arguments;
+        stamp = now();
+        trailingCall = true;
+
+        if (!timeoutId) {
+            timeoutId = setTimeout(delayed, wait);
+        }
+        return result;
+    }
+    return debounced;
+}
+
+module.exports = debounce;
+
+},{}],4:[function(_dereq_,module,exports){
+'use strict';
+
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+
+var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;'
+};
+
+/**
+ * Used by `escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+function escapeHtmlChar(match) {
+    return htmlEscapes[match];
+}
+
+var reUnescapedHtml = new RegExp('[' + Object.keys(htmlEscapes).join('') + ']', 'g');
+
+/**
+ * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding HTML entities.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} string The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Fred, Wilma, & Pebbles');
+ * // => 'Fred, Wilma, &amp; Pebbles'
+ */
+function escape(string) {
+    return string ? String(string).replace(reUnescapedHtml, escapeHtmlChar) : '';
+}
+
+module.exports = escape;
+
+},{}],5:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var debounce = _dereq_(3);
+
+var Selectivity = _dereq_(8);
+
+_dereq_(13);
+
+/**
+ * Option listener that implements a convenience query function for performing AJAX requests.
+ */
+Selectivity.OptionListeners.unshift(function(selectivity, options) {
+
+    var ajax = options.ajax;
+    if (ajax && ajax.url) {
+        var formatError = ajax.formatError || Selectivity.Locale.ajaxError;
+        var minimumInputLength = ajax.minimumInputLength || 0;
+        var params = ajax.params;
+        var processItem = ajax.processItem || function(item) { return item; };
+        var quietMillis = ajax.quietMillis || 0;
+        var resultsCb = ajax.results || function(data) { return { results: data, more: false }; };
+        var transport = ajax.transport || $.ajax;
+
+        if (quietMillis) {
+            transport = debounce(transport, quietMillis);
+        }
+
+        options.query = function(queryOptions) {
+            var offset = queryOptions.offset;
+            var term = queryOptions.term;
+            if (term.length < minimumInputLength) {
+                queryOptions.error(
+                    Selectivity.Locale.needMoreCharacters(minimumInputLength - term.length)
+                );
+            } else {
+                selectivity.dropdown.showLoading();
+
+                var url = (ajax.url instanceof Function ? ajax.url() : ajax.url);
+                if (params) {
+                    url += (url.indexOf('?') > -1 ? '&' : '?') + $.param(params(term, offset));
+                }
+
+                var success = ajax.success;
+                var error = ajax.error;
+
+                transport($.extend({}, ajax, {
+                    url: url,
+                    success: function(data, textStatus, jqXHR) {
+                        if (success) {
+                            success(data, textStatus, jqXHR);
+                        }
+
+                        var results = resultsCb(data, offset);
+                        results.results = results.results.map(processItem);
+                        queryOptions.callback(results);
+                    },
+                    error: function(jqXHR, textStatus, errorThrown) {
+                        if (error) {
+                            error(jqXHR, textStatus, errorThrown);
+                        }
+
+                        queryOptions.error(
+                            formatError(term, jqXHR, textStatus, errorThrown),
+                            { escape: false }
+                        );
+                    }
+                }));
+            }
+        };
+    }
+});
+
+},{"13":13,"3":3,"8":8,"jquery":"jquery"}],6:[function(_dereq_,module,exports){
+'use strict';
+
+var Selectivity = _dereq_(8);
+
+var latestQueryNum = 0;
+
+/**
+ * Option listener that will discard any callbacks from the query function if another query has
+ * been called afterwards. This prevents responses from remote sources arriving out-of-order.
+ */
+Selectivity.OptionListeners.push(function(selectivity, options) {
+
+    var query = options.query;
+    if (query && !query._async) {
+        options.query = function(queryOptions) {
+            latestQueryNum++;
+            var queryNum = latestQueryNum;
+
+            var callback = queryOptions.callback;
+            var error = queryOptions.error;
+            queryOptions.callback = function() {
+                if (queryNum === latestQueryNum) {
+                    callback.apply(null, arguments);
+                }
+            };
+            queryOptions.error = function() {
+                if (queryNum === latestQueryNum) {
+                    error.apply(null, arguments);
+                }
+            };
+            query(queryOptions);
+        };
+        options.query._async = true;
+    }
+});
+
+},{"8":8}],7:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var SelectivityDropdown = _dereq_(10);
+
+/**
+ * Methods.
+ */
+$.extend(SelectivityDropdown.prototype, {
+
+    /**
+     * @inherit
+     */
+    removeCloseHandler: function() {
+
+        if (this._$backdrop && !this.parentMenu) {
+            this._$backdrop.remove();
+            this._$backdrop = null;
+        }
+    },
+
+    /**
+     * @inherit
+     */
+    setupCloseHandler: function() {
+
+        var $backdrop;
+        if (this.parentMenu) {
+            $backdrop = this.parentMenu._$backdrop;
+        } else {
+            $backdrop = $('<div>').addClass('selectivity-backdrop');
+
+            $('body').append($backdrop);
+        }
+
+        $backdrop.on('click', this.close.bind(this));
+
+        this._$backdrop = $backdrop;
+    }
+
+});
+
+},{"10":10,"jquery":"jquery"}],8:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var EventDelegator = _dereq_(2);
+
+/**
+ * Create a new Selectivity instance or invoke a method on an instance.
+ *
+ * @param methodName Optional name of a method to call. If omitted, a Selectivity instance is
+ *                   created for each element in the set of matched elements. If an element in the
+ *                   set already has a Selectivity instance, the result is the same as if the
+ *                   setOptions() method is called.
+ * @param options Optional options object to pass to the given method or the constructor. See the
+ *                documentation for the respective methods to see which options they accept. In case
+ *                a new instance is being created, the following property is used:
+ *                inputType - The input type to use. Default input types include 'Multiple' and
+ *                            'Single', but you can add custom input types to the InputTypes map or
+ *                            just specify one here as a function. The default value is 'Single',
+ *                            unless multiple is true in which case it is 'Multiple'.
+ *                multiple - Boolean determining whether multiple items may be selected
+ *                           (default: false). If true, a MultipleSelectivity instance is created,
+ *                           otherwise a SingleSelectivity instance is created.
+ *
+ * @return If the given method returns a value, this method returns the value of that method
+ *         executed on the first element in the set of matched elements.
+ */
+function selectivity(methodName, options) {
+    /* jshint validthis: true */
+
+    var result;
+
+    this.each(function() {
+        var instance = this.selectivity;
+
+        if (instance) {
+            if ($.type(methodName) !== 'string') {
+                options = methodName;
+                methodName = 'setOptions';
+            }
+
+            if ($.type(instance[methodName]) === 'function') {
+                if (result === undefined) {
+                    result = instance[methodName].call(instance, options);
+                }
+            } else {
+                throw new Error('Unknown method: ' + methodName);
+            }
+        } else {
+            if ($.type(methodName) === 'string') {
+                if (methodName !== 'destroy') {
+                    throw new Error('Cannot call method on element without Selectivity instance');
+                }
+            } else {
+                options = $.extend({}, methodName, { element: this });
+
+                // this is a one-time hack to facilitate the selectivity-traditional module, because
+                // the module is not able to hook this early into creation of the instance
+                var $this = $(this);
+                if ($this.is('select') && $this.prop('multiple')) {
+                    options.multiple = true;
+                }
+
+                var InputTypes = Selectivity.InputTypes;
+                var InputType = (options.inputType || (options.multiple ? 'Multiple' : 'Single'));
+                if ($.type(InputType) !== 'function') {
+                    if (InputTypes[InputType]) {
+                        InputType = InputTypes[InputType];
+                    } else {
+                        throw new Error('Unknown Selectivity input type: ' + InputType);
+                    }
+                }
+
+                this.selectivity = new InputType(options);
+            }
+        }
+    });
+
+    return (result === undefined ? this : result);
+}
+
+/**
+ * Selectivity Base Constructor.
+ *
+ * You will never use this constructor directly. Instead, you use $(selector).selectivity(options)
+ * to create an instance of either MultipleSelectivity or SingleSelectivity. This class defines all
+ * functionality that is common between both.
+ *
+ * @param options Options object. Accepts the same options as the setOptions method(), in addition
+ *                to the following ones:
+ *                data - Initial selection data to set. This should be an array of objects with 'id'
+ *                       and 'text' properties. This option is mutually exclusive with 'value'.
+ *                element - The DOM element to which to attach the Selectivity instance. This
+ *                          property is set automatically by the $.fn.selectivity() function.
+ *                value - Initial value to set. This should be an array of IDs. This property is
+ *                        mutually exclusive with 'data'.
+ */
+function Selectivity(options) {
+
+    if (!(this instanceof Selectivity)) {
+        return selectivity.apply(this, arguments);
+    }
+
+    /**
+     * jQuery container for the element to which this instance is attached.
+     */
+    this.$el = $(options.element);
+
+    /**
+     * jQuery container for the search input.
+     *
+     * May be null as long as there is no visible search input. It is set by initSearchInput().
+     */
+    this.$searchInput = null;
+
+    /**
+     * Reference to the currently open dropdown.
+     */
+    this.dropdown = null;
+
+    /**
+     * Whether the input is enabled.
+     *
+     * This is false when the option readOnly is false or the option removeOnly is false.
+     */
+    this.enabled = true;
+
+    /**
+     * Boolean whether the browser has touch input.
+     */
+    this.hasTouch = (typeof window !== 'undefined' && 'ontouchstart' in window);
+
+    /**
+     * Boolean whether the browser has a physical keyboard attached to it.
+     *
+     * Given that there is no way for JavaScript to reliably detect this yet, we just assume it's
+     * the opposite of hasTouch for now.
+     */
+    this.hasKeyboard = !this.hasTouch;
+
+    /**
+     * Array of items from which to select. If set, this will be an array of objects with 'id' and
+     * 'text' properties.
+     *
+     * If given, all items are expected to be available locally and all selection operations operate
+     * on this local array only. If null, items are not available locally, and a query function
+     * should be provided to fetch remote data.
+     */
+    this.items = null;
+
+    /**
+     * The function to be used for matching search results.
+     */
+    this.matcher = Selectivity.matcher;
+
+    /**
+     * Options passed to the Selectivity instance or set through setOptions().
+     */
+    this.options = {};
+
+    /**
+     * Results from a search query.
+     */
+    this.results = [];
+
+    /**
+     * Array of search input listeners.
+     *
+     * Custom listeners can be specified in the options object.
+     */
+    this.searchInputListeners = Selectivity.SearchInputListeners;
+
+    /**
+     * Mapping of templates.
+     *
+     * Custom templates can be specified in the options object.
+     */
+    this.templates = $.extend({}, Selectivity.Templates);
+
+    /**
+     * The last used search term.
+     */
+    this.term = '';
+
+    this.setOptions(options);
+
+    if (options.value) {
+        this.value(options.value, { triggerChange: false });
+    } else {
+        this.data(options.data || null, { triggerChange: false });
+    }
+
+    this._$searchInputs = [];
+
+    this.$el.on('selectivity-close', this._closed.bind(this));
+
+    EventDelegator.call(this);
+}
+
+/**
+ * Methods.
+ */
+$.extend(Selectivity.prototype, EventDelegator.prototype, {
+
+    /**
+     * Convenience shortcut for this.$el.find(selector).
+     */
+    $: function(selector) {
+
+        return this.$el.find(selector);
+    },
+
+    /**
+     * Closes the dropdown.
+     */
+    close: function() {
+
+        if (this.dropdown) {
+            this.dropdown.close();
+        }
+    },
+
+    /**
+     * Sets or gets the selection data.
+     *
+     * The selection data contains both IDs and text labels. If you only want to set or get the IDs,
+     * you should use the value() method.
+     *
+     * @param newData Optional new data to set. For a MultipleSelectivity instance the data must be
+     *                an array of objects with 'id' and 'text' properties, for a SingleSelectivity
+     *                instance the data must be a single such object or null to indicate no item is
+     *                selected.
+     * @param options Optional options object. May contain the following property:
+     *                triggerChange - Set to false to suppress the "change" event being triggered.
+     *
+     * @return If newData is omitted, this method returns the current data.
+     */
+    data: function(newData, options) {
+
+        options = options || {};
+
+        if (newData === undefined) {
+            return this._data;
+        } else {
+            newData = this.validateData(newData);
+
+            this._data = newData;
+            this._value = this.getValueForData(newData);
+
+            if (options.triggerChange !== false) {
+                this.triggerChange();
+            }
+        }
+    },
+
+    /**
+     * Destroys the Selectivity instance.
+     */
+    destroy: function() {
+
+        this.undelegateEvents();
+
+        var $el = this.$el;
+        $el.children().remove();
+        $el[0].selectivity = null;
+        $el = null;
+    },
+
+    /**
+     * Filters the results to be displayed in the dropdown.
+     *
+     * The default implementation simply returns the results unfiltered, but the MultipleSelectivity
+     * class overrides this method to filter out any items that have already been selected.
+     *
+     * @param results Array of items with 'id' and 'text' properties.
+     *
+     * @return The filtered array.
+     */
+    filterResults: function(results) {
+
+        return results;
+    },
+
+    /**
+     * Applies focus to the input.
+     */
+    focus: function() {
+
+        if (this.$searchInput) {
+            this.$searchInput.focus();
+        }
+    },
+
+    /**
+     * Returns the correct item for a given ID.
+     *
+     * @param id The ID to get the item for.
+     *
+     * @return The corresponding item. Will be an object with 'id' and 'text' properties or null if
+     *         the item cannot be found. Note that if no items are defined, this method assumes the
+     *         text labels will be equal to the IDs.
+     */
+    getItemForId: function(id) {
+
+        var items = this.items;
+        if (items) {
+            return Selectivity.findNestedById(items, id);
+        } else {
+            return { id: id, text: '' + id };
+        }
+    },
+
+    /**
+     * Initializes the search input element.
+     *
+     * Sets the $searchInput property, invokes all search input listeners and attaches the default
+     * action of searching when something is typed.
+     *
+     * @param $input jQuery container for the input element.
+     * @param options Optional options object. May contain the following property:
+     *                noSearch - If false, no event handlers are setup to initiate searching when
+     *                           the user types in the input field. This is useful if you want to
+     *                           use the input only to handle keyboard support.
+     */
+    initSearchInput: function($input, options) {
+
+        this._$searchInputs.push($input);
+        this.$searchInput = $input;
+
+        this.searchInputListeners.forEach(function(listener) {
+            listener(this, $input);
+        }.bind(this));
+
+        if (!options || options.noSearch !== false) {
+            $input.on('keyup', function(event) {
+                if (!event.isDefaultPrevented()) {
+                    this.search();
+                }
+            }.bind(this));
+        }
+    },
+
+    /**
+     * Loads a follow-up page with results after a search.
+     *
+     * This method should only be called after a call to search() when the callback has indicated
+     * more results are available.
+     */
+    loadMore: function() {
+
+        this.options.query({
+            callback: function(response) {
+                if (response && response.results) {
+                    this._addResults(
+                        Selectivity.processItems(response.results),
+                        { hasMore: !!response.more }
+                    );
+                } else {
+                    throw new Error('callback must be passed a response object');
+                }
+            }.bind(this),
+            error: this._addResults.bind(this, []),
+            offset: this.results.length,
+            selectivity: this,
+            term: this.term
+        });
+    },
+
+    /**
+     * Opens the dropdown.
+     *
+     * @param options Optional options object. May contain the following property:
+     *                showSearchInput - Boolean whether a search input should be shown in the
+     *                                  dropdown. Default is false.
+     */
+    open: function(options) {
+
+        options = options || {};
+
+        if (!this.dropdown) {
+            if (this.triggerEvent('selectivity-opening')) {
+                var Dropdown = this.options.dropdown || Selectivity.Dropdown;
+                if (Dropdown) {
+                    this.dropdown = new Dropdown({
+                        position: this.options.positionDropdown,
+                        selectivity: this,
+                        showSearchInput: options.showSearchInput
+                    });
+                }
+
+                this.search('');
+            }
+        }
+    },
+
+    /**
+     * (Re-)positions the dropdown.
+     */
+    positionDropdown: function() {
+
+        if (this.dropdown) {
+            this.dropdown.position();
+        }
+    },
+
+    /**
+     * Removes the search input last initialized with initSearchInput().
+     */
+    removeSearchInput: function() {
+
+        this._$searchInputs.pop();
+
+        this.$searchInput = this._$searchInputs[this._$searchInputs.length - 1] || null;
+    },
+
+    /**
+     * Searches for results based on the term entered in the search input.
+     *
+     * If an items array has been passed with the options to the Selectivity instance, a local
+     * search will be performed among those items. Otherwise, the query function specified in the
+     * options will be used to perform the search. If neither is defined, nothing happens.
+     *
+     * @param term Optional term to search for. If ommitted, the value of the search input element
+     *             is used as term.
+     */
+    search: function(term) {
+
+        var self = this;
+        function setResults(results, resultOptions) {
+            self._setResults(results, $.extend({ term: term }, resultOptions));
+        }
+
+        if (term === undefined) {
+            if (!self.$searchInput) {
+                return;
+            }
+
+            term = self.$searchInput.val();
+        }
+
+        if (self.items) {
+            term = Selectivity.transformText(term);
+            var matcher = self.matcher;
+            setResults(self.items.map(function(item) {
+                return matcher(item, term);
+            }).filter(function(item) {
+                return !!item;
+            }));
+        } else if (self.options.query) {
+            self.options.query({
+                callback: function(response) {
+                    if (response && response.results) {
+                        setResults(
+                            Selectivity.processItems(response.results),
+                            { hasMore: !!response.more }
+                        );
+                    } else {
+                        throw new Error('callback must be passed a response object');
+                    }
+                },
+                error: self._showError.bind(self),
+                offset: 0,
+                selectivity: self,
+                term: term
+            });
+        }
+
+        self.term = term;
+    },
+
+    /**
+     * Sets one or more options on this Selectivity instance.
+     *
+     * @param options Options object. May contain one or more of the following properties:
+     *                closeOnSelect - Set to false to keep the dropdown open after the user has
+     *                                selected an item. This is useful if you want to allow the user
+     *                                to quickly select multiple items. The default value is true.
+     *                dropdown - Custom dropdown implementation to use for this instance.
+     *                initSelection - Function to map values by ID to selection data. This function
+     *                                receives two arguments, 'value' and 'callback'. The value is
+     *                                the current value of the selection, which is an ID or an array
+     *                                of IDs depending on the input type. The callback should be
+     *                                invoked with an object or array of objects, respectively,
+     *                                containing 'id' and 'text' properties.
+     *                items - Array of items from which to select. Should be an array of objects
+     *                        with 'id' and 'text' properties. As convenience, you may also pass an
+     *                        array of strings, in which case the same string is used for both the
+     *                        'id' and 'text' properties. If items are given, all items are expected
+     *                        to be available locally and all selection operations operate on this
+     *                        local array only. If null, items are not available locally, and a
+     *                        query function should be provided to fetch remote data.
+     *                matcher - Function to determine whether text matches a given search term. Note
+     *                          this function is only used if you have specified an array of items.
+     *                          Receives two arguments:
+     *                          item - The item that should match the search term.
+     *                          term - The search term. Note that for performance reasons, the term
+     *                                 has always been already processed using
+     *                                 Selectivity.transformText().
+     *                          The method should return the item if it matches, and null otherwise.
+     *                          If the item has a children array, the matcher is expected to filter
+     *                          those itself (be sure to only return the filtered array of children
+     *                          in the returned item and not to modify the children of the item
+     *                          argument).
+     *                placeholder - Placeholder text to display when the element has no focus and
+     *                              no selected items.
+     *                positionDropdown - Function to position the dropdown. Receives two arguments:
+     *                                   $dropdownEl - The element to be positioned.
+     *                                   $selectEl - The element of the Selectivity instance, that
+     *                                               you can position the dropdown to.
+     *                                   The default implementation positions the dropdown element
+     *                                   under the Selectivity's element and gives it the same
+     *                                   width.
+     *                query - Function to use for querying items. Receives a single object as
+     *                        argument with the following properties:
+     *                        callback - Callback to invoke when the results are available. This
+     *                                   callback should be passed a single object as argument with
+     *                                   the following properties:
+     *                                   more - Boolean that can be set to true to indicate there
+     *                                          are more results available. Additional results may
+     *                                          be fetched by the user through pagination.
+     *                                   results - Array of result items. The format for the result
+     *                                             items is the same as for passing local items.
+     *                        offset - This property is only used for pagination and indicates how
+     *                                 many results should be skipped when returning more results.
+     *                        selectivity - The Selectivity instance the query function is used on.
+     *                        term - The search term the user is searching for. Unlike with the
+     *                               matcher function, the term has not been processed using
+     *                               Selectivity.transformText().
+     *                readOnly - If true, disables any modification of the input.
+     *                removeOnly - If true, disables any modification of the input except removing
+     *                             of selected items.
+     *                searchInputListeners - Array of search input listeners. By default, the global
+     *                                       array Selectivity.SearchInputListeners is used.
+     *                showDropdown - Set to false if you don't want to use any dropdown (you can
+     *                               still open it programmatically using open()).
+     *                templates - Object with instance-specific templates to override the global
+     *                            templates assigned to Selectivity.Templates.
+     */
+    setOptions: function(options) {
+
+        options = options || {};
+
+        Selectivity.OptionListeners.forEach(function(listener) {
+            listener(this, options);
+        }.bind(this));
+
+        $.extend(this.options, options);
+
+        var allowedTypes = $.extend({
+            closeOnSelect: 'boolean',
+            dropdown: 'function|null',
+            initSelection: 'function|null',
+            matcher: 'function|null',
+            placeholder: 'string',
+            positionDropdown: 'function|null',
+            query: 'function|null',
+            readOnly: 'boolean',
+            removeOnly: 'boolean',
+            searchInputListeners: 'array'
+        }, options.allowedTypes);
+
+        $.each(options, function(key, value) {
+            var type = allowedTypes[key];
+            if (type && !type.split('|').some(function(type) { return $.type(value) === type; })) {
+                throw new Error(key + ' must be of type ' + type);
+            }
+
+            switch (key) {
+            case 'items':
+                this.items = (value === null ? value : Selectivity.processItems(value));
+                break;
+
+            case 'matcher':
+                this.matcher = value;
+                break;
+
+            case 'searchInputListeners':
+                this.searchInputListeners = value;
+                break;
+
+            case 'templates':
+                $.extend(this.templates, value);
+                break;
+            }
+        }.bind(this));
+
+        this.enabled = (!this.options.readOnly && !this.options.removeOnly);
+    },
+
+    /**
+     * Returns the result of the given template.
+     *
+     * @param templateName Name of the template to process.
+     * @param options Options to pass to the template.
+     *
+     * @return String containing HTML.
+     */
+    template: function(templateName, options) {
+
+        var template = this.templates[templateName];
+        if (template) {
+            if ($.type(template) === 'function') {
+                return template(options);
+            } else if (template.render) {
+                return template.render(options);
+            } else {
+                return template.toString();
+            }
+        } else {
+            throw new Error('Unknown template: ' + templateName);
+        }
+    },
+
+    /**
+     * Triggers the change event.
+     *
+     * The event object at least contains the following property:
+     * value - The new value of the Selectivity instance.
+     *
+     * @param Optional additional options added to the event object.
+     */
+    triggerChange: function(options) {
+
+        this.triggerEvent('change', $.extend({ value: this._value }, options));
+    },
+
+    /**
+     * Triggers an event on the instance's element.
+     *
+     * @param Optional event data to be added to the event object.
+     *
+     * @return Whether the default action of the event may be executed, ie. returns false if
+     *         preventDefault() has been called.
+     */
+    triggerEvent: function(eventName, data) {
+
+        var event = $.Event(eventName, data || {});
+        this.$el.trigger(event);
+        return !event.isDefaultPrevented();
+    },
+
+    /**
+     * Shorthand for value().
+     */
+    val: function(newValue) {
+
+        return this.value(newValue);
+    },
+
+    /**
+     * Validates a single item. Throws an exception if the item is invalid.
+     *
+     * @param item The item to validate.
+     *
+     * @return The validated item. May differ from the input item.
+     */
+    validateItem: function(item) {
+
+        if (item && Selectivity.isValidId(item.id) && $.type(item.text) === 'string') {
+            return item;
+        } else {
+            throw new Error('Item should have id (number or string) and text (string) properties');
+        }
+    },
+
+    /**
+     * Sets or gets the value of the selection.
+     *
+     * The value of the selection only concerns the IDs of the selection items. If you are
+     * interested in the IDs and the text labels, you should use the data() method.
+     *
+     * Note that if neither the items option nor the initSelection option have been set, Selectivity
+     * will have no way to determine what text labels should be used with the given IDs in which
+     * case it will assume the text is equal to the ID. This is useful if you're working with tags,
+     * or selecting e-mail addresses for instance, but may not always be what you want.
+     *
+     * @param newValue Optional new value to set. For a MultipleSelectivity instance the value must be
+     *                 an array of IDs, for a SingleSelectivity instance the value must be a single ID
+     *                 (a string or a number) or null to indicate no item is selected.
+     * @param options Optional options object. May contain the following property:
+     *                triggerChange - Set to false to suppress the "change" event being triggered.
+     *
+     * @return If newValue is omitted, this method returns the current value.
+     */
+    value: function(newValue, options) {
+
+        options = options || {};
+
+        if (newValue === undefined) {
+            return this._value;
+        } else {
+            newValue = this.validateValue(newValue);
+
+            this._value = newValue;
+
+            if (this.options.initSelection) {
+                this.options.initSelection(newValue, function(data) {
+                    if (this._value === newValue) {
+                        this._data = this.validateData(data);
+
+                        if (options.triggerChange !== false) {
+                            this.triggerChange();
+                        }
+                    }
+                }.bind(this));
+            } else {
+                this._data = this.getDataForValue(newValue);
+
+                if (options.triggerChange !== false) {
+                    this.triggerChange();
+                }
+            }
+        }
+    },
+
+    /**
+     * @private
+     */
+    _addResults: function(results, options) {
+
+        this.results = this.results.concat(results);
+
+        if (this.dropdown) {
+            this.dropdown.showResults(
+                this.filterResults(results),
+                $.extend({ add: true }, options)
+            );
+        }
+    },
+
+    /**
+     * @private
+     */
+    _closed: function() {
+
+        this.dropdown = null;
+    },
+
+    /**
+     * @private
+     */
+    _getItemId: function(elementOrEvent) {
+
+        // returns the item ID related to an element or event target.
+        // IDs can be either numbers or strings, but attribute values are always strings, so we
+        // will have to find out whether the item ID ought to be a number or string ourselves.
+        // $.fn.data() is a bit overzealous for our case, because it returns a number whenever the
+        // attribute value can be parsed as a number. however, it is possible an item had an ID
+        // which is a string but which is parseable as number, in which case we verify if the ID
+        // as number is actually found among the data or results. if it isn't, we assume it was
+        // supposed to be a string after all...
+
+        var $element;
+        if (elementOrEvent.target) {
+            $element = $(elementOrEvent.target).closest('[data-item-id]');
+        } else if (elementOrEvent.length) {
+            $element = elementOrEvent;
+        } else {
+            $element = $(elementOrEvent);
+        }
+
+        var id = $element.data('item-id');
+        if ($.type(id) === 'string') {
+            return id;
+        } else {
+            if (Selectivity.findById(this._data || [], id) ||
+                Selectivity.findNestedById(this.results, id)) {
+                return id;
+            } else {
+                return '' + id;
+            }
+        }
+    },
+
+    /**
+     * @private
+     */
+    _setResults: function(results, options) {
+
+        this.results = results;
+
+        if (this.dropdown) {
+            this.dropdown.showResults(this.filterResults(results), options || {});
+        }
+    },
+
+    /**
+     * @private
+     */
+    _showError: function(error, options) {
+
+        this.results = [];
+
+        if (this.dropdown) {
+            this.dropdown.showError(error, options);
+        }
+    }
+
+});
+
+/**
+ * Dropdown class to use for displaying dropdowns.
+ *
+ * The default implementation of a dropdown is defined in the selectivity-dropdown module.
+ */
+Selectivity.Dropdown = null;
+
+/**
+ * Mapping of input types.
+ */
+Selectivity.InputTypes = {};
+
+/**
+ * Array of option listeners.
+ *
+ * Option listeners are invoked when setOptions() is called. Every listener receives two arguments:
+ *
+ * selectivity - The Selectivity instance.
+ * options - The options that are about to be set. The listener may modify this options object.
+ *
+ * An example of an option listener is the selectivity-traditional module.
+ */
+Selectivity.OptionListeners = [];
+
+/**
+ * Array of search input listeners.
+ *
+ * Search input listeners are invoked when initSearchInput() is called (typically right after the
+ * search input is created). Every listener receives two arguments:
+ *
+ * selectivity - The Selectivity instance.
+ * $input - jQuery container with the search input.
+ *
+ * An example of a search input listener is the selectivity-keyboard module.
+ */
+Selectivity.SearchInputListeners = [];
+
+/**
+ * Mapping with templates to use for rendering select boxes and dropdowns. See
+ * selectivity-templates.js for a useful set of default templates, as well as for documentation of
+ * the individual templates.
+ */
+Selectivity.Templates = {};
+
+/**
+ * Finds an item in the given array with the specified ID.
+ *
+ * @param array Array to search in.
+ * @param id ID to search for.
+ *
+ * @return The item in the array with the given ID, or null if the item was not found.
+ */
+Selectivity.findById = function(array, id) {
+
+    var index = Selectivity.findIndexById(array, id);
+    return (index > -1 ? array[index] : null);
+};
+
+/**
+ * Finds the index of an item in the given array with the specified ID.
+ *
+ * @param array Array to search in.
+ * @param id ID to search for.
+ *
+ * @return The index of the item in the array with the given ID, or -1 if the item was not found.
+ */
+Selectivity.findIndexById = function(array, id) {
+
+    for (var i = 0, length = array.length; i < length; i++) {
+        if (array[i].id === id) {
+            return i;
+        }
+    }
+    return -1;
+};
+
+/**
+ * Finds an item in the given array with the specified ID. Items in the array may contain 'children'
+ * properties which in turn will be searched for the item.
+ *
+ * @param array Array to search in.
+ * @param id ID to search for.
+ *
+ * @return The item in the array with the given ID, or null if the item was not found.
+ */
+Selectivity.findNestedById =  null && function(array, id) {
+
+    for (var i = 0, length = array.length; i < length; i++) {
+        var item = array[i];
+        if (item.id === id) {
+            return item;
+        } else if (item.children) {
+            var result = Selectivity.findNestedById(item.children, id);
+            if (result) {
+                return result;
+            }
+        }
+    }
+    return null;
+};
+
+/**
+ * Utility method for inheriting another class.
+ *
+ * @param SubClass Constructor function of the subclass.
+ * @param SuperClass Optional constructor function of the superclass. If omitted, Selectivity is
+ *                   used as superclass.
+ * @param prototype Object with methods you want to add to the subclass prototype.
+ *
+ * @return A utility function for calling the methods of the superclass. This function receives two
+ *         arguments: The this object on which you want to execute the method and the name of the
+ *         method. Any arguments past those are passed to the superclass method.
+ */
+Selectivity.inherits = function(SubClass, SuperClass, prototype) {
+
+    if (arguments.length === 2) {
+        prototype = SuperClass;
+        SuperClass = Selectivity;
+    }
+
+    SubClass.prototype = $.extend(
+        Object.create(SuperClass.prototype),
+        { constructor: SubClass },
+        prototype
+    );
+
+    return function(self, methodName) {
+        SuperClass.prototype[methodName].apply(self, Array.prototype.slice.call(arguments, 2));
+    };
+};
+
+/**
+ * Checks whether a value can be used as a valid ID for selection items. Only numbers and strings
+ * are accepted to be used as IDs.
+ *
+ * @param id The value to check whether it is a valid ID.
+ *
+ * @return true if the value is a valid ID, false otherwise.
+ */
+Selectivity.isValidId = function(id) {
+
+    var type = $.type(id);
+    return type === 'number' || type === 'string';
+};
+
+/**
+ * Decides whether a given item matches a search term. The default implementation simply
+ * checks whether the term is contained within the item's text, after transforming them using
+ * transformText().
+ *
+ * @param item The item that should match the search term.
+ * @param term The search term. Note that for performance reasons, the term has always been already
+ *             processed using transformText().
+ *
+ * @return true if the text matches the term, false otherwise.
+ */
+Selectivity.matcher = function(item, term) {
+
+    var result = null;
+    if (Selectivity.transformText(item.text).indexOf(term) > -1) {
+        result = item;
+    } else if (item.children) {
+        var matchingChildren = item.children.map(function(child) {
+            return Selectivity.matcher(child, term);
+        }).filter(function(child) {
+            return !!child;
+        });
+        if (matchingChildren.length) {
+            result = { id: item.id, text: item.text, children: matchingChildren };
+        }
+    }
+    return result;
+};
+
+/**
+ * Helper function for processing items.
+ *
+ * @param item The item to process, either as object containing 'id' and 'text' properties or just
+ *             as ID. The 'id' property of an item is optional if it has a 'children' property
+ *             containing an array of items.
+ *
+ * @return Object containing 'id' and 'text' properties.
+ */
+Selectivity.processItem = function(item) {
+
+    if (Selectivity.isValidId(item)) {
+        return { id: item, text: '' + item };
+    } else if (item &&
+               (Selectivity.isValidId(item.id) || item.children) &&
+               $.type(item.text) === 'string') {
+        if (item.children) {
+            item.children = Selectivity.processItems(item.children);
+        }
+
+        return item;
+    } else {
+        throw new Error('invalid item');
+    }
+};
+
+/**
+ * Helper function for processing an array of items.
+ *
+ * @param items Array of items to process. See processItem() for details about a single item.
+ *
+ * @return Array with items.
+ */
+Selectivity.processItems = function(items) {
+
+    if ($.type(items) === 'array') {
+        return items.map(Selectivity.processItem);
+    } else {
+        throw new Error('invalid items');
+    }
+};
+
+/**
+ * Quotes a string so it can be used in a CSS attribute selector. It adds double quotes to the
+ * string and escapes all occurrences of the quote character inside the string.
+ *
+ * @param string The string to quote.
+ *
+ * @return The quoted string.
+ */
+Selectivity.quoteCssAttr = function(string) {
+
+    return '"' + ('' + string).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
+};
+
+/**
+ * Transforms text in order to find matches. The default implementation casts all strings to
+ * lower-case so that any matches found will be case-insensitive.
+ *
+ * @param string The string to transform.
+ *
+ * @return The transformed string.
+ */
+Selectivity.transformText = function(string) {
+
+    return string.toLowerCase();
+};
+
+module.exports = $.fn.selectivity = Selectivity;
+
+},{"2":2,"jquery":"jquery"}],9:[function(_dereq_,module,exports){
+'use strict';
+
+var DIACRITICS = {
+    '\u24B6': 'A',
+    '\uFF21': 'A',
+    '\u00C0': 'A',
+    '\u00C1': 'A',
+    '\u00C2': 'A',
+    '\u1EA6': 'A',
+    '\u1EA4': 'A',
+    '\u1EAA': 'A',
+    '\u1EA8': 'A',
+    '\u00C3': 'A',
+    '\u0100': 'A',
+    '\u0102': 'A',
+    '\u1EB0': 'A',
+    '\u1EAE': 'A',
+    '\u1EB4': 'A',
+    '\u1EB2': 'A',
+    '\u0226': 'A',
+    '\u01E0': 'A',
+    '\u00C4': 'A',
+    '\u01DE': 'A',
+    '\u1EA2': 'A',
+    '\u00C5': 'A',
+    '\u01FA': 'A',
+    '\u01CD': 'A',
+    '\u0200': 'A',
+    '\u0202': 'A',
+    '\u1EA0': 'A',
+    '\u1EAC': 'A',
+    '\u1EB6': 'A',
+    '\u1E00': 'A',
+    '\u0104': 'A',
+    '\u023A': 'A',
+    '\u2C6F': 'A',
+    '\uA732': 'AA',
+    '\u00C6': 'AE',
+    '\u01FC': 'AE',
+    '\u01E2': 'AE',
+    '\uA734': 'AO',
+    '\uA736': 'AU',
+    '\uA738': 'AV',
+    '\uA73A': 'AV',
+    '\uA73C': 'AY',
+    '\u24B7': 'B',
+    '\uFF22': 'B',
+    '\u1E02': 'B',
+    '\u1E04': 'B',
+    '\u1E06': 'B',
+    '\u0243': 'B',
+    '\u0182': 'B',
+    '\u0181': 'B',
+    '\u24B8': 'C',
+    '\uFF23': 'C',
+    '\u0106': 'C',
+    '\u0108': 'C',
+    '\u010A': 'C',
+    '\u010C': 'C',
+    '\u00C7': 'C',
+    '\u1E08': 'C',
+    '\u0187': 'C',
+    '\u023B': 'C',
+    '\uA73E': 'C',
+    '\u24B9': 'D',
+    '\uFF24': 'D',
+    '\u1E0A': 'D',
+    '\u010E': 'D',
+    '\u1E0C': 'D',
+    '\u1E10': 'D',
+    '\u1E12': 'D',
+    '\u1E0E': 'D',
+    '\u0110': 'D',
+    '\u018B': 'D',
+    '\u018A': 'D',
+    '\u0189': 'D',
+    '\uA779': 'D',
+    '\u01F1': 'DZ',
+    '\u01C4': 'DZ',
+    '\u01F2': 'Dz',
+    '\u01C5': 'Dz',
+    '\u24BA': 'E',
+    '\uFF25': 'E',
+    '\u00C8': 'E',
+    '\u00C9': 'E',
+    '\u00CA': 'E',
+    '\u1EC0': 'E',
+    '\u1EBE': 'E',
+    '\u1EC4': 'E',
+    '\u1EC2': 'E',
+    '\u1EBC': 'E',
+    '\u0112': 'E',
+    '\u1E14': 'E',
+    '\u1E16': 'E',
+    '\u0114': 'E',
+    '\u0116': 'E',
+    '\u00CB': 'E',
+    '\u1EBA': 'E',
+    '\u011A': 'E',
+    '\u0204': 'E',
+    '\u0206': 'E',
+    '\u1EB8': 'E',
+    '\u1EC6': 'E',
+    '\u0228': 'E',
+    '\u1E1C': 'E',
+    '\u0118': 'E',
+    '\u1E18': 'E',
+    '\u1E1A': 'E',
+    '\u0190': 'E',
+    '\u018E': 'E',
+    '\u24BB': 'F',
+    '\uFF26': 'F',
+    '\u1E1E': 'F',
+    '\u0191': 'F',
+    '\uA77B': 'F',
+    '\u24BC': 'G',
+    '\uFF27': 'G',
+    '\u01F4': 'G',
+    '\u011C': 'G',
+    '\u1E20': 'G',
+    '\u011E': 'G',
+    '\u0120': 'G',
+    '\u01E6': 'G',
+    '\u0122': 'G',
+    '\u01E4': 'G',
+    '\u0193': 'G',
+    '\uA7A0': 'G',
+    '\uA77D': 'G',
+    '\uA77E': 'G',
+    '\u24BD': 'H',
+    '\uFF28': 'H',
+    '\u0124': 'H',
+    '\u1E22': 'H',
+    '\u1E26': 'H',
+    '\u021E': 'H',
+    '\u1E24': 'H',
+    '\u1E28': 'H',
+    '\u1E2A': 'H',
+    '\u0126': 'H',
+    '\u2C67': 'H',
+    '\u2C75': 'H',
+    '\uA78D': 'H',
+    '\u24BE': 'I',
+    '\uFF29': 'I',
+    '\u00CC': 'I',
+    '\u00CD': 'I',
+    '\u00CE': 'I',
+    '\u0128': 'I',
+    '\u012A': 'I',
+    '\u012C': 'I',
+    '\u0130': 'I',
+    '\u00CF': 'I',
+    '\u1E2E': 'I',
+    '\u1EC8': 'I',
+    '\u01CF': 'I',
+    '\u0208': 'I',
+    '\u020A': 'I',
+    '\u1ECA': 'I',
+    '\u012E': 'I',
+    '\u1E2C': 'I',
+    '\u0197': 'I',
+    '\u24BF': 'J',
+    '\uFF2A': 'J',
+    '\u0134': 'J',
+    '\u0248': 'J',
+    '\u24C0': 'K',
+    '\uFF2B': 'K',
+    '\u1E30': 'K',
+    '\u01E8': 'K',
+    '\u1E32': 'K',
+    '\u0136': 'K',
+    '\u1E34': 'K',
+    '\u0198': 'K',
+    '\u2C69': 'K',
+    '\uA740': 'K',
+    '\uA742': 'K',
+    '\uA744': 'K',
+    '\uA7A2': 'K',
+    '\u24C1': 'L',
+    '\uFF2C': 'L',
+    '\u013F': 'L',
+    '\u0139': 'L',
+    '\u013D': 'L',
+    '\u1E36': 'L',
+    '\u1E38': 'L',
+    '\u013B': 'L',
+    '\u1E3C': 'L',
+    '\u1E3A': 'L',
+    '\u0141': 'L',
+    '\u023D': 'L',
+    '\u2C62': 'L',
+    '\u2C60': 'L',
+    '\uA748': 'L',
+    '\uA746': 'L',
+    '\uA780': 'L',
+    '\u01C7': 'LJ',
+    '\u01C8': 'Lj',
+    '\u24C2': 'M',
+    '\uFF2D': 'M',
+    '\u1E3E': 'M',
+    '\u1E40': 'M',
+    '\u1E42': 'M',
+    '\u2C6E': 'M',
+    '\u019C': 'M',
+    '\u24C3': 'N',
+    '\uFF2E': 'N',
+    '\u01F8': 'N',
+    '\u0143': 'N',
+    '\u00D1': 'N',
+    '\u1E44': 'N',
+    '\u0147': 'N',
+    '\u1E46': 'N',
+    '\u0145': 'N',
+    '\u1E4A': 'N',
+    '\u1E48': 'N',
+    '\u0220': 'N',
+    '\u019D': 'N',
+    '\uA790': 'N',
+    '\uA7A4': 'N',
+    '\u01CA': 'NJ',
+    '\u01CB': 'Nj',
+    '\u24C4': 'O',
+    '\uFF2F': 'O',
+    '\u00D2': 'O',
+    '\u00D3': 'O',
+    '\u00D4': 'O',
+    '\u1ED2': 'O',
+    '\u1ED0': 'O',
+    '\u1ED6': 'O',
+    '\u1ED4': 'O',
+    '\u00D5': 'O',
+    '\u1E4C': 'O',
+    '\u022C': 'O',
+    '\u1E4E': 'O',
+    '\u014C': 'O',
+    '\u1E50': 'O',
+    '\u1E52': 'O',
+    '\u014E': 'O',
+    '\u022E': 'O',
+    '\u0230': 'O',
+    '\u00D6': 'O',
+    '\u022A': 'O',
+    '\u1ECE': 'O',
+    '\u0150': 'O',
+    '\u01D1': 'O',
+    '\u020C': 'O',
+    '\u020E': 'O',
+    '\u01A0': 'O',
+    '\u1EDC': 'O',
+    '\u1EDA': 'O',
+    '\u1EE0': 'O',
+    '\u1EDE': 'O',
+    '\u1EE2': 'O',
+    '\u1ECC': 'O',
+    '\u1ED8': 'O',
+    '\u01EA': 'O',
+    '\u01EC': 'O',
+    '\u00D8': 'O',
+    '\u01FE': 'O',
+    '\u0186': 'O',
+    '\u019F': 'O',
+    '\uA74A': 'O',
+    '\uA74C': 'O',
+    '\u01A2': 'OI',
+    '\uA74E': 'OO',
+    '\u0222': 'OU',
+    '\u24C5': 'P',
+    '\uFF30': 'P',
+    '\u1E54': 'P',
+    '\u1E56': 'P',
+    '\u01A4': 'P',
+    '\u2C63': 'P',
+    '\uA750': 'P',
+    '\uA752': 'P',
+    '\uA754': 'P',
+    '\u24C6': 'Q',
+    '\uFF31': 'Q',
+    '\uA756': 'Q',
+    '\uA758': 'Q',
+    '\u024A': 'Q',
+    '\u24C7': 'R',
+    '\uFF32': 'R',
+    '\u0154': 'R',
+    '\u1E58': 'R',
+    '\u0158': 'R',
+    '\u0210': 'R',
+    '\u0212': 'R',
+    '\u1E5A': 'R',
+    '\u1E5C': 'R',
+    '\u0156': 'R',
+    '\u1E5E': 'R',
+    '\u024C': 'R',
+    '\u2C64': 'R',
+    '\uA75A': 'R',
+    '\uA7A6': 'R',
+    '\uA782': 'R',
+    '\u24C8': 'S',
+    '\uFF33': 'S',
+    '\u1E9E': 'S',
+    '\u015A': 'S',
+    '\u1E64': 'S',
+    '\u015C': 'S',
+    '\u1E60': 'S',
+    '\u0160': 'S',
+    '\u1E66': 'S',
+    '\u1E62': 'S',
+    '\u1E68': 'S',
+    '\u0218': 'S',
+    '\u015E': 'S',
+    '\u2C7E': 'S',
+    '\uA7A8': 'S',
+    '\uA784': 'S',
+    '\u24C9': 'T',
+    '\uFF34': 'T',
+    '\u1E6A': 'T',
+    '\u0164': 'T',
+    '\u1E6C': 'T',
+    '\u021A': 'T',
+    '\u0162': 'T',
+    '\u1E70': 'T',
+    '\u1E6E': 'T',
+    '\u0166': 'T',
+    '\u01AC': 'T',
+    '\u01AE': 'T',
+    '\u023E': 'T',
+    '\uA786': 'T',
+    '\uA728': 'TZ',
+    '\u24CA': 'U',
+    '\uFF35': 'U',
+    '\u00D9': 'U',
+    '\u00DA': 'U',
+    '\u00DB': 'U',
+    '\u0168': 'U',
+    '\u1E78': 'U',
+    '\u016A': 'U',
+    '\u1E7A': 'U',
+    '\u016C': 'U',
+    '\u00DC': 'U',
+    '\u01DB': 'U',
+    '\u01D7': 'U',
+    '\u01D5': 'U',
+    '\u01D9': 'U',
+    '\u1EE6': 'U',
+    '\u016E': 'U',
+    '\u0170': 'U',
+    '\u01D3': 'U',
+    '\u0214': 'U',
+    '\u0216': 'U',
+    '\u01AF': 'U',
+    '\u1EEA': 'U',
+    '\u1EE8': 'U',
+    '\u1EEE': 'U',
+    '\u1EEC': 'U',
+    '\u1EF0': 'U',
+    '\u1EE4': 'U',
+    '\u1E72': 'U',
+    '\u0172': 'U',
+    '\u1E76': 'U',
+    '\u1E74': 'U',
+    '\u0244': 'U',
+    '\u24CB': 'V',
+    '\uFF36': 'V',
+    '\u1E7C': 'V',
+    '\u1E7E': 'V',
+    '\u01B2': 'V',
+    '\uA75E': 'V',
+    '\u0245': 'V',
+    '\uA760': 'VY',
+    '\u24CC': 'W',
+    '\uFF37': 'W',
+    '\u1E80': 'W',
+    '\u1E82': 'W',
+    '\u0174': 'W',
+    '\u1E86': 'W',
+    '\u1E84': 'W',
+    '\u1E88': 'W',
+    '\u2C72': 'W',
+    '\u24CD': 'X',
+    '\uFF38': 'X',
+    '\u1E8A': 'X',
+    '\u1E8C': 'X',
+    '\u24CE': 'Y',
+    '\uFF39': 'Y',
+    '\u1EF2': 'Y',
+    '\u00DD': 'Y',
+    '\u0176': 'Y',
+    '\u1EF8': 'Y',
+    '\u0232': 'Y',
+    '\u1E8E': 'Y',
+    '\u0178': 'Y',
+    '\u1EF6': 'Y',
+    '\u1EF4': 'Y',
+    '\u01B3': 'Y',
+    '\u024E': 'Y',
+    '\u1EFE': 'Y',
+    '\u24CF': 'Z',
+    '\uFF3A': 'Z',
+    '\u0179': 'Z',
+    '\u1E90': 'Z',
+    '\u017B': 'Z',
+    '\u017D': 'Z',
+    '\u1E92': 'Z',
+    '\u1E94': 'Z',
+    '\u01B5': 'Z',
+    '\u0224': 'Z',
+    '\u2C7F': 'Z',
+    '\u2C6B': 'Z',
+    '\uA762': 'Z',
+    '\u24D0': 'a',
+    '\uFF41': 'a',
+    '\u1E9A': 'a',
+    '\u00E0': 'a',
+    '\u00E1': 'a',
+    '\u00E2': 'a',
+    '\u1EA7': 'a',
+    '\u1EA5': 'a',
+    '\u1EAB': 'a',
+    '\u1EA9': 'a',
+    '\u00E3': 'a',
+    '\u0101': 'a',
+    '\u0103': 'a',
+    '\u1EB1': 'a',
+    '\u1EAF': 'a',
+    '\u1EB5': 'a',
+    '\u1EB3': 'a',
+    '\u0227': 'a',
+    '\u01E1': 'a',
+    '\u00E4': 'a',
+    '\u01DF': 'a',
+    '\u1EA3': 'a',
+    '\u00E5': 'a',
+    '\u01FB': 'a',
+    '\u01CE': 'a',
+    '\u0201': 'a',
+    '\u0203': 'a',
+    '\u1EA1': 'a',
+    '\u1EAD': 'a',
+    '\u1EB7': 'a',
+    '\u1E01': 'a',
+    '\u0105': 'a',
+    '\u2C65': 'a',
+    '\u0250': 'a',
+    '\uA733': 'aa',
+    '\u00E6': 'ae',
+    '\u01FD': 'ae',
+    '\u01E3': 'ae',
+    '\uA735': 'ao',
+    '\uA737': 'au',
+    '\uA739': 'av',
+    '\uA73B': 'av',
+    '\uA73D': 'ay',
+    '\u24D1': 'b',
+    '\uFF42': 'b',
+    '\u1E03': 'b',
+    '\u1E05': 'b',
+    '\u1E07': 'b',
+    '\u0180': 'b',
+    '\u0183': 'b',
+    '\u0253': 'b',
+    '\u24D2': 'c',
+    '\uFF43': 'c',
+    '\u0107': 'c',
+    '\u0109': 'c',
+    '\u010B': 'c',
+    '\u010D': 'c',
+    '\u00E7': 'c',
+    '\u1E09': 'c',
+    '\u0188': 'c',
+    '\u023C': 'c',
+    '\uA73F': 'c',
+    '\u2184': 'c',
+    '\u24D3': 'd',
+    '\uFF44': 'd',
+    '\u1E0B': 'd',
+    '\u010F': 'd',
+    '\u1E0D': 'd',
+    '\u1E11': 'd',
+    '\u1E13': 'd',
+    '\u1E0F': 'd',
+    '\u0111': 'd',
+    '\u018C': 'd',
+    '\u0256': 'd',
+    '\u0257': 'd',
+    '\uA77A': 'd',
+    '\u01F3': 'dz',
+    '\u01C6': 'dz',
+    '\u24D4': 'e',
+    '\uFF45': 'e',
+    '\u00E8': 'e',
+    '\u00E9': 'e',
+    '\u00EA': 'e',
+    '\u1EC1': 'e',
+    '\u1EBF': 'e',
+    '\u1EC5': 'e',
+    '\u1EC3': 'e',
+    '\u1EBD': 'e',
+    '\u0113': 'e',
+    '\u1E15': 'e',
+    '\u1E17': 'e',
+    '\u0115': 'e',
+    '\u0117': 'e',
+    '\u00EB': 'e',
+    '\u1EBB': 'e',
+    '\u011B': 'e',
+    '\u0205': 'e',
+    '\u0207': 'e',
+    '\u1EB9': 'e',
+    '\u1EC7': 'e',
+    '\u0229': 'e',
+    '\u1E1D': 'e',
+    '\u0119': 'e',
+    '\u1E19': 'e',
+    '\u1E1B': 'e',
+    '\u0247': 'e',
+    '\u025B': 'e',
+    '\u01DD': 'e',
+    '\u24D5': 'f',
+    '\uFF46': 'f',
+    '\u1E1F': 'f',
+    '\u0192': 'f',
+    '\uA77C': 'f',
+    '\u24D6': 'g',
+    '\uFF47': 'g',
+    '\u01F5': 'g',
+    '\u011D': 'g',
+    '\u1E21': 'g',
+    '\u011F': 'g',
+    '\u0121': 'g',
+    '\u01E7': 'g',
+    '\u0123': 'g',
+    '\u01E5': 'g',
+    '\u0260': 'g',
+    '\uA7A1': 'g',
+    '\u1D79': 'g',
+    '\uA77F': 'g',
+    '\u24D7': 'h',
+    '\uFF48': 'h',
+    '\u0125': 'h',
+    '\u1E23': 'h',
+    '\u1E27': 'h',
+    '\u021F': 'h',
+    '\u1E25': 'h',
+    '\u1E29': 'h',
+    '\u1E2B': 'h',
+    '\u1E96': 'h',
+    '\u0127': 'h',
+    '\u2C68': 'h',
+    '\u2C76': 'h',
+    '\u0265': 'h',
+    '\u0195': 'hv',
+    '\u24D8': 'i',
+    '\uFF49': 'i',
+    '\u00EC': 'i',
+    '\u00ED': 'i',
+    '\u00EE': 'i',
+    '\u0129': 'i',
+    '\u012B': 'i',
+    '\u012D': 'i',
+    '\u00EF': 'i',
+    '\u1E2F': 'i',
+    '\u1EC9': 'i',
+    '\u01D0': 'i',
+    '\u0209': 'i',
+    '\u020B': 'i',
+    '\u1ECB': 'i',
+    '\u012F': 'i',
+    '\u1E2D': 'i',
+    '\u0268': 'i',
+    '\u0131': 'i',
+    '\u24D9': 'j',
+    '\uFF4A': 'j',
+    '\u0135': 'j',
+    '\u01F0': 'j',
+    '\u0249': 'j',
+    '\u24DA': 'k',
+    '\uFF4B': 'k',
+    '\u1E31': 'k',
+    '\u01E9': 'k',
+    '\u1E33': 'k',
+    '\u0137': 'k',
+    '\u1E35': 'k',
+    '\u0199': 'k',
+    '\u2C6A': 'k',
+    '\uA741': 'k',
+    '\uA743': 'k',
+    '\uA745': 'k',
+    '\uA7A3': 'k',
+    '\u24DB': 'l',
+    '\uFF4C': 'l',
+    '\u0140': 'l',
+    '\u013A': 'l',
+    '\u013E': 'l',
+    '\u1E37': 'l',
+    '\u1E39': 'l',
+    '\u013C': 'l',
+    '\u1E3D': 'l',
+    '\u1E3B': 'l',
+    '\u017F': 'l',
+    '\u0142': 'l',
+    '\u019A': 'l',
+    '\u026B': 'l',
+    '\u2C61': 'l',
+    '\uA749': 'l',
+    '\uA781': 'l',
+    '\uA747': 'l',
+    '\u01C9': 'lj',
+    '\u24DC': 'm',
+    '\uFF4D': 'm',
+    '\u1E3F': 'm',
+    '\u1E41': 'm',
+    '\u1E43': 'm',
+    '\u0271': 'm',
+    '\u026F': 'm',
+    '\u24DD': 'n',
+    '\uFF4E': 'n',
+    '\u01F9': 'n',
+    '\u0144': 'n',
+    '\u00F1': 'n',
+    '\u1E45': 'n',
+    '\u0148': 'n',
+    '\u1E47': 'n',
+    '\u0146': 'n',
+    '\u1E4B': 'n',
+    '\u1E49': 'n',
+    '\u019E': 'n',
+    '\u0272': 'n',
+    '\u0149': 'n',
+    '\uA791': 'n',
+    '\uA7A5': 'n',
+    '\u01CC': 'nj',
+    '\u24DE': 'o',
+    '\uFF4F': 'o',
+    '\u00F2': 'o',
+    '\u00F3': 'o',
+    '\u00F4': 'o',
+    '\u1ED3': 'o',
+    '\u1ED1': 'o',
+    '\u1ED7': 'o',
+    '\u1ED5': 'o',
+    '\u00F5': 'o',
+    '\u1E4D': 'o',
+    '\u022D': 'o',
+    '\u1E4F': 'o',
+    '\u014D': 'o',
+    '\u1E51': 'o',
+    '\u1E53': 'o',
+    '\u014F': 'o',
+    '\u022F': 'o',
+    '\u0231': 'o',
+    '\u00F6': 'o',
+    '\u022B': 'o',
+    '\u1ECF': 'o',
+    '\u0151': 'o',
+    '\u01D2': 'o',
+    '\u020D': 'o',
+    '\u020F': 'o',
+    '\u01A1': 'o',
+    '\u1EDD': 'o',
+    '\u1EDB': 'o',
+    '\u1EE1': 'o',
+    '\u1EDF': 'o',
+    '\u1EE3': 'o',
+    '\u1ECD': 'o',
+    '\u1ED9': 'o',
+    '\u01EB': 'o',
+    '\u01ED': 'o',
+    '\u00F8': 'o',
+    '\u01FF': 'o',
+    '\u0254': 'o',
+    '\uA74B': 'o',
+    '\uA74D': 'o',
+    '\u0275': 'o',
+    '\u01A3': 'oi',
+    '\u0223': 'ou',
+    '\uA74F': 'oo',
+    '\u24DF': 'p',
+    '\uFF50': 'p',
+    '\u1E55': 'p',
+    '\u1E57': 'p',
+    '\u01A5': 'p',
+    '\u1D7D': 'p',
+    '\uA751': 'p',
+    '\uA753': 'p',
+    '\uA755': 'p',
+    '\u24E0': 'q',
+    '\uFF51': 'q',
+    '\u024B': 'q',
+    '\uA757': 'q',
+    '\uA759': 'q',
+    '\u24E1': 'r',
+    '\uFF52': 'r',
+    '\u0155': 'r',
+    '\u1E59': 'r',
+    '\u0159': 'r',
+    '\u0211': 'r',
+    '\u0213': 'r',
+    '\u1E5B': 'r',
+    '\u1E5D': 'r',
+    '\u0157': 'r',
+    '\u1E5F': 'r',
+    '\u024D': 'r',
+    '\u027D': 'r',
+    '\uA75B': 'r',
+    '\uA7A7': 'r',
+    '\uA783': 'r',
+    '\u24E2': 's',
+    '\uFF53': 's',
+    '\u00DF': 's',
+    '\u015B': 's',
+    '\u1E65': 's',
+    '\u015D': 's',
+    '\u1E61': 's',
+    '\u0161': 's',
+    '\u1E67': 's',
+    '\u1E63': 's',
+    '\u1E69': 's',
+    '\u0219': 's',
+    '\u015F': 's',
+    '\u023F': 's',
+    '\uA7A9': 's',
+    '\uA785': 's',
+    '\u1E9B': 's',
+    '\u24E3': 't',
+    '\uFF54': 't',
+    '\u1E6B': 't',
+    '\u1E97': 't',
+    '\u0165': 't',
+    '\u1E6D': 't',
+    '\u021B': 't',
+    '\u0163': 't',
+    '\u1E71': 't',
+    '\u1E6F': 't',
+    '\u0167': 't',
+    '\u01AD': 't',
+    '\u0288': 't',
+    '\u2C66': 't',
+    '\uA787': 't',
+    '\uA729': 'tz',
+    '\u24E4': 'u',
+    '\uFF55': 'u',
+    '\u00F9': 'u',
+    '\u00FA': 'u',
+    '\u00FB': 'u',
+    '\u0169': 'u',
+    '\u1E79': 'u',
+    '\u016B': 'u',
+    '\u1E7B': 'u',
+    '\u016D': 'u',
+    '\u00FC': 'u',
+    '\u01DC': 'u',
+    '\u01D8': 'u',
+    '\u01D6': 'u',
+    '\u01DA': 'u',
+    '\u1EE7': 'u',
+    '\u016F': 'u',
+    '\u0171': 'u',
+    '\u01D4': 'u',
+    '\u0215': 'u',
+    '\u0217': 'u',
+    '\u01B0': 'u',
+    '\u1EEB': 'u',
+    '\u1EE9': 'u',
+    '\u1EEF': 'u',
+    '\u1EED': 'u',
+    '\u1EF1': 'u',
+    '\u1EE5': 'u',
+    '\u1E73': 'u',
+    '\u0173': 'u',
+    '\u1E77': 'u',
+    '\u1E75': 'u',
+    '\u0289': 'u',
+    '\u24E5': 'v',
+    '\uFF56': 'v',
+    '\u1E7D': 'v',
+    '\u1E7F': 'v',
+    '\u028B': 'v',
+    '\uA75F': 'v',
+    '\u028C': 'v',
+    '\uA761': 'vy',
+    '\u24E6': 'w',
+    '\uFF57': 'w',
+    '\u1E81': 'w',
+    '\u1E83': 'w',
+    '\u0175': 'w',
+    '\u1E87': 'w',
+    '\u1E85': 'w',
+    '\u1E98': 'w',
+    '\u1E89': 'w',
+    '\u2C73': 'w',
+    '\u24E7': 'x',
+    '\uFF58': 'x',
+    '\u1E8B': 'x',
+    '\u1E8D': 'x',
+    '\u24E8': 'y',
+    '\uFF59': 'y',
+    '\u1EF3': 'y',
+    '\u00FD': 'y',
+    '\u0177': 'y',
+    '\u1EF9': 'y',
+    '\u0233': 'y',
+    '\u1E8F': 'y',
+    '\u00FF': 'y',
+    '\u1EF7': 'y',
+    '\u1E99': 'y',
+    '\u1EF5': 'y',
+    '\u01B4': 'y',
+    '\u024F': 'y',
+    '\u1EFF': 'y',
+    '\u24E9': 'z',
+    '\uFF5A': 'z',
+    '\u017A': 'z',
+    '\u1E91': 'z',
+    '\u017C': 'z',
+    '\u017E': 'z',
+    '\u1E93': 'z',
+    '\u1E95': 'z',
+    '\u01B6': 'z',
+    '\u0225': 'z',
+    '\u0240': 'z',
+    '\u2C6C': 'z',
+    '\uA763': 'z',
+    '\u0386': '\u0391',
+    '\u0388': '\u0395',
+    '\u0389': '\u0397',
+    '\u038A': '\u0399',
+    '\u03AA': '\u0399',
+    '\u038C': '\u039F',
+    '\u038E': '\u03A5',
+    '\u03AB': '\u03A5',
+    '\u038F': '\u03A9',
+    '\u03AC': '\u03B1',
+    '\u03AD': '\u03B5',
+    '\u03AE': '\u03B7',
+    '\u03AF': '\u03B9',
+    '\u03CA': '\u03B9',
+    '\u0390': '\u03B9',
+    '\u03CC': '\u03BF',
+    '\u03CD': '\u03C5',
+    '\u03CB': '\u03C5',
+    '\u03B0': '\u03C5',
+    '\u03C9': '\u03C9',
+    '\u03C2': '\u03C3'
+};
+
+var Selectivity = _dereq_(8);
+var previousTransform = Selectivity.transformText;
+
+/**
+ * Extended version of the transformText() function that simplifies diacritics to their latin1
+ * counterparts.
+ *
+ * Note that if all query functions fetch their results from a remote server, you may not need this
+ * function, because it makes sense to remove diacritics server-side in such cases.
+ */
+Selectivity.transformText = function(string) {
+    var result = '';
+    for (var i = 0, length = string.length; i < length; i++) {
+        var character = string[i];
+        result += DIACRITICS[character] || character;
+    }
+    return previousTransform(result);
+};
+
+},{"8":8}],10:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var debounce = _dereq_(3);
+
+var EventDelegator = _dereq_(2);
+
+var Selectivity = _dereq_(8);
+
+/**
+ * selectivity Dropdown Constructor.
+ *
+ * @param options Options object. Should have the following properties:
+ *                selectivity - Selectivity instance to show the dropdown for.
+ *                showSearchInput - Boolean whether a search input should be shown.
+ */
+function SelectivityDropdown(options) {
+
+    var selectivity = options.selectivity;
+
+    this.$el = $(selectivity.template('dropdown', {
+        dropdownCssClass: selectivity.options.dropdownCssClass,
+        searchInputPlaceholder: selectivity.options.searchInputPlaceholder,
+        showSearchInput: options.showSearchInput
+    }));
+
+    /**
+     * jQuery container to add the results to.
+     */
+    this.$results = this.$('.selectivity-results-container');
+
+    /**
+     * Boolean indicating whether more results are available than currently displayed in the
+     * dropdown.
+     */
+    this.hasMore = false;
+
+    /**
+     * The currently highlighted result item.
+     */
+    this.highlightedResult = null;
+
+    /**
+     * Boolean whether the load more link is currently highlighted.
+     */
+    this.loadMoreHighlighted = false;
+
+    /**
+     * Options passed to the dropdown constructor.
+     */
+    this.options = options;
+
+    /**
+     * The results displayed in the dropdown.
+     */
+    this.results = [];
+
+    /**
+     * Selectivity instance.
+     */
+    this.selectivity = selectivity;
+
+    this._closed = false;
+
+    this._closeProxy = this.close.bind(this);
+    if (selectivity.options.closeOnSelect !== false) {
+        selectivity.$el.on('selectivity-selecting', this._closeProxy);
+    }
+
+    this._lastMousePosition = {};
+
+    this.addToDom();
+    this.position();
+    this.setupCloseHandler();
+
+    this._suppressMouseWheel();
+
+    if (options.showSearchInput) {
+        selectivity.initSearchInput(this.$('.selectivity-search-input'));
+        selectivity.focus();
+    }
+
+    EventDelegator.call(this);
+
+    this.$results.on('scroll touchmove touchend', debounce(this._scrolled.bind(this), 50));
+
+    this.showLoading();
+
+    setTimeout(this.triggerOpen.bind(this), 1);
+}
+
+/**
+ * Methods.
+ */
+$.extend(SelectivityDropdown.prototype, EventDelegator.prototype, {
+
+    /**
+     * Convenience shortcut for this.$el.find(selector).
+     */
+    $: function(selector) {
+
+        return this.$el.find(selector);
+    },
+
+    /**
+     * Adds the dropdown to the DOM.
+     */
+    addToDom: function() {
+
+        var $next;
+        var $anchor = this.selectivity.$el;
+        while (($next = $anchor.next('.selectivity-dropdown')).length) {
+            $anchor = $next;
+        }
+        this.$el.insertAfter($anchor);
+    },
+
+    /**
+     * Closes the dropdown.
+     */
+    close: function() {
+
+        if (!this._closed) {
+            this._closed = true;
+
+            if (this.options.showSearchInput) {
+                this.selectivity.removeSearchInput();
+            }
+
+            this.$el.remove();
+
+            this.removeCloseHandler();
+
+            this.selectivity.$el.off('selectivity-selecting', this._closeProxy);
+
+            this.triggerClose();
+        }
+    },
+
+    /**
+     * Events map.
+     *
+     * Follows the same format as Backbone: http://backbonejs.org/#View-delegateEvents
+     */
+    events: {
+        'click .selectivity-load-more': '_loadMoreClicked',
+        'click .selectivity-result-item': '_resultClicked',
+        'mouseenter .selectivity-load-more': '_loadMoreHovered',
+        'mouseenter .selectivity-result-item': '_resultHovered'
+    },
+
+    /**
+     * Highlights a result item.
+     *
+     * @param item The item to highlight.
+     */
+    highlight: function(item) {
+
+        if (this.loadMoreHighlighted) {
+            this.$('.selectivity-load-more').removeClass('highlight');
+        }
+
+        this.$('.selectivity-result-item').removeClass('highlight')
+            .filter('[data-item-id=' + Selectivity.quoteCssAttr(item.id) + ']')
+            .addClass('highlight');
+
+        this.highlightedResult = item;
+        this.loadMoreHighlighted = false;
+
+        this.selectivity.triggerEvent('selectivity-highlight', { item: item, id: item.id });
+    },
+
+    /**
+     * Highlights the load more link.
+     *
+     * @param item The item to highlight.
+     */
+    highlightLoadMore: function() {
+
+        this.$('.selectivity-result-item').removeClass('highlight');
+
+        this.$('.selectivity-load-more').addClass('highlight');
+
+        this.highlightedResult = null;
+        this.loadMoreHighlighted = true;
+    },
+
+    /**
+     * Positions the dropdown inside the DOM.
+     */
+    position: function() {
+
+        var position = this.options.position;
+        if (position) {
+            position(this.$el, this.selectivity.$el);
+        }
+
+        this._scrolled();
+    },
+
+    /**
+     * Removes the event handler to close the dropdown.
+     */
+    removeCloseHandler: function() {
+
+        $('body').off('click', this._closeProxy);
+    },
+
+    /**
+     * Renders an array of result items.
+     *
+     * @param items Array of result items.
+     *
+     * @return HTML-formatted string to display the result items.
+     */
+    renderItems: function(items) {
+
+        var selectivity = this.selectivity;
+        return items.map(function(item) {
+            var result = selectivity.template(item.id ? 'resultItem' : 'resultLabel', item);
+            if (item.children) {
+                result += selectivity.template('resultChildren', {
+                    childrenHtml: this.renderItems(item.children)
+                });
+            }
+            return result;
+        }, this).join('');
+    },
+
+    /**
+     * Selects the highlighted item.
+     */
+    selectHighlight: function() {
+
+        if (this.highlightedResult) {
+            this.selectItem(this.highlightedResult.id);
+        } else if (this.loadMoreHighlighted) {
+            this._loadMoreClicked();
+        }
+    },
+
+    /**
+     * Selects the item with the given ID.
+     *
+     * @param id ID of the item to select.
+     */
+    selectItem: function(id) {
+
+        var selectivity = this.selectivity;
+        var item = Selectivity.findNestedById(selectivity.results, id);
+        if (item) {
+            var options = { id: id, item: item };
+            if (selectivity.triggerEvent('selectivity-selecting', options)) {
+                selectivity.triggerEvent('selectivity-selected', options);
+            }
+        }
+    },
+
+    /**
+     * Sets up an event handler that will close the dropdown when the Selectivity control loses
+     * focus.
+     */
+    setupCloseHandler: function() {
+
+        $('body').on('click', this._closeProxy);
+    },
+
+    /**
+     * Shows an error message.
+     *
+     * @param message Error message to display.
+     * @param options Options object. May contain the following property:
+     *                escape - Set to false to disable HTML-escaping of the message. Useful if you
+     *                         want to set raw HTML as the message, but may open you up to XSS
+     *                         attacks if you're not careful with escaping user input.
+     */
+    showError: function(message, options) {
+
+        options = options || {};
+
+        this.$results.html(this.selectivity.template('error', {
+            escape: options.escape !== false,
+            message: message
+        }));
+
+        this.hasMore = false;
+        this.results = [];
+
+        this.highlightedResult = null;
+        this.loadMoreHighlighted = false;
+
+        this.position();
+    },
+
+    /**
+     * Shows a loading indicator in the dropdown.
+     */
+    showLoading: function() {
+
+        this.$results.html(this.selectivity.template('loading'));
+
+        this.hasMore = false;
+        this.results = [];
+
+        this.highlightedResult = null;
+        this.loadMoreHighlighted = false;
+
+        this.position();
+    },
+
+    /**
+     * Shows the results from a search query.
+     *
+     * @param results Array of result items.
+     * @param options Options object. May contain the following properties:
+     *                add - True if the results should be added to any already shown results.
+     *                hasMore - Boolean whether more results can be fetched using the query()
+     *                          function.
+     *                term - The search term for which the results are displayed.
+     */
+    showResults: function(results, options) {
+
+        var resultsHtml = this.renderItems(results);
+        if (options.hasMore) {
+            resultsHtml += this.selectivity.template('loadMore');
+        } else {
+            if (!resultsHtml && !options.add) {
+                resultsHtml = this.selectivity.template('noResults', { term: options.term });
+            }
+        }
+
+        if (options.add) {
+            this.$('.selectivity-loading').replaceWith(resultsHtml);
+
+            this.results = this.results.concat(results);
+        } else {
+            this.$results.html(resultsHtml);
+
+            this.results = results;
+        }
+
+        this.hasMore = options.hasMore;
+
+        if (!options.add || this.loadMoreHighlighted) {
+            this._highlightFirstItem(results);
+        }
+
+        this.position();
+    },
+
+    /**
+     * Triggers the 'selectivity-close' event.
+     */
+    triggerClose: function() {
+
+        this.selectivity.$el.trigger('selectivity-close');
+    },
+
+    /**
+     * Triggers the 'selectivity-open' event.
+     */
+    triggerOpen: function() {
+
+        this.selectivity.$el.trigger('selectivity-open');
+    },
+
+    /**
+     * @private
+     */
+    _highlightFirstItem: function(results) {
+
+        function findFirstItem(results) {
+            for (var i = 0, length = results.length; i < length; i++) {
+                var result = results[i];
+                if (result.id) {
+                    return result;
+                } else if (result.children) {
+                    var item = findFirstItem(result.children);
+                    if (item) {
+                        return item;
+                    }
+                }
+            }
+        }
+
+        var firstItem = findFirstItem(results);
+        if (firstItem) {
+            this.highlight(firstItem);
+        } else {
+            this.highlightedResult = null;
+            this.loadMoreHighlighted = false;
+        }
+    },
+
+    /**
+     * @private
+     */
+    _loadMoreClicked: function() {
+
+        this.$('.selectivity-load-more').replaceWith(this.selectivity.template('loading'));
+
+        this.selectivity.loadMore();
+
+        this.selectivity.focus();
+
+        return false;
+    },
+
+    /**
+     * @private
+     */
+    _loadMoreHovered: function(event) {
+
+        if (event.screenX === undefined || event.screenX !== this._lastMousePosition.x ||
+            event.screenY === undefined || event.screenY !== this._lastMousePosition.y) {
+            this.highlightLoadMore();
+
+            this._recordMousePosition(event);
+        }
+    },
+
+    /**
+     * @private
+     */
+    _recordMousePosition: function(event) {
+
+        this._lastMousePosition = { x: event.screenX, y: event.screenY };
+    },
+
+    /**
+     * @private
+     */
+    _resultClicked: function(event) {
+
+        this.selectItem(this.selectivity._getItemId(event));
+
+        return false;
+    },
+
+    /**
+     * @private
+     */
+    _resultHovered: function(event) {
+
+        if (event.screenX === undefined || event.screenX !== this._lastMousePosition.x ||
+            event.screenY === undefined || event.screenY !== this._lastMousePosition.y) {
+            var id = this.selectivity._getItemId(event);
+            var item = Selectivity.findNestedById(this.results, id);
+            if (item) {
+                this.highlight(item);
+            }
+
+            this._recordMousePosition(event);
+        }
+    },
+
+    /**
+     * @private
+     */
+    _scrolled: function() {
+
+        var $loadMore = this.$('.selectivity-load-more');
+        if ($loadMore.length) {
+            if ($loadMore[0].offsetTop - this.$results[0].scrollTop < this.$el.height()) {
+                this._loadMoreClicked();
+            }
+        }
+    },
+
+    /**
+     * @private
+     */
+    _suppressMouseWheel: function() {
+
+        var suppressMouseWheelSelector = this.selectivity.options.suppressMouseWheelSelector;
+        if (suppressMouseWheelSelector === null) {
+            return;
+        }
+
+        var selector = suppressMouseWheelSelector || '.selectivity-results-container';
+        this.$el.on('DOMMouseScroll mousewheel', selector, function(event) {
+
+            // Thanks to Troy Alford:
+            // http://stackoverflow.com/questions/5802467/prevent-scrolling-of-parent-element
+
+            var $el = $(this),
+                scrollTop = this.scrollTop,
+                scrollHeight = this.scrollHeight,
+                height = $el.height(),
+                originalEvent = event.originalEvent,
+                delta = (event.type === 'DOMMouseScroll' ? originalEvent.detail * -40
+                                                         : originalEvent.wheelDelta),
+                up = delta > 0;
+
+            function prevent() {
+                event.stopPropagation();
+                event.preventDefault();
+                event.returnValue = false;
+                return false;
+            }
+
+            if (scrollHeight > height) {
+                if (!up && -delta > scrollHeight - height - scrollTop) {
+                    // Scrolling down, but this will take us past the bottom.
+                    $el.scrollTop(scrollHeight);
+                    return prevent();
+                } else if (up && delta > scrollTop) {
+                    // Scrolling up, but this will take us past the top.
+                    $el.scrollTop(0);
+                    return prevent();
+                }
+            }
+        });
+    }
+
+});
+
+module.exports = Selectivity.Dropdown = SelectivityDropdown;
+
+},{"2":2,"3":3,"8":8,"jquery":"jquery"}],11:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var Selectivity = _dereq_(8);
+var MultipleSelectivity = _dereq_(14);
+
+function isValidEmail(email) {
+
+    var atIndex = email.indexOf('@');
+    var dotIndex = email.lastIndexOf('.');
+    var spaceIndex = email.indexOf(' ');
+    return (atIndex > 0 && dotIndex > atIndex + 1 &&
+            dotIndex < email.length - 2 && spaceIndex === -1);
+}
+
+function lastWord(token, length) {
+
+    length = (length === undefined ? token.length : length);
+    for (var i = length - 1; i >= 0; i--) {
+        if ((/\s/).test(token[i])) {
+            return token.slice(i + 1, length);
+        }
+    }
+    return token.slice(0, length);
+}
+
+function stripEnclosure(token, enclosure) {
+
+    if (token.slice(0, 1) === enclosure[0] && token.slice(-1) === enclosure[1]) {
+        return token.slice(1, -1).trim();
+    } else {
+        return token.trim();
+    }
+}
+
+function createEmailItem(token) {
+
+    var email = lastWord(token);
+    var name = token.slice(0, -email.length).trim();
+    if (isValidEmail(email)) {
+        email = stripEnclosure(stripEnclosure(email, '()'), '<>');
+        name = stripEnclosure(name, '""').trim() || email;
+        return { id: email, text: name };
+    } else {
+        return (token.trim() ? { id: token, text: token } : null);
+    }
+}
+
+function emailTokenizer(input, selection, createToken) {
+
+    function hasToken(input) {
+        if (input) {
+            for (var i = 0, length = input.length; i < length; i++) {
+                switch (input[i]) {
+                case ';':
+                case ',':
+                case '\n':
+                    return true;
+                case ' ':
+                case '\t':
+                    if (isValidEmail(lastWord(input, i))) {
+                        return true;
+                    }
+                    break;
+                case '"':
+                    do { i++; } while(i < length && input[i] !== '"');
+                    break;
+                default:
+                    continue;
+                }
+            }
+        }
+        return false;
+    }
+
+    function takeToken(input) {
+        for (var i = 0, length = input.length; i < length; i++) {
+            switch (input[i]) {
+            case ';':
+            case ',':
+            case '\n':
+                return { term: input.slice(0, i), input: input.slice(i + 1) };
+            case ' ':
+            case '\t':
+                if (isValidEmail(lastWord(input, i))) {
+                    return { term: input.slice(0, i), input: input.slice(i + 1) };
+                }
+                break;
+            case '"':
+                do { i++; } while(i < length && input[i] !== '"');
+                break;
+            default:
+                continue;
+            }
+        }
+        return {};
+    }
+
+    while (hasToken(input)) {
+        var token = takeToken(input);
+        if (token.term) {
+            var item = createEmailItem(token.term);
+            if (item && !(item.id && Selectivity.findById(selection, item.id))) {
+                createToken(item);
+            }
+        }
+        input = token.input;
+    }
+
+    return input;
+}
+
+/**
+ * Emailselectivity Constructor.
+ *
+ * @param options Options object. Accepts all options from the MultipleSelectivity Constructor.
+ */
+function Emailselectivity(options) {
+
+    MultipleSelectivity.call(this, options);
+}
+
+/**
+ * Methods.
+ */
+var callSuper = Selectivity.inherits(Emailselectivity, MultipleSelectivity, {
+
+    /**
+     * @inherit
+     */
+    initSearchInput: function($input) {
+
+        callSuper(this, 'initSearchInput', $input);
+
+        $input.on('blur', function() {
+            var term = $input.val();
+            if (isValidEmail(lastWord(term))) {
+                this.add(createEmailItem(term));
+            }
+        }.bind(this));
+    },
+
+    /**
+     * @inherit
+     *
+     * Note that for the Email input type the option showDropdown is set to false and the tokenizer
+     * option is set to a tokenizer specialized for email addresses.
+     */
+    setOptions: function(options) {
+
+        options = $.extend({
+            createTokenItem: createEmailItem,
+            showDropdown: false,
+            tokenizer: emailTokenizer
+        }, options);
+
+        callSuper(this, 'setOptions', options);
+    }
+
+});
+
+module.exports = Selectivity.InputTypes.Email = Emailselectivity;
+
+},{"14":14,"8":8,"jquery":"jquery"}],12:[function(_dereq_,module,exports){
+'use strict';
+
+var Selectivity = _dereq_(8);
+
+var KEY_BACKSPACE = 8;
+var KEY_DOWN_ARROW = 40;
+var KEY_ENTER = 13;
+var KEY_ESCAPE = 27;
+var KEY_TAB = 9;
+var KEY_UP_ARROW = 38;
+
+/**
+ * Search input listener providing keyboard support for navigating the dropdown.
+ */
+function listener(selectivity, $input) {
+
+    var keydownCanceled = false;
+    var closeSubmenu = null;
+
+    /**
+     * Moves a dropdown's highlight to the next or previous result item.
+     *
+     * @param delta Either 1 to move to the next item, or -1 to move to the previous item.
+     */
+    function moveHighlight(dropdown, delta) {
+
+        function findElementIndex($elements, selector) {
+            for (var i = 0, length = $elements.length; i < length; i++) {
+                if ($elements.eq(i).is(selector)) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        function scrollToHighlight() {
+            var $el;
+            if (dropdown.highlightedResult) {
+                var quotedId = Selectivity.quoteCssAttr(dropdown.highlightedResult.id);
+                $el = dropdown.$('.selectivity-result-item[data-item-id=' + quotedId + ']');
+            } else if (dropdown.loadMoreHighlighted) {
+                $el = dropdown.$('.selectivity-load-more');
+            } else {
+                return; // no highlight to scroll to
+            }
+
+            var position = $el.position();
+            if (!position) {
+                return;
+            }
+
+            var top = position.top;
+            var resultsHeight = dropdown.$results.height();
+            var elHeight = ($el.outerHeight ? $el.outerHeight() : $el.height());
+            if (top < 0 || top > resultsHeight - elHeight) {
+                top += dropdown.$results.scrollTop();
+                dropdown.$results.scrollTop(delta < 0 ? top : top - resultsHeight + elHeight);
+            }
+        }
+
+        if (dropdown.submenu) {
+            moveHighlight(dropdown.submenu, delta);
+            return;
+        }
+
+        var results = dropdown.results;
+        if (results.length) {
+            var $results = dropdown.$('.selectivity-result-item');
+            var defaultIndex = (delta > 0 ? 0 : $results.length - 1);
+            var index = defaultIndex;
+            var highlightedResult = dropdown.highlightedR

<TRUNCATED>

Mime
View raw message