rails_tokeninput 1.6.1.rc1 → 1.7.0

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