selectivity-rails 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ });