selectivity-rails 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4619 @@
1
+ !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){
2
+ _dereq_(4);_dereq_(5);_dereq_(6);_dereq_(8);_dereq_(9);_dereq_(10);_dereq_(11);_dereq_(12);_dereq_(13);_dereq_(14);_dereq_(15);_dereq_(16);_dereq_(17);_dereq_(18);module.exports=_dereq_(7);
3
+ },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9}],2:[function(_dereq_,module,exports){
4
+ 'use strict';
5
+
6
+ /**
7
+ * @license
8
+ * lodash 3.3.1 (Custom Build) <https://lodash.com/>
9
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
10
+ * Based on Underscore.js 1.8.2 <http://underscorejs.org/LICENSE>
11
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
12
+ * Available under MIT license <https://lodash.com/license>
13
+ */
14
+
15
+ /**
16
+ * Gets the number of milliseconds that have elapsed since the Unix epoch
17
+ * (1 January 1970 00:00:00 UTC).
18
+ *
19
+ * @static
20
+ * @memberOf _
21
+ * @category Date
22
+ * @example
23
+ *
24
+ * _.defer(function(stamp) {
25
+ * console.log(_.now() - stamp);
26
+ * }, _.now());
27
+ * // => logs the number of milliseconds it took for the deferred function to be invoked
28
+ */
29
+ var now = Date.now;
30
+
31
+ /**
32
+ * Creates a function that delays invoking `func` until after `wait` milliseconds
33
+ * have elapsed since the last time it was invoked.
34
+ *
35
+ * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
36
+ * for details over the differences between `_.debounce` and `_.throttle`.
37
+ *
38
+ * @static
39
+ * @memberOf _
40
+ * @category Function
41
+ * @param {Function} func The function to debounce.
42
+ * @param {number} [wait=0] The number of milliseconds to delay.
43
+ * @returns {Function} Returns the new debounced function.
44
+ * @example
45
+ *
46
+ * // avoid costly calculations while the window size is in flux
47
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
48
+ */
49
+ function debounce(func, wait) {
50
+ var args,
51
+ result,
52
+ stamp,
53
+ timeoutId,
54
+ trailingCall,
55
+ lastCalled = 0;
56
+
57
+ wait = wait < 0 ? 0 : (+wait || 0);
58
+
59
+ function delayed() {
60
+ var remaining = wait - (now() - stamp);
61
+ if (remaining <= 0 || remaining > wait) {
62
+ var isCalled = trailingCall;
63
+ timeoutId = trailingCall = undefined;
64
+ if (isCalled) {
65
+ lastCalled = now();
66
+ result = func.apply(null, args);
67
+ if (!timeoutId) {
68
+ args = null;
69
+ }
70
+ }
71
+ } else {
72
+ timeoutId = setTimeout(delayed, remaining);
73
+ }
74
+ }
75
+
76
+ function debounced() {
77
+ args = arguments;
78
+ stamp = now();
79
+ trailingCall = true;
80
+
81
+ if (!timeoutId) {
82
+ timeoutId = setTimeout(delayed, wait);
83
+ }
84
+ return result;
85
+ }
86
+ return debounced;
87
+ }
88
+
89
+ module.exports = debounce;
90
+
91
+ },{}],3:[function(_dereq_,module,exports){
92
+ 'use strict';
93
+
94
+ /**
95
+ * @license
96
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
97
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
98
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
99
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
100
+ * Available under MIT license <http://lodash.com/license>
101
+ */
102
+
103
+ var htmlEscapes = {
104
+ '&': '&amp;',
105
+ '<': '&lt;',
106
+ '>': '&gt;',
107
+ '"': '&quot;',
108
+ "'": '&#39;'
109
+ };
110
+
111
+ /**
112
+ * Used by `escape` to convert characters to HTML entities.
113
+ *
114
+ * @private
115
+ * @param {string} match The matched character to escape.
116
+ * @returns {string} Returns the escaped character.
117
+ */
118
+ function escapeHtmlChar(match) {
119
+ return htmlEscapes[match];
120
+ }
121
+
122
+ var reUnescapedHtml = new RegExp('[' + Object.keys(htmlEscapes).join('') + ']', 'g');
123
+
124
+ /**
125
+ * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
126
+ * corresponding HTML entities.
127
+ *
128
+ * @static
129
+ * @memberOf _
130
+ * @category Utilities
131
+ * @param {string} string The string to escape.
132
+ * @returns {string} Returns the escaped string.
133
+ * @example
134
+ *
135
+ * _.escape('Fred, Wilma, & Pebbles');
136
+ * // => 'Fred, Wilma, &amp; Pebbles'
137
+ */
138
+ function escape(string) {
139
+ return string ? String(string).replace(reUnescapedHtml, escapeHtmlChar) : '';
140
+ }
141
+
142
+ module.exports = escape;
143
+
144
+ },{}],4:[function(_dereq_,module,exports){
145
+ 'use strict';
146
+
147
+ var $ = window.jQuery || window.Zepto;
148
+
149
+ var debounce = _dereq_(2);
150
+
151
+ var Selectivity = _dereq_(7);
152
+
153
+ _dereq_(12);
154
+
155
+ /**
156
+ * Option listener that implements a convenience query function for performing AJAX requests.
157
+ */
158
+ Selectivity.OptionListeners.unshift(function(selectivity, options) {
159
+
160
+ var ajax = options.ajax;
161
+ if (ajax && ajax.url) {
162
+ var formatError = ajax.formatError || Selectivity.Locale.ajaxError;
163
+ var minimumInputLength = ajax.minimumInputLength || 0;
164
+ var params = ajax.params;
165
+ var processItem = ajax.processItem || function(item) { return item; };
166
+ var quietMillis = ajax.quietMillis || 0;
167
+ var resultsCb = ajax.results || function(data) { return { results: data, more: false }; };
168
+ var transport = ajax.transport || $.ajax;
169
+
170
+ if (quietMillis) {
171
+ transport = debounce(transport, quietMillis);
172
+ }
173
+
174
+ options.query = function(queryOptions) {
175
+ var offset = queryOptions.offset;
176
+ var term = queryOptions.term;
177
+ if (term.length < minimumInputLength) {
178
+ queryOptions.error(
179
+ Selectivity.Locale.needMoreCharacters(minimumInputLength - term.length)
180
+ );
181
+ } else {
182
+ selectivity.dropdown.showLoading();
183
+
184
+ var url = (ajax.url instanceof Function ? ajax.url() : ajax.url);
185
+ if (params) {
186
+ url += (url.indexOf('?') > -1 ? '&' : '?') + $.param(params(term, offset));
187
+ }
188
+
189
+ var success = ajax.success;
190
+ var error = ajax.error;
191
+
192
+ transport($.extend({}, ajax, {
193
+ url: url,
194
+ success: function(data, textStatus, jqXHR) {
195
+ if (success) {
196
+ success(data, textStatus, jqXHR);
197
+ }
198
+
199
+ var results = resultsCb(data, offset);
200
+ results.results = results.results.map(processItem);
201
+ queryOptions.callback(results);
202
+ },
203
+ error: function(jqXHR, textStatus, errorThrown) {
204
+ if (error) {
205
+ error(jqXHR, textStatus, errorThrown);
206
+ }
207
+
208
+ queryOptions.error(
209
+ formatError(term, jqXHR, textStatus, errorThrown),
210
+ { escape: false }
211
+ );
212
+ }
213
+ }));
214
+ }
215
+ };
216
+ }
217
+ });
218
+
219
+ },{"12":12,"2":2,"7":7,"jquery":"jquery"}],5:[function(_dereq_,module,exports){
220
+ 'use strict';
221
+
222
+ var Selectivity = _dereq_(7);
223
+
224
+ var latestQueryNum = 0;
225
+
226
+ /**
227
+ * Option listener that will discard any callbacks from the query function if another query has
228
+ * been called afterwards. This prevents responses from remote sources arriving out-of-order.
229
+ */
230
+ Selectivity.OptionListeners.push(function(selectivity, options) {
231
+
232
+ var query = options.query;
233
+ if (query) {
234
+ options.query = function(queryOptions) {
235
+ latestQueryNum++;
236
+ var queryNum = latestQueryNum;
237
+
238
+ var callback = queryOptions.callback;
239
+ var error = queryOptions.error;
240
+ queryOptions.callback = function() {
241
+ if (queryNum === latestQueryNum) {
242
+ callback.apply(null, arguments);
243
+ }
244
+ };
245
+ queryOptions.error = function() {
246
+ if (queryNum === latestQueryNum) {
247
+ error.apply(null, arguments);
248
+ }
249
+ };
250
+ query(queryOptions);
251
+ };
252
+ }
253
+ });
254
+
255
+ },{"7":7}],6:[function(_dereq_,module,exports){
256
+ 'use strict';
257
+
258
+ var $ = window.jQuery || window.Zepto;
259
+
260
+ var SelectivityDropdown = _dereq_(9);
261
+
262
+ /**
263
+ * Methods.
264
+ */
265
+ $.extend(SelectivityDropdown.prototype, {
266
+
267
+ /**
268
+ * @inherit
269
+ */
270
+ removeCloseHandler: function() {
271
+
272
+ if (this._$backdrop && !this.parentMenu) {
273
+ this._$backdrop.remove();
274
+ this._$backdrop = null;
275
+ }
276
+ },
277
+
278
+ /**
279
+ * @inherit
280
+ */
281
+ setupCloseHandler: function() {
282
+
283
+ var $backdrop;
284
+ if (this.parentMenu) {
285
+ $backdrop = this.parentMenu._$backdrop;
286
+ } else {
287
+ $backdrop = $('<div>').addClass('selectivity-backdrop');
288
+
289
+ $('body').append($backdrop);
290
+ }
291
+
292
+ $backdrop.on('click', this.close.bind(this));
293
+
294
+ this._$backdrop = $backdrop;
295
+ }
296
+
297
+ });
298
+
299
+ },{"9":9,"jquery":"jquery"}],7:[function(_dereq_,module,exports){
300
+ 'use strict';
301
+
302
+ var $ = window.jQuery || window.Zepto;
303
+
304
+ /**
305
+ * Create a new Selectivity instance or invoke a method on an instance.
306
+ *
307
+ * @param methodName Optional name of a method to call. If omitted, a Selectivity instance is
308
+ * created for each element in the set of matched elements. If an element in the
309
+ * set already has a Selectivity instance, the result is the same as if the
310
+ * setOptions() method is called.
311
+ * @param options Optional options object to pass to the given method or the constructor. See the
312
+ * documentation for the respective methods to see which options they accept. In case
313
+ * a new instance is being created, the following property is used:
314
+ * inputType - The input type to use. Default input types include 'Multiple' and
315
+ * 'Single', but you can add custom input types to the InputTypes map or
316
+ * just specify one here as a function. The default value is 'Single',
317
+ * unless multiple is true in which case it is 'Multiple'.
318
+ * multiple - Boolean determining whether multiple items may be selected
319
+ * (default: false). If true, a MultipleSelectivity instance is created,
320
+ * otherwise a SingleSelectivity instance is created.
321
+ *
322
+ * @return If the given method returns a value, this method returns the value of that method
323
+ * executed on the first element in the set of matched elements.
324
+ */
325
+ function selectivity(methodName, options) {
326
+ /* jshint validthis: true */
327
+
328
+ var result;
329
+
330
+ this.each(function() {
331
+ var instance = this.selectivity;
332
+
333
+ if (instance) {
334
+ if ($.type(methodName) !== 'string') {
335
+ options = methodName;
336
+ methodName = 'setOptions';
337
+ }
338
+
339
+ if ($.type(instance[methodName]) === 'function') {
340
+ if (result === undefined) {
341
+ result = instance[methodName].call(instance, options);
342
+ }
343
+ } else {
344
+ throw new Error('Unknown method: ' + methodName);
345
+ }
346
+ } else {
347
+ if ($.type(methodName) === 'string') {
348
+ if (methodName !== 'destroy') {
349
+ throw new Error('Cannot call method on element without Selectivity instance');
350
+ }
351
+ } else {
352
+ options = $.extend({}, methodName, { element: this });
353
+
354
+ // this is a one-time hack to facilitate the selectivity-traditional module, because
355
+ // the module is not able to hook this early into creation of the instance
356
+ var $this = $(this);
357
+ if ($this.is('select') && $this.prop('multiple')) {
358
+ options.multiple = true;
359
+ }
360
+
361
+ var InputTypes = Selectivity.InputTypes;
362
+ var InputType = (options.inputType || (options.multiple ? 'Multiple' : 'Single'));
363
+ if ($.type(InputType) !== 'function') {
364
+ if (InputTypes[InputType]) {
365
+ InputType = InputTypes[InputType];
366
+ } else {
367
+ throw new Error('Unknown Selectivity input type: ' + InputType);
368
+ }
369
+ }
370
+
371
+ this.selectivity = new InputType(options);
372
+ }
373
+ }
374
+ });
375
+
376
+ return (result === undefined ? this : result);
377
+ }
378
+
379
+ /**
380
+ * Selectivity Base Constructor.
381
+ *
382
+ * You will never use this constructor directly. Instead, you use $(selector).selectivity(options)
383
+ * to create an instance of either MultipleSelectivity or SingleSelectivity. This class defines all
384
+ * functionality that is common between both.
385
+ *
386
+ * @param options Options object. Accepts the same options as the setOptions method(), in addition
387
+ * to the following ones:
388
+ * data - Initial selection data to set. This should be an array of objects with 'id'
389
+ * and 'text' properties. This option is mutually exclusive with 'value'.
390
+ * element - The DOM element to which to attach the Selectivity instance. This
391
+ * property is set automatically by the $.fn.selectivity() function.
392
+ * value - Initial value to set. This should be an array of IDs. This property is
393
+ * mutually exclusive with 'data'.
394
+ */
395
+ function Selectivity(options) {
396
+
397
+ if (!(this instanceof Selectivity)) {
398
+ return selectivity.apply(this, arguments);
399
+ }
400
+
401
+ /**
402
+ * jQuery container for the element to which this instance is attached.
403
+ */
404
+ this.$el = $(options.element);
405
+
406
+ /**
407
+ * jQuery container for the search input.
408
+ *
409
+ * May be null as long as there is no visible search input. It is set by initSearchInput().
410
+ */
411
+ this.$searchInput = null;
412
+
413
+ /**
414
+ * Reference to the currently open dropdown.
415
+ */
416
+ this.dropdown = null;
417
+
418
+ /**
419
+ * Whether the input is enabled.
420
+ *
421
+ * This is false when the option readOnly is false or the option removeOnly is false.
422
+ */
423
+ this.enabled = true;
424
+
425
+ /**
426
+ * Boolean whether the browser has touch input.
427
+ */
428
+ this.hasTouch = (typeof window !== 'undefined' && 'ontouchstart' in window);
429
+
430
+ /**
431
+ * Boolean whether the browser has a physical keyboard attached to it.
432
+ *
433
+ * Given that there is no way for JavaScript to reliably detect this yet, we just assume it's
434
+ * the opposite of hasTouch for now.
435
+ */
436
+ this.hasKeyboard = !this.hasTouch;
437
+
438
+ /**
439
+ * Array of items from which to select. If set, this will be an array of objects with 'id' and
440
+ * 'text' properties.
441
+ *
442
+ * If given, all items are expected to be available locally and all selection operations operate
443
+ * on this local array only. If null, items are not available locally, and a query function
444
+ * should be provided to fetch remote data.
445
+ */
446
+ this.items = null;
447
+
448
+ /**
449
+ * The function to be used for matching search results.
450
+ */
451
+ this.matcher = Selectivity.matcher;
452
+
453
+ /**
454
+ * Options passed to the Selectivity instance or set through setOptions().
455
+ */
456
+ this.options = {};
457
+
458
+ /**
459
+ * Results from a search query.
460
+ */
461
+ this.results = [];
462
+
463
+ /**
464
+ * Array of search input listeners.
465
+ *
466
+ * Custom listeners can be specified in the options object.
467
+ */
468
+ this.searchInputListeners = Selectivity.SearchInputListeners;
469
+
470
+ /**
471
+ * Mapping of templates.
472
+ *
473
+ * Custom templates can be specified in the options object.
474
+ */
475
+ this.templates = $.extend({}, Selectivity.Templates);
476
+
477
+ /**
478
+ * The last used search term.
479
+ */
480
+ this.term = '';
481
+
482
+ this.setOptions(options);
483
+
484
+ if (options.value) {
485
+ this.value(options.value, { triggerChange: false });
486
+ } else {
487
+ this.data(options.data || null, { triggerChange: false });
488
+ }
489
+
490
+ this._events = [];
491
+
492
+ this._$searchInputs = [];
493
+
494
+ this.$el.on('selectivity-close', this._closed.bind(this));
495
+
496
+ this.delegateEvents();
497
+ }
498
+
499
+ /**
500
+ * Methods.
501
+ */
502
+ $.extend(Selectivity.prototype, {
503
+
504
+ /**
505
+ * Convenience shortcut for this.$el.find(selector).
506
+ */
507
+ $: function(selector) {
508
+
509
+ return this.$el.find(selector);
510
+ },
511
+
512
+ /**
513
+ * Closes the dropdown.
514
+ */
515
+ close: function() {
516
+
517
+ if (this.dropdown) {
518
+ this.dropdown.close();
519
+ }
520
+ },
521
+
522
+ /**
523
+ * Sets or gets the selection data.
524
+ *
525
+ * The selection data contains both IDs and text labels. If you only want to set or get the IDs,
526
+ * you should use the value() method.
527
+ *
528
+ * @param newData Optional new data to set. For a MultipleSelectivity instance the data must be
529
+ * an array of objects with 'id' and 'text' properties, for a SingleSelectivity
530
+ * instance the data must be a single such object or null to indicate no item is
531
+ * selected.
532
+ * @param options Optional options object. May contain the following property:
533
+ * triggerChange - Set to false to suppress the "change" event being triggered.
534
+ *
535
+ * @return If newData is omitted, this method returns the current data.
536
+ */
537
+ data: function(newData, options) {
538
+
539
+ options = options || {};
540
+
541
+ if (newData === undefined) {
542
+ return this._data;
543
+ } else {
544
+ newData = this.validateData(newData);
545
+
546
+ this._data = newData;
547
+ this._value = this.getValueForData(newData);
548
+
549
+ if (options.triggerChange !== false) {
550
+ this.triggerChange();
551
+ }
552
+ }
553
+ },
554
+
555
+ /**
556
+ * Attaches all listeners from the events map to the instance's element.
557
+ *
558
+ * Normally, you should not have to call this method yourself as it's called automatically in
559
+ * the constructor.
560
+ */
561
+ delegateEvents: function() {
562
+
563
+ this.undelegateEvents();
564
+
565
+ $.each(this.events, function(event, listener) {
566
+ var selector, index = event.indexOf(' ');
567
+ if (index > -1) {
568
+ selector = event.slice(index + 1);
569
+ event = event.slice(0, index);
570
+ }
571
+
572
+ if ($.type(listener) === 'string') {
573
+ listener = this[listener];
574
+ }
575
+
576
+ listener = listener.bind(this);
577
+
578
+ if (selector) {
579
+ this.$el.on(event, selector, listener);
580
+ } else {
581
+ this.$el.on(event, listener);
582
+ }
583
+
584
+ this._events.push({ event: event, selector: selector, listener: listener });
585
+ }.bind(this));
586
+ },
587
+
588
+ /**
589
+ * Destroys the Selectivity instance.
590
+ */
591
+ destroy: function() {
592
+
593
+ this.undelegateEvents();
594
+
595
+ var $el = this.$el;
596
+ $el.children().remove();
597
+ $el[0].selectivity = null;
598
+ $el = null;
599
+ },
600
+
601
+ /**
602
+ * Filters the results to be displayed in the dropdown.
603
+ *
604
+ * The default implementation simply returns the results unfiltered, but the MultipleSelectivity
605
+ * class overrides this method to filter out any items that have already been selected.
606
+ *
607
+ * @param results Array of items with 'id' and 'text' properties.
608
+ *
609
+ * @return The filtered array.
610
+ */
611
+ filterResults: function(results) {
612
+
613
+ return results;
614
+ },
615
+
616
+ /**
617
+ * Applies focus to the input.
618
+ */
619
+ focus: function() {
620
+
621
+ if (this.$searchInput) {
622
+ this.$searchInput.focus();
623
+ }
624
+ },
625
+
626
+ /**
627
+ * Returns the correct item for a given ID.
628
+ *
629
+ * @param id The ID to get the item for.
630
+ *
631
+ * @return The corresponding item. Will be an object with 'id' and 'text' properties or null if
632
+ * the item cannot be found. Note that if no items are defined, this method assumes the
633
+ * text labels will be equal to the IDs.
634
+ */
635
+ getItemForId: function(id) {
636
+
637
+ var items = this.items;
638
+ if (items) {
639
+ return Selectivity.findNestedById(items, id);
640
+ } else {
641
+ return { id: id, text: '' + id };
642
+ }
643
+ },
644
+
645
+ /**
646
+ * Initializes the search input element.
647
+ *
648
+ * Sets the $searchInput property, invokes all search input listeners and attaches the default
649
+ * action of searching when something is typed.
650
+ *
651
+ * @param $input jQuery container for the input element.
652
+ * @param options Optional options object. May contain the following property:
653
+ * noSearch - If false, no event handlers are setup to initiate searching when
654
+ * the user types in the input field. This is useful if you want to
655
+ * use the input only to handle keyboard support.
656
+ */
657
+ initSearchInput: function($input, options) {
658
+
659
+ this._$searchInputs.push($input);
660
+ this.$searchInput = $input;
661
+
662
+ this.searchInputListeners.forEach(function(listener) {
663
+ listener(this, $input);
664
+ }.bind(this));
665
+
666
+ if (!options || options.noSearch !== false) {
667
+ $input.on('keyup', function(event) {
668
+ if (!event.isDefaultPrevented()) {
669
+ this.search();
670
+ }
671
+ }.bind(this));
672
+ }
673
+ },
674
+
675
+ /**
676
+ * Loads a follow-up page with results after a search.
677
+ *
678
+ * This method should only be called after a call to search() when the callback has indicated
679
+ * more results are available.
680
+ */
681
+ loadMore: function() {
682
+
683
+ this.options.query({
684
+ callback: function(response) {
685
+ if (response && response.results) {
686
+ this._addResults(
687
+ Selectivity.processItems(response.results),
688
+ { hasMore: !!response.more }
689
+ );
690
+ } else {
691
+ throw new Error('callback must be passed a response object');
692
+ }
693
+ }.bind(this),
694
+ error: this._addResults.bind(this, []),
695
+ offset: this.results.length,
696
+ selectivity: this,
697
+ term: this.term
698
+ });
699
+ },
700
+
701
+ /**
702
+ * Opens the dropdown.
703
+ *
704
+ * @param options Optional options object. May contain the following property:
705
+ * showSearchInput - Boolean whether a search input should be shown in the
706
+ * dropdown. Default is false.
707
+ */
708
+ open: function(options) {
709
+
710
+ options = options || {};
711
+
712
+ if (!this.dropdown) {
713
+ if (this.triggerEvent('selectivity-opening')) {
714
+ var Dropdown = this.options.dropdown || Selectivity.Dropdown;
715
+ if (Dropdown) {
716
+ this.dropdown = new Dropdown({
717
+ position: this.options.positionDropdown,
718
+ selectivity: this,
719
+ showSearchInput: options.showSearchInput
720
+ });
721
+ }
722
+
723
+ this.search('');
724
+ }
725
+ }
726
+ },
727
+
728
+ /**
729
+ * (Re-)positions the dropdown.
730
+ */
731
+ positionDropdown: function() {
732
+
733
+ if (this.dropdown) {
734
+ this.dropdown.position();
735
+ }
736
+ },
737
+
738
+ /**
739
+ * Removes the search input last initialized with initSearchInput().
740
+ */
741
+ removeSearchInput: function() {
742
+
743
+ this._$searchInputs.pop();
744
+
745
+ this.$searchInput = this._$searchInputs[this._$searchInputs.length - 1] || null;
746
+ },
747
+
748
+ /**
749
+ * Searches for results based on the term entered in the search input.
750
+ *
751
+ * If an items array has been passed with the options to the Selectivity instance, a local
752
+ * search will be performed among those items. Otherwise, the query function specified in the
753
+ * options will be used to perform the search. If neither is defined, nothing happens.
754
+ *
755
+ * @param term Optional term to search for. If ommitted, the value of the search input element
756
+ * is used as term.
757
+ */
758
+ search: function(term) {
759
+
760
+ var self = this;
761
+ function setResults(results, resultOptions) {
762
+ self._setResults(results, $.extend({ term: term }, resultOptions));
763
+ }
764
+
765
+ if (term === undefined) {
766
+ if (!self.$searchInput) {
767
+ return;
768
+ }
769
+
770
+ term = self.$searchInput.val();
771
+ }
772
+
773
+ if (self.items) {
774
+ term = Selectivity.transformText(term);
775
+ var matcher = self.matcher;
776
+ setResults(self.items.map(function(item) {
777
+ return matcher(item, term);
778
+ }).filter(function(item) {
779
+ return !!item;
780
+ }));
781
+ } else if (self.options.query) {
782
+ self.options.query({
783
+ callback: function(response) {
784
+ if (response && response.results) {
785
+ setResults(
786
+ Selectivity.processItems(response.results),
787
+ { hasMore: !!response.more }
788
+ );
789
+ } else {
790
+ throw new Error('callback must be passed a response object');
791
+ }
792
+ },
793
+ error: self._showError.bind(self),
794
+ offset: 0,
795
+ selectivity: self,
796
+ term: term
797
+ });
798
+ }
799
+
800
+ self.term = term;
801
+ },
802
+
803
+ /**
804
+ * Sets one or more options on this Selectivity instance.
805
+ *
806
+ * @param options Options object. May contain one or more of the following properties:
807
+ * closeOnSelect - Set to false to keep the dropdown open after the user has
808
+ * selected an item. This is useful if you want to allow the user
809
+ * to quickly select multiple items. The default value is true.
810
+ * dropdown - Custom dropdown implementation to use for this instance.
811
+ * initSelection - Function to map values by ID to selection data. This function
812
+ * receives two arguments, 'value' and 'callback'. The value is
813
+ * the current value of the selection, which is an ID or an array
814
+ * of IDs depending on the input type. The callback should be
815
+ * invoked with an object or array of objects, respectively,
816
+ * containing 'id' and 'text' properties.
817
+ * items - Array of items from which to select. Should be an array of objects
818
+ * with 'id' and 'text' properties. As convenience, you may also pass an
819
+ * array of strings, in which case the same string is used for both the
820
+ * 'id' and 'text' properties. If items are given, all items are expected
821
+ * to be available locally and all selection operations operate on this
822
+ * local array only. If null, items are not available locally, and a
823
+ * query function should be provided to fetch remote data.
824
+ * matcher - Function to determine whether text matches a given search term. Note
825
+ * this function is only used if you have specified an array of items.
826
+ * Receives two arguments:
827
+ * item - The item that should match the search term.
828
+ * term - The search term. Note that for performance reasons, the term
829
+ * has always been already processed using
830
+ * Selectivity.transformText().
831
+ * The method should return the item if it matches, and null otherwise.
832
+ * If the item has a children array, the matcher is expected to filter
833
+ * those itself (be sure to only return the filtered array of children
834
+ * in the returned item and not to modify the children of the item
835
+ * argument).
836
+ * placeholder - Placeholder text to display when the element has no focus and
837
+ * no selected items.
838
+ * positionDropdown - Function to position the dropdown. Receives two arguments:
839
+ * $dropdownEl - The element to be positioned.
840
+ * $selectEl - The element of the Selectivity instance, that
841
+ * you can position the dropdown to.
842
+ * The default implementation positions the dropdown element
843
+ * under the Selectivity's element and gives it the same
844
+ * width.
845
+ * query - Function to use for querying items. Receives a single object as
846
+ * argument with the following properties:
847
+ * callback - Callback to invoke when the results are available. This
848
+ * callback should be passed a single object as argument with
849
+ * the following properties:
850
+ * more - Boolean that can be set to true to indicate there
851
+ * are more results available. Additional results may
852
+ * be fetched by the user through pagination.
853
+ * results - Array of result items. The format for the result
854
+ * items is the same as for passing local items.
855
+ * offset - This property is only used for pagination and indicates how
856
+ * many results should be skipped when returning more results.
857
+ * selectivity - The Selectivity instance the query function is used on.
858
+ * term - The search term the user is searching for. Unlike with the
859
+ * matcher function, the term has not been processed using
860
+ * Selectivity.transformText().
861
+ * readOnly - If true, disables any modification of the input.
862
+ * removeOnly - If true, disables any modification of the input except removing
863
+ * of selected items.
864
+ * searchInputListeners - Array of search input listeners. By default, the global
865
+ * array Selectivity.SearchInputListeners is used.
866
+ * showDropdown - Set to false if you don't want to use any dropdown (you can
867
+ * still open it programmatically using open()).
868
+ * templates - Object with instance-specific templates to override the global
869
+ * templates assigned to Selectivity.Templates.
870
+ */
871
+ setOptions: function(options) {
872
+
873
+ options = options || {};
874
+
875
+ Selectivity.OptionListeners.forEach(function(listener) {
876
+ listener(this, options);
877
+ }.bind(this));
878
+
879
+ $.extend(this.options, options);
880
+
881
+ var allowedTypes = $.extend({
882
+ closeOnSelect: 'boolean',
883
+ dropdown: 'function|null',
884
+ initSelection: 'function|null',
885
+ matcher: 'function|null',
886
+ placeholder: 'string',
887
+ positionDropdown: 'function|null',
888
+ query: 'function|null',
889
+ readOnly: 'boolean',
890
+ removeOnly: 'boolean',
891
+ searchInputListeners: 'array'
892
+ }, options.allowedTypes);
893
+
894
+ $.each(options, function(key, value) {
895
+ var type = allowedTypes[key];
896
+ if (type && !type.split('|').some(function(type) { return $.type(value) === type; })) {
897
+ throw new Error(key + ' must be of type ' + type);
898
+ }
899
+
900
+ switch (key) {
901
+ case 'items':
902
+ this.items = (value === null ? value : Selectivity.processItems(value));
903
+ break;
904
+
905
+ case 'matcher':
906
+ this.matcher = value;
907
+ break;
908
+
909
+ case 'searchInputListeners':
910
+ this.searchInputListeners = value;
911
+ break;
912
+
913
+ case 'templates':
914
+ $.extend(this.templates, value);
915
+ break;
916
+ }
917
+ }.bind(this));
918
+
919
+ this.enabled = (!this.options.readOnly && !this.options.removeOnly);
920
+ },
921
+
922
+ /**
923
+ * Returns the result of the given template.
924
+ *
925
+ * @param templateName Name of the template to process.
926
+ * @param options Options to pass to the template.
927
+ *
928
+ * @return String containing HTML.
929
+ */
930
+ template: function(templateName, options) {
931
+
932
+ var template = this.templates[templateName];
933
+ if (template) {
934
+ if ($.type(template) === 'function') {
935
+ return template(options);
936
+ } else if (template.render) {
937
+ return template.render(options);
938
+ } else {
939
+ return template.toString();
940
+ }
941
+ } else {
942
+ throw new Error('Unknown template: ' + templateName);
943
+ }
944
+ },
945
+
946
+ /**
947
+ * Triggers the change event.
948
+ *
949
+ * The event object at least contains the following property:
950
+ * value - The new value of the Selectivity instance.
951
+ *
952
+ * @param Optional additional options added to the event object.
953
+ */
954
+ triggerChange: function(options) {
955
+
956
+ this.triggerEvent('change', $.extend({ value: this._value }, options));
957
+ },
958
+
959
+ /**
960
+ * Triggers an event on the instance's element.
961
+ *
962
+ * @param Optional event data to be added to the event object.
963
+ *
964
+ * @return Whether the default action of the event may be executed, ie. returns false if
965
+ * preventDefault() has been called.
966
+ */
967
+ triggerEvent: function(eventName, data) {
968
+
969
+ var event = $.Event(eventName, data || {});
970
+ this.$el.trigger(event);
971
+ return !event.isDefaultPrevented();
972
+ },
973
+
974
+ /**
975
+ * Detaches all listeners from the events map from the instance's element.
976
+ *
977
+ * Normally, you should not have to call this method yourself as it's called automatically in
978
+ * the destroy() method.
979
+ */
980
+ undelegateEvents: function() {
981
+
982
+ this._events.forEach(function(event) {
983
+ if (event.selector) {
984
+ this.$el.off(event.event, event.selector, event.listener);
985
+ } else {
986
+ this.$el.off(event.event, event.listener);
987
+ }
988
+ }, this);
989
+
990
+ this._events = [];
991
+ },
992
+
993
+ /**
994
+ * Shorthand for value().
995
+ */
996
+ val: function(newValue) {
997
+
998
+ return this.value(newValue);
999
+ },
1000
+
1001
+ /**
1002
+ * Validates a single item. Throws an exception if the item is invalid.
1003
+ *
1004
+ * @param item The item to validate.
1005
+ *
1006
+ * @return The validated item. May differ from the input item.
1007
+ */
1008
+ validateItem: function(item) {
1009
+
1010
+ if (item && Selectivity.isValidId(item.id) && $.type(item.text) === 'string') {
1011
+ return item;
1012
+ } else {
1013
+ throw new Error('Item should have id (number or string) and text (string) properties');
1014
+ }
1015
+ },
1016
+
1017
+ /**
1018
+ * Sets or gets the value of the selection.
1019
+ *
1020
+ * The value of the selection only concerns the IDs of the selection items. If you are
1021
+ * interested in the IDs and the text labels, you should use the data() method.
1022
+ *
1023
+ * Note that if neither the items option nor the initSelection option have been set, Selectivity
1024
+ * will have no way to determine what text labels should be used with the given IDs in which
1025
+ * case it will assume the text is equal to the ID. This is useful if you're working with tags,
1026
+ * or selecting e-mail addresses for instance, but may not always be what you want.
1027
+ *
1028
+ * @param newValue Optional new value to set. For a MultipleSelectivity instance the value must be
1029
+ * an array of IDs, for a SingleSelectivity instance the value must be a single ID
1030
+ * (a string or a number) or null to indicate no item is selected.
1031
+ * @param options Optional options object. May contain the following property:
1032
+ * triggerChange - Set to false to suppress the "change" event being triggered.
1033
+ *
1034
+ * @return If newValue is omitted, this method returns the current value.
1035
+ */
1036
+ value: function(newValue, options) {
1037
+
1038
+ options = options || {};
1039
+
1040
+ if (newValue === undefined) {
1041
+ return this._value;
1042
+ } else {
1043
+ newValue = this.validateValue(newValue);
1044
+
1045
+ this._value = newValue;
1046
+
1047
+ if (this.options.initSelection) {
1048
+ this.options.initSelection(newValue, function(data) {
1049
+ if (this._value === newValue) {
1050
+ this._data = this.validateData(data);
1051
+
1052
+ if (options.triggerChange !== false) {
1053
+ this.triggerChange();
1054
+ }
1055
+ }
1056
+ }.bind(this));
1057
+ } else {
1058
+ this._data = this.getDataForValue(newValue);
1059
+
1060
+ if (options.triggerChange !== false) {
1061
+ this.triggerChange();
1062
+ }
1063
+ }
1064
+ }
1065
+ },
1066
+
1067
+ /**
1068
+ * @private
1069
+ */
1070
+ _addResults: function(results, options) {
1071
+
1072
+ this.results = this.results.concat(results);
1073
+
1074
+ if (this.dropdown) {
1075
+ this.dropdown.showResults(
1076
+ this.filterResults(results),
1077
+ $.extend({ add: true }, options)
1078
+ );
1079
+ }
1080
+ },
1081
+
1082
+ /**
1083
+ * @private
1084
+ */
1085
+ _closed: function() {
1086
+
1087
+ this.dropdown = null;
1088
+ },
1089
+
1090
+ /**
1091
+ * @private
1092
+ */
1093
+ _getItemId: function(elementOrEvent) {
1094
+
1095
+ // returns the item ID related to an element or event target.
1096
+ // IDs can be either numbers or strings, but attribute values are always strings, so we
1097
+ // will have to find out whether the item ID ought to be a number or string ourselves.
1098
+ // $.fn.data() is a bit overzealous for our case, because it returns a number whenever the
1099
+ // attribute value can be parsed as a number. however, it is possible an item had an ID
1100
+ // which is a string but which is parseable as number, in which case we verify if the ID
1101
+ // as number is actually found among the data or results. if it isn't, we assume it was
1102
+ // supposed to be a string after all...
1103
+
1104
+ var $element;
1105
+ if (elementOrEvent.target) {
1106
+ $element = $(elementOrEvent.target).closest('[data-item-id]');
1107
+ } else if (elementOrEvent.length) {
1108
+ $element = elementOrEvent;
1109
+ } else {
1110
+ $element = $(elementOrEvent);
1111
+ }
1112
+
1113
+ var id = $element.data('item-id');
1114
+ if ($.type(id) === 'string') {
1115
+ return id;
1116
+ } else {
1117
+ if (Selectivity.findById(this._data || [], id) ||
1118
+ Selectivity.findNestedById(this.results, id)) {
1119
+ return id;
1120
+ } else {
1121
+ return '' + id;
1122
+ }
1123
+ }
1124
+ },
1125
+
1126
+ /**
1127
+ * @private
1128
+ */
1129
+ _setResults: function(results, options) {
1130
+
1131
+ this.results = results;
1132
+
1133
+ if (this.dropdown) {
1134
+ this.dropdown.showResults(this.filterResults(results), options || {});
1135
+ }
1136
+ },
1137
+
1138
+ /**
1139
+ * @private
1140
+ */
1141
+ _showError: function(error, options) {
1142
+
1143
+ this.results = [];
1144
+
1145
+ if (this.dropdown) {
1146
+ this.dropdown.showError(error, options);
1147
+ }
1148
+ }
1149
+
1150
+ });
1151
+
1152
+ /**
1153
+ * Dropdown class to use for displaying dropdowns.
1154
+ *
1155
+ * The default implementation of a dropdown is defined in the selectivity-dropdown module.
1156
+ */
1157
+ Selectivity.Dropdown = null;
1158
+
1159
+ /**
1160
+ * Mapping of input types.
1161
+ */
1162
+ Selectivity.InputTypes = {};
1163
+
1164
+ /**
1165
+ * Array of option listeners.
1166
+ *
1167
+ * Option listeners are invoked when setOptions() is called. Every listener receives two arguments:
1168
+ *
1169
+ * selectivity - The Selectivity instance.
1170
+ * options - The options that are about to be set. The listener may modify this options object.
1171
+ *
1172
+ * An example of an option listener is the selectivity-traditional module.
1173
+ */
1174
+ Selectivity.OptionListeners = [];
1175
+
1176
+ /**
1177
+ * Array of search input listeners.
1178
+ *
1179
+ * Search input listeners are invoked when initSearchInput() is called (typically right after the
1180
+ * search input is created). Every listener receives two arguments:
1181
+ *
1182
+ * selectivity - The Selectivity instance.
1183
+ * $input - jQuery container with the search input.
1184
+ *
1185
+ * An example of a search input listener is the selectivity-keyboard module.
1186
+ */
1187
+ Selectivity.SearchInputListeners = [];
1188
+
1189
+ /**
1190
+ * Mapping with templates to use for rendering select boxes and dropdowns. See
1191
+ * selectivity-templates.js for a useful set of default templates, as well as for documentation of
1192
+ * the individual templates.
1193
+ */
1194
+ Selectivity.Templates = {};
1195
+
1196
+ /**
1197
+ * Finds an item in the given array with the specified ID.
1198
+ *
1199
+ * @param array Array to search in.
1200
+ * @param id ID to search for.
1201
+ *
1202
+ * @return The item in the array with the given ID, or null if the item was not found.
1203
+ */
1204
+ Selectivity.findById = function(array, id) {
1205
+
1206
+ var index = Selectivity.findIndexById(array, id);
1207
+ return (index > -1 ? array[index] : null);
1208
+ };
1209
+
1210
+ /**
1211
+ * Finds the index of an item in the given array with the specified ID.
1212
+ *
1213
+ * @param array Array to search in.
1214
+ * @param id ID to search for.
1215
+ *
1216
+ * @return The index of the item in the array with the given ID, or -1 if the item was not found.
1217
+ */
1218
+ Selectivity.findIndexById = function(array, id) {
1219
+
1220
+ for (var i = 0, length = array.length; i < length; i++) {
1221
+ if (array[i].id === id) {
1222
+ return i;
1223
+ }
1224
+ }
1225
+ return -1;
1226
+ };
1227
+
1228
+ /**
1229
+ * Finds an item in the given array with the specified ID. Items in the array may contain 'children'
1230
+ * properties which in turn will be searched for the item.
1231
+ *
1232
+ * @param array Array to search in.
1233
+ * @param id ID to search for.
1234
+ *
1235
+ * @return The item in the array with the given ID, or null if the item was not found.
1236
+ */
1237
+ Selectivity.findNestedById = null && function(array, id) {
1238
+
1239
+ for (var i = 0, length = array.length; i < length; i++) {
1240
+ var item = array[i];
1241
+ if (item.id === id) {
1242
+ return item;
1243
+ } else if (item.children) {
1244
+ var result = Selectivity.findNestedById(item.children, id);
1245
+ if (result) {
1246
+ return result;
1247
+ }
1248
+ }
1249
+ }
1250
+ return null;
1251
+ };
1252
+
1253
+ /**
1254
+ * Utility method for inheriting another class.
1255
+ *
1256
+ * @param SubClass Constructor function of the subclass.
1257
+ * @param SuperClass Optional constructor function of the superclass. If omitted, Selectivity is
1258
+ * used as superclass.
1259
+ * @param prototype Object with methods you want to add to the subclass prototype.
1260
+ *
1261
+ * @return A utility function for calling the methods of the superclass. This function receives two
1262
+ * arguments: The this object on which you want to execute the method and the name of the
1263
+ * method. Any arguments past those are passed to the superclass method.
1264
+ */
1265
+ Selectivity.inherits = function(SubClass, SuperClass, prototype) {
1266
+
1267
+ if (arguments.length === 2) {
1268
+ prototype = SuperClass;
1269
+ SuperClass = Selectivity;
1270
+ }
1271
+
1272
+ SubClass.prototype = $.extend(
1273
+ Object.create(SuperClass.prototype),
1274
+ { constructor: SubClass },
1275
+ prototype
1276
+ );
1277
+
1278
+ return function(self, methodName) {
1279
+ SuperClass.prototype[methodName].apply(self, Array.prototype.slice.call(arguments, 2));
1280
+ };
1281
+ };
1282
+
1283
+ /**
1284
+ * Checks whether a value can be used as a valid ID for selection items. Only numbers and strings
1285
+ * are accepted to be used as IDs.
1286
+ *
1287
+ * @param id The value to check whether it is a valid ID.
1288
+ *
1289
+ * @return true if the value is a valid ID, false otherwise.
1290
+ */
1291
+ Selectivity.isValidId = function(id) {
1292
+
1293
+ var type = $.type(id);
1294
+ return type === 'number' || type === 'string';
1295
+ };
1296
+
1297
+ /**
1298
+ * Decides whether a given item matches a search term. The default implementation simply
1299
+ * checks whether the term is contained within the item's text, after transforming them using
1300
+ * transformText().
1301
+ *
1302
+ * @param item The item that should match the search term.
1303
+ * @param term The search term. Note that for performance reasons, the term has always been already
1304
+ * processed using transformText().
1305
+ *
1306
+ * @return true if the text matches the term, false otherwise.
1307
+ */
1308
+ Selectivity.matcher = function(item, term) {
1309
+
1310
+ var result = null;
1311
+ if (Selectivity.transformText(item.text).indexOf(term) > -1) {
1312
+ result = item;
1313
+ } else if (item.children) {
1314
+ var matchingChildren = item.children.map(function(child) {
1315
+ return Selectivity.matcher(child, term);
1316
+ }).filter(function(child) {
1317
+ return !!child;
1318
+ });
1319
+ if (matchingChildren.length) {
1320
+ result = { id: item.id, text: item.text, children: matchingChildren };
1321
+ }
1322
+ }
1323
+ return result;
1324
+ };
1325
+
1326
+ /**
1327
+ * Helper function for processing items.
1328
+ *
1329
+ * @param item The item to process, either as object containing 'id' and 'text' properties or just
1330
+ * as ID. The 'id' property of an item is optional if it has a 'children' property
1331
+ * containing an array of items.
1332
+ *
1333
+ * @return Object containing 'id' and 'text' properties.
1334
+ */
1335
+ Selectivity.processItem = function(item) {
1336
+
1337
+ if (Selectivity.isValidId(item)) {
1338
+ return { id: item, text: '' + item };
1339
+ } else if (item &&
1340
+ (Selectivity.isValidId(item.id) || item.children) &&
1341
+ $.type(item.text) === 'string') {
1342
+ if (item.children) {
1343
+ item.children = Selectivity.processItems(item.children);
1344
+ }
1345
+
1346
+ return item;
1347
+ } else {
1348
+ throw new Error('invalid item');
1349
+ }
1350
+ };
1351
+
1352
+ /**
1353
+ * Helper function for processing an array of items.
1354
+ *
1355
+ * @param items Array of items to process. See processItem() for details about a single item.
1356
+ *
1357
+ * @return Array with items.
1358
+ */
1359
+ Selectivity.processItems = function(items) {
1360
+
1361
+ if ($.type(items) === 'array') {
1362
+ return items.map(Selectivity.processItem);
1363
+ } else {
1364
+ throw new Error('invalid items');
1365
+ }
1366
+ };
1367
+
1368
+ /**
1369
+ * Quotes a string so it can be used in a CSS attribute selector. It adds double quotes to the
1370
+ * string and escapes all occurrences of the quote character inside the string.
1371
+ *
1372
+ * @param string The string to quote.
1373
+ *
1374
+ * @return The quoted string.
1375
+ */
1376
+ Selectivity.quoteCssAttr = function(string) {
1377
+
1378
+ return '"' + ('' + string).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
1379
+ };
1380
+
1381
+ /**
1382
+ * Transforms text in order to find matches. The default implementation casts all strings to
1383
+ * lower-case so that any matches found will be case-insensitive.
1384
+ *
1385
+ * @param string The string to transform.
1386
+ *
1387
+ * @return The transformed string.
1388
+ */
1389
+ Selectivity.transformText = function(string) {
1390
+
1391
+ return string.toLowerCase();
1392
+ };
1393
+
1394
+ module.exports = $.fn.selectivity = Selectivity;
1395
+
1396
+ },{"jquery":"jquery"}],8:[function(_dereq_,module,exports){
1397
+ 'use strict';
1398
+
1399
+ var DIACRITICS = {
1400
+ '\u24B6': 'A',
1401
+ '\uFF21': 'A',
1402
+ '\u00C0': 'A',
1403
+ '\u00C1': 'A',
1404
+ '\u00C2': 'A',
1405
+ '\u1EA6': 'A',
1406
+ '\u1EA4': 'A',
1407
+ '\u1EAA': 'A',
1408
+ '\u1EA8': 'A',
1409
+ '\u00C3': 'A',
1410
+ '\u0100': 'A',
1411
+ '\u0102': 'A',
1412
+ '\u1EB0': 'A',
1413
+ '\u1EAE': 'A',
1414
+ '\u1EB4': 'A',
1415
+ '\u1EB2': 'A',
1416
+ '\u0226': 'A',
1417
+ '\u01E0': 'A',
1418
+ '\u00C4': 'A',
1419
+ '\u01DE': 'A',
1420
+ '\u1EA2': 'A',
1421
+ '\u00C5': 'A',
1422
+ '\u01FA': 'A',
1423
+ '\u01CD': 'A',
1424
+ '\u0200': 'A',
1425
+ '\u0202': 'A',
1426
+ '\u1EA0': 'A',
1427
+ '\u1EAC': 'A',
1428
+ '\u1EB6': 'A',
1429
+ '\u1E00': 'A',
1430
+ '\u0104': 'A',
1431
+ '\u023A': 'A',
1432
+ '\u2C6F': 'A',
1433
+ '\uA732': 'AA',
1434
+ '\u00C6': 'AE',
1435
+ '\u01FC': 'AE',
1436
+ '\u01E2': 'AE',
1437
+ '\uA734': 'AO',
1438
+ '\uA736': 'AU',
1439
+ '\uA738': 'AV',
1440
+ '\uA73A': 'AV',
1441
+ '\uA73C': 'AY',
1442
+ '\u24B7': 'B',
1443
+ '\uFF22': 'B',
1444
+ '\u1E02': 'B',
1445
+ '\u1E04': 'B',
1446
+ '\u1E06': 'B',
1447
+ '\u0243': 'B',
1448
+ '\u0182': 'B',
1449
+ '\u0181': 'B',
1450
+ '\u24B8': 'C',
1451
+ '\uFF23': 'C',
1452
+ '\u0106': 'C',
1453
+ '\u0108': 'C',
1454
+ '\u010A': 'C',
1455
+ '\u010C': 'C',
1456
+ '\u00C7': 'C',
1457
+ '\u1E08': 'C',
1458
+ '\u0187': 'C',
1459
+ '\u023B': 'C',
1460
+ '\uA73E': 'C',
1461
+ '\u24B9': 'D',
1462
+ '\uFF24': 'D',
1463
+ '\u1E0A': 'D',
1464
+ '\u010E': 'D',
1465
+ '\u1E0C': 'D',
1466
+ '\u1E10': 'D',
1467
+ '\u1E12': 'D',
1468
+ '\u1E0E': 'D',
1469
+ '\u0110': 'D',
1470
+ '\u018B': 'D',
1471
+ '\u018A': 'D',
1472
+ '\u0189': 'D',
1473
+ '\uA779': 'D',
1474
+ '\u01F1': 'DZ',
1475
+ '\u01C4': 'DZ',
1476
+ '\u01F2': 'Dz',
1477
+ '\u01C5': 'Dz',
1478
+ '\u24BA': 'E',
1479
+ '\uFF25': 'E',
1480
+ '\u00C8': 'E',
1481
+ '\u00C9': 'E',
1482
+ '\u00CA': 'E',
1483
+ '\u1EC0': 'E',
1484
+ '\u1EBE': 'E',
1485
+ '\u1EC4': 'E',
1486
+ '\u1EC2': 'E',
1487
+ '\u1EBC': 'E',
1488
+ '\u0112': 'E',
1489
+ '\u1E14': 'E',
1490
+ '\u1E16': 'E',
1491
+ '\u0114': 'E',
1492
+ '\u0116': 'E',
1493
+ '\u00CB': 'E',
1494
+ '\u1EBA': 'E',
1495
+ '\u011A': 'E',
1496
+ '\u0204': 'E',
1497
+ '\u0206': 'E',
1498
+ '\u1EB8': 'E',
1499
+ '\u1EC6': 'E',
1500
+ '\u0228': 'E',
1501
+ '\u1E1C': 'E',
1502
+ '\u0118': 'E',
1503
+ '\u1E18': 'E',
1504
+ '\u1E1A': 'E',
1505
+ '\u0190': 'E',
1506
+ '\u018E': 'E',
1507
+ '\u24BB': 'F',
1508
+ '\uFF26': 'F',
1509
+ '\u1E1E': 'F',
1510
+ '\u0191': 'F',
1511
+ '\uA77B': 'F',
1512
+ '\u24BC': 'G',
1513
+ '\uFF27': 'G',
1514
+ '\u01F4': 'G',
1515
+ '\u011C': 'G',
1516
+ '\u1E20': 'G',
1517
+ '\u011E': 'G',
1518
+ '\u0120': 'G',
1519
+ '\u01E6': 'G',
1520
+ '\u0122': 'G',
1521
+ '\u01E4': 'G',
1522
+ '\u0193': 'G',
1523
+ '\uA7A0': 'G',
1524
+ '\uA77D': 'G',
1525
+ '\uA77E': 'G',
1526
+ '\u24BD': 'H',
1527
+ '\uFF28': 'H',
1528
+ '\u0124': 'H',
1529
+ '\u1E22': 'H',
1530
+ '\u1E26': 'H',
1531
+ '\u021E': 'H',
1532
+ '\u1E24': 'H',
1533
+ '\u1E28': 'H',
1534
+ '\u1E2A': 'H',
1535
+ '\u0126': 'H',
1536
+ '\u2C67': 'H',
1537
+ '\u2C75': 'H',
1538
+ '\uA78D': 'H',
1539
+ '\u24BE': 'I',
1540
+ '\uFF29': 'I',
1541
+ '\u00CC': 'I',
1542
+ '\u00CD': 'I',
1543
+ '\u00CE': 'I',
1544
+ '\u0128': 'I',
1545
+ '\u012A': 'I',
1546
+ '\u012C': 'I',
1547
+ '\u0130': 'I',
1548
+ '\u00CF': 'I',
1549
+ '\u1E2E': 'I',
1550
+ '\u1EC8': 'I',
1551
+ '\u01CF': 'I',
1552
+ '\u0208': 'I',
1553
+ '\u020A': 'I',
1554
+ '\u1ECA': 'I',
1555
+ '\u012E': 'I',
1556
+ '\u1E2C': 'I',
1557
+ '\u0197': 'I',
1558
+ '\u24BF': 'J',
1559
+ '\uFF2A': 'J',
1560
+ '\u0134': 'J',
1561
+ '\u0248': 'J',
1562
+ '\u24C0': 'K',
1563
+ '\uFF2B': 'K',
1564
+ '\u1E30': 'K',
1565
+ '\u01E8': 'K',
1566
+ '\u1E32': 'K',
1567
+ '\u0136': 'K',
1568
+ '\u1E34': 'K',
1569
+ '\u0198': 'K',
1570
+ '\u2C69': 'K',
1571
+ '\uA740': 'K',
1572
+ '\uA742': 'K',
1573
+ '\uA744': 'K',
1574
+ '\uA7A2': 'K',
1575
+ '\u24C1': 'L',
1576
+ '\uFF2C': 'L',
1577
+ '\u013F': 'L',
1578
+ '\u0139': 'L',
1579
+ '\u013D': 'L',
1580
+ '\u1E36': 'L',
1581
+ '\u1E38': 'L',
1582
+ '\u013B': 'L',
1583
+ '\u1E3C': 'L',
1584
+ '\u1E3A': 'L',
1585
+ '\u0141': 'L',
1586
+ '\u023D': 'L',
1587
+ '\u2C62': 'L',
1588
+ '\u2C60': 'L',
1589
+ '\uA748': 'L',
1590
+ '\uA746': 'L',
1591
+ '\uA780': 'L',
1592
+ '\u01C7': 'LJ',
1593
+ '\u01C8': 'Lj',
1594
+ '\u24C2': 'M',
1595
+ '\uFF2D': 'M',
1596
+ '\u1E3E': 'M',
1597
+ '\u1E40': 'M',
1598
+ '\u1E42': 'M',
1599
+ '\u2C6E': 'M',
1600
+ '\u019C': 'M',
1601
+ '\u24C3': 'N',
1602
+ '\uFF2E': 'N',
1603
+ '\u01F8': 'N',
1604
+ '\u0143': 'N',
1605
+ '\u00D1': 'N',
1606
+ '\u1E44': 'N',
1607
+ '\u0147': 'N',
1608
+ '\u1E46': 'N',
1609
+ '\u0145': 'N',
1610
+ '\u1E4A': 'N',
1611
+ '\u1E48': 'N',
1612
+ '\u0220': 'N',
1613
+ '\u019D': 'N',
1614
+ '\uA790': 'N',
1615
+ '\uA7A4': 'N',
1616
+ '\u01CA': 'NJ',
1617
+ '\u01CB': 'Nj',
1618
+ '\u24C4': 'O',
1619
+ '\uFF2F': 'O',
1620
+ '\u00D2': 'O',
1621
+ '\u00D3': 'O',
1622
+ '\u00D4': 'O',
1623
+ '\u1ED2': 'O',
1624
+ '\u1ED0': 'O',
1625
+ '\u1ED6': 'O',
1626
+ '\u1ED4': 'O',
1627
+ '\u00D5': 'O',
1628
+ '\u1E4C': 'O',
1629
+ '\u022C': 'O',
1630
+ '\u1E4E': 'O',
1631
+ '\u014C': 'O',
1632
+ '\u1E50': 'O',
1633
+ '\u1E52': 'O',
1634
+ '\u014E': 'O',
1635
+ '\u022E': 'O',
1636
+ '\u0230': 'O',
1637
+ '\u00D6': 'O',
1638
+ '\u022A': 'O',
1639
+ '\u1ECE': 'O',
1640
+ '\u0150': 'O',
1641
+ '\u01D1': 'O',
1642
+ '\u020C': 'O',
1643
+ '\u020E': 'O',
1644
+ '\u01A0': 'O',
1645
+ '\u1EDC': 'O',
1646
+ '\u1EDA': 'O',
1647
+ '\u1EE0': 'O',
1648
+ '\u1EDE': 'O',
1649
+ '\u1EE2': 'O',
1650
+ '\u1ECC': 'O',
1651
+ '\u1ED8': 'O',
1652
+ '\u01EA': 'O',
1653
+ '\u01EC': 'O',
1654
+ '\u00D8': 'O',
1655
+ '\u01FE': 'O',
1656
+ '\u0186': 'O',
1657
+ '\u019F': 'O',
1658
+ '\uA74A': 'O',
1659
+ '\uA74C': 'O',
1660
+ '\u01A2': 'OI',
1661
+ '\uA74E': 'OO',
1662
+ '\u0222': 'OU',
1663
+ '\u24C5': 'P',
1664
+ '\uFF30': 'P',
1665
+ '\u1E54': 'P',
1666
+ '\u1E56': 'P',
1667
+ '\u01A4': 'P',
1668
+ '\u2C63': 'P',
1669
+ '\uA750': 'P',
1670
+ '\uA752': 'P',
1671
+ '\uA754': 'P',
1672
+ '\u24C6': 'Q',
1673
+ '\uFF31': 'Q',
1674
+ '\uA756': 'Q',
1675
+ '\uA758': 'Q',
1676
+ '\u024A': 'Q',
1677
+ '\u24C7': 'R',
1678
+ '\uFF32': 'R',
1679
+ '\u0154': 'R',
1680
+ '\u1E58': 'R',
1681
+ '\u0158': 'R',
1682
+ '\u0210': 'R',
1683
+ '\u0212': 'R',
1684
+ '\u1E5A': 'R',
1685
+ '\u1E5C': 'R',
1686
+ '\u0156': 'R',
1687
+ '\u1E5E': 'R',
1688
+ '\u024C': 'R',
1689
+ '\u2C64': 'R',
1690
+ '\uA75A': 'R',
1691
+ '\uA7A6': 'R',
1692
+ '\uA782': 'R',
1693
+ '\u24C8': 'S',
1694
+ '\uFF33': 'S',
1695
+ '\u1E9E': 'S',
1696
+ '\u015A': 'S',
1697
+ '\u1E64': 'S',
1698
+ '\u015C': 'S',
1699
+ '\u1E60': 'S',
1700
+ '\u0160': 'S',
1701
+ '\u1E66': 'S',
1702
+ '\u1E62': 'S',
1703
+ '\u1E68': 'S',
1704
+ '\u0218': 'S',
1705
+ '\u015E': 'S',
1706
+ '\u2C7E': 'S',
1707
+ '\uA7A8': 'S',
1708
+ '\uA784': 'S',
1709
+ '\u24C9': 'T',
1710
+ '\uFF34': 'T',
1711
+ '\u1E6A': 'T',
1712
+ '\u0164': 'T',
1713
+ '\u1E6C': 'T',
1714
+ '\u021A': 'T',
1715
+ '\u0162': 'T',
1716
+ '\u1E70': 'T',
1717
+ '\u1E6E': 'T',
1718
+ '\u0166': 'T',
1719
+ '\u01AC': 'T',
1720
+ '\u01AE': 'T',
1721
+ '\u023E': 'T',
1722
+ '\uA786': 'T',
1723
+ '\uA728': 'TZ',
1724
+ '\u24CA': 'U',
1725
+ '\uFF35': 'U',
1726
+ '\u00D9': 'U',
1727
+ '\u00DA': 'U',
1728
+ '\u00DB': 'U',
1729
+ '\u0168': 'U',
1730
+ '\u1E78': 'U',
1731
+ '\u016A': 'U',
1732
+ '\u1E7A': 'U',
1733
+ '\u016C': 'U',
1734
+ '\u00DC': 'U',
1735
+ '\u01DB': 'U',
1736
+ '\u01D7': 'U',
1737
+ '\u01D5': 'U',
1738
+ '\u01D9': 'U',
1739
+ '\u1EE6': 'U',
1740
+ '\u016E': 'U',
1741
+ '\u0170': 'U',
1742
+ '\u01D3': 'U',
1743
+ '\u0214': 'U',
1744
+ '\u0216': 'U',
1745
+ '\u01AF': 'U',
1746
+ '\u1EEA': 'U',
1747
+ '\u1EE8': 'U',
1748
+ '\u1EEE': 'U',
1749
+ '\u1EEC': 'U',
1750
+ '\u1EF0': 'U',
1751
+ '\u1EE4': 'U',
1752
+ '\u1E72': 'U',
1753
+ '\u0172': 'U',
1754
+ '\u1E76': 'U',
1755
+ '\u1E74': 'U',
1756
+ '\u0244': 'U',
1757
+ '\u24CB': 'V',
1758
+ '\uFF36': 'V',
1759
+ '\u1E7C': 'V',
1760
+ '\u1E7E': 'V',
1761
+ '\u01B2': 'V',
1762
+ '\uA75E': 'V',
1763
+ '\u0245': 'V',
1764
+ '\uA760': 'VY',
1765
+ '\u24CC': 'W',
1766
+ '\uFF37': 'W',
1767
+ '\u1E80': 'W',
1768
+ '\u1E82': 'W',
1769
+ '\u0174': 'W',
1770
+ '\u1E86': 'W',
1771
+ '\u1E84': 'W',
1772
+ '\u1E88': 'W',
1773
+ '\u2C72': 'W',
1774
+ '\u24CD': 'X',
1775
+ '\uFF38': 'X',
1776
+ '\u1E8A': 'X',
1777
+ '\u1E8C': 'X',
1778
+ '\u24CE': 'Y',
1779
+ '\uFF39': 'Y',
1780
+ '\u1EF2': 'Y',
1781
+ '\u00DD': 'Y',
1782
+ '\u0176': 'Y',
1783
+ '\u1EF8': 'Y',
1784
+ '\u0232': 'Y',
1785
+ '\u1E8E': 'Y',
1786
+ '\u0178': 'Y',
1787
+ '\u1EF6': 'Y',
1788
+ '\u1EF4': 'Y',
1789
+ '\u01B3': 'Y',
1790
+ '\u024E': 'Y',
1791
+ '\u1EFE': 'Y',
1792
+ '\u24CF': 'Z',
1793
+ '\uFF3A': 'Z',
1794
+ '\u0179': 'Z',
1795
+ '\u1E90': 'Z',
1796
+ '\u017B': 'Z',
1797
+ '\u017D': 'Z',
1798
+ '\u1E92': 'Z',
1799
+ '\u1E94': 'Z',
1800
+ '\u01B5': 'Z',
1801
+ '\u0224': 'Z',
1802
+ '\u2C7F': 'Z',
1803
+ '\u2C6B': 'Z',
1804
+ '\uA762': 'Z',
1805
+ '\u24D0': 'a',
1806
+ '\uFF41': 'a',
1807
+ '\u1E9A': 'a',
1808
+ '\u00E0': 'a',
1809
+ '\u00E1': 'a',
1810
+ '\u00E2': 'a',
1811
+ '\u1EA7': 'a',
1812
+ '\u1EA5': 'a',
1813
+ '\u1EAB': 'a',
1814
+ '\u1EA9': 'a',
1815
+ '\u00E3': 'a',
1816
+ '\u0101': 'a',
1817
+ '\u0103': 'a',
1818
+ '\u1EB1': 'a',
1819
+ '\u1EAF': 'a',
1820
+ '\u1EB5': 'a',
1821
+ '\u1EB3': 'a',
1822
+ '\u0227': 'a',
1823
+ '\u01E1': 'a',
1824
+ '\u00E4': 'a',
1825
+ '\u01DF': 'a',
1826
+ '\u1EA3': 'a',
1827
+ '\u00E5': 'a',
1828
+ '\u01FB': 'a',
1829
+ '\u01CE': 'a',
1830
+ '\u0201': 'a',
1831
+ '\u0203': 'a',
1832
+ '\u1EA1': 'a',
1833
+ '\u1EAD': 'a',
1834
+ '\u1EB7': 'a',
1835
+ '\u1E01': 'a',
1836
+ '\u0105': 'a',
1837
+ '\u2C65': 'a',
1838
+ '\u0250': 'a',
1839
+ '\uA733': 'aa',
1840
+ '\u00E6': 'ae',
1841
+ '\u01FD': 'ae',
1842
+ '\u01E3': 'ae',
1843
+ '\uA735': 'ao',
1844
+ '\uA737': 'au',
1845
+ '\uA739': 'av',
1846
+ '\uA73B': 'av',
1847
+ '\uA73D': 'ay',
1848
+ '\u24D1': 'b',
1849
+ '\uFF42': 'b',
1850
+ '\u1E03': 'b',
1851
+ '\u1E05': 'b',
1852
+ '\u1E07': 'b',
1853
+ '\u0180': 'b',
1854
+ '\u0183': 'b',
1855
+ '\u0253': 'b',
1856
+ '\u24D2': 'c',
1857
+ '\uFF43': 'c',
1858
+ '\u0107': 'c',
1859
+ '\u0109': 'c',
1860
+ '\u010B': 'c',
1861
+ '\u010D': 'c',
1862
+ '\u00E7': 'c',
1863
+ '\u1E09': 'c',
1864
+ '\u0188': 'c',
1865
+ '\u023C': 'c',
1866
+ '\uA73F': 'c',
1867
+ '\u2184': 'c',
1868
+ '\u24D3': 'd',
1869
+ '\uFF44': 'd',
1870
+ '\u1E0B': 'd',
1871
+ '\u010F': 'd',
1872
+ '\u1E0D': 'd',
1873
+ '\u1E11': 'd',
1874
+ '\u1E13': 'd',
1875
+ '\u1E0F': 'd',
1876
+ '\u0111': 'd',
1877
+ '\u018C': 'd',
1878
+ '\u0256': 'd',
1879
+ '\u0257': 'd',
1880
+ '\uA77A': 'd',
1881
+ '\u01F3': 'dz',
1882
+ '\u01C6': 'dz',
1883
+ '\u24D4': 'e',
1884
+ '\uFF45': 'e',
1885
+ '\u00E8': 'e',
1886
+ '\u00E9': 'e',
1887
+ '\u00EA': 'e',
1888
+ '\u1EC1': 'e',
1889
+ '\u1EBF': 'e',
1890
+ '\u1EC5': 'e',
1891
+ '\u1EC3': 'e',
1892
+ '\u1EBD': 'e',
1893
+ '\u0113': 'e',
1894
+ '\u1E15': 'e',
1895
+ '\u1E17': 'e',
1896
+ '\u0115': 'e',
1897
+ '\u0117': 'e',
1898
+ '\u00EB': 'e',
1899
+ '\u1EBB': 'e',
1900
+ '\u011B': 'e',
1901
+ '\u0205': 'e',
1902
+ '\u0207': 'e',
1903
+ '\u1EB9': 'e',
1904
+ '\u1EC7': 'e',
1905
+ '\u0229': 'e',
1906
+ '\u1E1D': 'e',
1907
+ '\u0119': 'e',
1908
+ '\u1E19': 'e',
1909
+ '\u1E1B': 'e',
1910
+ '\u0247': 'e',
1911
+ '\u025B': 'e',
1912
+ '\u01DD': 'e',
1913
+ '\u24D5': 'f',
1914
+ '\uFF46': 'f',
1915
+ '\u1E1F': 'f',
1916
+ '\u0192': 'f',
1917
+ '\uA77C': 'f',
1918
+ '\u24D6': 'g',
1919
+ '\uFF47': 'g',
1920
+ '\u01F5': 'g',
1921
+ '\u011D': 'g',
1922
+ '\u1E21': 'g',
1923
+ '\u011F': 'g',
1924
+ '\u0121': 'g',
1925
+ '\u01E7': 'g',
1926
+ '\u0123': 'g',
1927
+ '\u01E5': 'g',
1928
+ '\u0260': 'g',
1929
+ '\uA7A1': 'g',
1930
+ '\u1D79': 'g',
1931
+ '\uA77F': 'g',
1932
+ '\u24D7': 'h',
1933
+ '\uFF48': 'h',
1934
+ '\u0125': 'h',
1935
+ '\u1E23': 'h',
1936
+ '\u1E27': 'h',
1937
+ '\u021F': 'h',
1938
+ '\u1E25': 'h',
1939
+ '\u1E29': 'h',
1940
+ '\u1E2B': 'h',
1941
+ '\u1E96': 'h',
1942
+ '\u0127': 'h',
1943
+ '\u2C68': 'h',
1944
+ '\u2C76': 'h',
1945
+ '\u0265': 'h',
1946
+ '\u0195': 'hv',
1947
+ '\u24D8': 'i',
1948
+ '\uFF49': 'i',
1949
+ '\u00EC': 'i',
1950
+ '\u00ED': 'i',
1951
+ '\u00EE': 'i',
1952
+ '\u0129': 'i',
1953
+ '\u012B': 'i',
1954
+ '\u012D': 'i',
1955
+ '\u00EF': 'i',
1956
+ '\u1E2F': 'i',
1957
+ '\u1EC9': 'i',
1958
+ '\u01D0': 'i',
1959
+ '\u0209': 'i',
1960
+ '\u020B': 'i',
1961
+ '\u1ECB': 'i',
1962
+ '\u012F': 'i',
1963
+ '\u1E2D': 'i',
1964
+ '\u0268': 'i',
1965
+ '\u0131': 'i',
1966
+ '\u24D9': 'j',
1967
+ '\uFF4A': 'j',
1968
+ '\u0135': 'j',
1969
+ '\u01F0': 'j',
1970
+ '\u0249': 'j',
1971
+ '\u24DA': 'k',
1972
+ '\uFF4B': 'k',
1973
+ '\u1E31': 'k',
1974
+ '\u01E9': 'k',
1975
+ '\u1E33': 'k',
1976
+ '\u0137': 'k',
1977
+ '\u1E35': 'k',
1978
+ '\u0199': 'k',
1979
+ '\u2C6A': 'k',
1980
+ '\uA741': 'k',
1981
+ '\uA743': 'k',
1982
+ '\uA745': 'k',
1983
+ '\uA7A3': 'k',
1984
+ '\u24DB': 'l',
1985
+ '\uFF4C': 'l',
1986
+ '\u0140': 'l',
1987
+ '\u013A': 'l',
1988
+ '\u013E': 'l',
1989
+ '\u1E37': 'l',
1990
+ '\u1E39': 'l',
1991
+ '\u013C': 'l',
1992
+ '\u1E3D': 'l',
1993
+ '\u1E3B': 'l',
1994
+ '\u017F': 'l',
1995
+ '\u0142': 'l',
1996
+ '\u019A': 'l',
1997
+ '\u026B': 'l',
1998
+ '\u2C61': 'l',
1999
+ '\uA749': 'l',
2000
+ '\uA781': 'l',
2001
+ '\uA747': 'l',
2002
+ '\u01C9': 'lj',
2003
+ '\u24DC': 'm',
2004
+ '\uFF4D': 'm',
2005
+ '\u1E3F': 'm',
2006
+ '\u1E41': 'm',
2007
+ '\u1E43': 'm',
2008
+ '\u0271': 'm',
2009
+ '\u026F': 'm',
2010
+ '\u24DD': 'n',
2011
+ '\uFF4E': 'n',
2012
+ '\u01F9': 'n',
2013
+ '\u0144': 'n',
2014
+ '\u00F1': 'n',
2015
+ '\u1E45': 'n',
2016
+ '\u0148': 'n',
2017
+ '\u1E47': 'n',
2018
+ '\u0146': 'n',
2019
+ '\u1E4B': 'n',
2020
+ '\u1E49': 'n',
2021
+ '\u019E': 'n',
2022
+ '\u0272': 'n',
2023
+ '\u0149': 'n',
2024
+ '\uA791': 'n',
2025
+ '\uA7A5': 'n',
2026
+ '\u01CC': 'nj',
2027
+ '\u24DE': 'o',
2028
+ '\uFF4F': 'o',
2029
+ '\u00F2': 'o',
2030
+ '\u00F3': 'o',
2031
+ '\u00F4': 'o',
2032
+ '\u1ED3': 'o',
2033
+ '\u1ED1': 'o',
2034
+ '\u1ED7': 'o',
2035
+ '\u1ED5': 'o',
2036
+ '\u00F5': 'o',
2037
+ '\u1E4D': 'o',
2038
+ '\u022D': 'o',
2039
+ '\u1E4F': 'o',
2040
+ '\u014D': 'o',
2041
+ '\u1E51': 'o',
2042
+ '\u1E53': 'o',
2043
+ '\u014F': 'o',
2044
+ '\u022F': 'o',
2045
+ '\u0231': 'o',
2046
+ '\u00F6': 'o',
2047
+ '\u022B': 'o',
2048
+ '\u1ECF': 'o',
2049
+ '\u0151': 'o',
2050
+ '\u01D2': 'o',
2051
+ '\u020D': 'o',
2052
+ '\u020F': 'o',
2053
+ '\u01A1': 'o',
2054
+ '\u1EDD': 'o',
2055
+ '\u1EDB': 'o',
2056
+ '\u1EE1': 'o',
2057
+ '\u1EDF': 'o',
2058
+ '\u1EE3': 'o',
2059
+ '\u1ECD': 'o',
2060
+ '\u1ED9': 'o',
2061
+ '\u01EB': 'o',
2062
+ '\u01ED': 'o',
2063
+ '\u00F8': 'o',
2064
+ '\u01FF': 'o',
2065
+ '\u0254': 'o',
2066
+ '\uA74B': 'o',
2067
+ '\uA74D': 'o',
2068
+ '\u0275': 'o',
2069
+ '\u01A3': 'oi',
2070
+ '\u0223': 'ou',
2071
+ '\uA74F': 'oo',
2072
+ '\u24DF': 'p',
2073
+ '\uFF50': 'p',
2074
+ '\u1E55': 'p',
2075
+ '\u1E57': 'p',
2076
+ '\u01A5': 'p',
2077
+ '\u1D7D': 'p',
2078
+ '\uA751': 'p',
2079
+ '\uA753': 'p',
2080
+ '\uA755': 'p',
2081
+ '\u24E0': 'q',
2082
+ '\uFF51': 'q',
2083
+ '\u024B': 'q',
2084
+ '\uA757': 'q',
2085
+ '\uA759': 'q',
2086
+ '\u24E1': 'r',
2087
+ '\uFF52': 'r',
2088
+ '\u0155': 'r',
2089
+ '\u1E59': 'r',
2090
+ '\u0159': 'r',
2091
+ '\u0211': 'r',
2092
+ '\u0213': 'r',
2093
+ '\u1E5B': 'r',
2094
+ '\u1E5D': 'r',
2095
+ '\u0157': 'r',
2096
+ '\u1E5F': 'r',
2097
+ '\u024D': 'r',
2098
+ '\u027D': 'r',
2099
+ '\uA75B': 'r',
2100
+ '\uA7A7': 'r',
2101
+ '\uA783': 'r',
2102
+ '\u24E2': 's',
2103
+ '\uFF53': 's',
2104
+ '\u00DF': 's',
2105
+ '\u015B': 's',
2106
+ '\u1E65': 's',
2107
+ '\u015D': 's',
2108
+ '\u1E61': 's',
2109
+ '\u0161': 's',
2110
+ '\u1E67': 's',
2111
+ '\u1E63': 's',
2112
+ '\u1E69': 's',
2113
+ '\u0219': 's',
2114
+ '\u015F': 's',
2115
+ '\u023F': 's',
2116
+ '\uA7A9': 's',
2117
+ '\uA785': 's',
2118
+ '\u1E9B': 's',
2119
+ '\u24E3': 't',
2120
+ '\uFF54': 't',
2121
+ '\u1E6B': 't',
2122
+ '\u1E97': 't',
2123
+ '\u0165': 't',
2124
+ '\u1E6D': 't',
2125
+ '\u021B': 't',
2126
+ '\u0163': 't',
2127
+ '\u1E71': 't',
2128
+ '\u1E6F': 't',
2129
+ '\u0167': 't',
2130
+ '\u01AD': 't',
2131
+ '\u0288': 't',
2132
+ '\u2C66': 't',
2133
+ '\uA787': 't',
2134
+ '\uA729': 'tz',
2135
+ '\u24E4': 'u',
2136
+ '\uFF55': 'u',
2137
+ '\u00F9': 'u',
2138
+ '\u00FA': 'u',
2139
+ '\u00FB': 'u',
2140
+ '\u0169': 'u',
2141
+ '\u1E79': 'u',
2142
+ '\u016B': 'u',
2143
+ '\u1E7B': 'u',
2144
+ '\u016D': 'u',
2145
+ '\u00FC': 'u',
2146
+ '\u01DC': 'u',
2147
+ '\u01D8': 'u',
2148
+ '\u01D6': 'u',
2149
+ '\u01DA': 'u',
2150
+ '\u1EE7': 'u',
2151
+ '\u016F': 'u',
2152
+ '\u0171': 'u',
2153
+ '\u01D4': 'u',
2154
+ '\u0215': 'u',
2155
+ '\u0217': 'u',
2156
+ '\u01B0': 'u',
2157
+ '\u1EEB': 'u',
2158
+ '\u1EE9': 'u',
2159
+ '\u1EEF': 'u',
2160
+ '\u1EED': 'u',
2161
+ '\u1EF1': 'u',
2162
+ '\u1EE5': 'u',
2163
+ '\u1E73': 'u',
2164
+ '\u0173': 'u',
2165
+ '\u1E77': 'u',
2166
+ '\u1E75': 'u',
2167
+ '\u0289': 'u',
2168
+ '\u24E5': 'v',
2169
+ '\uFF56': 'v',
2170
+ '\u1E7D': 'v',
2171
+ '\u1E7F': 'v',
2172
+ '\u028B': 'v',
2173
+ '\uA75F': 'v',
2174
+ '\u028C': 'v',
2175
+ '\uA761': 'vy',
2176
+ '\u24E6': 'w',
2177
+ '\uFF57': 'w',
2178
+ '\u1E81': 'w',
2179
+ '\u1E83': 'w',
2180
+ '\u0175': 'w',
2181
+ '\u1E87': 'w',
2182
+ '\u1E85': 'w',
2183
+ '\u1E98': 'w',
2184
+ '\u1E89': 'w',
2185
+ '\u2C73': 'w',
2186
+ '\u24E7': 'x',
2187
+ '\uFF58': 'x',
2188
+ '\u1E8B': 'x',
2189
+ '\u1E8D': 'x',
2190
+ '\u24E8': 'y',
2191
+ '\uFF59': 'y',
2192
+ '\u1EF3': 'y',
2193
+ '\u00FD': 'y',
2194
+ '\u0177': 'y',
2195
+ '\u1EF9': 'y',
2196
+ '\u0233': 'y',
2197
+ '\u1E8F': 'y',
2198
+ '\u00FF': 'y',
2199
+ '\u1EF7': 'y',
2200
+ '\u1E99': 'y',
2201
+ '\u1EF5': 'y',
2202
+ '\u01B4': 'y',
2203
+ '\u024F': 'y',
2204
+ '\u1EFF': 'y',
2205
+ '\u24E9': 'z',
2206
+ '\uFF5A': 'z',
2207
+ '\u017A': 'z',
2208
+ '\u1E91': 'z',
2209
+ '\u017C': 'z',
2210
+ '\u017E': 'z',
2211
+ '\u1E93': 'z',
2212
+ '\u1E95': 'z',
2213
+ '\u01B6': 'z',
2214
+ '\u0225': 'z',
2215
+ '\u0240': 'z',
2216
+ '\u2C6C': 'z',
2217
+ '\uA763': 'z',
2218
+ '\u0386': '\u0391',
2219
+ '\u0388': '\u0395',
2220
+ '\u0389': '\u0397',
2221
+ '\u038A': '\u0399',
2222
+ '\u03AA': '\u0399',
2223
+ '\u038C': '\u039F',
2224
+ '\u038E': '\u03A5',
2225
+ '\u03AB': '\u03A5',
2226
+ '\u038F': '\u03A9',
2227
+ '\u03AC': '\u03B1',
2228
+ '\u03AD': '\u03B5',
2229
+ '\u03AE': '\u03B7',
2230
+ '\u03AF': '\u03B9',
2231
+ '\u03CA': '\u03B9',
2232
+ '\u0390': '\u03B9',
2233
+ '\u03CC': '\u03BF',
2234
+ '\u03CD': '\u03C5',
2235
+ '\u03CB': '\u03C5',
2236
+ '\u03B0': '\u03C5',
2237
+ '\u03C9': '\u03C9',
2238
+ '\u03C2': '\u03C3'
2239
+ };
2240
+
2241
+ var Selectivity = _dereq_(7);
2242
+ var previousTransform = Selectivity.transformText;
2243
+
2244
+ /**
2245
+ * Extended version of the transformText() function that simplifies diacritics to their latin1
2246
+ * counterparts.
2247
+ *
2248
+ * Note that if all query functions fetch their results from a remote server, you may not need this
2249
+ * function, because it makes sense to remove diacritics server-side in such cases.
2250
+ */
2251
+ Selectivity.transformText = function(string) {
2252
+ var result = '';
2253
+ for (var i = 0, length = string.length; i < length; i++) {
2254
+ var character = string[i];
2255
+ result += DIACRITICS[character] || character;
2256
+ }
2257
+ return previousTransform(result);
2258
+ };
2259
+
2260
+ },{"7":7}],9:[function(_dereq_,module,exports){
2261
+ 'use strict';
2262
+
2263
+ var $ = window.jQuery || window.Zepto;
2264
+
2265
+ var debounce = _dereq_(2);
2266
+
2267
+ var Selectivity = _dereq_(7);
2268
+
2269
+ /**
2270
+ * selectivity Dropdown Constructor.
2271
+ *
2272
+ * @param options Options object. Should have the following properties:
2273
+ * selectivity - Selectivity instance to show the dropdown for.
2274
+ * showSearchInput - Boolean whether a search input should be shown.
2275
+ */
2276
+ function SelectivityDropdown(options) {
2277
+
2278
+ var selectivity = options.selectivity;
2279
+
2280
+ this.$el = $(selectivity.template('dropdown', {
2281
+ dropdownCssClass: selectivity.options.dropdownCssClass,
2282
+ searchInputPlaceholder: selectivity.options.searchInputPlaceholder,
2283
+ showSearchInput: options.showSearchInput
2284
+ }));
2285
+
2286
+ /**
2287
+ * jQuery container to add the results to.
2288
+ */
2289
+ this.$results = this.$('.selectivity-results-container');
2290
+
2291
+ /**
2292
+ * Boolean indicating whether more results are available than currently displayed in the
2293
+ * dropdown.
2294
+ */
2295
+ this.hasMore = false;
2296
+
2297
+ /**
2298
+ * The currently highlighted result item.
2299
+ */
2300
+ this.highlightedResult = null;
2301
+
2302
+ /**
2303
+ * Boolean whether the load more link is currently highlighted.
2304
+ */
2305
+ this.loadMoreHighlighted = false;
2306
+
2307
+ /**
2308
+ * Options passed to the dropdown constructor.
2309
+ */
2310
+ this.options = options;
2311
+
2312
+ /**
2313
+ * The results displayed in the dropdown.
2314
+ */
2315
+ this.results = [];
2316
+
2317
+ /**
2318
+ * Selectivity instance.
2319
+ */
2320
+ this.selectivity = selectivity;
2321
+
2322
+ this._closeProxy = this.close.bind(this);
2323
+ if (selectivity.options.closeOnSelect !== false) {
2324
+ selectivity.$el.on('selectivity-selecting', this._closeProxy);
2325
+ }
2326
+
2327
+ this.addToDom();
2328
+ this.position();
2329
+ this.setupCloseHandler();
2330
+
2331
+ this._scrolledProxy = debounce(this._scrolled.bind(this), 50);
2332
+
2333
+ this._suppressMouseWheel();
2334
+
2335
+ if (options.showSearchInput) {
2336
+ selectivity.initSearchInput(this.$('.selectivity-search-input'));
2337
+ selectivity.focus();
2338
+ }
2339
+
2340
+ this._delegateEvents();
2341
+
2342
+ this.showLoading();
2343
+
2344
+ setTimeout(this.triggerOpen.bind(this), 1);
2345
+ }
2346
+
2347
+ /**
2348
+ * Methods.
2349
+ */
2350
+ $.extend(SelectivityDropdown.prototype, {
2351
+
2352
+ /**
2353
+ * Convenience shortcut for this.$el.find(selector).
2354
+ */
2355
+ $: function(selector) {
2356
+
2357
+ return this.$el.find(selector);
2358
+ },
2359
+
2360
+ /**
2361
+ * Adds the dropdown to the DOM.
2362
+ */
2363
+ addToDom: function() {
2364
+
2365
+ this.$el.appendTo(this.selectivity.$el[0].ownerDocument.body);
2366
+ },
2367
+
2368
+ /**
2369
+ * Closes the dropdown.
2370
+ */
2371
+ close: function() {
2372
+
2373
+ if (this.options.showSearchInput) {
2374
+ this.selectivity.removeSearchInput();
2375
+ }
2376
+
2377
+ this.$el.remove();
2378
+
2379
+ this.removeCloseHandler();
2380
+
2381
+ this.selectivity.$el.off('selectivity-selecting', this._closeProxy);
2382
+
2383
+ this.triggerClose();
2384
+ },
2385
+
2386
+ /**
2387
+ * Events map.
2388
+ *
2389
+ * Follows the same format as Backbone: http://backbonejs.org/#View-delegateEvents
2390
+ */
2391
+ events: {
2392
+ 'click .selectivity-load-more': '_loadMoreClicked',
2393
+ 'click .selectivity-result-item': '_resultClicked',
2394
+ 'mouseenter .selectivity-load-more': 'highlightLoadMore',
2395
+ 'mouseenter .selectivity-result-item': '_resultHovered'
2396
+ },
2397
+
2398
+ /**
2399
+ * Highlights a result item.
2400
+ *
2401
+ * @param item The item to highlight.
2402
+ */
2403
+ highlight: function(item) {
2404
+
2405
+ if (this.loadMoreHighlighted) {
2406
+ this.$('.selectivity-load-more').removeClass('highlight');
2407
+ }
2408
+
2409
+ this.$('.selectivity-result-item').removeClass('highlight')
2410
+ .filter('[data-item-id=' + Selectivity.quoteCssAttr(item.id) + ']')
2411
+ .addClass('highlight');
2412
+
2413
+ this.highlightedResult = item;
2414
+ this.loadMoreHighlighted = false;
2415
+
2416
+ this.selectivity.triggerEvent('selectivity-highlight', { item: item, id: item.id });
2417
+ },
2418
+
2419
+ /**
2420
+ * Highlights the load more link.
2421
+ *
2422
+ * @param item The item to highlight.
2423
+ */
2424
+ highlightLoadMore: function() {
2425
+
2426
+ this.$('.selectivity-result-item').removeClass('highlight');
2427
+
2428
+ this.$('.selectivity-load-more').addClass('highlight');
2429
+
2430
+ this.highlightedResult = null;
2431
+ this.loadMoreHighlighted = true;
2432
+ },
2433
+
2434
+ /**
2435
+ * Positions the dropdown inside the DOM.
2436
+ */
2437
+ position: function() {
2438
+
2439
+ var position = this.options.position;
2440
+ if (position) {
2441
+ position(this.$el, this.selectivity.$el);
2442
+ }
2443
+
2444
+ this._scrolled();
2445
+ },
2446
+
2447
+ /**
2448
+ * Removes the event handler to close the dropdown.
2449
+ */
2450
+ removeCloseHandler: function() {
2451
+
2452
+ $('body').off('click', this._closeProxy);
2453
+ },
2454
+
2455
+ /**
2456
+ * Renders an array of result items.
2457
+ *
2458
+ * @param items Array of result items.
2459
+ *
2460
+ * @return HTML-formatted string to display the result items.
2461
+ */
2462
+ renderItems: function(items) {
2463
+
2464
+ var selectivity = this.selectivity;
2465
+ return items.map(function(item) {
2466
+ var result = selectivity.template(item.id ? 'resultItem' : 'resultLabel', item);
2467
+ if (item.children) {
2468
+ result += selectivity.template('resultChildren', {
2469
+ childrenHtml: this.renderItems(item.children)
2470
+ });
2471
+ }
2472
+ return result;
2473
+ }, this).join('');
2474
+ },
2475
+
2476
+ /**
2477
+ * Selects the highlighted item.
2478
+ */
2479
+ selectHighlight: function() {
2480
+
2481
+ if (this.highlightedResult) {
2482
+ this.selectItem(this.highlightedResult.id);
2483
+ } else if (this.loadMoreHighlighted) {
2484
+ this._loadMoreClicked();
2485
+ }
2486
+ },
2487
+
2488
+ /**
2489
+ * Selects the item with the given ID.
2490
+ *
2491
+ * @param id ID of the item to select.
2492
+ */
2493
+ selectItem: function(id) {
2494
+
2495
+ var selectivity = this.selectivity;
2496
+ var item = Selectivity.findNestedById(selectivity.results, id);
2497
+ if (item) {
2498
+ var options = { id: id, item: item };
2499
+ if (selectivity.triggerEvent('selectivity-selecting', options)) {
2500
+ selectivity.triggerEvent('selectivity-selected', options);
2501
+ }
2502
+ }
2503
+ },
2504
+
2505
+ /**
2506
+ * Sets up an event handler that will close the dropdown when the Selectivity control loses
2507
+ * focus.
2508
+ */
2509
+ setupCloseHandler: function() {
2510
+
2511
+ $('body').on('click', this._closeProxy);
2512
+ },
2513
+
2514
+ /**
2515
+ * Shows an error message.
2516
+ *
2517
+ * @param message Error message to display.
2518
+ * @param options Options object. May contain the following property:
2519
+ * escape - Set to false to disable HTML-escaping of the message. Useful if you
2520
+ * want to set raw HTML as the message, but may open you up to XSS
2521
+ * attacks if you're not careful with escaping user input.
2522
+ */
2523
+ showError: function(message, options) {
2524
+
2525
+ options = options || {};
2526
+
2527
+ this.$results.html(this.selectivity.template('error', {
2528
+ escape: options.escape !== false,
2529
+ message: message
2530
+ }));
2531
+
2532
+ this.hasMore = false;
2533
+ this.results = [];
2534
+
2535
+ this.highlightedResult = null;
2536
+ this.loadMoreHighlighted = false;
2537
+
2538
+ this.position();
2539
+ },
2540
+
2541
+ /**
2542
+ * Shows a loading indicator in the dropdown.
2543
+ */
2544
+ showLoading: function() {
2545
+
2546
+ this.$results.html(this.selectivity.template('loading'));
2547
+
2548
+ this.hasMore = false;
2549
+ this.results = [];
2550
+
2551
+ this.highlightedResult = null;
2552
+ this.loadMoreHighlighted = false;
2553
+
2554
+ this.position();
2555
+ },
2556
+
2557
+ /**
2558
+ * Shows the results from a search query.
2559
+ *
2560
+ * @param results Array of result items.
2561
+ * @param options Options object. May contain the following properties:
2562
+ * add - True if the results should be added to any already shown results.
2563
+ * hasMore - Boolean whether more results can be fetched using the query()
2564
+ * function.
2565
+ * term - The search term for which the results are displayed.
2566
+ */
2567
+ showResults: function(results, options) {
2568
+
2569
+ var resultsHtml = this.renderItems(results);
2570
+ if (options.hasMore) {
2571
+ resultsHtml += this.selectivity.template('loadMore');
2572
+ } else {
2573
+ if (!resultsHtml && !options.add) {
2574
+ resultsHtml = this.selectivity.template('noResults', { term: options.term });
2575
+ }
2576
+ }
2577
+
2578
+ if (options.add) {
2579
+ this.$('.selectivity-loading').replaceWith(resultsHtml);
2580
+
2581
+ this.results = this.results.concat(results);
2582
+ } else {
2583
+ this.$results.html(resultsHtml);
2584
+
2585
+ this.results = results;
2586
+ }
2587
+
2588
+ this.hasMore = options.hasMore;
2589
+
2590
+ if (!options.add || this.loadMoreHighlighted) {
2591
+ this._highlightFirstItem(results);
2592
+ }
2593
+
2594
+ this.position();
2595
+ },
2596
+
2597
+ /**
2598
+ * Triggers the 'selectivity-close' event.
2599
+ */
2600
+ triggerClose: function() {
2601
+
2602
+ this.selectivity.$el.trigger('selectivity-close');
2603
+ },
2604
+
2605
+ /**
2606
+ * Triggers the 'selectivity-open' event.
2607
+ */
2608
+ triggerOpen: function() {
2609
+
2610
+ this.selectivity.$el.trigger('selectivity-open');
2611
+ },
2612
+
2613
+ /**
2614
+ * @private
2615
+ */
2616
+ _delegateEvents: function() {
2617
+
2618
+ $.each(this.events, function(event, listener) {
2619
+ var index = event.indexOf(' ');
2620
+ var selector = event.slice(index + 1);
2621
+ event = event.slice(0, index);
2622
+
2623
+ if ($.type(listener) === 'string') {
2624
+ listener = this[listener];
2625
+ }
2626
+
2627
+ listener = listener.bind(this);
2628
+
2629
+ this.$el.on(event, selector, listener);
2630
+ }.bind(this));
2631
+
2632
+ this.$results.on('scroll touchmove touchend', this._scrolledProxy);
2633
+ },
2634
+
2635
+ /**
2636
+ * @private
2637
+ */
2638
+ _highlightFirstItem: function(results) {
2639
+
2640
+ function findFirstItem(results) {
2641
+ for (var i = 0, length = results.length; i < length; i++) {
2642
+ var result = results[i];
2643
+ if (result.id) {
2644
+ return result;
2645
+ } else if (result.children) {
2646
+ var item = findFirstItem(result.children);
2647
+ if (item) {
2648
+ return item;
2649
+ }
2650
+ }
2651
+ }
2652
+ }
2653
+
2654
+ var firstItem = findFirstItem(results);
2655
+ if (firstItem) {
2656
+ this.highlight(firstItem);
2657
+ } else {
2658
+ this.highlightedResult = null;
2659
+ this.loadMoreHighlighted = false;
2660
+ }
2661
+ },
2662
+
2663
+ /**
2664
+ * @private
2665
+ */
2666
+ _loadMoreClicked: function() {
2667
+
2668
+ this.$('.selectivity-load-more').replaceWith(this.selectivity.template('loading'));
2669
+
2670
+ this.selectivity.loadMore();
2671
+
2672
+ this.selectivity.focus();
2673
+
2674
+ return false;
2675
+ },
2676
+
2677
+ /**
2678
+ * @private
2679
+ */
2680
+ _resultClicked: function(event) {
2681
+
2682
+ this.selectItem(this.selectivity._getItemId(event));
2683
+
2684
+ return false;
2685
+ },
2686
+
2687
+ /**
2688
+ * @private
2689
+ */
2690
+ _resultHovered: function(event) {
2691
+
2692
+ var id = this.selectivity._getItemId(event);
2693
+ var item = Selectivity.findNestedById(this.results, id);
2694
+ if (item) {
2695
+ this.highlight(item);
2696
+ }
2697
+ },
2698
+
2699
+ /**
2700
+ * @private
2701
+ */
2702
+ _scrolled: function() {
2703
+
2704
+ var $loadMore = this.$('.selectivity-load-more');
2705
+ if ($loadMore.length) {
2706
+ if ($loadMore[0].offsetTop - this.$results[0].scrollTop < this.$el.height()) {
2707
+ this._loadMoreClicked();
2708
+ }
2709
+ }
2710
+ },
2711
+
2712
+ /**
2713
+ * @private
2714
+ */
2715
+ _scrollToHighlight: function(options) {
2716
+
2717
+ var el;
2718
+ if (this.highlightedResult) {
2719
+ var quotedId = Selectivity.quoteCssAttr(this.highlightedResult.id);
2720
+ el = this.$('.selectivity-result-item[data-item-id=' + quotedId + ']')[0];
2721
+ } else if (this.loadMoreHighlighted) {
2722
+ el = this.$('.selectivity-load-more')[0];
2723
+ } else {
2724
+ return; // no highlight to scroll to
2725
+ }
2726
+
2727
+ var rect = el.getBoundingClientRect(),
2728
+ containerRect = this.$results[0].getBoundingClientRect();
2729
+
2730
+ if (rect.top < containerRect.top || rect.bottom > containerRect.bottom) {
2731
+ el.scrollIntoView(options.alignToTop);
2732
+ }
2733
+ },
2734
+
2735
+ /**
2736
+ * @private
2737
+ */
2738
+ _suppressMouseWheel: function() {
2739
+
2740
+ var suppressMouseWheelSelector = this.selectivity.options.suppressMouseWheelSelector;
2741
+ if (suppressMouseWheelSelector === null) {
2742
+ return;
2743
+ }
2744
+
2745
+ var selector = suppressMouseWheelSelector || '.selectivity-results-container';
2746
+ this.$el.on('DOMMouseScroll mousewheel', selector, function(event) {
2747
+
2748
+ // Thanks to Troy Alford:
2749
+ // http://stackoverflow.com/questions/5802467/prevent-scrolling-of-parent-element
2750
+
2751
+ var $el = $(this),
2752
+ scrollTop = this.scrollTop,
2753
+ scrollHeight = this.scrollHeight,
2754
+ height = $el.height(),
2755
+ originalEvent = event.originalEvent,
2756
+ delta = (event.type === 'DOMMouseScroll' ? originalEvent.detail * -40
2757
+ : originalEvent.wheelDelta),
2758
+ up = delta > 0;
2759
+
2760
+ function prevent() {
2761
+ event.stopPropagation();
2762
+ event.preventDefault();
2763
+ event.returnValue = false;
2764
+ return false;
2765
+ }
2766
+
2767
+ if (scrollHeight > height) {
2768
+ if (!up && -delta > scrollHeight - height - scrollTop) {
2769
+ // Scrolling down, but this will take us past the bottom.
2770
+ $el.scrollTop(scrollHeight);
2771
+ return prevent();
2772
+ } else if (up && delta > scrollTop) {
2773
+ // Scrolling up, but this will take us past the top.
2774
+ $el.scrollTop(0);
2775
+ return prevent();
2776
+ }
2777
+ }
2778
+ });
2779
+ }
2780
+
2781
+ });
2782
+
2783
+ module.exports = Selectivity.Dropdown = SelectivityDropdown;
2784
+
2785
+ },{"2":2,"7":7,"jquery":"jquery"}],10:[function(_dereq_,module,exports){
2786
+ 'use strict';
2787
+
2788
+ var $ = window.jQuery || window.Zepto;
2789
+
2790
+ var Selectivity = _dereq_(7);
2791
+ var MultipleSelectivity = _dereq_(13);
2792
+
2793
+ function isValidEmail(email) {
2794
+
2795
+ var atIndex = email.indexOf('@');
2796
+ var dotIndex = email.lastIndexOf('.');
2797
+ var spaceIndex = email.indexOf(' ');
2798
+ return (atIndex > 0 && dotIndex > atIndex + 1 &&
2799
+ dotIndex < email.length - 2 && spaceIndex === -1);
2800
+ }
2801
+
2802
+ function lastWord(token, length) {
2803
+
2804
+ length = (length === undefined ? token.length : length);
2805
+ for (var i = length - 1; i >= 0; i--) {
2806
+ if ((/\s/).test(token[i])) {
2807
+ return token.slice(i + 1, length);
2808
+ }
2809
+ }
2810
+ return token.slice(0, length);
2811
+ }
2812
+
2813
+ function stripEnclosure(token, enclosure) {
2814
+
2815
+ if (token.slice(0, 1) === enclosure[0] && token.slice(-1) === enclosure[1]) {
2816
+ return token.slice(1, -1).trim();
2817
+ } else {
2818
+ return token.trim();
2819
+ }
2820
+ }
2821
+
2822
+ function createEmailItem(token) {
2823
+
2824
+ var email = lastWord(token);
2825
+ var name = token.slice(0, -email.length).trim();
2826
+ if (isValidEmail(email)) {
2827
+ email = stripEnclosure(stripEnclosure(email, '()'), '<>');
2828
+ name = stripEnclosure(name, '""').trim() || email;
2829
+ return { id: email, text: name };
2830
+ } else {
2831
+ return (token.trim() ? { id: token, text: token } : null);
2832
+ }
2833
+ }
2834
+
2835
+ function emailTokenizer(input, selection, createToken) {
2836
+
2837
+ function hasToken(input) {
2838
+ if (input) {
2839
+ for (var i = 0, length = input.length; i < length; i++) {
2840
+ switch (input[i]) {
2841
+ case ';':
2842
+ case ',':
2843
+ case '\n':
2844
+ return true;
2845
+ case ' ':
2846
+ case '\t':
2847
+ if (isValidEmail(lastWord(input, i))) {
2848
+ return true;
2849
+ }
2850
+ break;
2851
+ case '"':
2852
+ do { i++; } while(i < length && input[i] !== '"');
2853
+ break;
2854
+ default:
2855
+ continue;
2856
+ }
2857
+ }
2858
+ }
2859
+ return false;
2860
+ }
2861
+
2862
+ function takeToken(input) {
2863
+ for (var i = 0, length = input.length; i < length; i++) {
2864
+ switch (input[i]) {
2865
+ case ';':
2866
+ case ',':
2867
+ case '\n':
2868
+ return { term: input.slice(0, i), input: input.slice(i + 1) };
2869
+ case ' ':
2870
+ case '\t':
2871
+ if (isValidEmail(lastWord(input, i))) {
2872
+ return { term: input.slice(0, i), input: input.slice(i + 1) };
2873
+ }
2874
+ break;
2875
+ case '"':
2876
+ do { i++; } while(i < length && input[i] !== '"');
2877
+ break;
2878
+ default:
2879
+ continue;
2880
+ }
2881
+ }
2882
+ return {};
2883
+ }
2884
+
2885
+ while (hasToken(input)) {
2886
+ var token = takeToken(input);
2887
+ if (token.term) {
2888
+ var item = createEmailItem(token.term);
2889
+ if (item && !(item.id && Selectivity.findById(selection, item.id))) {
2890
+ createToken(item);
2891
+ }
2892
+ }
2893
+ input = token.input;
2894
+ }
2895
+
2896
+ return input;
2897
+ }
2898
+
2899
+ /**
2900
+ * Emailselectivity Constructor.
2901
+ *
2902
+ * @param options Options object. Accepts all options from the MultipleSelectivity Constructor.
2903
+ */
2904
+ function Emailselectivity(options) {
2905
+
2906
+ MultipleSelectivity.call(this, options);
2907
+ }
2908
+
2909
+ /**
2910
+ * Methods.
2911
+ */
2912
+ var callSuper = Selectivity.inherits(Emailselectivity, MultipleSelectivity, {
2913
+
2914
+ /**
2915
+ * @inherit
2916
+ */
2917
+ initSearchInput: function($input) {
2918
+
2919
+ callSuper(this, 'initSearchInput', $input);
2920
+
2921
+ $input.on('blur', function() {
2922
+ var term = $input.val();
2923
+ if (isValidEmail(lastWord(term))) {
2924
+ this.add(createEmailItem(term));
2925
+ }
2926
+ }.bind(this));
2927
+ },
2928
+
2929
+ /**
2930
+ * @inherit
2931
+ *
2932
+ * Note that for the Email input type the option showDropdown is set to false and the tokenizer
2933
+ * option is set to a tokenizer specialized for email addresses.
2934
+ */
2935
+ setOptions: function(options) {
2936
+
2937
+ options = $.extend({
2938
+ createTokenItem: createEmailItem,
2939
+ showDropdown: false,
2940
+ tokenizer: emailTokenizer
2941
+ }, options);
2942
+
2943
+ callSuper(this, 'setOptions', options);
2944
+ }
2945
+
2946
+ });
2947
+
2948
+ module.exports = Selectivity.InputTypes.Email = Emailselectivity;
2949
+
2950
+ },{"13":13,"7":7,"jquery":"jquery"}],11:[function(_dereq_,module,exports){
2951
+ 'use strict';
2952
+
2953
+ var Selectivity = _dereq_(7);
2954
+
2955
+ var KEY_BACKSPACE = 8;
2956
+ var KEY_DOWN_ARROW = 40;
2957
+ var KEY_ENTER = 13;
2958
+ var KEY_ESCAPE = 27;
2959
+ var KEY_TAB = 9;
2960
+ var KEY_UP_ARROW = 38;
2961
+
2962
+ /**
2963
+ * Search input listener providing keyboard support for navigating the dropdown.
2964
+ */
2965
+ function listener(selectivity, $input) {
2966
+
2967
+ /**
2968
+ * Moves a dropdown's highlight to the next or previous result item.
2969
+ *
2970
+ * @param delta Either 1 to move to the next item, or -1 to move to the previous item.
2971
+ */
2972
+ function moveHighlight(dropdown, delta) {
2973
+
2974
+ function findElementIndex($elements, selector) {
2975
+ for (var i = 0, length = $elements.length; i < length; i++) {
2976
+ if ($elements.eq(i).is(selector)) {
2977
+ return i;
2978
+ }
2979
+ }
2980
+ return -1;
2981
+ }
2982
+
2983
+ function scrollToHighlight() {
2984
+ var el;
2985
+ if (dropdown.highlightedResult) {
2986
+ var quotedId = Selectivity.quoteCssAttr(dropdown.highlightedResult.id);
2987
+ el = dropdown.$('.selectivity-result-item[data-item-id=' + quotedId + ']')[0];
2988
+ } else if (dropdown.loadMoreHighlighted) {
2989
+ el = dropdown.$('.selectivity-load-more')[0];
2990
+ } else {
2991
+ return; // no highlight to scroll to
2992
+ }
2993
+
2994
+ var rect = el.getBoundingClientRect(),
2995
+ containerRect = dropdown.$results[0].getBoundingClientRect();
2996
+
2997
+ if (rect.top < containerRect.top || rect.bottom > containerRect.bottom) {
2998
+ el.scrollIntoView(delta < 0);
2999
+ }
3000
+ }
3001
+
3002
+ if (dropdown.submenu) {
3003
+ moveHighlight(dropdown.submenu, delta);
3004
+ return;
3005
+ }
3006
+
3007
+ var results = dropdown.results;
3008
+ if (results.length) {
3009
+ var $results = dropdown.$('.selectivity-result-item');
3010
+ var defaultIndex = (delta > 0 ? 0 : $results.length - 1);
3011
+ var index = defaultIndex;
3012
+ var highlightedResult = dropdown.highlightedResult;
3013
+ if (highlightedResult) {
3014
+ var quotedId = Selectivity.quoteCssAttr(highlightedResult.id);
3015
+ index = findElementIndex($results, '[data-item-id=' + quotedId + ']') + delta;
3016
+ if (delta > 0 ? index >= $results.length : index < 0) {
3017
+ if (dropdown.hasMore) {
3018
+ dropdown.highlightLoadMore();
3019
+ scrollToHighlight();
3020
+ return;
3021
+ } else {
3022
+ index = defaultIndex;
3023
+ }
3024
+ }
3025
+ }
3026
+
3027
+ var result = Selectivity.findNestedById(results,
3028
+ selectivity._getItemId($results[index]));
3029
+ if (result) {
3030
+ dropdown.highlight(result);
3031
+ scrollToHighlight();
3032
+ }
3033
+ }
3034
+ }
3035
+
3036
+ function keyHeld(event) {
3037
+
3038
+ var dropdown = selectivity.dropdown;
3039
+ if (dropdown) {
3040
+ if (event.keyCode === KEY_DOWN_ARROW) {
3041
+ moveHighlight(dropdown, 1);
3042
+ } else if (event.keyCode === KEY_UP_ARROW) {
3043
+ moveHighlight(dropdown, -1);
3044
+ } else if (event.keyCode === KEY_TAB) {
3045
+ setTimeout(function() {
3046
+ selectivity.close({ keepFocus: false });
3047
+ }, 1);
3048
+ }
3049
+ }
3050
+ }
3051
+
3052
+ function keyReleased(event) {
3053
+
3054
+ function open() {
3055
+ if (selectivity.options.showDropdown !== false) {
3056
+ selectivity.open();
3057
+ }
3058
+ }
3059
+
3060
+ var dropdown = selectivity.dropdown;
3061
+ if (event.keyCode === KEY_BACKSPACE) {
3062
+ if (!$input.val()) {
3063
+ if (dropdown && dropdown.submenu) {
3064
+ var submenu = dropdown.submenu;
3065
+ while (submenu.submenu) {
3066
+ submenu = submenu.submenu;
3067
+ }
3068
+ submenu.close();
3069
+ selectivity.focus();
3070
+ }
3071
+
3072
+ event.preventDefault();
3073
+ }
3074
+ } else if (event.keyCode === KEY_ENTER && !event.ctrlKey) {
3075
+ if (dropdown) {
3076
+ dropdown.selectHighlight();
3077
+ } else if (selectivity.options.showDropdown !== false) {
3078
+ open();
3079
+ }
3080
+
3081
+ event.preventDefault();
3082
+ } else if (event.keyCode === KEY_ESCAPE) {
3083
+ selectivity.close();
3084
+
3085
+ event.preventDefault();
3086
+ } else if (event.keyCode === KEY_DOWN_ARROW || event.keyCode === KEY_UP_ARROW) {
3087
+ // handled in keyHeld() because the response feels faster and it works with repeated
3088
+ // events if the user holds the key for a longer period
3089
+ // still, we issue an open() call here in case the dropdown was not yet open...
3090
+ open();
3091
+
3092
+ event.preventDefault();
3093
+ } else {
3094
+ open();
3095
+ }
3096
+ }
3097
+
3098
+ $input.on('keydown', keyHeld).on('keyup', keyReleased);
3099
+ }
3100
+
3101
+ Selectivity.SearchInputListeners.push(listener);
3102
+
3103
+ },{"7":7}],12:[function(_dereq_,module,exports){
3104
+ 'use strict';
3105
+
3106
+ var escape = _dereq_(3);
3107
+ var Selectivity = _dereq_(7);
3108
+
3109
+ /**
3110
+ * Localizable elements of the Selectivity Templates.
3111
+ *
3112
+ * Be aware that these strings are added straight to the HTML output of the templates, so any
3113
+ * non-safe strings should be escaped.
3114
+ */
3115
+ Selectivity.Locale = {
3116
+
3117
+ ajaxError: function(term) { return 'Failed to fetch results for <b>' + escape(term) + '</b>'; },
3118
+ loading: 'Loading...',
3119
+ loadMore: 'Load more...',
3120
+ needMoreCharacters: function(numCharacters) {
3121
+ return 'Enter ' + numCharacters + ' more characters to search';
3122
+ },
3123
+ noResults: 'No results found',
3124
+ noResultsForTerm: function(term) { return 'No results for <b>' + escape(term) + '</b>'; }
3125
+
3126
+ };
3127
+
3128
+ },{"3":3,"7":7}],13:[function(_dereq_,module,exports){
3129
+ 'use strict';
3130
+
3131
+ var $ = window.jQuery || window.Zepto;
3132
+
3133
+ var Selectivity = _dereq_(7);
3134
+
3135
+ var KEY_BACKSPACE = 8;
3136
+ var KEY_DELETE = 46;
3137
+ var KEY_ENTER = 13;
3138
+
3139
+ /**
3140
+ * MultipleSelectivity Constructor.
3141
+ *
3142
+ * @param options Options object. Accepts all options from the Selectivity Base Constructor in
3143
+ * addition to those accepted by MultipleSelectivity.setOptions().
3144
+ */
3145
+ function MultipleSelectivity(options) {
3146
+
3147
+ Selectivity.call(this, options);
3148
+
3149
+ this.$el.html(this.template('multipleSelectInput', { enabled: this.enabled }))
3150
+ .trigger('selectivity-init', 'multiple');
3151
+
3152
+ this._highlightedItemId = null;
3153
+
3154
+ this.initSearchInput(this.$('.selectivity-multiple-input:not(.selectivity-width-detector)'));
3155
+
3156
+ this._rerenderSelection();
3157
+
3158
+ if (!options.positionDropdown) {
3159
+ this.options.positionDropdown = function($el, $selectEl) {
3160
+ var offset = $selectEl.offset(),
3161
+ elHeight = $el.height(),
3162
+ selectHeight = $selectEl.height(),
3163
+ bottom = $selectEl[0].getBoundingClientRect().top + selectHeight + elHeight;
3164
+
3165
+ $el.css({
3166
+ left: offset.left + 'px',
3167
+ top: offset.top + (typeof window !== 'undefined' &&
3168
+ bottom > $(window).height() ? -elHeight : selectHeight) + 'px'
3169
+ }).width($selectEl.width());
3170
+ };
3171
+ }
3172
+ }
3173
+
3174
+ /**
3175
+ * Methods.
3176
+ */
3177
+ var callSuper = Selectivity.inherits(MultipleSelectivity, {
3178
+
3179
+ /**
3180
+ * Adds an item to the selection, if it's not selected yet.
3181
+ *
3182
+ * @param item The item to add. May be an item with 'id' and 'text' properties or just an ID.
3183
+ */
3184
+ add: function(item) {
3185
+
3186
+ var itemIsId = Selectivity.isValidId(item);
3187
+ var id = (itemIsId ? item : this.validateItem(item) && item.id);
3188
+
3189
+ if (this._value.indexOf(id) === -1) {
3190
+ this._value.push(id);
3191
+
3192
+ if (itemIsId && this.options.initSelection) {
3193
+ this.options.initSelection([id], function(data) {
3194
+ if (this._value.indexOf(id) > -1) {
3195
+ item = this.validateItem(data[0]);
3196
+ this._data.push(item);
3197
+
3198
+ this.triggerChange({ added: item });
3199
+ }
3200
+ }.bind(this));
3201
+ } else {
3202
+ if (itemIsId) {
3203
+ item = this.getItemForId(id);
3204
+ }
3205
+ this._data.push(item);
3206
+
3207
+ this.triggerChange({ added: item });
3208
+ }
3209
+ }
3210
+
3211
+ this.$searchInput.val('');
3212
+ },
3213
+
3214
+ /**
3215
+ * Clears the data and value.
3216
+ */
3217
+ clear: function() {
3218
+
3219
+ this.data([]);
3220
+ },
3221
+
3222
+ /**
3223
+ * Events map.
3224
+ *
3225
+ * Follows the same format as Backbone: http://backbonejs.org/#View-delegateEvents
3226
+ */
3227
+ events: {
3228
+ 'change': '_rerenderSelection',
3229
+ 'change .selectivity-multiple-input': function() { return false; },
3230
+ 'click': '_clicked',
3231
+ 'click .selectivity-multiple-selected-item': '_itemClicked',
3232
+ 'keydown .selectivity-multiple-input': '_keyHeld',
3233
+ 'keyup .selectivity-multiple-input': '_keyReleased',
3234
+ 'paste .selectivity-multiple-input': '_onPaste',
3235
+ 'selectivity-selected': '_resultSelected'
3236
+ },
3237
+
3238
+ /**
3239
+ * @inherit
3240
+ */
3241
+ filterResults: function(results) {
3242
+
3243
+ return results.filter(function(item) {
3244
+ return !Selectivity.findById(this._data, item.id);
3245
+ }, this);
3246
+ },
3247
+
3248
+ /**
3249
+ * Returns the correct data for a given value.
3250
+ *
3251
+ * @param value The value to get the data for. Should be an array of IDs.
3252
+ *
3253
+ * @return The corresponding data. Will be an array of objects with 'id' and 'text' properties.
3254
+ * Note that if no items are defined, this method assumes the text labels will be equal
3255
+ * to the IDs.
3256
+ */
3257
+ getDataForValue: function(value) {
3258
+
3259
+ return value.map(this.getItemForId.bind(this)).filter(function(item) { return !!item; });
3260
+ },
3261
+
3262
+ /**
3263
+ * Returns the correct value for the given data.
3264
+ *
3265
+ * @param data The data to get the value for. Should be an array of objects with 'id' and 'text'
3266
+ * properties.
3267
+ *
3268
+ * @return The corresponding value. Will be an array of IDs.
3269
+ */
3270
+ getValueForData: function(data) {
3271
+
3272
+ return data.map(function(item) { return item.id; });
3273
+ },
3274
+
3275
+ /**
3276
+ * Removes an item from the selection, if it is selected.
3277
+ *
3278
+ * @param item The item to remove. May be an item with 'id' and 'text' properties or just an ID.
3279
+ */
3280
+ remove: function(item) {
3281
+
3282
+ var id = ($.type(item) === 'object' ? item.id : item);
3283
+
3284
+ var removedItem;
3285
+ var index = Selectivity.findIndexById(this._data, id);
3286
+ if (index > -1) {
3287
+ removedItem = this._data[index];
3288
+ this._data.splice(index, 1);
3289
+ }
3290
+
3291
+ if (this._value[index] !== id) {
3292
+ index = this._value.indexOf(id);
3293
+ }
3294
+ if (index > -1) {
3295
+ this._value.splice(index, 1);
3296
+ }
3297
+
3298
+ if (removedItem) {
3299
+ this.triggerChange({ removed: removedItem });
3300
+ }
3301
+
3302
+ if (id === this._highlightedItemId) {
3303
+ this._highlightedItemId = null;
3304
+ }
3305
+ },
3306
+
3307
+ /**
3308
+ * @inherit
3309
+ */
3310
+ search: function() {
3311
+
3312
+ var term = this.$searchInput.val();
3313
+
3314
+ if (this.options.tokenizer) {
3315
+ term = this.options.tokenizer(term, this._data, this.add.bind(this), this.options);
3316
+
3317
+ if ($.type(term) === 'string') {
3318
+ this.$searchInput.val(term);
3319
+ } else {
3320
+ term = '';
3321
+ }
3322
+ }
3323
+
3324
+ if (this.dropdown) {
3325
+ callSuper(this, 'search');
3326
+ }
3327
+ },
3328
+
3329
+ /**
3330
+ * @inherit
3331
+ *
3332
+ * @param options Options object. In addition to the options supported in the base
3333
+ * implementation, this may contain the following properties:
3334
+ * backspaceHighlightsBeforeDelete - If set to true, when the user enters a
3335
+ * backspace while there is no text in the
3336
+ * search field but there are selected items,
3337
+ * the last selected item will be highlighted
3338
+ * and when a second backspace is entered the
3339
+ * item is deleted. If false, the item gets
3340
+ * deleted on the first backspace. The default
3341
+ * value is true on devices that have touch
3342
+ * input and false on devices that don't.
3343
+ * createTokenItem - Function to create a new item from a user's search term.
3344
+ * This is used to turn the term into an item when dropdowns
3345
+ * are disabled and the user presses Enter. It is also used by
3346
+ * the default tokenizer to create items for individual tokens.
3347
+ * The function receives a 'token' parameter which is the
3348
+ * search term (or part of a search term) to create an item for
3349
+ * and must return an item object with 'id' and 'text'
3350
+ * properties or null if no token can be created from the term.
3351
+ * The default is a function that returns an item where the id
3352
+ * and text both match the token for any non-empty string and
3353
+ * which returns null otherwise.
3354
+ * tokenizer - Function for tokenizing search terms. Will receive the following
3355
+ * parameters:
3356
+ * input - The input string to tokenize.
3357
+ * selection - The current selection data.
3358
+ * createToken - Callback to create a token from the search terms.
3359
+ * Should be passed an item object with 'id' and 'text'
3360
+ * properties.
3361
+ * options - The options set on the Selectivity instance.
3362
+ * Any string returned by the tokenizer function is treated as the
3363
+ * remainder of untokenized input.
3364
+ */
3365
+ setOptions: function(options) {
3366
+
3367
+ options = options || {};
3368
+
3369
+ var backspaceHighlightsBeforeDelete = 'backspaceHighlightsBeforeDelete';
3370
+ if (options[backspaceHighlightsBeforeDelete] === undefined) {
3371
+ options[backspaceHighlightsBeforeDelete] = this.hasTouch;
3372
+ }
3373
+
3374
+ options.allowedTypes = options.allowedTypes || {};
3375
+ options.allowedTypes[backspaceHighlightsBeforeDelete] = 'boolean';
3376
+
3377
+ callSuper(this, 'setOptions', options);
3378
+ },
3379
+
3380
+ /**
3381
+ * Validates data to set. Throws an exception if the data is invalid.
3382
+ *
3383
+ * @param data The data to validate. Should be an array of objects with 'id' and 'text'
3384
+ * properties.
3385
+ *
3386
+ * @return The validated data. This may differ from the input data.
3387
+ */
3388
+ validateData: function(data) {
3389
+
3390
+ if (data === null) {
3391
+ return [];
3392
+ } else if ($.type(data) === 'array') {
3393
+ return data.map(this.validateItem.bind(this));
3394
+ } else {
3395
+ throw new Error('Data for MultiSelectivity instance should be array');
3396
+ }
3397
+ },
3398
+
3399
+ /**
3400
+ * Validates a value to set. Throws an exception if the value is invalid.
3401
+ *
3402
+ * @param value The value to validate. Should be an array of IDs.
3403
+ *
3404
+ * @return The validated value. This may differ from the input value.
3405
+ */
3406
+ validateValue: function(value) {
3407
+
3408
+ if (value === null) {
3409
+ return [];
3410
+ } else if ($.type(value) === 'array') {
3411
+ if (value.every(Selectivity.isValidId)) {
3412
+ return value;
3413
+ } else {
3414
+ throw new Error('Value contains invalid IDs');
3415
+ }
3416
+ } else {
3417
+ throw new Error('Value for MultiSelectivity instance should be an array');
3418
+ }
3419
+ },
3420
+
3421
+ /**
3422
+ * @private
3423
+ */
3424
+ _backspacePressed: function() {
3425
+
3426
+ if (this.options.backspaceHighlightsBeforeDelete) {
3427
+ if (this._highlightedItemId) {
3428
+ this._deletePressed();
3429
+ } else if (this._value.length) {
3430
+ this._highlightItem(this._value.slice(-1)[0]);
3431
+ }
3432
+ } else if (this._value.length) {
3433
+ this.remove(this._value.slice(-1)[0]);
3434
+ }
3435
+ },
3436
+
3437
+ /**
3438
+ * @private
3439
+ */
3440
+ _clicked: function() {
3441
+
3442
+ if (this.enabled) {
3443
+ this.focus();
3444
+
3445
+ this._open();
3446
+
3447
+ return false;
3448
+ }
3449
+ },
3450
+
3451
+ /**
3452
+ * @private
3453
+ */
3454
+ _createToken: function() {
3455
+
3456
+ var term = this.$searchInput.val();
3457
+ var createTokenItem = this.options.createTokenItem;
3458
+
3459
+ if (term && createTokenItem) {
3460
+ var item = createTokenItem(term);
3461
+ if (item) {
3462
+ this.add(item);
3463
+ }
3464
+ }
3465
+ },
3466
+
3467
+ /**
3468
+ * @private
3469
+ */
3470
+ _deletePressed: function() {
3471
+
3472
+ if (this._highlightedItemId) {
3473
+ this.remove(this._highlightedItemId);
3474
+ }
3475
+ },
3476
+
3477
+ /**
3478
+ * @private
3479
+ */
3480
+ _highlightItem: function(id) {
3481
+
3482
+ this._highlightedItemId = id;
3483
+ this.$('.selectivity-multiple-selected-item').removeClass('highlighted')
3484
+ .filter('[data-item-id=' + Selectivity.quoteCssAttr(id) + ']').addClass('highlighted');
3485
+
3486
+ if (this.hasKeyboard) {
3487
+ this.focus();
3488
+ }
3489
+ },
3490
+
3491
+ /**
3492
+ * @private
3493
+ */
3494
+ _itemClicked: function(event) {
3495
+
3496
+ if (this.enabled) {
3497
+ this._highlightItem(this._getItemId(event));
3498
+ }
3499
+ },
3500
+
3501
+ /**
3502
+ * @private
3503
+ */
3504
+ _itemRemoveClicked: function(event) {
3505
+
3506
+ this.remove(this._getItemId(event));
3507
+
3508
+ this._updateInputWidth();
3509
+
3510
+ return false;
3511
+ },
3512
+
3513
+ /**
3514
+ * @private
3515
+ */
3516
+ _keyHeld: function(event) {
3517
+
3518
+ this._originalValue = this.$searchInput.val();
3519
+
3520
+ if (event.keyCode === KEY_ENTER && !event.ctrlKey) {
3521
+ event.preventDefault();
3522
+ }
3523
+ },
3524
+
3525
+ /**
3526
+ * @private
3527
+ */
3528
+ _keyReleased: function(event) {
3529
+
3530
+ var inputHadText = !!this._originalValue;
3531
+
3532
+ if (event.keyCode === KEY_ENTER && !event.ctrlKey) {
3533
+ if (this.options.createTokenItem) {
3534
+ this._createToken();
3535
+ }
3536
+ } else if (event.keyCode === KEY_BACKSPACE && !inputHadText) {
3537
+ this._backspacePressed();
3538
+ } else if (event.keyCode === KEY_DELETE && !inputHadText) {
3539
+ this._deletePressed();
3540
+ }
3541
+
3542
+ this._updateInputWidth();
3543
+ },
3544
+
3545
+ /**
3546
+ * @private
3547
+ */
3548
+ _onPaste: function() {
3549
+
3550
+ setTimeout(function() {
3551
+ this.search();
3552
+
3553
+ if (this.options.createTokenItem) {
3554
+ this._createToken();
3555
+ }
3556
+ }.bind(this), 10);
3557
+ },
3558
+
3559
+ /**
3560
+ * @private
3561
+ */
3562
+ _open: function() {
3563
+
3564
+ if (this.options.showDropdown !== false) {
3565
+ this.open();
3566
+ }
3567
+ },
3568
+
3569
+ _renderSelectedItem: function(item) {
3570
+
3571
+ this.$searchInput.before(this.template('multipleSelectedItem', $.extend({
3572
+ highlighted: (item.id === this._highlightedItemId),
3573
+ removable: !this.options.readOnly
3574
+ }, item)));
3575
+
3576
+ var quotedId = Selectivity.quoteCssAttr(item.id);
3577
+ this.$('.selectivity-multiple-selected-item[data-item-id=' + quotedId + ']')
3578
+ .find('.selectivity-multiple-selected-item-remove')
3579
+ .on('click', this._itemRemoveClicked.bind(this));
3580
+ },
3581
+
3582
+ /**
3583
+ * @private
3584
+ */
3585
+ _rerenderSelection: function(event) {
3586
+
3587
+ event = event || {};
3588
+
3589
+ if (event.added) {
3590
+ this._renderSelectedItem(event.added);
3591
+
3592
+ this._scrollToBottom();
3593
+ } else if (event.removed) {
3594
+ var quotedId = Selectivity.quoteCssAttr(event.removed.id);
3595
+ this.$('.selectivity-multiple-selected-item[data-item-id=' + quotedId + ']').remove();
3596
+ } else {
3597
+ this.$('.selectivity-multiple-selected-item').remove();
3598
+
3599
+ this._data.forEach(this._renderSelectedItem, this);
3600
+
3601
+ this._updateInputWidth();
3602
+ }
3603
+
3604
+ if (event.added || event.removed) {
3605
+ if (this.dropdown) {
3606
+ this.dropdown.showResults(this.filterResults(this.results), {
3607
+ hasMore: this.dropdown.hasMore
3608
+ });
3609
+ }
3610
+
3611
+ if (this.hasKeyboard) {
3612
+ this.focus();
3613
+ }
3614
+ }
3615
+
3616
+ this.positionDropdown();
3617
+
3618
+ this._updatePlaceholder();
3619
+ },
3620
+
3621
+ /**
3622
+ * @private
3623
+ */
3624
+ _resultSelected: function(event) {
3625
+
3626
+ if (this._value.indexOf(event.id) === -1) {
3627
+ this.add(event.item);
3628
+ } else {
3629
+ this.remove(event.item);
3630
+ }
3631
+ },
3632
+
3633
+ /**
3634
+ * @private
3635
+ */
3636
+ _scrollToBottom: function() {
3637
+
3638
+ var $inputContainer = this.$('.selectivity-multiple-input-container');
3639
+ $inputContainer.scrollTop($inputContainer.height());
3640
+ },
3641
+
3642
+ /**
3643
+ * @private
3644
+ */
3645
+ _updateInputWidth: function() {
3646
+
3647
+ if (this.enabled) {
3648
+ var $input = this.$searchInput, $widthDetector = this.$('.selectivity-width-detector');
3649
+ $widthDetector.text($input.val() ||
3650
+ !this._data.length && this.options.placeholder ||
3651
+ '');
3652
+ $input.width($widthDetector.width() + 20);
3653
+
3654
+ this.positionDropdown();
3655
+ }
3656
+ },
3657
+
3658
+ /**
3659
+ * @private
3660
+ */
3661
+ _updatePlaceholder: function() {
3662
+
3663
+ var placeholder = this._data.length ? '' : this.options.placeholder;
3664
+ if (this.enabled) {
3665
+ this.$searchInput.attr('placeholder', placeholder);
3666
+ } else {
3667
+ this.$('.selectivity-placeholder').text(placeholder);
3668
+ }
3669
+ }
3670
+
3671
+ });
3672
+
3673
+ module.exports = Selectivity.InputTypes.Multiple = MultipleSelectivity;
3674
+
3675
+ },{"7":7,"jquery":"jquery"}],14:[function(_dereq_,module,exports){
3676
+ 'use strict';
3677
+
3678
+ var $ = window.jQuery || window.Zepto;
3679
+
3680
+ var Selectivity = _dereq_(7);
3681
+
3682
+ /**
3683
+ * SingleSelectivity Constructor.
3684
+ *
3685
+ * @param options Options object. Accepts all options from the Selectivity Base Constructor in
3686
+ * addition to those accepted by SingleSelectivity.setOptions().
3687
+ */
3688
+ function SingleSelectivity(options) {
3689
+
3690
+ Selectivity.call(this, options);
3691
+
3692
+ this.$el.html(this.template('singleSelectInput', this.options))
3693
+ .trigger('selectivity-init', 'single');
3694
+
3695
+ this._rerenderSelection();
3696
+
3697
+ if (!options.positionDropdown) {
3698
+ this.options.positionDropdown = function($el, $selectEl) {
3699
+ var offset = $selectEl.offset(),
3700
+ top = offset.top + $selectEl.height();
3701
+
3702
+ if (typeof window !== 'undefined') {
3703
+ var fixedOffset = $selectEl[0].getBoundingClientRect(),
3704
+ elHeight = $el.height(),
3705
+ windowHeight = $(window).height();
3706
+
3707
+ if (fixedOffset.top + elHeight > windowHeight) {
3708
+ top = Math.max(windowHeight - elHeight + offset.top - fixedOffset.top, 0);
3709
+ }
3710
+ }
3711
+
3712
+ $el.css({ left: offset.left + 'px', top: top + 'px' }).width($selectEl.width());
3713
+ };
3714
+ }
3715
+
3716
+ if (options.showSearchInputInDropdown === false) {
3717
+ this.initSearchInput(this.$('.selectivity-single-select-input'), { noSearch: true });
3718
+ }
3719
+ }
3720
+
3721
+ /**
3722
+ * Methods.
3723
+ */
3724
+ var callSuper = Selectivity.inherits(SingleSelectivity, {
3725
+
3726
+ /**
3727
+ * Events map.
3728
+ *
3729
+ * Follows the same format as Backbone: http://backbonejs.org/#View-delegateEvents
3730
+ */
3731
+ events: {
3732
+ 'change': '_rerenderSelection',
3733
+ 'click': '_clicked',
3734
+ 'focus .selectivity-single-select-input': '_focused',
3735
+ 'selectivity-selected': '_resultSelected'
3736
+ },
3737
+
3738
+ /**
3739
+ * Clears the data and value.
3740
+ */
3741
+ clear: function() {
3742
+
3743
+ this.data(null);
3744
+ },
3745
+
3746
+ /**
3747
+ * @inherit
3748
+ *
3749
+ * @param options Optional options object. May contain the following property:
3750
+ * keepFocus - If false, the focus won't remain on the input.
3751
+ */
3752
+ close: function(options) {
3753
+
3754
+ this._closing = true;
3755
+
3756
+ callSuper(this, 'close');
3757
+
3758
+ if (!options || options.keepFocus !== false) {
3759
+ this.$('.selectivity-single-select-input').focus();
3760
+ }
3761
+
3762
+ this._closing = false;
3763
+ },
3764
+
3765
+ /**
3766
+ * Returns the correct data for a given value.
3767
+ *
3768
+ * @param value The value to get the data for. Should be an ID.
3769
+ *
3770
+ * @return The corresponding data. Will be an object with 'id' and 'text' properties. Note that
3771
+ * if no items are defined, this method assumes the text label will be equal to the ID.
3772
+ */
3773
+ getDataForValue: function(value) {
3774
+
3775
+ return this.getItemForId(value);
3776
+ },
3777
+
3778
+ /**
3779
+ * Returns the correct value for the given data.
3780
+ *
3781
+ * @param data The data to get the value for. Should be an object with 'id' and 'text'
3782
+ * properties or null.
3783
+ *
3784
+ * @return The corresponding value. Will be an ID or null.
3785
+ */
3786
+ getValueForData: function(data) {
3787
+
3788
+ return (data ? data.id : null);
3789
+ },
3790
+
3791
+ /**
3792
+ * @inherit
3793
+ */
3794
+ open: function(options) {
3795
+
3796
+ var showSearchInput = (this.options.showSearchInputInDropdown !== false);
3797
+
3798
+ callSuper(this, 'open', $.extend({ showSearchInput: showSearchInput }, options));
3799
+
3800
+ if (!showSearchInput) {
3801
+ this.focus();
3802
+ }
3803
+ },
3804
+
3805
+ /**
3806
+ * @inherit
3807
+ *
3808
+ * @param options Options object. In addition to the options supported in the base
3809
+ * implementation, this may contain the following properties:
3810
+ * allowClear - Boolean whether the selected item may be removed.
3811
+ * showSearchInputInDropdown - Set to false to remove the search input used in
3812
+ * dropdowns. The default is true.
3813
+ */
3814
+ setOptions: function(options) {
3815
+
3816
+ options = options || {};
3817
+
3818
+ options.allowedTypes = $.extend(options.allowedTypes || {}, {
3819
+ allowClear: 'boolean',
3820
+ showSearchInputInDropdown: 'boolean'
3821
+ });
3822
+
3823
+ callSuper(this, 'setOptions', options);
3824
+ },
3825
+
3826
+ /**
3827
+ * Validates data to set. Throws an exception if the data is invalid.
3828
+ *
3829
+ * @param data The data to validate. Should be an object with 'id' and 'text' properties or null
3830
+ * to indicate no item is selected.
3831
+ *
3832
+ * @return The validated data. This may differ from the input data.
3833
+ */
3834
+ validateData: function(data) {
3835
+
3836
+ return (data === null ? data : this.validateItem(data));
3837
+ },
3838
+
3839
+ /**
3840
+ * Validates a value to set. Throws an exception if the value is invalid.
3841
+ *
3842
+ * @param value The value to validate. Should be null or a valid ID.
3843
+ *
3844
+ * @return The validated value. This may differ from the input value.
3845
+ */
3846
+ validateValue: function(value) {
3847
+
3848
+ if (value === null || Selectivity.isValidId(value)) {
3849
+ return value;
3850
+ } else {
3851
+ throw new Error('Value for SingleSelectivity instance should be a valid ID or null');
3852
+ }
3853
+ },
3854
+
3855
+ /**
3856
+ * @private
3857
+ */
3858
+ _clicked: function() {
3859
+
3860
+ if (this.enabled) {
3861
+ if (this.dropdown) {
3862
+ this.close();
3863
+ } else if (this.options.showDropdown !== false) {
3864
+ this.open();
3865
+ }
3866
+
3867
+ return false;
3868
+ }
3869
+ },
3870
+
3871
+ /**
3872
+ * @private
3873
+ */
3874
+ _focused: function() {
3875
+
3876
+ if (this.enabled && !this._closing && this.options.showDropdown !== false) {
3877
+ this.open();
3878
+ }
3879
+ },
3880
+
3881
+ /**
3882
+ * @private
3883
+ */
3884
+ _itemRemoveClicked: function() {
3885
+
3886
+ this.data(null);
3887
+
3888
+ return false;
3889
+ },
3890
+
3891
+ /**
3892
+ * @private
3893
+ */
3894
+ _rerenderSelection: function() {
3895
+
3896
+ var $container = this.$('.selectivity-single-result-container');
3897
+ if (this._data) {
3898
+ $container.html(
3899
+ this.template('singleSelectedItem', $.extend({
3900
+ removable: this.options.allowClear && !this.options.readOnly
3901
+ }, this._data))
3902
+ );
3903
+
3904
+ $container.find('.selectivity-single-selected-item-remove')
3905
+ .on('click', this._itemRemoveClicked.bind(this));
3906
+ } else {
3907
+ $container.html(
3908
+ this.template('singleSelectPlaceholder', { placeholder: this.options.placeholder })
3909
+ );
3910
+ }
3911
+ },
3912
+
3913
+ /**
3914
+ * @private
3915
+ */
3916
+ _resultSelected: function(event) {
3917
+
3918
+ this.data(event.item);
3919
+
3920
+ this.close();
3921
+ }
3922
+
3923
+ });
3924
+
3925
+ module.exports = Selectivity.InputTypes.Single = SingleSelectivity;
3926
+
3927
+ },{"7":7,"jquery":"jquery"}],15:[function(_dereq_,module,exports){
3928
+ 'use strict';
3929
+
3930
+ var Selectivity = _dereq_(7);
3931
+ var SelectivityDropdown = _dereq_(9);
3932
+
3933
+ /**
3934
+ * Extended dropdown that supports submenus.
3935
+ */
3936
+ function SelectivitySubmenu(options) {
3937
+
3938
+ /**
3939
+ * Optional parent dropdown menu from which this dropdown was opened.
3940
+ */
3941
+ this.parentMenu = options.parentMenu;
3942
+
3943
+ SelectivityDropdown.call(this, options);
3944
+
3945
+ this._closeSubmenuTimeout = 0;
3946
+ }
3947
+
3948
+ var callSuper = Selectivity.inherits(SelectivitySubmenu, SelectivityDropdown, {
3949
+
3950
+ /**
3951
+ * @inherit
3952
+ */
3953
+ close: function() {
3954
+
3955
+ if (this.options.restoreOptions) {
3956
+ this.selectivity.setOptions(this.options.restoreOptions);
3957
+ }
3958
+ if (this.options.restoreResults) {
3959
+ this.selectivity.results = this.options.restoreResults;
3960
+ }
3961
+
3962
+ if (this.submenu) {
3963
+ this.submenu.close();
3964
+ }
3965
+
3966
+ callSuper(this, 'close');
3967
+
3968
+ if (this.parentMenu) {
3969
+ this.parentMenu.submenu = null;
3970
+ this.parentMenu = null;
3971
+ }
3972
+ },
3973
+
3974
+ /**
3975
+ * @inherit
3976
+ */
3977
+ highlight: function(item) {
3978
+
3979
+ if (this.submenu) {
3980
+ if (!this.highlightedResult || this.highlightedResult.id !== item.id) {
3981
+ if (this._closeSubmenuTimeout) {
3982
+ clearTimeout(this._closeSubmenuTimeout);
3983
+ }
3984
+ this._closeSubmenuTimeout = setTimeout(
3985
+ this._closeSubmenuAndHighlight.bind(this, item), 100
3986
+ );
3987
+ return;
3988
+ }
3989
+ } else {
3990
+ if (this.parentMenu && this.parentMenu._closeSubmenuTimeout) {
3991
+ clearTimeout(this.parentMenu._closeSubmenuTimeout);
3992
+ this.parentMenu._closeSubmenuTimeout = 0;
3993
+ }
3994
+ }
3995
+
3996
+ this._doHighlight(item);
3997
+ },
3998
+
3999
+ /**
4000
+ * @inherit
4001
+ */
4002
+ selectHighlight: function() {
4003
+
4004
+ if (this.submenu) {
4005
+ this.submenu.selectHighlight();
4006
+ } else {
4007
+ callSuper(this, 'selectHighlight');
4008
+ }
4009
+ },
4010
+
4011
+ /**
4012
+ * @inherit
4013
+ */
4014
+ selectItem: function(id) {
4015
+
4016
+ var selectivity = this.selectivity;
4017
+ var item = Selectivity.findNestedById(selectivity.results, id);
4018
+ if (item && !item.submenu) {
4019
+ var options = { id: id, item: item };
4020
+ if (selectivity.triggerEvent('selectivity-selecting', options)) {
4021
+ selectivity.triggerEvent('selectivity-selected', options);
4022
+ }
4023
+ }
4024
+ },
4025
+
4026
+ /**
4027
+ * @inherit
4028
+ */
4029
+ showResults: function(results, options) {
4030
+
4031
+ if (this.submenu) {
4032
+ this.submenu.showResults(results, options);
4033
+ } else {
4034
+ callSuper(this, 'showResults', results, options);
4035
+ }
4036
+ },
4037
+
4038
+ /**
4039
+ * @inherit
4040
+ */
4041
+ triggerClose: function() {
4042
+
4043
+ if (this.parentMenu) {
4044
+ this.selectivity.$el.trigger('selectivity-close-submenu');
4045
+ } else {
4046
+ callSuper(this, 'triggerClose');
4047
+ }
4048
+ },
4049
+
4050
+ /**
4051
+ * @inherit
4052
+ */
4053
+ triggerOpen: function() {
4054
+
4055
+ if (this.parentMenu) {
4056
+ this.selectivity.$el.trigger('selectivity-open-submenu');
4057
+ } else {
4058
+ callSuper(this, 'triggerOpen');
4059
+ }
4060
+ },
4061
+
4062
+ /**
4063
+ * @private
4064
+ */
4065
+ _closeSubmenuAndHighlight: function(item) {
4066
+
4067
+ if (this.submenu) {
4068
+ this.submenu.close();
4069
+ }
4070
+
4071
+ this._doHighlight(item);
4072
+ },
4073
+
4074
+ /**
4075
+ * @private
4076
+ */
4077
+ _doHighlight: function(item) {
4078
+
4079
+ callSuper(this, 'highlight', item);
4080
+
4081
+ if (item.submenu && !this.submenu) {
4082
+ var selectivity = this.selectivity;
4083
+ var Dropdown = selectivity.options.dropdown || Selectivity.Dropdown;
4084
+ if (Dropdown) {
4085
+ var quotedId = Selectivity.quoteCssAttr(item.id);
4086
+ var $item = this.$('.selectivity-result-item[data-item-id=' + quotedId + ']');
4087
+ var $dropdownEl = this.$el;
4088
+
4089
+ this.submenu = new Dropdown({
4090
+ parentMenu: this,
4091
+ position: item.submenu.positionDropdown || function($el) {
4092
+ var offset = $item.offset();
4093
+ var width = $dropdownEl.width();
4094
+ $el.css({
4095
+ left: offset.left + width + 'px',
4096
+ top: offset.top + 'px'
4097
+ }).width(width);
4098
+ },
4099
+ restoreOptions: {
4100
+ items: selectivity.items,
4101
+ query: selectivity.options.query || null
4102
+ },
4103
+ restoreResults: selectivity.results,
4104
+ selectivity: selectivity,
4105
+ showSearchInput: item.submenu.showSearchInput
4106
+ });
4107
+
4108
+ selectivity.setOptions({
4109
+ items: item.submenu.items || null,
4110
+ query: item.submenu.query || null
4111
+ });
4112
+
4113
+ selectivity.search('');
4114
+ }
4115
+ }
4116
+ }
4117
+
4118
+ });
4119
+
4120
+ Selectivity.Dropdown = SelectivitySubmenu;
4121
+
4122
+ Selectivity.findNestedById = function(array, id) {
4123
+
4124
+ for (var i = 0, length = array.length; i < length; i++) {
4125
+ var item = array[i], result;
4126
+ if (item.id === id) {
4127
+ result = item;
4128
+ } else if (item.children) {
4129
+ result = Selectivity.findNestedById(item.children, id);
4130
+ } else if (item.submenu && item.submenu.items) {
4131
+ result = Selectivity.findNestedById(item.submenu.items, id);
4132
+ }
4133
+ if (result) {
4134
+ return result;
4135
+ }
4136
+ }
4137
+ return null;
4138
+ };
4139
+
4140
+ module.exports = SelectivitySubmenu;
4141
+
4142
+ },{"7":7,"9":9}],16:[function(_dereq_,module,exports){
4143
+ 'use strict';
4144
+
4145
+ var escape = _dereq_(3);
4146
+
4147
+ var Selectivity = _dereq_(7);
4148
+
4149
+ _dereq_(12);
4150
+
4151
+ /**
4152
+ * Default set of templates to use with Selectivity.js.
4153
+ *
4154
+ * Note that every template can be defined as either a string, a function returning a string (like
4155
+ * Handlebars templates, for instance) or as an object containing a render function (like Hogan.js
4156
+ * templates, for instance).
4157
+ */
4158
+ Selectivity.Templates = {
4159
+
4160
+ /**
4161
+ * Renders the dropdown.
4162
+ *
4163
+ * The template is expected to have at least one element with the class
4164
+ * 'selectivity-results-container', which is where all results will be added to.
4165
+ *
4166
+ * @param options Options object containing the following properties:
4167
+ * dropdownCssClass - Optional CSS class to add to the top-level element.
4168
+ * searchInputPlaceholder - Optional placeholder text to display in the search
4169
+ * input in the dropdown.
4170
+ * showSearchInput - Boolean whether a search input should be shown. If true,
4171
+ * an input element with the 'selectivity-search-input' is
4172
+ * expected.
4173
+ */
4174
+ dropdown: function(options) {
4175
+ var extraClass = (options.dropdownCssClass ? ' ' + options.dropdownCssClass : ''),
4176
+ searchInput = '';
4177
+ if (options.showSearchInput) {
4178
+ extraClass += ' has-search-input';
4179
+
4180
+ var placeholder = options.searchInputPlaceholder;
4181
+ searchInput = (
4182
+ '<div class="selectivity-search-input-container">' +
4183
+ '<input type="text" class="selectivity-search-input"' +
4184
+ (placeholder ? ' placeholder="' + escape(placeholder) + '"'
4185
+ : '') + '>' +
4186
+ '</div>'
4187
+ );
4188
+ }
4189
+ return (
4190
+ '<div class="selectivity-dropdown' + extraClass + '">' +
4191
+ searchInput +
4192
+ '<div class="selectivity-results-container"></div>' +
4193
+ '</div>'
4194
+ );
4195
+ },
4196
+
4197
+ /**
4198
+ * Renders an error message in the dropdown.
4199
+ *
4200
+ * @param options Options object containing the following properties:
4201
+ * escape - Boolean whether the message should be HTML-escaped.
4202
+ * message - The message to display.
4203
+ */
4204
+ error: function(options) {
4205
+ return (
4206
+ '<div class="selectivity-error">' +
4207
+ (options.escape ? escape(options.message) : options.message) +
4208
+ '</div>'
4209
+ );
4210
+ },
4211
+
4212
+ /**
4213
+ * Renders a loading indicator in the dropdown.
4214
+ *
4215
+ * This template is expected to have an element with a 'selectivity-loading' class which may be
4216
+ * replaced with actual results.
4217
+ */
4218
+ loading: function() {
4219
+ return '<div class="selectivity-loading">' + Selectivity.Locale.loading + '</div>';
4220
+ },
4221
+
4222
+ /**
4223
+ * Load more indicator.
4224
+ *
4225
+ * This template is expected to have an element with a 'selectivity-load-more' class which, when
4226
+ * clicked, will load more results.
4227
+ */
4228
+ loadMore: function() {
4229
+ return '<div class="selectivity-load-more">' + Selectivity.Locale.loadMore + '</div>';
4230
+ },
4231
+
4232
+ /**
4233
+ * Renders multi-selection input boxes.
4234
+ *
4235
+ * The template is expected to have at least have elements with the following classes:
4236
+ * 'selectivity-multiple-input-container' - The element containing all the selected items and
4237
+ * the input for selecting additional items.
4238
+ * 'selectivity-multiple-input' - The actual input element that allows the user to type to
4239
+ * search for more items. When selected items are added, they are
4240
+ * inserted right before this element.
4241
+ * 'selectivity-width-detector' - This element is optional, but important to make sure the
4242
+ * '.selectivity-multiple-input' element will fit in the
4243
+ * container. The width detector also has the
4244
+ * 'select2-multiple-input' class on purpose to be able to detect
4245
+ * the width of text entered in the input element.
4246
+ *
4247
+ * @param options Options object containing the following property:
4248
+ * enabled - Boolean whether the input is enabled.
4249
+ */
4250
+ multipleSelectInput: function(options) {
4251
+ return (
4252
+ '<div class="selectivity-multiple-input-container">' +
4253
+ (options.enabled ? '<input type="text" autocomplete="off" autocorrect="off" ' +
4254
+ 'autocapitalize="off" ' +
4255
+ 'class="selectivity-multiple-input">' +
4256
+ '<span class="selectivity-multiple-input ' +
4257
+ 'selectivity-width-detector"></span>'
4258
+ : '<div class="selectivity-multiple-input ' +
4259
+ 'selectivity-placeholder"></div>') +
4260
+ '<div class="selectivity-clearfix"></div>' +
4261
+ '</div>'
4262
+ );
4263
+ },
4264
+
4265
+ /**
4266
+ * Renders a selected item in multi-selection input boxes.
4267
+ *
4268
+ * The template is expected to have a top-level element with the class
4269
+ * 'selectivity-multiple-selected-item'. This element is also required to have a 'data-item-id'
4270
+ * attribute with the ID set to that passed through the options object.
4271
+ *
4272
+ * An element with the class 'selectivity-multiple-selected-item-remove' should be present
4273
+ * which, when clicked, will cause the element to be removed.
4274
+ *
4275
+ * @param options Options object containing the following properties:
4276
+ * highlighted - Boolean whether this item is currently highlighted.
4277
+ * id - Identifier for the item.
4278
+ * removable - Boolean whether a remove icon should be displayed.
4279
+ * text - Text label which the user sees.
4280
+ */
4281
+ multipleSelectedItem: function(options) {
4282
+ var extraClass = (options.highlighted ? ' highlighted' : '');
4283
+ return (
4284
+ '<span class="selectivity-multiple-selected-item' + extraClass + '" ' +
4285
+ 'data-item-id="' + escape(options.id) + '">' +
4286
+ escape(options.text) +
4287
+ (options.removable ? '<a class="selectivity-multiple-selected-item-remove">' +
4288
+ '<i class="fa fa-remove"></i>' +
4289
+ '</a>'
4290
+ : '') +
4291
+ '</span>'
4292
+ );
4293
+ },
4294
+
4295
+ /**
4296
+ * Renders a message there are no results for the given query.
4297
+ *
4298
+ * @param options Options object containing the following property:
4299
+ * term - Search term the user is searching for.
4300
+ */
4301
+ noResults: function(options) {
4302
+ var Locale = Selectivity.Locale;
4303
+ return (
4304
+ '<div class="selectivity-error">' +
4305
+ (options.term ? Locale.noResultsForTerm(options.term) : Locale.noResults) +
4306
+ '</div>'
4307
+ );
4308
+ },
4309
+
4310
+ /**
4311
+ * Renders a container for item children.
4312
+ *
4313
+ * The template is expected to have an element with the class 'selectivity-result-children'.
4314
+ *
4315
+ * @param options Options object containing the following property:
4316
+ * childrenHtml - Rendered HTML for the children.
4317
+ */
4318
+ resultChildren: function(options) {
4319
+ return '<div class="selectivity-result-children">' + options.childrenHtml + '</div>';
4320
+ },
4321
+
4322
+ /**
4323
+ * Render a result item in the dropdown.
4324
+ *
4325
+ * The template is expected to have a top-level element with the class
4326
+ * 'selectivity-result-item'. This element is also required to have a 'data-item-id' attribute
4327
+ * with the ID set to that passed through the options object.
4328
+ *
4329
+ * @param options Options object containing the following properties:
4330
+ * id - Identifier for the item.
4331
+ * text - Text label which the user sees.
4332
+ * submenu - Truthy if the result item has a menu with subresults.
4333
+ */
4334
+ resultItem: function(options) {
4335
+ return (
4336
+ '<div class="selectivity-result-item" data-item-id="' + escape(options.id) + '">' +
4337
+ escape(options.text) +
4338
+ (options.submenu ? '<i class="selectivity-submenu-icon fa fa-chevron-right"></i>'
4339
+ : '') +
4340
+ '</div>'
4341
+ );
4342
+ },
4343
+
4344
+ /**
4345
+ * Render a result label in the dropdown.
4346
+ *
4347
+ * The template is expected to have a top-level element with the class
4348
+ * 'selectivity-result-label'.
4349
+ *
4350
+ * @param options Options object containing the following properties:
4351
+ * text - Text label.
4352
+ */
4353
+ resultLabel: function(options) {
4354
+ return '<div class="selectivity-result-label">' + escape(options.text) + '</div>';
4355
+ },
4356
+
4357
+ /**
4358
+ * Renders single-select input boxes.
4359
+ *
4360
+ * The template is expected to have at least one element with the class
4361
+ * 'selectivity-single-result-container' which is the element containing the selected item or
4362
+ * the placeholder.
4363
+ */
4364
+ singleSelectInput: (
4365
+ '<div class="selectivity-single-select">' +
4366
+ '<input type="text" class="selectivity-single-select-input">' +
4367
+ '<div class="selectivity-single-result-container"></div>' +
4368
+ '<i class="fa fa-sort-desc selectivity-caret"></i>' +
4369
+ '</div>'
4370
+ ),
4371
+
4372
+ /**
4373
+ * Renders the placeholder for single-select input boxes.
4374
+ *
4375
+ * The template is expected to have a top-level element with the class
4376
+ * 'selectivity-placeholder'.
4377
+ *
4378
+ * @param options Options object containing the following property:
4379
+ * placeholder - The placeholder text.
4380
+ */
4381
+ singleSelectPlaceholder: function(options) {
4382
+ return (
4383
+ '<div class="selectivity-placeholder">' +
4384
+ escape(options.placeholder) +
4385
+ '</div>'
4386
+ );
4387
+ },
4388
+
4389
+ /**
4390
+ * Renders the selected item in single-select input boxes.
4391
+ *
4392
+ * The template is expected to have a top-level element with the class
4393
+ * 'selectivity-single-selected-item'. This element is also required to have a 'data-item-id'
4394
+ * attribute with the ID set to that passed through the options object.
4395
+ *
4396
+ * @param options Options object containing the following properties:
4397
+ * id - Identifier for the item.
4398
+ * removable - Boolean whether a remove icon should be displayed.
4399
+ * text - Text label which the user sees.
4400
+ */
4401
+ singleSelectedItem: function(options) {
4402
+ return (
4403
+ '<span class="selectivity-single-selected-item" ' +
4404
+ 'data-item-id="' + escape(options.id) + '">' +
4405
+ (options.removable ? '<a class="selectivity-single-selected-item-remove">' +
4406
+ '<i class="fa fa-remove"></i>' +
4407
+ '</a>'
4408
+ : '') +
4409
+ escape(options.text) +
4410
+ '</span>'
4411
+ );
4412
+ },
4413
+
4414
+ /**
4415
+ * Renders select-box inside single-select input that was initialized on
4416
+ * traditional <select> element.
4417
+ *
4418
+ * @param options Options object containing the following properties:
4419
+ * name - Name of the <select> element.
4420
+ * mode - Mode in which select exists, single or multiple.
4421
+ */
4422
+ selectCompliance: function(options) {
4423
+ return ('<select name="' + options.name + '"' + (options.mode === 'multiple' ? ' multiple' : '') + '></select>');
4424
+ },
4425
+
4426
+ /**
4427
+ * Renders the selected item in compliance <select> element as <option>.
4428
+ *
4429
+ * @param options Options object containing the following properties
4430
+ * id - Identifier for the item.
4431
+ * text - Text label which the user sees.
4432
+ */
4433
+ selectOptionCompliance: function(options) {
4434
+ return (
4435
+ '<option value="' + escape(options.id) + '" selected>' +
4436
+ escape(options.text) +
4437
+ '</option>'
4438
+ );
4439
+ }
4440
+
4441
+ };
4442
+
4443
+ },{"12":12,"3":3,"7":7}],17:[function(_dereq_,module,exports){
4444
+ 'use strict';
4445
+
4446
+ var $ = window.jQuery || window.Zepto;
4447
+
4448
+ var Selectivity = _dereq_(7);
4449
+
4450
+ function defaultTokenizer(input, selection, createToken, options) {
4451
+
4452
+ var createTokenItem = options.createTokenItem || function(token) {
4453
+ return token ? { id: token, text: token } : null;
4454
+ };
4455
+
4456
+ var separators = options.tokenSeparators;
4457
+
4458
+ function hasToken(input) {
4459
+ return input ? separators.some(function(separator) {
4460
+ return input.indexOf(separator) > -1;
4461
+ }) : false;
4462
+ }
4463
+
4464
+ function takeToken(input) {
4465
+ for (var i = 0, length = input.length; i < length; i++) {
4466
+ if (separators.indexOf(input[i]) > -1) {
4467
+ return { term: input.slice(0, i), input: input.slice(i + 1) };
4468
+ }
4469
+ }
4470
+ return {};
4471
+ }
4472
+
4473
+ while (hasToken(input)) {
4474
+ var token = takeToken(input);
4475
+ if (token.term) {
4476
+ var item = createTokenItem(token.term);
4477
+ if (item && !Selectivity.findById(selection, item.id)) {
4478
+ createToken(item);
4479
+ }
4480
+ }
4481
+ input = token.input;
4482
+ }
4483
+
4484
+ return input;
4485
+ }
4486
+
4487
+ /**
4488
+ * Option listener that provides a default tokenizer which is used when the tokenSeparators option
4489
+ * is specified.
4490
+ *
4491
+ * @param options Options object. In addition to the options supported in the multi-input
4492
+ * implementation, this may contain the following property:
4493
+ * tokenSeparators - Array of string separators which are used to separate the search
4494
+ * string into tokens. If specified and the tokenizer property is
4495
+ * not set, the tokenizer property will be set to a function which
4496
+ * splits the search term into tokens separated by any of the given
4497
+ * separators. The tokens will be converted into selectable items
4498
+ * using the 'createTokenItem' function. The default tokenizer also
4499
+ * filters out already selected items.
4500
+ */
4501
+ Selectivity.OptionListeners.push(function(selectivity, options) {
4502
+
4503
+ if (options.tokenSeparators) {
4504
+ options.allowedTypes = $.extend({ tokenSeparators: 'array' }, options.allowedTypes);
4505
+
4506
+ options.tokenizer = options.tokenizer || defaultTokenizer;
4507
+ }
4508
+ });
4509
+
4510
+ },{"7":7,"jquery":"jquery"}],18:[function(_dereq_,module,exports){
4511
+ 'use strict';
4512
+
4513
+ var $ = window.jQuery || window.Zepto;
4514
+
4515
+ var Selectivity = _dereq_(7);
4516
+
4517
+ function replaceSelectElement($el, options) {
4518
+
4519
+ var value = (options.multiple ? [] : null);
4520
+
4521
+ var mapOptions = function() {
4522
+ var $this = $(this);
4523
+ if ($this.is('option')) {
4524
+ var id = $this.attr('value') || $this.text();
4525
+ if ($this.prop('selected')) {
4526
+ if (options.multiple) {
4527
+ value.push(id);
4528
+ } else {
4529
+ value = id;
4530
+ }
4531
+ }
4532
+
4533
+ return {
4534
+ id: id,
4535
+ text: $this.attr('label') || $this.text()
4536
+ };
4537
+ } else {
4538
+ return {
4539
+ text: $this.attr('label'),
4540
+ children: $this.children('option,optgroup').map(mapOptions).get()
4541
+ };
4542
+ }
4543
+ };
4544
+
4545
+ options.allowClear = ('allowClear' in options ? options.allowClear : !$el.prop('required'));
4546
+
4547
+ options.items = $el.children('option,optgroup').map(mapOptions).get();
4548
+
4549
+ options.placeholder = options.placeholder || $el.data('placeholder') || '';
4550
+
4551
+ options.value = value;
4552
+
4553
+ var classes = ($el.attr('class') || 'selectivity-input').split(' ');
4554
+ if (classes.indexOf('selectivity-input') === -1) {
4555
+ classes.push('selectivity-input');
4556
+ }
4557
+
4558
+ var $div = $('<div>').attr({
4559
+ 'id': $el.attr('id'),
4560
+ 'class': classes.join(' '),
4561
+ 'style': $el.attr('style'),
4562
+ 'data-name': $el.attr('name')
4563
+ });
4564
+ $el.replaceWith($div);
4565
+ return $div;
4566
+ }
4567
+
4568
+ function bindTraditionalSelectEvents(selectivity) {
4569
+
4570
+ var $el = selectivity.$el;
4571
+
4572
+ $el.on('selectivity-init', function(event, mode) {
4573
+
4574
+ $el.append(selectivity.template('selectCompliance', {name: $el.attr('data-name'), mode: mode}))
4575
+ .removeAttr('data-name');
4576
+ })
4577
+ .on('selectivity-init change', function() {
4578
+ var data = selectivity._data;
4579
+ var $select = $el.find('select');
4580
+
4581
+ if (data instanceof Array) {
4582
+ $select.empty();
4583
+
4584
+ data.forEach(function(item) {
4585
+ $select.append(selectivity.template('selectOptionCompliance', item));
4586
+ });
4587
+ } else {
4588
+ if (data) {
4589
+ $select.html(selectivity.template('selectOptionCompliance', data));
4590
+ } else {
4591
+ $select.empty();
4592
+ }
4593
+ }
4594
+ });
4595
+ }
4596
+
4597
+ /**
4598
+ * Option listener providing support for converting traditional <select> boxes into Selectivity
4599
+ * instances.
4600
+ */
4601
+ Selectivity.OptionListeners.push(function(selectivity, options) {
4602
+
4603
+ var $el = selectivity.$el;
4604
+ if ($el.is('select')) {
4605
+ if ($el.attr('autofocus')) {
4606
+ setTimeout(function() {
4607
+ selectivity.focus();
4608
+ }, 1);
4609
+ }
4610
+
4611
+ selectivity.$el = replaceSelectElement($el, options);
4612
+ selectivity.$el[0].selectivity = selectivity;
4613
+
4614
+ bindTraditionalSelectEvents(selectivity);
4615
+ }
4616
+ });
4617
+
4618
+ },{"7":7,"jquery":"jquery"}]},{},[1])(1)
4619
+ });