Swapped out tagsManager with selectivity for licensing purposes
diff --git a/LICENSE b/LICENSE
index 401ee55..4924c2e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -200,7 +200,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
==========================================================================
The Apache License, Version 2.0 applies to the following libraries:
@@ -370,6 +370,13 @@
./blur-console/src/main/webapp/libs/moment/moment.js
Copyright (c) 2011-2014 Tim Wood, Iskren Chernev, Moment.js contributors
+selectivity.js
+./blur-console/src/main/webapp/libs/selectivity/selectivity-full.js
+./blur-console/src/main/webapp/less/selectivity-full.css
+Copyright (c) 2014-2015 Arend van Beelen jr.
+ (c) 2015 Speakap BV
+https://github.com/arendjr/selectivity/blob/master/LICENSE
+
==========================================================================
The following license applies to the following libraries: d3
./blur-gui/src/main/webapp/js/d3.v2.js
@@ -402,10 +409,3 @@
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-===========================================================================
-The following license applies to the following libraries: tagmanager
-./blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
----------------------------------------------------------------------------
-Copyright 2012 MaxFavilli.com
-Licensed under the Mozilla Public License, Version 2.0
-
diff --git a/blur-console/src/main/webapp/Gruntfile.js b/blur-console/src/main/webapp/Gruntfile.js
index 1c6f70c..b3e6eb3 100644
--- a/blur-console/src/main/webapp/Gruntfile.js
+++ b/blur-console/src/main/webapp/Gruntfile.js
@@ -43,7 +43,7 @@
'libs/flot/jquery.flot.categories.js',
'libs/flot/jquery.flot.stack.js',
'libs/typeahead/typeahead.jquery.js',
- 'libs/tagmanager/tagmanager.js',
+ 'libs/selectivity/selectivity-full.js',
'libs/moment/moment.js',
'js/blurconsole.js',
'js/*\.js'
@@ -203,6 +203,7 @@
main: {
files: [
{expand: true, src: ['index.html','img/*','views/*'], dest: 'public/'},
+ {expand: true, flatten: true, src: ['less/*.css'], dest: 'public/css'},
{expand: true, flatten: true, src: ['libs/modernizr/modernizr.js'], dest: 'public/js'},
{expand: true, flatten: true, src: ['libs/bootstrap/fonts/*'], dest: 'public/css/fonts'}
]
diff --git a/blur-console/src/main/webapp/js/blurconsole.facets.js b/blur-console/src/main/webapp/js/blurconsole.facets.js
index a512505..4eb9e0f 100644
--- a/blur-console/src/main/webapp/js/blurconsole.facets.js
+++ b/blur-console/src/main/webapp/js/blurconsole.facets.js
@@ -44,8 +44,9 @@
+ '</div>'
+ '<div class="col-md-6">'
+ '<div class="form-group">'
- + '<label for="facetTerms">Terms</label>'
- + '<input type="text" name="facetTerms" class="form-control tm-input" id="facetTerms" autocomplete="off"/>'
+ + '<label>Terms</label>'
+ + '<span id="facetTerms"></span>'
+ // + '<input type="text" name="facetTerms" class="form-control tm-input" id="facetTerms" autocomplete="off"/>'
+ '<div class="facetTermList"></div>'
+ '</div>'
+ '<div class="form-group">'
@@ -101,8 +102,9 @@
jqueryMap.facetResults = $('.facetResults', jqueryMap.modal);
jqueryMap.terms = $('#facetTerms', jqueryMap.modal);
- jqueryMap.terms.tagsManager({
- tagsContainer: $('.facetTermList', jqueryMap.modal)
+ jqueryMap.terms.selectivity({
+ inputType: 'Email',
+ placeholder: 'Enter in terms'
});
$('#facetSubmit', jqueryMap.modal).on('click', _runFacetCounts);
@@ -142,12 +144,7 @@
function _runFacetCounts() {
var family = jqueryMap.familyChooser.val();
var column = jqueryMap.columnChooser.val();
- var terms = jqueryMap.terms.tagsManager('tags');
-
- if (jqueryMap.terms.val()) {
- jqueryMap.terms.tagsManager('pushTag', jqueryMap.terms.val());
- terms = jqueryMap.terms.tagsManager('tags');
- }
+ var terms = jqueryMap.terms.selectivity('value');
if (family && column && terms.length > 0) {
$('.facetWarning', jqueryMap.modal).hide();
diff --git a/blur-console/src/main/webapp/less/blurconsole.less b/blur-console/src/main/webapp/less/blurconsole.less
index 226a549..31c878e 100644
--- a/blur-console/src/main/webapp/less/blurconsole.less
+++ b/blur-console/src/main/webapp/less/blurconsole.less
@@ -30,4 +30,4 @@
@import 'blurconsole.queries.less';
@import 'blurconsole.search.less';
@import 'typeahead';
-@import 'tagmanager';
+@import 'selectivity-full.css';
diff --git a/blur-console/src/main/webapp/less/selectivity-full.css b/blur-console/src/main/webapp/less/selectivity-full.css
new file mode 100644
index 0000000..a153458
--- /dev/null
+++ b/blur-console/src/main/webapp/less/selectivity-full.css
@@ -0,0 +1,214 @@
+/**
+ * All CSS that comes with Selectivity.js can be used as is, or tweaked to your heart's content :)
+ *
+ * Please realize though there is no "API contract" regarding styling of CSS classes, meaning that
+ * any customized CSS made may need to be updated without warning if you want to upgrade the
+ * Selectivity version you use. You can mitigate this problem by using your own templates instead of
+ * those defined in selectivity-templates.js, since templates will at the very least continue
+ * working across patch versions and any changes necessary to templates will be documented in the
+ * changelog.
+ */
+.selectivity-clearfix {
+ clear: both; }
+
+.selectivity-input {
+ display: inline-block;
+ width: 250px; }
+ .selectivity-input select {
+ display: none; }
+
+.selectivity-placeholder {
+ color: #999; }
+
+/**
+ * Backdrop
+ */
+.selectivity-backdrop {
+ background: transparent;
+ position: fixed;
+ z-index: 9998;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0; }
+
+/**
+ * Dropdown
+ */
+.selectivity-dropdown {
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 0 1px 5px 1px rgba(0, 0, 0, 0.15), 0 10px 16px 0 rgba(0, 0, 0, 0.2);
+ position: absolute;
+ z-index: 9999; }
+
+.selectivity-search-input-container {
+ border-bottom: 1px solid #eee; }
+
+.selectivity-search-input {
+ background: transparent;
+ border: 0;
+ outline: 0;
+ width: 100%; }
+
+.selectivity-results-container {
+ max-height: 28em;
+ overflow: auto;
+ position: relative; }
+
+.selectivity-load-more,
+.selectivity-result-item {
+ cursor: pointer;
+ padding: 7px; }
+
+.selectivity-result-children .selectivity-result-item {
+ padding-left: 17px; }
+
+.selectivity-load-more.highlight,
+.selectivity-result-item.highlight {
+ background: #4484c7;
+ color: #fff; }
+
+.selectivity-result-item:first-child {
+ border-radius: 4px 4px 0 0; }
+
+.selectivity-dropdown.has-search-input .selectivity-result-item:first-child {
+ border-radius: 0; }
+
+.selectivity-result-label {
+ font-weight: bold; }
+
+.selectivity-load-more,
+.selectivity-result-item:last-child,
+.selectivity-result-children:last-child .selectivity-result-item:last-child {
+ border-radius: 0 0 4px 4px; }
+
+.selectivity-result-children .selectivity-result-item:last-child {
+ border-radius: 0; }
+
+.selectivity-error,
+.selectivity-loading,
+.selectivity-search-input-container,
+.selectivity-result-label {
+ padding: 7px; }
+
+/**
+ * Multi-selection input
+ */
+.selectivity-multiple-input-container {
+ background: #eee;
+ border-radius: 2px;
+ cursor: text;
+ max-height: 10em;
+ min-height: -webkit-calc(2em + 4px);
+ min-height: calc(2em + 4px);
+ overflow: auto;
+ padding: 5px; }
+
+.selectivity-multiple-input-container .selectivity-placeholder {
+ height: -webkit-calc(2em + 4px);
+ height: calc(2em + 4px);
+ line-height: -webkit-calc(2em + 4px);
+ line-height: calc(2em + 4px); }
+
+.selectivity-multiple-input,
+input[type='text'].selectivity-multiple-input {
+ background-color: transparent;
+ border: none;
+ float: left;
+ height: -webkit-calc(2em + 4px);
+ height: calc(2em + 4px);
+ max-width: 100%;
+ outline: 0;
+ padding: 0; }
+ .selectivity-multiple-input:focus,
+ input[type='text'].selectivity-multiple-input:focus {
+ background-color: transparent;
+ box-shadow: none;
+ outline: none; }
+
+.selectivity-multiple-input::-ms-clear {
+ display: none; }
+
+.selectivity-multiple-input.selectivity-width-detector {
+ position: absolute;
+ top: -10000px;
+ left: 0;
+ white-space: pre; }
+
+.selectivity-multiple-selected-item {
+ background: #4484c7;
+ border-radius: 3px;
+ color: #fff;
+ cursor: default;
+ float: left;
+ line-height: 2em;
+ margin: 2px;
+ padding-right: 5px;
+ position: relative;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ white-space: nowrap; }
+ .selectivity-multiple-selected-item.highlighted {
+ background-color: #ccc; }
+
+.selectivity-multiple-selected-item-remove {
+ color: #fff;
+ cursor: pointer;
+ padding: 5px; }
+
+/**
+ * Single-selection input
+ */
+.selectivity-single-select {
+ background: #eee;
+ border-radius: 2px;
+ cursor: pointer;
+ min-height: 2em;
+ padding: 5px;
+ position: relative;
+ box-sizing: content-box; }
+
+.selectivity-single-select-input {
+ opacity: 0; }
+
+.selectivity-single-result-container {
+ position: absolute;
+ top: 0.8em;
+ right: 15px;
+ left: 5px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap; }
+
+.selectivity-single-selected-item {
+ color: #000; }
+
+.selectivity-single-selected-item-remove {
+ color: #000;
+ float: right;
+ padding: 0 5px; }
+
+.selectivity-caret {
+ position: absolute;
+ right: 5px;
+ top: 0.7em; }
+
+@media only screen and (max-device-width: 480px) {
+ .selectivity-single-select {
+ background: #eee;
+ border-radius: 2px; }
+
+ .selectivity-single-result-container {
+ right: 5px; }
+
+ .selectivity-caret {
+ display: none; } }
+/**
+ * Submenu
+ */
+.selectivity-submenu-icon {
+ position: absolute;
+ right: 4px; }
diff --git a/blur-console/src/main/webapp/less/tagmanager.less b/blur-console/src/main/webapp/less/tagmanager.less
deleted file mode 100644
index 76aad99..0000000
--- a/blur-console/src/main/webapp/less/tagmanager.less
+++ /dev/null
@@ -1,195 +0,0 @@
-//
-// Bootstrap TagManager
-// --------------------------------------------------
-
-// Tag Variables
-// --------------------------------------------------
-
-// Colors
-// -------------------------
-
-@white: #ffffff;
-@black: #000000;
-@gray: #555555;
-@grayDark: #333333;
-
-@textColor: @grayDark;
-
-@tagText: @gray;
-@tagBackground: #f5f5f5;
-@tagBorder: #bbb;
-
-@tagWarningText: #945203;
-@tagWarningBackground: #f2c889;
-@tagWarningBorder: #f0a12f;
-
-@tagErrorText: #84212e;
-@tagErrorBackground: #e69ca6;
-@tagErrorBorder: #d24a5d;
-
-@tagSuccessText: #638421;
-@tagSuccessBackground: #cde69c;
-@tagSuccessBorder: #a5d24a;
-
-@tagInfoText: #4594b5;
-@tagInfoBackground: #c5eefa;
-@tagInfoBorder: #5dc8f7;
-
-@tagInverseText: #ccc;
-@tagInverseBackground: @gray;
-@tagInverseBorder: @grayDark;
-
-@tagDisabledText: #aaa;
-@tagDisabledBackground: #e6e6e6;
-@tagDisabledBorder: #ccc;
-
-// Sizing
-// -------------------------
-
-@tagFontSize: 13px;
-@tagFontSizeLarge: @tagFontSize * 1.25; // ~16px
-@tagFontSizeSmall: @tagFontSize * 0.85; // ~11px
-@tagFontSizeMini: @tagFontSize * 0.75; // ~10px
-
-@tagPadding: 4px;
-@tagMargin: 5px;
-
-@borderRadiusSmall: 3px;
-@baseBorderRadius: 4px;
-
-@baseLineHeight: 20px;
-
-// Tag Classes
-// --------------------------------------------------
-
-// Fonts
-// --------------------------------------------------
-
-@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
-
-// Base tag class
-// -------------------------
-
-.tm-tag {
- color: @tagText;
- background-color: @tagBackground;
- border: @tagBorder 1px solid;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset;
- display: inline-block;
- border-radius: @borderRadiusSmall;
- font-family: @sansFontFamily;
- font-size: @tagFontSize;
- margin: 0 @tagMargin @tagMargin 0;
- padding: @tagPadding;
- text-decoration: none;
- transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
- -moz-transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
- -webkit-transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
- vertical-align: middle;
-
- // Remove button
- // -------------------------
-
- .tm-tag-remove {
- color: @black;
- font-weight: bold;
- margin-left: @tagPadding;
- opacity: 0.2;
- &:hover {
- color: @black;
- text-decoration: none;
- opacity: 0.4;
- }
- }
-
- // Semantic Colors
- // -------------------------
-
- &.tm-tag-warning {
- color: @tagWarningText;
- background-color: @tagWarningBackground;
- border-color: @tagWarningBorder;
- }
- &.tm-tag-error {
- color: @tagErrorText;
- background-color: @tagErrorBackground;
- border-color: @tagErrorBorder;
- }
- &.tm-tag-success {
- color: @tagSuccessText;
- background-color: @tagSuccessBackground;
- border-color: @tagSuccessBorder;
- }
- &.tm-tag-info {
- color: @tagInfoText;
- background-color: @tagInfoBackground;
- border-color: @tagInfoBorder;
- }
- &.tm-tag-inverse {
- color: @tagInverseText;
- background-color: @tagInverseBackground;
- border-color: @tagInverseBorder;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset;
- .tm-tag-remove {
- color: @white;
- }
- }
-
- // Sizes
- // -------------------------
-
- &.tm-tag-large {
- font-size: @tagFontSizeLarge;
- border-radius: @baseBorderRadius;
- padding: 11px 7px;
- }
- &.tm-tag-small {
- font-size: @tagFontSizeSmall;
- border-radius: @borderRadiusSmall;
- padding: 2px 4px;
- }
- &.tm-tag-mini {
- font-size: @tagFontSizeMini;
- border-radius: 2px;
- padding: 0px 2px;
- }
-
- // Miscellaneous Styles
- // -------------------------
-
- &.tm-tag-plain {
- color: @textColor;
- box-shadow: none;
- background: none;
- border: none;
- }
- &.tm-tag-disabled {
- color: @tagDisabledText;
- background-color: @tagDisabledBackground;
- border-color: @tagDisabledBorder;
- box-shadow: none;
- .tm-tag-remove {
- display: none;
- }
- }
-}
-
-// Forms
-// --------------------------------------------------
-
-// Input style (Recommended)
-// -------------------------
-
-input[type="text"].tm-input {
- margin-bottom: @tagMargin;
-}
-
-// Form wrappers (Optional)
-// -------------------------
-
-.control-group.tm-group {
- margin-bottom: (@baseLineHeight / 2) - @tagMargin;
-}
-.form-horizontal .control-group.tm-group {
- margin-bottom: @baseLineHeight - @tagMargin;
-}
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 = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+};
+
+/**
+ * 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, & 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.highlightedResult;
+ if (highlightedResult) {
+ var quotedId = Selectivity.quoteCssAttr(highlightedResult.id);
+ index = findElementIndex($results, '[data-item-id=' + quotedId + ']') + delta;
+ if (delta > 0 ? index >= $results.length : index < 0) {
+ if (dropdown.hasMore) {
+ dropdown.highlightLoadMore();
+ scrollToHighlight();
+ return;
+ } else {
+ index = defaultIndex;
+ }
+ }
+ }
+
+ var result = Selectivity.findNestedById(results,
+ selectivity._getItemId($results[index]));
+ if (result) {
+ dropdown.highlight(result, { delay: !!result.submenu });
+ scrollToHighlight();
+ }
+ }
+ }
+
+ function keyHeld(event) {
+
+ var dropdown = selectivity.dropdown;
+ if (dropdown) {
+ if (event.keyCode === KEY_BACKSPACE) {
+ if (!$input.val()) {
+ if (dropdown.submenu) {
+ var submenu = dropdown.submenu;
+ while (submenu.submenu) {
+ submenu = submenu.submenu;
+ }
+ closeSubmenu = submenu;
+ }
+
+ event.preventDefault();
+ keydownCanceled = true;
+ }
+ } else if (event.keyCode === KEY_DOWN_ARROW) {
+ moveHighlight(dropdown, 1);
+ } else if (event.keyCode === KEY_UP_ARROW) {
+ moveHighlight(dropdown, -1);
+ } else if (event.keyCode === KEY_TAB) {
+ setTimeout(function() {
+ selectivity.close({ keepFocus: false });
+ }, 1);
+ }
+ }
+ }
+
+ function keyReleased(event) {
+
+ function open() {
+ if (selectivity.options.showDropdown !== false) {
+ selectivity.open();
+ }
+ }
+
+ var dropdown = selectivity.dropdown;
+ if (keydownCanceled) {
+ event.preventDefault();
+ keydownCanceled = false;
+
+ if (closeSubmenu) {
+ closeSubmenu.close();
+ selectivity.focus();
+ closeSubmenu = null;
+ }
+ } else if (event.keyCode === KEY_BACKSPACE) {
+ if (!dropdown && selectivity.options.allowClear) {
+ selectivity.clear();
+ }
+ } else if (event.keyCode === KEY_ENTER && !event.ctrlKey) {
+ if (dropdown) {
+ dropdown.selectHighlight();
+ } else if (selectivity.options.showDropdown !== false) {
+ open();
+ }
+
+ event.preventDefault();
+ } else if (event.keyCode === KEY_ESCAPE) {
+ selectivity.close();
+
+ event.preventDefault();
+ } else if (event.keyCode === KEY_DOWN_ARROW || event.keyCode === KEY_UP_ARROW) {
+ // handled in keyHeld() because the response feels faster and it works with repeated
+ // events if the user holds the key for a longer period
+ // still, we issue an open() call here in case the dropdown was not yet open...
+ open();
+
+ event.preventDefault();
+ } else {
+ open();
+ }
+ }
+
+ $input.on('keydown', keyHeld).on('keyup', keyReleased);
+}
+
+Selectivity.SearchInputListeners.push(listener);
+
+},{"8":8}],13:[function(_dereq_,module,exports){
+'use strict';
+
+var escape = _dereq_(4);
+var Selectivity = _dereq_(8);
+
+/**
+ * Localizable elements of the Selectivity Templates.
+ *
+ * Be aware that these strings are added straight to the HTML output of the templates, so any
+ * non-safe strings should be escaped.
+ */
+Selectivity.Locale = {
+
+ ajaxError: function(term) { return 'Failed to fetch results for <b>' + escape(term) + '</b>'; },
+ loading: 'Loading...',
+ loadMore: 'Load more...',
+ needMoreCharacters: function(numCharacters) {
+ return 'Enter ' + numCharacters + ' more characters to search';
+ },
+ noResults: 'No results found',
+ noResultsForTerm: function(term) { return 'No results for <b>' + escape(term) + '</b>'; }
+
+};
+
+},{"4":4,"8":8}],14:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var Selectivity = _dereq_(8);
+
+var KEY_BACKSPACE = 8;
+var KEY_DELETE = 46;
+var KEY_ENTER = 13;
+
+/**
+ * MultipleSelectivity Constructor.
+ *
+ * @param options Options object. Accepts all options from the Selectivity Base Constructor in
+ * addition to those accepted by MultipleSelectivity.setOptions().
+ */
+function MultipleSelectivity(options) {
+
+ Selectivity.call(this, options);
+
+ this.$el.html(this.template('multipleSelectInput', { enabled: this.enabled }))
+ .trigger('selectivity-init', 'multiple');
+
+ this._highlightedItemId = null;
+
+ this.initSearchInput(this.$('.selectivity-multiple-input:not(.selectivity-width-detector)'));
+
+ this._rerenderSelection();
+
+ if (!options.positionDropdown) {
+ // dropdowns for multiple-value inputs should open below the select box,
+ // unless there is not enough space below, but there is space enough above, then it should
+ // open upwards
+ this.options.positionDropdown = function($el, $selectEl) {
+ var position = $selectEl.position(),
+ dropdownHeight = $el.height(),
+ selectHeight = $selectEl.height(),
+ top = $selectEl[0].getBoundingClientRect().top,
+ bottom = top + selectHeight + dropdownHeight,
+ openUpwards = (typeof window !== 'undefined' && bottom > $(window).height() &&
+ top - dropdownHeight > 0);
+
+ var width = $selectEl.outerWidth ? $selectEl.outerWidth() : $selectEl.width();
+ $el.css({
+ left: position.left + 'px',
+ top: position.top + (openUpwards ? -dropdownHeight : selectHeight) + 'px'
+ }).width(width);
+ };
+ }
+}
+
+/**
+ * Methods.
+ */
+var callSuper = Selectivity.inherits(MultipleSelectivity, {
+
+ /**
+ * Adds an item to the selection, if it's not selected yet.
+ *
+ * @param item The item to add. May be an item with 'id' and 'text' properties or just an ID.
+ */
+ add: function(item) {
+
+ var itemIsId = Selectivity.isValidId(item);
+ var id = (itemIsId ? item : this.validateItem(item) && item.id);
+
+ if (this._value.indexOf(id) === -1) {
+ this._value.push(id);
+
+ if (itemIsId && this.options.initSelection) {
+ this.options.initSelection([id], function(data) {
+ if (this._value.indexOf(id) > -1) {
+ item = this.validateItem(data[0]);
+ this._data.push(item);
+
+ this.triggerChange({ added: item });
+ }
+ }.bind(this));
+ } else {
+ if (itemIsId) {
+ item = this.getItemForId(id);
+ }
+ this._data.push(item);
+
+ this.triggerChange({ added: item });
+ }
+ }
+
+ this.$searchInput.val('');
+ },
+
+ /**
+ * Clears the data and value.
+ */
+ clear: function() {
+
+ this.data([]);
+ },
+
+ /**
+ * Events map.
+ *
+ * Follows the same format as Backbone: http://backbonejs.org/#View-delegateEvents
+ */
+ events: {
+ 'change': '_rerenderSelection',
+ 'change .selectivity-multiple-input': function() { return false; },
+ 'click': '_clicked',
+ 'click .selectivity-multiple-selected-item': '_itemClicked',
+ 'keydown .selectivity-multiple-input': '_keyHeld',
+ 'keyup .selectivity-multiple-input': '_keyReleased',
+ 'paste .selectivity-multiple-input': '_onPaste',
+ 'selectivity-selected': '_resultSelected'
+ },
+
+ /**
+ * @inherit
+ */
+ filterResults: function(results) {
+
+ return results.filter(function(item) {
+ return !Selectivity.findById(this._data, item.id);
+ }, this);
+ },
+
+ /**
+ * Returns the correct data for a given value.
+ *
+ * @param value The value to get the data for. Should be an array of IDs.
+ *
+ * @return The corresponding data. Will be an array of objects with 'id' and 'text' properties.
+ * Note that if no items are defined, this method assumes the text labels will be equal
+ * to the IDs.
+ */
+ getDataForValue: function(value) {
+
+ return value.map(this.getItemForId.bind(this)).filter(function(item) { return !!item; });
+ },
+
+ /**
+ * Returns the correct value for the given data.
+ *
+ * @param data The data to get the value for. Should be an array of objects with 'id' and 'text'
+ * properties.
+ *
+ * @return The corresponding value. Will be an array of IDs.
+ */
+ getValueForData: function(data) {
+
+ return data.map(function(item) { return item.id; });
+ },
+
+ /**
+ * Removes an item from the selection, if it is selected.
+ *
+ * @param item The item to remove. May be an item with 'id' and 'text' properties or just an ID.
+ */
+ remove: function(item) {
+
+ var id = ($.type(item) === 'object' ? item.id : item);
+
+ var removedItem;
+ var index = Selectivity.findIndexById(this._data, id);
+ if (index > -1) {
+ removedItem = this._data[index];
+ this._data.splice(index, 1);
+ }
+
+ if (this._value[index] !== id) {
+ index = this._value.indexOf(id);
+ }
+ if (index > -1) {
+ this._value.splice(index, 1);
+ }
+
+ if (removedItem) {
+ this.triggerChange({ removed: removedItem });
+ }
+
+ if (id === this._highlightedItemId) {
+ this._highlightedItemId = null;
+ }
+ },
+
+ /**
+ * @inherit
+ */
+ search: function() {
+
+ var term = this.$searchInput.val();
+
+ if (this.options.tokenizer) {
+ term = this.options.tokenizer(term, this._data, this.add.bind(this), this.options);
+
+ if ($.type(term) === 'string') {
+ this.$searchInput.val(term);
+ } else {
+ term = '';
+ }
+ }
+
+ if (this.dropdown) {
+ callSuper(this, 'search');
+ }
+ },
+
+ /**
+ * @inherit
+ *
+ * @param options Options object. In addition to the options supported in the base
+ * implementation, this may contain the following properties:
+ * backspaceHighlightsBeforeDelete - If set to true, when the user enters a
+ * backspace while there is no text in the
+ * search field but there are selected items,
+ * the last selected item will be highlighted
+ * and when a second backspace is entered the
+ * item is deleted. If false, the item gets
+ * deleted on the first backspace. The default
+ * value is true on devices that have touch
+ * input and false on devices that don't.
+ * createTokenItem - Function to create a new item from a user's search term.
+ * This is used to turn the term into an item when dropdowns
+ * are disabled and the user presses Enter. It is also used by
+ * the default tokenizer to create items for individual tokens.
+ * The function receives a 'token' parameter which is the
+ * search term (or part of a search term) to create an item for
+ * and must return an item object with 'id' and 'text'
+ * properties or null if no token can be created from the term.
+ * The default is a function that returns an item where the id
+ * and text both match the token for any non-empty string and
+ * which returns null otherwise.
+ * tokenizer - Function for tokenizing search terms. Will receive the following
+ * parameters:
+ * input - The input string to tokenize.
+ * selection - The current selection data.
+ * createToken - Callback to create a token from the search terms.
+ * Should be passed an item object with 'id' and 'text'
+ * properties.
+ * options - The options set on the Selectivity instance.
+ * Any string returned by the tokenizer function is treated as the
+ * remainder of untokenized input.
+ */
+ setOptions: function(options) {
+
+ options = options || {};
+
+ var backspaceHighlightsBeforeDelete = 'backspaceHighlightsBeforeDelete';
+ if (options[backspaceHighlightsBeforeDelete] === undefined) {
+ options[backspaceHighlightsBeforeDelete] = this.hasTouch;
+ }
+
+ options.allowedTypes = options.allowedTypes || {};
+ options.allowedTypes[backspaceHighlightsBeforeDelete] = 'boolean';
+
+ callSuper(this, 'setOptions', options);
+ },
+
+ /**
+ * Validates data to set. Throws an exception if the data is invalid.
+ *
+ * @param data The data to validate. Should be an array of objects with 'id' and 'text'
+ * properties.
+ *
+ * @return The validated data. This may differ from the input data.
+ */
+ validateData: function(data) {
+
+ if (data === null) {
+ return [];
+ } else if ($.type(data) === 'array') {
+ return data.map(this.validateItem.bind(this));
+ } else {
+ throw new Error('Data for MultiSelectivity instance should be array');
+ }
+ },
+
+ /**
+ * Validates a value to set. Throws an exception if the value is invalid.
+ *
+ * @param value The value to validate. Should be an array of IDs.
+ *
+ * @return The validated value. This may differ from the input value.
+ */
+ validateValue: function(value) {
+
+ if (value === null) {
+ return [];
+ } else if ($.type(value) === 'array') {
+ if (value.every(Selectivity.isValidId)) {
+ return value;
+ } else {
+ throw new Error('Value contains invalid IDs');
+ }
+ } else {
+ throw new Error('Value for MultiSelectivity instance should be an array');
+ }
+ },
+
+ /**
+ * @private
+ */
+ _backspacePressed: function() {
+
+ if (this.options.backspaceHighlightsBeforeDelete) {
+ if (this._highlightedItemId) {
+ this._deletePressed();
+ } else if (this._value.length) {
+ this._highlightItem(this._value.slice(-1)[0]);
+ }
+ } else if (this._value.length) {
+ this.remove(this._value.slice(-1)[0]);
+ }
+ },
+
+ /**
+ * @private
+ */
+ _clicked: function() {
+
+ if (this.enabled) {
+ this.focus();
+
+ this._open();
+
+ return false;
+ }
+ },
+
+ /**
+ * @private
+ */
+ _createToken: function() {
+
+ var term = this.$searchInput.val();
+ var createTokenItem = this.options.createTokenItem;
+
+ if (term && createTokenItem) {
+ var item = createTokenItem(term);
+ if (item) {
+ this.add(item);
+ }
+ }
+ },
+
+ /**
+ * @private
+ */
+ _deletePressed: function() {
+
+ if (this._highlightedItemId) {
+ this.remove(this._highlightedItemId);
+ }
+ },
+
+ /**
+ * @private
+ */
+ _highlightItem: function(id) {
+
+ this._highlightedItemId = id;
+ this.$('.selectivity-multiple-selected-item').removeClass('highlighted')
+ .filter('[data-item-id=' + Selectivity.quoteCssAttr(id) + ']').addClass('highlighted');
+
+ if (this.hasKeyboard) {
+ this.focus();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _itemClicked: function(event) {
+
+ if (this.enabled) {
+ this._highlightItem(this._getItemId(event));
+ }
+ },
+
+ /**
+ * @private
+ */
+ _itemRemoveClicked: function(event) {
+
+ this.remove(this._getItemId(event));
+
+ this._updateInputWidth();
+
+ return false;
+ },
+
+ /**
+ * @private
+ */
+ _keyHeld: function(event) {
+
+ this._originalValue = this.$searchInput.val();
+
+ if (event.keyCode === KEY_ENTER && !event.ctrlKey) {
+ event.preventDefault();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _keyReleased: function(event) {
+
+ var inputHadText = !!this._originalValue;
+
+ if (event.keyCode === KEY_ENTER && !event.ctrlKey) {
+ if (this.options.createTokenItem) {
+ this._createToken();
+ }
+ } else if (event.keyCode === KEY_BACKSPACE && !inputHadText) {
+ this._backspacePressed();
+ } else if (event.keyCode === KEY_DELETE && !inputHadText) {
+ this._deletePressed();
+ }
+
+ this._updateInputWidth();
+ },
+
+ /**
+ * @private
+ */
+ _onPaste: function() {
+
+ setTimeout(function() {
+ this.search();
+
+ if (this.options.createTokenItem) {
+ this._createToken();
+ }
+ }.bind(this), 10);
+ },
+
+ /**
+ * @private
+ */
+ _open: function() {
+
+ if (this.options.showDropdown !== false) {
+ this.open();
+ }
+ },
+
+ _renderSelectedItem: function(item) {
+
+ this.$searchInput.before(this.template('multipleSelectedItem', $.extend({
+ highlighted: (item.id === this._highlightedItemId),
+ removable: !this.options.readOnly
+ }, item)));
+
+ var quotedId = Selectivity.quoteCssAttr(item.id);
+ this.$('.selectivity-multiple-selected-item[data-item-id=' + quotedId + ']')
+ .find('.selectivity-multiple-selected-item-remove')
+ .on('click', this._itemRemoveClicked.bind(this));
+ },
+
+ /**
+ * @private
+ */
+ _rerenderSelection: function(event) {
+
+ event = event || {};
+
+ if (event.added) {
+ this._renderSelectedItem(event.added);
+
+ this._scrollToBottom();
+ } else if (event.removed) {
+ var quotedId = Selectivity.quoteCssAttr(event.removed.id);
+ this.$('.selectivity-multiple-selected-item[data-item-id=' + quotedId + ']').remove();
+ } else {
+ this.$('.selectivity-multiple-selected-item').remove();
+
+ this._data.forEach(this._renderSelectedItem, this);
+
+ this._updateInputWidth();
+ }
+
+ if (event.added || event.removed) {
+ if (this.dropdown) {
+ this.dropdown.showResults(this.filterResults(this.results), {
+ hasMore: this.dropdown.hasMore
+ });
+ }
+
+ if (this.hasKeyboard) {
+ this.focus();
+ }
+ }
+
+ this.positionDropdown();
+
+ this._updatePlaceholder();
+ },
+
+ /**
+ * @private
+ */
+ _resultSelected: function(event) {
+
+ if (this._value.indexOf(event.id) === -1) {
+ this.add(event.item);
+ } else {
+ this.remove(event.item);
+ }
+ },
+
+ /**
+ * @private
+ */
+ _scrollToBottom: function() {
+
+ var $inputContainer = this.$('.selectivity-multiple-input-container');
+ $inputContainer.scrollTop($inputContainer.height());
+ },
+
+ /**
+ * @private
+ */
+ _updateInputWidth: function() {
+
+ if (this.enabled) {
+ var $input = this.$searchInput, $widthDetector = this.$('.selectivity-width-detector');
+ $widthDetector.text($input.val() ||
+ !this._data.length && this.options.placeholder ||
+ '');
+ $input.width($widthDetector.width() + 20);
+
+ this.positionDropdown();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _updatePlaceholder: function() {
+
+ var placeholder = this._data.length ? '' : this.options.placeholder;
+ if (this.enabled) {
+ this.$searchInput.attr('placeholder', placeholder);
+ } else {
+ this.$('.selectivity-placeholder').text(placeholder);
+ }
+ }
+
+});
+
+module.exports = Selectivity.InputTypes.Multiple = MultipleSelectivity;
+
+},{"8":8,"jquery":"jquery"}],15:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var Selectivity = _dereq_(8);
+
+/**
+ * SingleSelectivity Constructor.
+ *
+ * @param options Options object. Accepts all options from the Selectivity Base Constructor in
+ * addition to those accepted by SingleSelectivity.setOptions().
+ */
+function SingleSelectivity(options) {
+
+ Selectivity.call(this, options);
+
+ this.$el.html(this.template('singleSelectInput', this.options))
+ .trigger('selectivity-init', 'single');
+
+ this._rerenderSelection();
+
+ if (!options.positionDropdown) {
+ // dropdowns for single-value inputs should open below the select box,
+ // unless there is not enough space below, in which case the dropdown should be moved up
+ // just enough so it fits in the window, but never so much that it reaches above the top
+ this.options.positionDropdown = function($el, $selectEl) {
+ var position = $selectEl.position(),
+ dropdownHeight = $el.height(),
+ selectHeight = $selectEl.height(),
+ top = $selectEl[0].getBoundingClientRect().top,
+ bottom = top + selectHeight + dropdownHeight,
+ deltaUp = 0;
+
+ if (typeof window !== 'undefined') {
+ deltaUp = Math.min(Math.max(bottom - $(window).height(), 0), top + selectHeight);
+ }
+
+ var width = $selectEl.outerWidth ? $selectEl.outerWidth() : $selectEl.width();
+ $el.css({
+ left: position.left + 'px',
+ top: (position.top + selectHeight - deltaUp) + 'px'
+ }).width(width);
+ };
+ }
+
+ if (options.showSearchInputInDropdown === false) {
+ this.initSearchInput(this.$('.selectivity-single-select-input'), { noSearch: true });
+ }
+}
+
+/**
+ * Methods.
+ */
+var callSuper = Selectivity.inherits(SingleSelectivity, {
+
+ /**
+ * Events map.
+ *
+ * Follows the same format as Backbone: http://backbonejs.org/#View-delegateEvents
+ */
+ events: {
+ 'change': '_rerenderSelection',
+ 'click': '_clicked',
+ 'focus .selectivity-single-select-input': '_focused',
+ 'selectivity-selected': '_resultSelected'
+ },
+
+ /**
+ * Clears the data and value.
+ */
+ clear: function() {
+
+ this.data(null);
+ },
+
+ /**
+ * @inherit
+ *
+ * @param options Optional options object. May contain the following property:
+ * keepFocus - If false, the focus won't remain on the input.
+ */
+ close: function(options) {
+
+ this._closing = true;
+
+ callSuper(this, 'close');
+
+ var $input = this.$('.selectivity-single-select-input');
+ if (!this.$searchInput) {
+ this.initSearchInput($input, { noSearch: true });
+ }
+
+ if (!options || options.keepFocus !== false) {
+ $input.focus();
+ }
+
+ this._closing = false;
+ },
+
+ /**
+ * Returns the correct data for a given value.
+ *
+ * @param value The value to get the data for. Should be an ID.
+ *
+ * @return The corresponding data. Will be an object with 'id' and 'text' properties. Note that
+ * if no items are defined, this method assumes the text label will be equal to the ID.
+ */
+ getDataForValue: function(value) {
+
+ return this.getItemForId(value);
+ },
+
+ /**
+ * Returns the correct value for the given data.
+ *
+ * @param data The data to get the value for. Should be an object with 'id' and 'text'
+ * properties or null.
+ *
+ * @return The corresponding value. Will be an ID or null.
+ */
+ getValueForData: function(data) {
+
+ return (data ? data.id : null);
+ },
+
+ /**
+ * @inherit
+ */
+ open: function(options) {
+
+ var showSearchInput = (this.options.showSearchInputInDropdown !== false);
+
+ callSuper(this, 'open', $.extend({ showSearchInput: showSearchInput }, options));
+
+ if (!showSearchInput) {
+ this.focus();
+ }
+ },
+
+ /**
+ * @inherit
+ *
+ * @param options Options object. In addition to the options supported in the base
+ * implementation, this may contain the following properties:
+ * allowClear - Boolean whether the selected item may be removed.
+ * showSearchInputInDropdown - Set to false to remove the search input used in
+ * dropdowns. The default is true.
+ */
+ setOptions: function(options) {
+
+ options = options || {};
+
+ options.allowedTypes = $.extend(options.allowedTypes || {}, {
+ allowClear: 'boolean',
+ showSearchInputInDropdown: 'boolean'
+ });
+
+ callSuper(this, 'setOptions', options);
+ },
+
+ /**
+ * Validates data to set. Throws an exception if the data is invalid.
+ *
+ * @param data The data to validate. Should be an object with 'id' and 'text' properties or null
+ * to indicate no item is selected.
+ *
+ * @return The validated data. This may differ from the input data.
+ */
+ validateData: function(data) {
+
+ return (data === null ? data : this.validateItem(data));
+ },
+
+ /**
+ * Validates a value to set. Throws an exception if the value is invalid.
+ *
+ * @param value The value to validate. Should be null or a valid ID.
+ *
+ * @return The validated value. This may differ from the input value.
+ */
+ validateValue: function(value) {
+
+ if (value === null || Selectivity.isValidId(value)) {
+ return value;
+ } else {
+ throw new Error('Value for SingleSelectivity instance should be a valid ID or null');
+ }
+ },
+
+ /**
+ * @private
+ */
+ _clicked: function() {
+
+ if (this.enabled) {
+ if (this.dropdown) {
+ this.close();
+ } else if (this.options.showDropdown !== false) {
+ this.open();
+ }
+
+ return false;
+ }
+ },
+
+ /**
+ * @private
+ */
+ _focused: function() {
+
+ if (this.enabled && !this._closing && this.options.showDropdown !== false) {
+ this.open();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _itemRemoveClicked: function() {
+
+ this.data(null);
+
+ return false;
+ },
+
+ /**
+ * @private
+ */
+ _rerenderSelection: function() {
+
+ var $container = this.$('.selectivity-single-result-container');
+ if (this._data) {
+ $container.html(
+ this.template('singleSelectedItem', $.extend({
+ removable: this.options.allowClear && !this.options.readOnly
+ }, this._data))
+ );
+
+ $container.find('.selectivity-single-selected-item-remove')
+ .on('click', this._itemRemoveClicked.bind(this));
+ } else {
+ $container.html(
+ this.template('singleSelectPlaceholder', { placeholder: this.options.placeholder })
+ );
+ }
+ },
+
+ /**
+ * @private
+ */
+ _resultSelected: function(event) {
+
+ this.data(event.item);
+
+ this.close();
+ }
+
+});
+
+module.exports = Selectivity.InputTypes.Single = SingleSelectivity;
+
+},{"8":8,"jquery":"jquery"}],16:[function(_dereq_,module,exports){
+'use strict';
+
+var Selectivity = _dereq_(8);
+var SelectivityDropdown = _dereq_(10);
+
+/**
+ * Extended dropdown that supports submenus.
+ */
+function SelectivitySubmenu(options) {
+
+ /**
+ * Optional parent dropdown menu from which this dropdown was opened.
+ */
+ this.parentMenu = options.parentMenu;
+
+ SelectivityDropdown.call(this, options);
+
+ this._closeSubmenuTimeout = 0;
+
+ this._openSubmenuTimeout = 0;
+}
+
+var callSuper = Selectivity.inherits(SelectivitySubmenu, SelectivityDropdown, {
+
+ /**
+ * @inherit
+ */
+ close: function() {
+
+ if (this.options.restoreOptions) {
+ this.selectivity.setOptions(this.options.restoreOptions);
+ }
+ if (this.options.restoreResults) {
+ this.selectivity.results = this.options.restoreResults;
+ }
+
+ if (this.submenu) {
+ this.submenu.close();
+ }
+
+ callSuper(this, 'close');
+
+ if (this.parentMenu) {
+ this.parentMenu.submenu = null;
+ this.parentMenu = null;
+ }
+
+ clearTimeout(this._closeSubmenuTimeout);
+ clearTimeout(this._openSubmenuTimeout);
+ },
+
+ /**
+ * @inherit
+ *
+ * @param options Optional options object. May contain the following property:
+ * delay - If true, indicates any submenu should not be opened until after some
+ * delay.
+ */
+ highlight: function(item, options) {
+
+ if (options && options.delay) {
+ callSuper(this, 'highlight', item);
+
+ clearTimeout(this._openSubmenuTimeout);
+ this._openSubmenuTimeout = setTimeout(this._doHighlight.bind(this, item), 300);
+ } else if (this.submenu) {
+ if (this.highlightedResult && this.highlightedResult.id === item.id) {
+ this._doHighlight(item);
+ } else {
+ clearTimeout(this._closeSubmenuTimeout);
+ this._closeSubmenuTimeout = setTimeout(
+ this._closeSubmenuAndHighlight.bind(this, item), 100
+ );
+ }
+ } else {
+ if (this.parentMenu && this.parentMenu._closeSubmenuTimeout) {
+ clearTimeout(this.parentMenu._closeSubmenuTimeout);
+ this.parentMenu._closeSubmenuTimeout = 0;
+ }
+
+ this._doHighlight(item);
+ }
+ },
+
+ /**
+ * @inherit
+ */
+ selectHighlight: function() {
+
+ if (this.submenu) {
+ this.submenu.selectHighlight();
+ } else {
+ callSuper(this, 'selectHighlight');
+ }
+ },
+
+ /**
+ * @inherit
+ */
+ selectItem: function(id) {
+
+ var selectivity = this.selectivity;
+ var item = Selectivity.findNestedById(selectivity.results, id);
+ if (item && !item.submenu) {
+ var options = { id: id, item: item };
+ if (selectivity.triggerEvent('selectivity-selecting', options)) {
+ selectivity.triggerEvent('selectivity-selected', options);
+ }
+ }
+ },
+
+ /**
+ * @inherit
+ */
+ showResults: function(results, options) {
+
+ if (this.submenu) {
+ this.submenu.showResults(results, options);
+ } else {
+ callSuper(this, 'showResults', results, options);
+ }
+ },
+
+ /**
+ * @inherit
+ */
+ triggerClose: function() {
+
+ if (this.parentMenu) {
+ this.selectivity.$el.trigger('selectivity-close-submenu');
+ } else {
+ callSuper(this, 'triggerClose');
+ }
+ },
+
+ /**
+ * @inherit
+ */
+ triggerOpen: function() {
+
+ if (this.parentMenu) {
+ this.selectivity.$el.trigger('selectivity-open-submenu');
+ } else {
+ callSuper(this, 'triggerOpen');
+ }
+ },
+
+ /**
+ * @private
+ */
+ _closeSubmenuAndHighlight: function(item) {
+
+ if (this.submenu) {
+ this.submenu.close();
+ }
+
+ this._doHighlight(item);
+ },
+
+ /**
+ * @private
+ */
+ _doHighlight: function(item) {
+
+ callSuper(this, 'highlight', item);
+
+ if (item.submenu && !this.submenu) {
+ var selectivity = this.selectivity;
+ var Dropdown = selectivity.options.dropdown || Selectivity.Dropdown;
+ if (Dropdown) {
+ var quotedId = Selectivity.quoteCssAttr(item.id);
+ var $item = this.$('.selectivity-result-item[data-item-id=' + quotedId + ']');
+ var $dropdownEl = this.$el;
+
+ this.submenu = new Dropdown({
+ parentMenu: this,
+ position: item.submenu.positionDropdown || function($el) {
+ var dropdownPosition = $dropdownEl.position();
+ var width = $dropdownEl.width();
+ $el.css({
+ left: dropdownPosition.left + width + 'px',
+ top: $item.position().top + dropdownPosition.top + 'px'
+ }).width(width);
+ },
+ restoreOptions: {
+ items: selectivity.items,
+ query: selectivity.options.query || null
+ },
+ restoreResults: selectivity.results,
+ selectivity: selectivity,
+ showSearchInput: item.submenu.showSearchInput
+ });
+
+ selectivity.setOptions({
+ items: item.submenu.items || null,
+ query: item.submenu.query || null
+ });
+
+ selectivity.search('');
+ }
+ }
+ }
+
+});
+
+Selectivity.Dropdown = SelectivitySubmenu;
+
+Selectivity.findNestedById = function(array, id) {
+
+ for (var i = 0, length = array.length; i < length; i++) {
+ var item = array[i], result;
+ if (item.id === id) {
+ result = item;
+ } else if (item.children) {
+ result = Selectivity.findNestedById(item.children, id);
+ } else if (item.submenu && item.submenu.items) {
+ result = Selectivity.findNestedById(item.submenu.items, id);
+ }
+ if (result) {
+ return result;
+ }
+ }
+ return null;
+};
+
+module.exports = SelectivitySubmenu;
+
+},{"10":10,"8":8}],17:[function(_dereq_,module,exports){
+'use strict';
+
+var escape = _dereq_(4);
+
+var Selectivity = _dereq_(8);
+
+_dereq_(13);
+
+/**
+ * Default set of templates to use with Selectivity.js.
+ *
+ * Note that every template can be defined as either a string, a function returning a string (like
+ * Handlebars templates, for instance) or as an object containing a render function (like Hogan.js
+ * templates, for instance).
+ */
+Selectivity.Templates = {
+
+ /**
+ * Renders the dropdown.
+ *
+ * The template is expected to have at least one element with the class
+ * 'selectivity-results-container', which is where all results will be added to.
+ *
+ * @param options Options object containing the following properties:
+ * dropdownCssClass - Optional CSS class to add to the top-level element.
+ * searchInputPlaceholder - Optional placeholder text to display in the search
+ * input in the dropdown.
+ * showSearchInput - Boolean whether a search input should be shown. If true,
+ * an input element with the 'selectivity-search-input' is
+ * expected.
+ */
+ dropdown: function(options) {
+ var extraClass = (options.dropdownCssClass ? ' ' + options.dropdownCssClass : ''),
+ searchInput = '';
+ if (options.showSearchInput) {
+ extraClass += ' has-search-input';
+
+ var placeholder = options.searchInputPlaceholder;
+ searchInput = (
+ '<div class="selectivity-search-input-container">' +
+ '<input type="text" class="selectivity-search-input"' +
+ (placeholder ? ' placeholder="' + escape(placeholder) + '"'
+ : '') + '>' +
+ '</div>'
+ );
+ }
+ return (
+ '<div class="selectivity-dropdown' + extraClass + '">' +
+ searchInput +
+ '<div class="selectivity-results-container"></div>' +
+ '</div>'
+ );
+ },
+
+ /**
+ * Renders an error message in the dropdown.
+ *
+ * @param options Options object containing the following properties:
+ * escape - Boolean whether the message should be HTML-escaped.
+ * message - The message to display.
+ */
+ error: function(options) {
+ return (
+ '<div class="selectivity-error">' +
+ (options.escape ? escape(options.message) : options.message) +
+ '</div>'
+ );
+ },
+
+ /**
+ * Renders a loading indicator in the dropdown.
+ *
+ * This template is expected to have an element with a 'selectivity-loading' class which may be
+ * replaced with actual results.
+ */
+ loading: function() {
+ return '<div class="selectivity-loading">' + Selectivity.Locale.loading + '</div>';
+ },
+
+ /**
+ * Load more indicator.
+ *
+ * This template is expected to have an element with a 'selectivity-load-more' class which, when
+ * clicked, will load more results.
+ */
+ loadMore: function() {
+ return '<div class="selectivity-load-more">' + Selectivity.Locale.loadMore + '</div>';
+ },
+
+ /**
+ * Renders multi-selection input boxes.
+ *
+ * The template is expected to have at least have elements with the following classes:
+ * 'selectivity-multiple-input-container' - The element containing all the selected items and
+ * the input for selecting additional items.
+ * 'selectivity-multiple-input' - The actual input element that allows the user to type to
+ * search for more items. When selected items are added, they are
+ * inserted right before this element.
+ * 'selectivity-width-detector' - This element is optional, but important to make sure the
+ * '.selectivity-multiple-input' element will fit in the
+ * container. The width detector also has the
+ * 'select2-multiple-input' class on purpose to be able to detect
+ * the width of text entered in the input element.
+ *
+ * @param options Options object containing the following property:
+ * enabled - Boolean whether the input is enabled.
+ */
+ multipleSelectInput: function(options) {
+ return (
+ '<div class="selectivity-multiple-input-container">' +
+ (options.enabled ? '<input type="text" autocomplete="off" autocorrect="off" ' +
+ 'autocapitalize="off" ' +
+ 'class="selectivity-multiple-input">' +
+ '<span class="selectivity-multiple-input ' +
+ 'selectivity-width-detector"></span>'
+ : '<div class="selectivity-multiple-input ' +
+ 'selectivity-placeholder"></div>') +
+ '<div class="selectivity-clearfix"></div>' +
+ '</div>'
+ );
+ },
+
+ /**
+ * Renders a selected item in multi-selection input boxes.
+ *
+ * The template is expected to have a top-level element with the class
+ * 'selectivity-multiple-selected-item'. This element is also required to have a 'data-item-id'
+ * attribute with the ID set to that passed through the options object.
+ *
+ * An element with the class 'selectivity-multiple-selected-item-remove' should be present
+ * which, when clicked, will cause the element to be removed.
+ *
+ * @param options Options object containing the following properties:
+ * highlighted - Boolean whether this item is currently highlighted.
+ * id - Identifier for the item.
+ * removable - Boolean whether a remove icon should be displayed.
+ * text - Text label which the user sees.
+ */
+ multipleSelectedItem: function(options) {
+ var extraClass = (options.highlighted ? ' highlighted' : '');
+ return (
+ '<span class="selectivity-multiple-selected-item' + extraClass + '" ' +
+ 'data-item-id="' + escape(options.id) + '">' +
+ (options.removable ? '<a class="selectivity-multiple-selected-item-remove">' +
+ '<i class="glyphicon glyphicon-remove"></i>' +
+ '</a>'
+ : '') +
+ escape(options.text) +
+ '</span>'
+ );
+ },
+
+ /**
+ * Renders a message there are no results for the given query.
+ *
+ * @param options Options object containing the following property:
+ * term - Search term the user is searching for.
+ */
+ noResults: function(options) {
+ var Locale = Selectivity.Locale;
+ return (
+ '<div class="selectivity-error">' +
+ (options.term ? Locale.noResultsForTerm(options.term) : Locale.noResults) +
+ '</div>'
+ );
+ },
+
+ /**
+ * Renders a container for item children.
+ *
+ * The template is expected to have an element with the class 'selectivity-result-children'.
+ *
+ * @param options Options object containing the following property:
+ * childrenHtml - Rendered HTML for the children.
+ */
+ resultChildren: function(options) {
+ return '<div class="selectivity-result-children">' + options.childrenHtml + '</div>';
+ },
+
+ /**
+ * Render a result item in the dropdown.
+ *
+ * The template is expected to have a top-level element with the class
+ * 'selectivity-result-item'. This element is also required to have a 'data-item-id' attribute
+ * with the ID set to that passed through the options object.
+ *
+ * @param options Options object containing the following properties:
+ * id - Identifier for the item.
+ * text - Text label which the user sees.
+ * submenu - Truthy if the result item has a menu with subresults.
+ */
+ resultItem: function(options) {
+ return (
+ '<div class="selectivity-result-item" data-item-id="' + escape(options.id) + '">' +
+ escape(options.text) +
+ (options.submenu ? '<i class="selectivity-submenu-icon fa fa-chevron-right"></i>'
+ : '') +
+ '</div>'
+ );
+ },
+
+ /**
+ * Render a result label in the dropdown.
+ *
+ * The template is expected to have a top-level element with the class
+ * 'selectivity-result-label'.
+ *
+ * @param options Options object containing the following properties:
+ * text - Text label.
+ */
+ resultLabel: function(options) {
+ return '<div class="selectivity-result-label">' + escape(options.text) + '</div>';
+ },
+
+ /**
+ * Renders single-select input boxes.
+ *
+ * The template is expected to have at least one element with the class
+ * 'selectivity-single-result-container' which is the element containing the selected item or
+ * the placeholder.
+ */
+ singleSelectInput: (
+ '<div class="selectivity-single-select">' +
+ '<input type="text" class="selectivity-single-select-input">' +
+ '<div class="selectivity-single-result-container"></div>' +
+ '<i class="fa fa-sort-desc selectivity-caret"></i>' +
+ '</div>'
+ ),
+
+ /**
+ * Renders the placeholder for single-select input boxes.
+ *
+ * The template is expected to have a top-level element with the class
+ * 'selectivity-placeholder'.
+ *
+ * @param options Options object containing the following property:
+ * placeholder - The placeholder text.
+ */
+ singleSelectPlaceholder: function(options) {
+ return (
+ '<div class="selectivity-placeholder">' +
+ escape(options.placeholder) +
+ '</div>'
+ );
+ },
+
+ /**
+ * Renders the selected item in single-select input boxes.
+ *
+ * The template is expected to have a top-level element with the class
+ * 'selectivity-single-selected-item'. This element is also required to have a 'data-item-id'
+ * attribute with the ID set to that passed through the options object.
+ *
+ * @param options Options object containing the following properties:
+ * id - Identifier for the item.
+ * removable - Boolean whether a remove icon should be displayed.
+ * text - Text label which the user sees.
+ */
+ singleSelectedItem: function(options) {
+ return (
+ '<span class="selectivity-single-selected-item" ' +
+ 'data-item-id="' + escape(options.id) + '">' +
+ (options.removable ? '<a class="selectivity-single-selected-item-remove">' +
+ '<i class="glyphicon glyphicon-remove"></i>' +
+ '</a>'
+ : '') +
+ escape(options.text) +
+ '</span>'
+ );
+ },
+
+ /**
+ * Renders select-box inside single-select input that was initialized on
+ * traditional <select> element.
+ *
+ * @param options Options object containing the following properties:
+ * name - Name of the <select> element.
+ * mode - Mode in which select exists, single or multiple.
+ */
+ selectCompliance: function(options) {
+ if (options.mode === 'multiple' && options.name.slice(-2) !== '[]') {
+ options.name += '[]';
+ }
+ return ('<select name="' + options.name + '"' + (options.mode === 'multiple' ? ' multiple' : '') + '></select>');
+ },
+
+ /**
+ * Renders the selected item in compliance <select> element as <option>.
+ *
+ * @param options Options object containing the following properties
+ * id - Identifier for the item.
+ * text - Text label which the user sees.
+ */
+ selectOptionCompliance: function(options) {
+ return (
+ '<option value="' + escape(options.id) + '" selected>' +
+ escape(options.text) +
+ '</option>'
+ );
+ }
+
+};
+
+},{"13":13,"4":4,"8":8}],18:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var Selectivity = _dereq_(8);
+
+function defaultTokenizer(input, selection, createToken, options) {
+
+ var createTokenItem = options.createTokenItem || function(token) {
+ return token ? { id: token, text: token } : null;
+ };
+
+ var separators = options.tokenSeparators;
+
+ function hasToken(input) {
+ return input ? separators.some(function(separator) {
+ return input.indexOf(separator) > -1;
+ }) : false;
+ }
+
+ function takeToken(input) {
+ for (var i = 0, length = input.length; i < length; i++) {
+ if (separators.indexOf(input[i]) > -1) {
+ return { term: input.slice(0, i), input: input.slice(i + 1) };
+ }
+ }
+ return {};
+ }
+
+ while (hasToken(input)) {
+ var token = takeToken(input);
+ if (token.term) {
+ var item = createTokenItem(token.term);
+ if (item && !Selectivity.findById(selection, item.id)) {
+ createToken(item);
+ }
+ }
+ input = token.input;
+ }
+
+ return input;
+}
+
+/**
+ * Option listener that provides a default tokenizer which is used when the tokenSeparators option
+ * is specified.
+ *
+ * @param options Options object. In addition to the options supported in the multi-input
+ * implementation, this may contain the following property:
+ * tokenSeparators - Array of string separators which are used to separate the search
+ * string into tokens. If specified and the tokenizer property is
+ * not set, the tokenizer property will be set to a function which
+ * splits the search term into tokens separated by any of the given
+ * separators. The tokens will be converted into selectable items
+ * using the 'createTokenItem' function. The default tokenizer also
+ * filters out already selected items.
+ */
+Selectivity.OptionListeners.push(function(selectivity, options) {
+
+ if (options.tokenSeparators) {
+ options.allowedTypes = $.extend({ tokenSeparators: 'array' }, options.allowedTypes);
+
+ options.tokenizer = options.tokenizer || defaultTokenizer;
+ }
+});
+
+},{"8":8,"jquery":"jquery"}],19:[function(_dereq_,module,exports){
+'use strict';
+
+var $ = window.jQuery || window.Zepto;
+
+var Selectivity = _dereq_(8);
+
+function replaceSelectElement($el, options) {
+
+ var data = (options.multiple ? [] : null);
+
+ var mapOptions = function() {
+ var $this = $(this);
+ if ($this.is('option')) {
+ var text = $this.text();
+ var id = $this.attr('value') || text;
+ if ($this.prop('selected')) {
+ var item = { id: id, text: text };
+ if (options.multiple) {
+ data.push(item);
+ } else {
+ data = item;
+ }
+ }
+
+ return {
+ id: id,
+ text: $this.attr('label') || text
+ };
+ } else {
+ return {
+ text: $this.attr('label'),
+ children: $this.children('option,optgroup').map(mapOptions).get()
+ };
+ }
+ };
+
+ options.allowClear = ('allowClear' in options ? options.allowClear : !$el.prop('required'));
+
+ var items = $el.children('option,optgroup').map(mapOptions).get();
+ options.items = (options.query ? null : items);
+
+ options.placeholder = options.placeholder || $el.data('placeholder') || '';
+
+ options.data = data;
+
+ var classes = ($el.attr('class') || 'selectivity-input').split(' ');
+ if (classes.indexOf('selectivity-input') === -1) {
+ classes.push('selectivity-input');
+ }
+
+ var $div = $('<div>').attr({
+ 'id': $el.attr('id'),
+ 'class': classes.join(' '),
+ 'style': $el.attr('style'),
+ 'data-name': $el.attr('name')
+ });
+ $el.replaceWith($div);
+ return $div;
+}
+
+function bindTraditionalSelectEvents(selectivity) {
+
+ var $el = selectivity.$el;
+
+ $el.on('selectivity-init', function(event, mode) {
+
+ $el.append(selectivity.template('selectCompliance', {name: $el.attr('data-name'), mode: mode}))
+ .removeAttr('data-name');
+ })
+ .on('selectivity-init change', function() {
+ var data = selectivity._data;
+ var $select = $el.find('select');
+
+ if (data instanceof Array) {
+ $select.empty();
+
+ data.forEach(function(item) {
+ $select.append(selectivity.template('selectOptionCompliance', item));
+ });
+ } else {
+ if (data) {
+ $select.html(selectivity.template('selectOptionCompliance', data));
+ } else {
+ $select.empty();
+ }
+ }
+ });
+}
+
+/**
+ * Option listener providing support for converting traditional <select> boxes into Selectivity
+ * instances.
+ */
+Selectivity.OptionListeners.push(function(selectivity, options) {
+
+ var $el = selectivity.$el;
+ if ($el.is('select')) {
+ if ($el.attr('autofocus')) {
+ setTimeout(function() {
+ selectivity.focus();
+ }, 1);
+ }
+
+ selectivity.$el = replaceSelectElement($el, options);
+ selectivity.$el[0].selectivity = selectivity;
+
+ bindTraditionalSelectEvents(selectivity);
+ }
+});
+
+},{"8":8,"jquery":"jquery"}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js b/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
deleted file mode 100644
index 03e7ce6..0000000
--- a/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
+++ /dev/null
@@ -1,514 +0,0 @@
-/* ===================================================
- * tagmanager.js v3.0.1
- * http://welldonethings.com/tags/manager
- * ===================================================
- * Copyright 2012 Max Favilli
- *
- * Licensed under the Mozilla Public License, Version 2.0 You may not use this work except in compliance with the License.
- *
- * http://www.mozilla.org/MPL/2.0/
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================== */
-(function($) {
-
- "use strict";
-
- var defaults = {
- prefilled: null,
- CapitalizeFirstLetter: false,
- preventSubmitOnEnter: true, // deprecated
- isClearInputOnEsc: true, // deprecated
- externalTagId: false,
- prefillIdFieldName: 'Id',
- prefillValueFieldName: 'Value',
- AjaxPush: null,
- AjaxPushAllTags: null,
- AjaxPushParameters: null,
- delimiters: [9, 13, 44], // tab, enter, comma
- backspace: [8],
- maxTags: 0,
- hiddenTagListName: null, // deprecated
- hiddenTagListId: null, // deprecated
- replace: true,
- output: null,
- deleteTagsOnBackspace: true, // deprecated
- tagsContainer: null,
- tagCloseIcon: 'x',
- tagClass: '',
- validator: null,
- onlyTagList: false,
- tagList: null,
- fillInputOnTagRemove: false
- },
-
- publicMethods = {
- pushTag : function (tag, ignoreEvents, externalTagId) {
- var $self = $(this), opts = $self.data('opts'), alreadyInList, tlisLowerCase, max, tagId,
- tlis = $self.data("tlis"), tlid = $self.data("tlid"), idx, newTagId, newTagRemoveId, escaped,
- html, $el, lastTagId, lastTagObj;
-
- tag = privateMethods.trimTag(tag, opts.delimiterChars);
-
- if (!tag || tag.length <= 0) { return; }
-
- // check if restricted only to the tagList suggestions
- if (opts.onlyTagList && undefined !== opts.tagList ){
-
- //if the list has been updated by look pushed tag in the tagList. if not found return
- if (opts.tagList){
- var $tagList = opts.tagList;
-
- // change each array item to lower case
- $.each($tagList, function(index, item) {
- $tagList[index] = item.toLowerCase();
- });
- var suggestion = $.inArray(tag.toLowerCase(), $tagList);
-
- if ( -1 === suggestion ) {
- //console.log("tag:" + tag + " not in tagList, not adding it");
- return;
- }
- }
-
- }
-
- if (opts.CapitalizeFirstLetter && tag.length > 1) {
- tag = tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase();
- }
-
- // call the validator (if any) and do not let the tag pass if invalid
- if (opts.validator && !opts.validator(tag)) {
- $self.trigger('tm:invalid', tag)
- return;
- }
-
- // dont accept new tags beyond the defined maximum
- if (opts.maxTags > 0 && tlis.length >= opts.maxTags) { return; }
-
- alreadyInList = false;
- //use jQuery.map to make this work in IE8 (pure JS map is JS 1.6 but IE8 only supports JS 1.5)
- tlisLowerCase = jQuery.map(tlis, function(elem) {
- return elem.toLowerCase();
- });
-
- idx = $.inArray(tag.toLowerCase(), tlisLowerCase);
-
- if (-1 !== idx) {
- // console.log("tag:" + tag + " !!already in list!!");
- alreadyInList = true;
- }
-
- if (alreadyInList) {
- $self.trigger('tm:duplicated', tag);
- if (opts.blinkClass) {
- for (var i = 0; i < 6; ++i) {
- $("#" + $self.data("tm_rndid") + "_" + tlid[idx]).queue(function(next) {
- $(this).toggleClass(opts.blinkClass);
- next();
- }).delay(100);
- }
- } else {
- $("#" + $self.data("tm_rndid") + "_" + tlid[idx]).stop()
- .animate({backgroundColor: opts.blinkBGColor_1}, 100)
- .animate({backgroundColor: opts.blinkBGColor_2}, 100)
- .animate({backgroundColor: opts.blinkBGColor_1}, 100)
- .animate({backgroundColor: opts.blinkBGColor_2}, 100)
- .animate({backgroundColor: opts.blinkBGColor_1}, 100)
- .animate({backgroundColor: opts.blinkBGColor_2}, 100);
- }
- } else {
- if (opts.externalTagId === true) {
- if (externalTagId === undefined) {
- $.error('externalTagId is not passed for tag -' + tag);
- }
- tagId = externalTagId;
- } else {
- max = Math.max.apply(null, tlid);
- max = max === -Infinity ? 0 : max;
-
- tagId = ++max;
- }
- if (!ignoreEvents) { $self.trigger('tm:pushing', [tag, tagId]); }
- tlis.push(tag);
- tlid.push(tagId);
-
- if (!ignoreEvents)
- if (opts.AjaxPush !== null && opts.AjaxPushAllTags == null) {
- if ($.inArray(tag, opts.prefilled) === -1) {
- $.post(opts.AjaxPush, $.extend({tag: tag}, opts.AjaxPushParameters));
- }
- }
-
- // console.log("tagList: " + tlis);
-
- newTagId = $self.data("tm_rndid") + '_' + tagId;
- newTagRemoveId = $self.data("tm_rndid") + '_Remover_' + tagId;
- escaped = $("<span/>").text(tag).html();
-
- html = '<span class="' + privateMethods.tagClasses.call($self) + '" id="' + newTagId + '">';
- html+= '<span>' + escaped + '</span>';
- html+= '<a href="#" class="tm-tag-remove" id="' + newTagRemoveId + '" TagIdToRemove="' + tagId + '">';
- html+= opts.tagCloseIcon + '</a></span> ';
- $el = $(html);
-
- if (opts.tagsContainer !== null) {
- $(opts.tagsContainer).append($el);
- } else {
- if (tlid.length > 1) {
- lastTagObj = $self.siblings("#" + $self.data("tm_rndid") + "_" + tlid[tlid.length - 2]);
- lastTagObj.after($el);
- } else {
- $self.before($el);
- }
- }
-
- $el.find("#" + newTagRemoveId).on("click", $self, function(e) {
- e.preventDefault();
- var TagIdToRemove = parseInt($(this).attr("TagIdToRemove"));
- privateMethods.spliceTag.call($self, TagIdToRemove, e.data);
- });
-
- privateMethods.refreshHiddenTagList.call($self);
-
- if (!ignoreEvents) { $self.trigger('tm:pushed', [tag, tagId]); }
-
- privateMethods.showOrHide.call($self);
- //if (tagManagerOptions.maxTags > 0 && tlis.length >= tagManagerOptions.maxTags) {
- // obj.hide();
- //}
- }
- $self.val("");
- },
-
- popTag : function () {
- var $self = $(this), tagId, tagBeingRemoved,
- tlis = $self.data("tlis"),
- tlid = $self.data("tlid");
-
- if (tlid.length > 0) {
- tagId = tlid.pop();
-
- tagBeingRemoved = tlis[tlis.length - 1];
- $self.trigger('tm:popping', [tagBeingRemoved, tagId]);
- tlis.pop();
-
- // console.log("TagIdToRemove: " + tagId);
- $("#" + $self.data("tm_rndid") + "_" + tagId).remove();
- privateMethods.refreshHiddenTagList.call($self);
- $self.trigger('tm:popped', [tagBeingRemoved, tagId]);
- // console.log(tlis);
- }
- },
-
- empty : function() {
- var $self = $(this), tlis = $self.data("tlis"), tlid = $self.data("tlid"), tagId;
-
- while (tlid.length > 0) {
- tagId = tlid.pop();
- tlis.pop();
- // console.log("TagIdToRemove: " + tagId);
- $("#" + $self.data("tm_rndid") + "_" + tagId).remove();
- privateMethods.refreshHiddenTagList.call($self);
- // console.log(tlis);
- }
- $self.trigger('tm:emptied', null);
-
- privateMethods.showOrHide.call($self);
- //if (tagManagerOptions.maxTags > 0 && tlis.length < tagManagerOptions.maxTags) {
- // obj.show();
- //}
- },
-
- tags : function() {
- var $self = this, tlis = $self.data("tlis");
- return tlis;
- }
- },
-
- privateMethods = {
- showOrHide : function () {
- var $self = this, opts = $self.data('opts'), tlis = $self.data("tlis");
-
- if (opts.maxTags > 0 && tlis.length < opts.maxTags) {
- $self.show();
- $self.trigger('tm:show');
- }
-
- if (opts.maxTags > 0 && tlis.length >= opts.maxTags) {
- $self.hide();
- $self.trigger('tm:hide');
- }
- },
-
- tagClasses : function () {
- var $self = $(this), opts = $self.data('opts'), tagBaseClass = opts.tagBaseClass,
- inputBaseClass = opts.inputBaseClass, cl;
- // 1) default class (tm-tag)
- cl = tagBaseClass;
- // 2) interpolate from input class: tm-input-xxx --> tm-tag-xxx
- if ($self.attr('class')) {
- $.each($self.attr('class').split(' '), function (index, value) {
- if (value.indexOf(inputBaseClass + '-') !== -1) {
- cl += ' ' + tagBaseClass + value.substring(inputBaseClass.length);
- }
- });
- }
- // 3) tags from tagClass option
- cl += (opts.tagClass ? ' ' + opts.tagClass : '');
- return cl;
- },
-
- trimTag : function (tag, delimiterChars) {
- var i;
- tag = $.trim(tag);
- // truncate at the first delimiter char
- i = 0;
- for (i; i < tag.length; i++) {
- if ($.inArray(tag.charCodeAt(i), delimiterChars) !== -1) { break; }
- }
- return tag.substring(0, i);
- },
-
- refreshHiddenTagList : function () {
- var $self = $(this), tlis = $self.data("tlis"), lhiddenTagList = $self.data("lhiddenTagList");
-
- if (lhiddenTagList) {
- $(lhiddenTagList).val(tlis.join($self.data('opts').baseDelimiter)).change();
- }
-
- $self.trigger('tm:refresh', tlis.join($self.data('opts').baseDelimiter));
- },
-
- killEvent : function (e) {
- e.cancelBubble = true;
- e.returnValue = false;
- e.stopPropagation();
- e.preventDefault();
- },
-
- keyInArray : function (e, ary) {
- return $.inArray(e.which, ary) !== -1;
- },
-
- applyDelimiter : function (e) {
- var $self = $(this);
- publicMethods.pushTag.call($self,$(this).val());
- e.preventDefault();
- },
-
- prefill: function (pta) {
- var $self = $(this);
- var opts = $self.data('opts')
- $.each(pta, function (key, val) {
- if (opts.externalTagId === true) {
- publicMethods.pushTag.call($self, val[opts.prefillValueFieldName], true, val[opts.prefillIdFieldName]);
- } else {
- publicMethods.pushTag.call($self, val, true);
- }
- });
- },
-
- pushAllTags : function (e, tag) {
- var $self = $(this), opts = $self.data('opts'), tlis = $self.data("tlis");
- if (opts.AjaxPushAllTags) {
- if (e.type !== 'tm:pushed' || $.inArray(tag, opts.prefilled) === -1) {
- $.post(opts.AjaxPush, $.extend({ tags: tlis.join(opts.baseDelimiter) }, opts.AjaxPushParameters));
- }
- }
- },
-
- spliceTag : function (tagId) {
- var $self = this, tlis = $self.data("tlis"), tlid = $self.data("tlid"), idx = $.inArray(tagId, tlid),
- tagBeingRemoved;
-
- // console.log("TagIdToRemove: " + tagId);
- // console.log("position: " + idx);
-
- if (-1 !== idx) {
- tagBeingRemoved = tlis[idx];
- $self.trigger('tm:splicing', [tagBeingRemoved, tagId]);
- $("#" + $self.data("tm_rndid") + "_" + tagId).remove();
- tlis.splice(idx, 1);
- tlid.splice(idx, 1);
- privateMethods.refreshHiddenTagList.call($self);
- $self.trigger('tm:spliced', [tagBeingRemoved, tagId]);
- // console.log(tlis);
- }
-
- privateMethods.showOrHide.call($self);
- //if (tagManagerOptions.maxTags > 0 && tlis.length < tagManagerOptions.maxTags) {
- // obj.show();
- //}
- },
-
- init : function (options) {
- var opts = $.extend({}, defaults, options), delimiters, keyNums;
-
- opts.hiddenTagListName = (opts.hiddenTagListName === null)
- ? 'hidden-' + this.attr('name')
- : opts.hiddenTagListName;
-
- delimiters = opts.delimeters || opts.delimiters; // 'delimeter' is deprecated
- keyNums = [9, 13, 17, 18, 19, 37, 38, 39, 40]; // delimiter values to be handled as key codes
- opts.delimiterChars = [];
- opts.delimiterKeys = [];
-
- $.each(delimiters, function (i, v) {
- if ($.inArray(v, keyNums) !== -1) {
- opts.delimiterKeys.push(v);
- } else {
- opts.delimiterChars.push(v);
- }
- });
-
- opts.baseDelimiter = String.fromCharCode(opts.delimiterChars[0] || 44);
- opts.tagBaseClass = 'tm-tag';
- opts.inputBaseClass = 'tm-input';
-
- if (!$.isFunction(opts.validator)) { opts.validator = null; }
-
- this.each(function() {
- var $self = $(this), hiddenObj ='', rndid ='', albet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
- // prevent double-initialization of TagManager
- if ($self.data('tagManager')) { return false; }
- $self.data('tagManager', true);
-
- for (var i = 0; i < 5; i++) {
- rndid += albet.charAt(Math.floor(Math.random() * albet.length));
- }
-
- $self.data("tm_rndid", rndid);
-
- // store instance-specific data in the DOM object
- $self.data('opts',opts)
- .data('tlis', []) //list of string tags
- .data('tlid', []); //list of ID of the string tags
-
- if (opts.output === null) {
- hiddenObj = $('<input/>', {
- type: 'hidden',
- name: opts.hiddenTagListName
- });
- $self.after(hiddenObj);
- $self.data("lhiddenTagList", hiddenObj);
- } else {
- $self.data("lhiddenTagList", $(opts.output));
- }
-
- if (opts.AjaxPushAllTags) {
- $self.on('tm:spliced', privateMethods.pushAllTags);
- $self.on('tm:popped', privateMethods.pushAllTags);
- $self.on('tm:pushed', privateMethods.pushAllTags);
- }
-
- // hide popovers on focus and keypress events
- $self.on('focus keypress', function(e) {
- if ($(this).popover) { $(this).popover('hide'); }
- });
-
- // handle ESC (keyup used for browser compatibility)
- if (opts.isClearInputOnEsc) {
- $self.on('keyup', function(e) {
- if (e.which === 27) {
- // console.log('esc detected');
- $(this).val('');
- privateMethods.killEvent(e);
- }
- });
- }
-
- $self.on('keypress', function(e) {
- // push ASCII-based delimiters
- if (privateMethods.keyInArray(e, opts.delimiterChars)) {
- privateMethods.applyDelimiter.call($self, e);
- }
- });
-
- $self.on('keydown', function(e) {
- // disable ENTER
- if (e.which === 13) {
- if (opts.preventSubmitOnEnter) {
- privateMethods.killEvent(e);
- }
- }
-
- // push key-based delimiters (includes <enter> by default)
- if (privateMethods.keyInArray(e, opts.delimiterKeys)) {
- privateMethods.applyDelimiter.call($self, e);
- }
- });
-
- // BACKSPACE (keydown used for browser compatibility)
- if (opts.deleteTagsOnBackspace) {
- $self.on('keydown', function(e) {
- if (privateMethods.keyInArray(e, opts.backspace)) {
- // console.log("backspace detected");
- if ($(this).val().length <= 0) {
- publicMethods.popTag.call($self);
- privateMethods.killEvent(e);
- }
- }
- });
- }
-
- // on tag pop fill back the tag's content to the input field
- if (opts.fillInputOnTagRemove) {
- $self.on('tm:popped', function(e, tag) {
- $(this).val(tag);
- });
- }
-
- $self.change(function(e) {
- if (!/webkit/.test(navigator.userAgent.toLowerCase())) {
- $self.focus();
- } // why?
-
- /* unimplemented mode to push tag on blur
- else if (tagManagerOptions.pushTagOnBlur) {
- console.log('change: pushTagOnBlur ' + tag);
- pushTag($(this).val());
- } */
- privateMethods.killEvent(e);
- });
-
- if (opts.prefilled !== null) {
- if (typeof (opts.prefilled) === "object") {
- privateMethods.prefill.call($self, opts.prefilled);
- } else if (typeof (opts.prefilled) === "string") {
- privateMethods.prefill.call($self, opts.prefilled.split(opts.baseDelimiter));
- } else if (typeof (opts.prefilled) === "function") {
- privateMethods.prefill.call($self, opts.prefilled());
- }
- } else if (opts.output !== null) {
- if ($(opts.output) && $(opts.output).val()) { var existing_tags = $(opts.output); }
- privateMethods.prefill.call($self,$(opts.output).val().split(opts.baseDelimiter));
- }
-
- });
-
- return this;
- }
- };
-
- $.fn.tagsManager = function(method) {
- var $self = $(this);
-
- if (!(0 in this)) { return this; }
-
- if ( publicMethods[method] ) {
- return publicMethods[method].apply( $self, Array.prototype.slice.call(arguments, 1) );
- } else if ( typeof method === 'object' || ! method ) {
- return privateMethods.init.apply( this, arguments );
- } else {
- $.error( 'Method ' + method + ' does not exist.' );
- return false;
- }
- };
-
-}(jQuery));