rails-jquery-tokeninput 0.0.1

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