taskwarrior-web 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1015 @@
1
+ /*
2
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
3
+ * Version 1.6.0
4
+ *
5
+ * Copyright (c) 2009 James Smith (http://loopj.com)
6
+ * Licensed jointly under the GPL and MIT licenses,
7
+ * choose which one suits your project best!
8
+ *
9
+ */
10
+
11
+ (function ($) {
12
+ // Default settings
13
+ var DEFAULT_SETTINGS = {
14
+ // Search settings
15
+ method: "GET",
16
+ queryParam: "q",
17
+ searchDelay: 300,
18
+ minChars: 1,
19
+ propertyToSearch: "name",
20
+ jsonContainer: null,
21
+ contentType: "json",
22
+
23
+ // Prepopulation settings
24
+ prePopulate: null,
25
+ processPrePopulate: false,
26
+
27
+ // Display settings
28
+ hintText: "Type in a search term",
29
+ noResultsText: "No results",
30
+ searchingText: "Searching...",
31
+ deleteText: "×",
32
+ animateDropdown: true,
33
+ theme: null,
34
+ zindex: 999,
35
+ resultsLimit: null,
36
+
37
+ enableHTML: false,
38
+
39
+ resultsFormatter: function(item) {
40
+ var string = item[this.propertyToSearch];
41
+ return "<li>" + (this.enableHTML ? string : _escapeHTML(string)) + "</li>";
42
+ },
43
+
44
+ tokenFormatter: function(item) {
45
+ var string = item[this.propertyToSearch];
46
+ return "<li><p>" + (this.enableHTML ? string : _escapeHTML(string)) + "</p></li>";
47
+ },
48
+
49
+ // Tokenization settings
50
+ tokenLimit: null,
51
+ tokenDelimiter: ",",
52
+ preventDuplicates: false,
53
+ tokenValue: "id",
54
+
55
+ // Behavioral settings
56
+ allowFreeTagging: false,
57
+
58
+ // Callbacks
59
+ onResult: null,
60
+ onCachedResult: null,
61
+ onAdd: null,
62
+ onFreeTaggingAdd: null,
63
+ onDelete: null,
64
+ onReady: null,
65
+
66
+ // Other settings
67
+ idPrefix: "token-input-",
68
+
69
+ // Keep track if the input is currently in disabled mode
70
+ disabled: false
71
+ };
72
+
73
+ // Default classes to use when theming
74
+ var DEFAULT_CLASSES = {
75
+ tokenList: "token-input-list",
76
+ token: "token-input-token",
77
+ tokenReadOnly: "token-input-token-readonly",
78
+ tokenDelete: "token-input-delete-token",
79
+ selectedToken: "token-input-selected-token",
80
+ highlightedToken: "token-input-highlighted-token",
81
+ dropdown: "token-input-dropdown",
82
+ dropdownItem: "token-input-dropdown-item",
83
+ dropdownItem2: "token-input-dropdown-item2",
84
+ selectedDropdownItem: "token-input-selected-dropdown-item",
85
+ inputToken: "token-input-input-token",
86
+ focused: "token-input-focused",
87
+ disabled: "token-input-disabled"
88
+ };
89
+
90
+ // Input box position "enum"
91
+ var POSITION = {
92
+ BEFORE: 0,
93
+ AFTER: 1,
94
+ END: 2
95
+ };
96
+
97
+ // Keys "enum"
98
+ var KEY = {
99
+ BACKSPACE: 8,
100
+ TAB: 9,
101
+ ENTER: 13,
102
+ ESCAPE: 27,
103
+ SPACE: 32,
104
+ PAGE_UP: 33,
105
+ PAGE_DOWN: 34,
106
+ END: 35,
107
+ HOME: 36,
108
+ LEFT: 37,
109
+ UP: 38,
110
+ RIGHT: 39,
111
+ DOWN: 40,
112
+ NUMPAD_ENTER: 108,
113
+ COMMA: 188
114
+ };
115
+
116
+ var HTML_ESCAPES = {
117
+ '&': '&amp;',
118
+ '<': '&lt;',
119
+ '>': '&gt;',
120
+ '"': '&quot;',
121
+ "'": '&#x27;',
122
+ '/': '&#x2F;'
123
+ };
124
+
125
+ var HTML_ESCAPE_CHARS = /[&<>"'\/]/g;
126
+
127
+ function coerceToString(val) {
128
+ return String((val === null || val === undefined) ? '' : val);
129
+ }
130
+
131
+ function _escapeHTML(text) {
132
+ return coerceToString(text).replace(HTML_ESCAPE_CHARS, function(match) {
133
+ return HTML_ESCAPES[match];
134
+ });
135
+ }
136
+
137
+ // Additional public (exposed) methods
138
+ var methods = {
139
+ init: function(url_or_data_or_function, options) {
140
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
141
+
142
+ return this.each(function () {
143
+ $(this).data("settings", settings);
144
+ $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
145
+ });
146
+ },
147
+ clear: function() {
148
+ this.data("tokenInputObject").clear();
149
+ return this;
150
+ },
151
+ add: function(item) {
152
+ this.data("tokenInputObject").add(item);
153
+ return this;
154
+ },
155
+ remove: function(item) {
156
+ this.data("tokenInputObject").remove(item);
157
+ return this;
158
+ },
159
+ get: function() {
160
+ return this.data("tokenInputObject").getTokens();
161
+ },
162
+ toggleDisabled: function(disable) {
163
+ this.data("tokenInputObject").toggleDisabled(disable);
164
+ return this;
165
+ },
166
+ setOptions: function(options){
167
+ $(this).data("settings", $.extend({}, $(this).data("settings"), options || {}));
168
+ return this;
169
+ }
170
+ }
171
+
172
+ // Expose the .tokenInput function to jQuery as a plugin
173
+ $.fn.tokenInput = function (method) {
174
+ // Method calling and initialization logic
175
+ if(methods[method]) {
176
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
177
+ } else {
178
+ return methods.init.apply(this, arguments);
179
+ }
180
+ };
181
+
182
+ // TokenList class for each input
183
+ $.TokenList = function (input, url_or_data, settings) {
184
+ //
185
+ // Initialization
186
+ //
187
+
188
+ // Configure the data source
189
+ if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
190
+ // Set the url to query against
191
+ $(input).data("settings").url = url_or_data;
192
+
193
+ // If the URL is a function, evaluate it here to do our initalization work
194
+ var url = computeURL();
195
+
196
+ // Make a smart guess about cross-domain if it wasn't explicitly specified
197
+ if($(input).data("settings").crossDomain === undefined && typeof url === "string") {
198
+ if(url.indexOf("://") === -1) {
199
+ $(input).data("settings").crossDomain = false;
200
+ } else {
201
+ $(input).data("settings").crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
202
+ }
203
+ }
204
+ } else if(typeof(url_or_data) === "object") {
205
+ // Set the local data to search through
206
+ $(input).data("settings").local_data = url_or_data;
207
+ }
208
+
209
+ // Build class names
210
+ if($(input).data("settings").classes) {
211
+ // Use custom class names
212
+ $(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes);
213
+ } else if($(input).data("settings").theme) {
214
+ // Use theme-suffixed default class names
215
+ $(input).data("settings").classes = {};
216
+ $.each(DEFAULT_CLASSES, function(key, value) {
217
+ $(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme;
218
+ });
219
+ } else {
220
+ $(input).data("settings").classes = DEFAULT_CLASSES;
221
+ }
222
+
223
+
224
+ // Save the tokens
225
+ var saved_tokens = [];
226
+
227
+ // Keep track of the number of tokens in the list
228
+ var token_count = 0;
229
+
230
+ // Basic cache to save on db hits
231
+ var cache = new $.TokenList.Cache();
232
+
233
+ // Keep track of the timeout, old vals
234
+ var timeout;
235
+ var input_val;
236
+
237
+ // Create a new text input an attach keyup events
238
+ var input_box = $("<input type=\"text\" autocomplete=\"off\">")
239
+ .css({
240
+ outline: "none"
241
+ })
242
+ .attr("id", $(input).data("settings").idPrefix + input.id)
243
+ .focus(function () {
244
+ if ($(input).data("settings").disabled) {
245
+ return false;
246
+ } else
247
+ if ($(input).data("settings").tokenLimit === null || $(input).data("settings").tokenLimit !== token_count) {
248
+ show_dropdown_hint();
249
+ }
250
+ token_list.addClass($(input).data("settings").classes.focused);
251
+ })
252
+ .blur(function () {
253
+ hide_dropdown();
254
+ $(this).val("");
255
+ token_list.removeClass($(input).data("settings").classes.focused);
256
+
257
+ if ($(input).data("settings").allowFreeTagging) {
258
+ add_freetagging_tokens();
259
+ } else {
260
+ $(this).val("");
261
+ }
262
+ token_list.removeClass($(input).data("settings").classes.focused);
263
+ })
264
+ .bind("keyup keydown blur update", resize_input)
265
+ .keydown(function (event) {
266
+ var previous_token;
267
+ var next_token;
268
+
269
+ switch(event.keyCode) {
270
+ case KEY.LEFT:
271
+ case KEY.RIGHT:
272
+ case KEY.UP:
273
+ case KEY.DOWN:
274
+ if(!$(this).val()) {
275
+ previous_token = input_token.prev();
276
+ next_token = input_token.next();
277
+
278
+ if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
279
+ // Check if there is a previous/next token and it is selected
280
+ if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
281
+ deselect_token($(selected_token), POSITION.BEFORE);
282
+ } else {
283
+ deselect_token($(selected_token), POSITION.AFTER);
284
+ }
285
+ } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
286
+ // We are moving left, select the previous token if it exists
287
+ select_token($(previous_token.get(0)));
288
+ } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
289
+ // We are moving right, select the next token if it exists
290
+ select_token($(next_token.get(0)));
291
+ }
292
+ } else {
293
+ var dropdown_item = null;
294
+
295
+ if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
296
+ dropdown_item = $(selected_dropdown_item).next();
297
+ } else {
298
+ dropdown_item = $(selected_dropdown_item).prev();
299
+ }
300
+
301
+ if(dropdown_item.length) {
302
+ select_dropdown_item(dropdown_item);
303
+ }
304
+ }
305
+ return false;
306
+ break;
307
+
308
+ case KEY.BACKSPACE:
309
+ previous_token = input_token.prev();
310
+
311
+ if(!$(this).val().length) {
312
+ if(selected_token) {
313
+ delete_token($(selected_token));
314
+ hidden_input.change();
315
+ } else if(previous_token.length) {
316
+ select_token($(previous_token.get(0)));
317
+ }
318
+
319
+ return false;
320
+ } else if($(this).val().length === 1) {
321
+ hide_dropdown();
322
+ } else {
323
+ // set a timeout just long enough to let this function finish.
324
+ setTimeout(function(){do_search();}, 5);
325
+ }
326
+ break;
327
+
328
+ case KEY.TAB:
329
+ case KEY.ENTER:
330
+ case KEY.NUMPAD_ENTER:
331
+ case KEY.COMMA:
332
+ if(selected_dropdown_item) {
333
+ add_token($(selected_dropdown_item).data("tokeninput"));
334
+ hidden_input.change();
335
+ } else {
336
+ if ($(input).data("settings").allowFreeTagging) {
337
+ add_freetagging_tokens();
338
+ } else {
339
+ $(this).val("");
340
+ }
341
+ event.stopPropagation();
342
+ event.preventDefault();
343
+ }
344
+ return false;
345
+
346
+ case KEY.ESCAPE:
347
+ hide_dropdown();
348
+ return true;
349
+
350
+ default:
351
+ if(String.fromCharCode(event.which)) {
352
+ // set a timeout just long enough to let this function finish.
353
+ setTimeout(function(){do_search();}, 5);
354
+ }
355
+ break;
356
+ }
357
+ });
358
+
359
+ // Keep a reference to the original input box
360
+ var hidden_input = $(input)
361
+ .hide()
362
+ .val("")
363
+ .focus(function () {
364
+ focus_with_timeout(input_box);
365
+ })
366
+ .blur(function () {
367
+ input_box.blur();
368
+ });
369
+
370
+ // Keep a reference to the selected token and dropdown item
371
+ var selected_token = null;
372
+ var selected_token_index = 0;
373
+ var selected_dropdown_item = null;
374
+
375
+ // The list to store the token items in
376
+ var token_list = $("<ul />")
377
+ .addClass($(input).data("settings").classes.tokenList)
378
+ .click(function (event) {
379
+ var li = $(event.target).closest("li");
380
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
381
+ toggle_select_token(li);
382
+ } else {
383
+ // Deselect selected token
384
+ if(selected_token) {
385
+ deselect_token($(selected_token), POSITION.END);
386
+ }
387
+
388
+ // Focus input box
389
+ focus_with_timeout(input_box);
390
+ }
391
+ })
392
+ .mouseover(function (event) {
393
+ var li = $(event.target).closest("li");
394
+ if(li && selected_token !== this) {
395
+ li.addClass($(input).data("settings").classes.highlightedToken);
396
+ }
397
+ })
398
+ .mouseout(function (event) {
399
+ var li = $(event.target).closest("li");
400
+ if(li && selected_token !== this) {
401
+ li.removeClass($(input).data("settings").classes.highlightedToken);
402
+ }
403
+ })
404
+ .insertBefore(hidden_input);
405
+
406
+ // The token holding the input box
407
+ var input_token = $("<li />")
408
+ .addClass($(input).data("settings").classes.inputToken)
409
+ .appendTo(token_list)
410
+ .append(input_box);
411
+
412
+ // The list to store the dropdown items in
413
+ var dropdown = $("<div>")
414
+ .addClass($(input).data("settings").classes.dropdown)
415
+ .appendTo("body")
416
+ .hide();
417
+
418
+ // Magic element to help us resize the text input
419
+ var input_resizer = $("<tester/>")
420
+ .insertAfter(input_box)
421
+ .css({
422
+ position: "absolute",
423
+ top: -9999,
424
+ left: -9999,
425
+ width: "auto",
426
+ fontSize: input_box.css("fontSize"),
427
+ fontFamily: input_box.css("fontFamily"),
428
+ fontWeight: input_box.css("fontWeight"),
429
+ letterSpacing: input_box.css("letterSpacing"),
430
+ whiteSpace: "nowrap"
431
+ });
432
+
433
+ // Pre-populate list if items exist
434
+ hidden_input.val("");
435
+ var li_data = $(input).data("settings").prePopulate || hidden_input.data("pre");
436
+ if($(input).data("settings").processPrePopulate && $.isFunction($(input).data("settings").onResult)) {
437
+ li_data = $(input).data("settings").onResult.call(hidden_input, li_data);
438
+ }
439
+ if(li_data && li_data.length) {
440
+ $.each(li_data, function (index, value) {
441
+ insert_token(value);
442
+ checkTokenLimit();
443
+ });
444
+ }
445
+
446
+ // Check if widget should initialize as disabled
447
+ if ($(input).data("settings").disabled) {
448
+ toggleDisabled(true);
449
+ }
450
+
451
+ // Initialization is done
452
+ if($.isFunction($(input).data("settings").onReady)) {
453
+ $(input).data("settings").onReady.call();
454
+ }
455
+
456
+ //
457
+ // Public functions
458
+ //
459
+
460
+ this.clear = function() {
461
+ token_list.children("li").each(function() {
462
+ if ($(this).children("input").length === 0) {
463
+ delete_token($(this));
464
+ }
465
+ });
466
+ }
467
+
468
+ this.add = function(item) {
469
+ add_token(item);
470
+ }
471
+
472
+ this.remove = function(item) {
473
+ token_list.children("li").each(function() {
474
+ if ($(this).children("input").length === 0) {
475
+ var currToken = $(this).data("tokeninput");
476
+ var match = true;
477
+ for (var prop in item) {
478
+ if (item[prop] !== currToken[prop]) {
479
+ match = false;
480
+ break;
481
+ }
482
+ }
483
+ if (match) {
484
+ delete_token($(this));
485
+ }
486
+ }
487
+ });
488
+ }
489
+
490
+ this.getTokens = function() {
491
+ return saved_tokens;
492
+ }
493
+
494
+ this.toggleDisabled = function(disable) {
495
+ toggleDisabled(disable);
496
+ }
497
+
498
+ //
499
+ // Private functions
500
+ //
501
+
502
+ function escapeHTML(text) {
503
+ return $(input).data("settings").enableHTML ? text : _escapeHTML(text);
504
+ }
505
+
506
+ // Toggles the widget between enabled and disabled state, or according
507
+ // to the [disable] parameter.
508
+ function toggleDisabled(disable) {
509
+ if (typeof disable === 'boolean') {
510
+ $(input).data("settings").disabled = disable
511
+ } else {
512
+ $(input).data("settings").disabled = !$(input).data("settings").disabled;
513
+ }
514
+ input_box.attr('disabled', $(input).data("settings").disabled);
515
+ token_list.toggleClass($(input).data("settings").classes.disabled, $(input).data("settings").disabled);
516
+ // if there is any token selected we deselect it
517
+ if(selected_token) {
518
+ deselect_token($(selected_token), POSITION.END);
519
+ }
520
+ hidden_input.attr('disabled', $(input).data("settings").disabled);
521
+ }
522
+
523
+ function checkTokenLimit() {
524
+ if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
525
+ input_box.hide();
526
+ hide_dropdown();
527
+ return;
528
+ }
529
+ }
530
+
531
+ function resize_input() {
532
+ if(input_val === (input_val = input_box.val())) {return;}
533
+
534
+ // Enter new content into resizer and resize input accordingly
535
+ input_resizer.html(_escapeHTML(input_val));
536
+ input_box.width(input_resizer.width() + 30);
537
+ }
538
+
539
+ function is_printable_character(keycode) {
540
+ return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
541
+ (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
542
+ (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
543
+ (keycode >= 219 && keycode <= 222)); // ( \ ) '
544
+ }
545
+
546
+ function add_freetagging_tokens() {
547
+ var value = $.trim(input_box.val());
548
+ var tokens = value.split($(input).data("settings").tokenDelimiter);
549
+ $.each(tokens, function(i, token) {
550
+ if (!token) {
551
+ return;
552
+ }
553
+
554
+ if ($.isFunction($(input).data("settings").onFreeTaggingAdd)) {
555
+ token = $(input).data("settings").onFreeTaggingAdd.call(hidden_input, token);
556
+ }
557
+ var object = {};
558
+ object[$(input).data("settings").tokenValue] = object[$(input).data("settings").propertyToSearch] = token;
559
+ add_token(object);
560
+ });
561
+ }
562
+
563
+ // Inner function to a token to the list
564
+ function insert_token(item) {
565
+ var $this_token = $($(input).data("settings").tokenFormatter(item));
566
+ var readonly = item.readonly === true ? true : false;
567
+
568
+ if(readonly) $this_token.addClass($(input).data("settings").classes.tokenReadOnly);
569
+
570
+ $this_token.addClass($(input).data("settings").classes.token).insertBefore(input_token);
571
+
572
+ // The 'delete token' button
573
+ if(!readonly) {
574
+ $("<span>" + $(input).data("settings").deleteText + "</span>")
575
+ .addClass($(input).data("settings").classes.tokenDelete)
576
+ .appendTo($this_token)
577
+ .click(function () {
578
+ if (!$(input).data("settings").disabled) {
579
+ delete_token($(this).parent());
580
+ hidden_input.change();
581
+ return false;
582
+ }
583
+ });
584
+ }
585
+
586
+ // Store data on the token
587
+ var token_data = item;
588
+ $.data($this_token.get(0), "tokeninput", item);
589
+
590
+ // Save this token for duplicate checking
591
+ saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
592
+ selected_token_index++;
593
+
594
+ // Update the hidden input
595
+ update_hidden_input(saved_tokens, hidden_input);
596
+
597
+ token_count += 1;
598
+
599
+ // Check the token limit
600
+ if($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
601
+ input_box.hide();
602
+ hide_dropdown();
603
+ }
604
+
605
+ return $this_token;
606
+ }
607
+
608
+ // Add a token to the token list based on user input
609
+ function add_token (item) {
610
+ var callback = $(input).data("settings").onAdd;
611
+
612
+ // See if the token already exists and select it if we don't want duplicates
613
+ if(token_count > 0 && $(input).data("settings").preventDuplicates) {
614
+ var found_existing_token = null;
615
+ token_list.children().each(function () {
616
+ var existing_token = $(this);
617
+ var existing_data = $.data(existing_token.get(0), "tokeninput");
618
+ if(existing_data && existing_data.id === item.id) {
619
+ found_existing_token = existing_token;
620
+ return false;
621
+ }
622
+ });
623
+
624
+ if(found_existing_token) {
625
+ select_token(found_existing_token);
626
+ input_token.insertAfter(found_existing_token);
627
+ focus_with_timeout(input_box);
628
+ return;
629
+ }
630
+ }
631
+
632
+ // Insert the new tokens
633
+ if($(input).data("settings").tokenLimit == null || token_count < $(input).data("settings").tokenLimit) {
634
+ insert_token(item);
635
+ checkTokenLimit();
636
+ }
637
+
638
+ // Clear input box
639
+ input_box.val("");
640
+
641
+ // Don't show the help dropdown, they've got the idea
642
+ hide_dropdown();
643
+
644
+ // Execute the onAdd callback if defined
645
+ if($.isFunction(callback)) {
646
+ callback.call(hidden_input,item);
647
+ }
648
+ }
649
+
650
+ // Select a token in the token list
651
+ function select_token (token) {
652
+ if (!$(input).data("settings").disabled) {
653
+ token.addClass($(input).data("settings").classes.selectedToken);
654
+ selected_token = token.get(0);
655
+
656
+ // Hide input box
657
+ input_box.val("");
658
+
659
+ // Hide dropdown if it is visible (eg if we clicked to select token)
660
+ hide_dropdown();
661
+ }
662
+ }
663
+
664
+ // Deselect a token in the token list
665
+ function deselect_token (token, position) {
666
+ token.removeClass($(input).data("settings").classes.selectedToken);
667
+ selected_token = null;
668
+
669
+ if(position === POSITION.BEFORE) {
670
+ input_token.insertBefore(token);
671
+ selected_token_index--;
672
+ } else if(position === POSITION.AFTER) {
673
+ input_token.insertAfter(token);
674
+ selected_token_index++;
675
+ } else {
676
+ input_token.appendTo(token_list);
677
+ selected_token_index = token_count;
678
+ }
679
+
680
+ // Show the input box and give it focus again
681
+ focus_with_timeout(input_box);
682
+ }
683
+
684
+ // Toggle selection of a token in the token list
685
+ function toggle_select_token(token) {
686
+ var previous_selected_token = selected_token;
687
+
688
+ if(selected_token) {
689
+ deselect_token($(selected_token), POSITION.END);
690
+ }
691
+
692
+ if(previous_selected_token === token.get(0)) {
693
+ deselect_token(token, POSITION.END);
694
+ } else {
695
+ select_token(token);
696
+ }
697
+ }
698
+
699
+ // Delete a token from the token list
700
+ function delete_token (token) {
701
+ // Remove the id from the saved list
702
+ var token_data = $.data(token.get(0), "tokeninput");
703
+ var callback = $(input).data("settings").onDelete;
704
+
705
+ var index = token.prevAll().length;
706
+ if(index > selected_token_index) index--;
707
+
708
+ // Delete the token
709
+ token.remove();
710
+ selected_token = null;
711
+
712
+ // Show the input box and give it focus again
713
+ focus_with_timeout(input_box);
714
+
715
+ // Remove this token from the saved list
716
+ saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
717
+ if(index < selected_token_index) selected_token_index--;
718
+
719
+ // Update the hidden input
720
+ update_hidden_input(saved_tokens, hidden_input);
721
+
722
+ token_count -= 1;
723
+
724
+ if($(input).data("settings").tokenLimit !== null) {
725
+ input_box
726
+ .show()
727
+ .val("");
728
+ focus_with_timeout(input_box);
729
+ }
730
+
731
+ // Execute the onDelete callback if defined
732
+ if($.isFunction(callback)) {
733
+ callback.call(hidden_input,token_data);
734
+ }
735
+ }
736
+
737
+ // Update the hidden input box value
738
+ function update_hidden_input(saved_tokens, hidden_input) {
739
+ var token_values = $.map(saved_tokens, function (el) {
740
+ if(typeof $(input).data("settings").tokenValue == 'function')
741
+ return $(input).data("settings").tokenValue.call(this, el);
742
+
743
+ return el[$(input).data("settings").tokenValue];
744
+ });
745
+ hidden_input.val(token_values.join($(input).data("settings").tokenDelimiter));
746
+
747
+ }
748
+
749
+ // Hide and clear the results dropdown
750
+ function hide_dropdown () {
751
+ dropdown.hide().empty();
752
+ selected_dropdown_item = null;
753
+ }
754
+
755
+ function show_dropdown() {
756
+ dropdown
757
+ .css({
758
+ position: "absolute",
759
+ top: $(token_list).offset().top + $(token_list).outerHeight(),
760
+ left: $(token_list).offset().left,
761
+ width: $(token_list).width(),
762
+ 'z-index': $(input).data("settings").zindex
763
+ })
764
+ .show();
765
+ }
766
+
767
+ function show_dropdown_searching () {
768
+ if($(input).data("settings").searchingText) {
769
+ dropdown.html("<p>" + escapeHTML($(input).data("settings").searchingText) + "</p>");
770
+ show_dropdown();
771
+ }
772
+ }
773
+
774
+ function show_dropdown_hint () {
775
+ if($(input).data("settings").hintText) {
776
+ dropdown.html("<p>" + escapeHTML($(input).data("settings").hintText) + "</p>");
777
+ show_dropdown();
778
+ }
779
+ }
780
+
781
+ var regexp_special_chars = new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g');
782
+ function regexp_escape(term) {
783
+ return term.replace(regexp_special_chars, '\\$&');
784
+ }
785
+
786
+ // Highlight the query part of the search term
787
+ function highlight_term(value, term) {
788
+ return value.replace(
789
+ new RegExp(
790
+ "(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(term) + ")(?![^<>]*>)(?![^&;]+;)",
791
+ "gi"
792
+ ), function(match, p1) {
793
+ return "<b>" + escapeHTML(p1) + "</b>";
794
+ }
795
+ );
796
+ }
797
+
798
+ function find_value_and_highlight_term(template, value, term) {
799
+ return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + regexp_escape(value) + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
800
+ }
801
+
802
+ // Populate the results dropdown with some results
803
+ function populate_dropdown (query, results) {
804
+ if(results && results.length) {
805
+ dropdown.empty();
806
+ var dropdown_ul = $("<ul>")
807
+ .appendTo(dropdown)
808
+ .mouseover(function (event) {
809
+ select_dropdown_item($(event.target).closest("li"));
810
+ })
811
+ .mousedown(function (event) {
812
+ add_token($(event.target).closest("li").data("tokeninput"));
813
+ hidden_input.change();
814
+ return false;
815
+ })
816
+ .hide();
817
+
818
+ if ($(input).data("settings").resultsLimit && results.length > $(input).data("settings").resultsLimit) {
819
+ results = results.slice(0, $(input).data("settings").resultsLimit);
820
+ }
821
+
822
+ $.each(results, function(index, value) {
823
+ var this_li = $(input).data("settings").resultsFormatter(value);
824
+
825
+ this_li = find_value_and_highlight_term(this_li ,value[$(input).data("settings").propertyToSearch], query);
826
+
827
+ this_li = $(this_li).appendTo(dropdown_ul);
828
+
829
+ if(index % 2) {
830
+ this_li.addClass($(input).data("settings").classes.dropdownItem);
831
+ } else {
832
+ this_li.addClass($(input).data("settings").classes.dropdownItem2);
833
+ }
834
+
835
+ if(index === 0) {
836
+ select_dropdown_item(this_li);
837
+ }
838
+
839
+ $.data(this_li.get(0), "tokeninput", value);
840
+ });
841
+
842
+ show_dropdown();
843
+
844
+ if($(input).data("settings").animateDropdown) {
845
+ dropdown_ul.slideDown("fast");
846
+ } else {
847
+ dropdown_ul.show();
848
+ }
849
+ } else {
850
+ if($(input).data("settings").noResultsText) {
851
+ dropdown.html("<p>" + escapeHTML($(input).data("settings").noResultsText) + "</p>");
852
+ show_dropdown();
853
+ }
854
+ }
855
+ }
856
+
857
+ // Highlight an item in the results dropdown
858
+ function select_dropdown_item (item) {
859
+ if(item) {
860
+ if(selected_dropdown_item) {
861
+ deselect_dropdown_item($(selected_dropdown_item));
862
+ }
863
+
864
+ item.addClass($(input).data("settings").classes.selectedDropdownItem);
865
+ selected_dropdown_item = item.get(0);
866
+ }
867
+ }
868
+
869
+ // Remove highlighting from an item in the results dropdown
870
+ function deselect_dropdown_item (item) {
871
+ item.removeClass($(input).data("settings").classes.selectedDropdownItem);
872
+ selected_dropdown_item = null;
873
+ }
874
+
875
+ // Do a search and show the "searching" dropdown if the input is longer
876
+ // than $(input).data("settings").minChars
877
+ function do_search() {
878
+ var query = input_box.val();
879
+
880
+ if(query && query.length) {
881
+ if(selected_token) {
882
+ deselect_token($(selected_token), POSITION.AFTER);
883
+ }
884
+
885
+ if(query.length >= $(input).data("settings").minChars) {
886
+ show_dropdown_searching();
887
+ clearTimeout(timeout);
888
+
889
+ timeout = setTimeout(function(){
890
+ run_search(query);
891
+ }, $(input).data("settings").searchDelay);
892
+ } else {
893
+ hide_dropdown();
894
+ }
895
+ }
896
+ }
897
+
898
+ // Do the actual search
899
+ function run_search(query) {
900
+ var cache_key = query + computeURL();
901
+ var cached_results = cache.get(cache_key);
902
+ if(cached_results) {
903
+ if ($.isFunction($(input).data("settings").onCachedResult)) {
904
+ cached_results = $(input).data("settings").onCachedResult.call(hidden_input, cached_results);
905
+ }
906
+ populate_dropdown(query, cached_results);
907
+ } else {
908
+ // Are we doing an ajax search or local data search?
909
+ if($(input).data("settings").url) {
910
+ var url = computeURL();
911
+ // Extract exisiting get params
912
+ var ajax_params = {};
913
+ ajax_params.data = {};
914
+ if(url.indexOf("?") > -1) {
915
+ var parts = url.split("?");
916
+ ajax_params.url = parts[0];
917
+
918
+ var param_array = parts[1].split("&");
919
+ $.each(param_array, function (index, value) {
920
+ var kv = value.split("=");
921
+ ajax_params.data[kv[0]] = kv[1];
922
+ });
923
+ } else {
924
+ ajax_params.url = url;
925
+ }
926
+
927
+ // Prepare the request
928
+ ajax_params.data[$(input).data("settings").queryParam] = query;
929
+ ajax_params.type = $(input).data("settings").method;
930
+ ajax_params.dataType = $(input).data("settings").contentType;
931
+ if($(input).data("settings").crossDomain) {
932
+ ajax_params.dataType = "jsonp";
933
+ }
934
+
935
+ // Attach the success callback
936
+ ajax_params.success = function(results) {
937
+ cache.add(cache_key, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
938
+ if($.isFunction($(input).data("settings").onResult)) {
939
+ results = $(input).data("settings").onResult.call(hidden_input, results);
940
+ }
941
+
942
+ // only populate the dropdown if the results are associated with the active search query
943
+ if(input_box.val() === query) {
944
+ populate_dropdown(query, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
945
+ }
946
+ };
947
+
948
+ // Make the request
949
+ $.ajax(ajax_params);
950
+ } else if($(input).data("settings").local_data) {
951
+ // Do the search through local data
952
+ var results = $.grep($(input).data("settings").local_data, function (row) {
953
+ return row[$(input).data("settings").propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
954
+ });
955
+
956
+ cache.add(cache_key, results);
957
+ if($.isFunction($(input).data("settings").onResult)) {
958
+ results = $(input).data("settings").onResult.call(hidden_input, results);
959
+ }
960
+ populate_dropdown(query, results);
961
+ }
962
+ }
963
+ }
964
+
965
+ // compute the dynamic URL
966
+ function computeURL() {
967
+ var url = $(input).data("settings").url;
968
+ if(typeof $(input).data("settings").url == 'function') {
969
+ url = $(input).data("settings").url.call($(input).data("settings"));
970
+ }
971
+ return url;
972
+ }
973
+
974
+ // Bring browser focus to the specified object.
975
+ // Use of setTimeout is to get around an IE bug.
976
+ // (See, e.g., http://stackoverflow.com/questions/2600186/focus-doesnt-work-in-ie)
977
+ //
978
+ // obj: a jQuery object to focus()
979
+ function focus_with_timeout(obj) {
980
+ setTimeout(function() { obj.focus(); }, 50);
981
+ }
982
+
983
+ };
984
+
985
+ // Really basic cache for the results
986
+ $.TokenList.Cache = function (options) {
987
+ var settings = $.extend({
988
+ max_size: 500
989
+ }, options);
990
+
991
+ var data = {};
992
+ var size = 0;
993
+
994
+ var flush = function () {
995
+ data = {};
996
+ size = 0;
997
+ };
998
+
999
+ this.add = function (query, results) {
1000
+ if(size > settings.max_size) {
1001
+ flush();
1002
+ }
1003
+
1004
+ if(!data[query]) {
1005
+ size += 1;
1006
+ }
1007
+
1008
+ data[query] = results;
1009
+ };
1010
+
1011
+ this.get = function (query) {
1012
+ return data[query];
1013
+ };
1014
+ };
1015
+ }(jQuery));