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