rails_tokeninput 1.6.1.rc1 → 1.7.0

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