Backup of db before drupal security update
[yaffs-website] / web / core / misc / autocomplete.js
1 /**
2  * @file
3  * Autocomplete based on jQuery UI.
4  */
5
6 (function ($, Drupal) {
7
8   'use strict';
9
10   var autocomplete;
11
12   /**
13    * Helper splitting terms from the autocomplete value.
14    *
15    * @function Drupal.autocomplete.splitValues
16    *
17    * @param {string} value
18    *   The value being entered by the user.
19    *
20    * @return {Array}
21    *   Array of values, split by comma.
22    */
23   function autocompleteSplitValues(value) {
24     // We will match the value against comma-separated terms.
25     var result = [];
26     var quote = false;
27     var current = '';
28     var valueLength = value.length;
29     var character;
30
31     for (var i = 0; i < valueLength; i++) {
32       character = value.charAt(i);
33       if (character === '"') {
34         current += character;
35         quote = !quote;
36       }
37       else if (character === ',' && !quote) {
38         result.push(current.trim());
39         current = '';
40       }
41       else {
42         current += character;
43       }
44     }
45     if (value.length > 0) {
46       result.push($.trim(current));
47     }
48
49     return result;
50   }
51
52   /**
53    * Returns the last value of an multi-value textfield.
54    *
55    * @function Drupal.autocomplete.extractLastTerm
56    *
57    * @param {string} terms
58    *   The value of the field.
59    *
60    * @return {string}
61    *   The last value of the input field.
62    */
63   function extractLastTerm(terms) {
64     return autocomplete.splitValues(terms).pop();
65   }
66
67   /**
68    * The search handler is called before a search is performed.
69    *
70    * @function Drupal.autocomplete.options.search
71    *
72    * @param {object} event
73    *   The event triggered.
74    *
75    * @return {bool}
76    *   Whether to perform a search or not.
77    */
78   function searchHandler(event) {
79     var options = autocomplete.options;
80
81     if (options.isComposing) {
82       return false;
83     }
84
85     var term = autocomplete.extractLastTerm(event.target.value);
86     // Abort search if the first character is in firstCharacterBlacklist.
87     if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {
88       return false;
89     }
90     // Only search when the term is at least the minimum length.
91     return term.length >= options.minLength;
92   }
93
94   /**
95    * JQuery UI autocomplete source callback.
96    *
97    * @param {object} request
98    *   The request object.
99    * @param {function} response
100    *   The function to call with the response.
101    */
102   function sourceData(request, response) {
103     var elementId = this.element.attr('id');
104
105     if (!(elementId in autocomplete.cache)) {
106       autocomplete.cache[elementId] = {};
107     }
108
109     /**
110      * Filter through the suggestions removing all terms already tagged and
111      * display the available terms to the user.
112      *
113      * @param {object} suggestions
114      *   Suggestions returned by the server.
115      */
116     function showSuggestions(suggestions) {
117       var tagged = autocomplete.splitValues(request.term);
118       var il = tagged.length;
119       for (var i = 0; i < il; i++) {
120         var index = suggestions.indexOf(tagged[i]);
121         if (index >= 0) {
122           suggestions.splice(index, 1);
123         }
124       }
125       response(suggestions);
126     }
127
128     /**
129      * Transforms the data object into an array and update autocomplete results.
130      *
131      * @param {object} data
132      *   The data sent back from the server.
133      */
134     function sourceCallbackHandler(data) {
135       autocomplete.cache[elementId][term] = data;
136
137       // Send the new string array of terms to the jQuery UI list.
138       showSuggestions(data);
139     }
140
141     // Get the desired term and construct the autocomplete URL for it.
142     var term = autocomplete.extractLastTerm(request.term);
143
144     // Check if the term is already cached.
145     if (autocomplete.cache[elementId].hasOwnProperty(term)) {
146       showSuggestions(autocomplete.cache[elementId][term]);
147     }
148     else {
149       var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);
150       $.ajax(this.element.attr('data-autocomplete-path'), options);
151     }
152   }
153
154   /**
155    * Handles an autocompletefocus event.
156    *
157    * @return {bool}
158    *   Always returns false.
159    */
160   function focusHandler() {
161     return false;
162   }
163
164   /**
165    * Handles an autocompleteselect event.
166    *
167    * @param {jQuery.Event} event
168    *   The event triggered.
169    * @param {object} ui
170    *   The jQuery UI settings object.
171    *
172    * @return {bool}
173    *   Returns false to indicate the event status.
174    */
175   function selectHandler(event, ui) {
176     var terms = autocomplete.splitValues(event.target.value);
177     // Remove the current input.
178     terms.pop();
179     // Add the selected item.
180     terms.push(ui.item.value);
181
182     event.target.value = terms.join(', ');
183     // Return false to tell jQuery UI that we've filled in the value already.
184     return false;
185   }
186
187   /**
188    * Override jQuery UI _renderItem function to output HTML by default.
189    *
190    * @param {jQuery} ul
191    *   jQuery collection of the ul element.
192    * @param {object} item
193    *   The list item to append.
194    *
195    * @return {jQuery}
196    *   jQuery collection of the ul element.
197    */
198   function renderItem(ul, item) {
199     return $('<li>')
200       .append($('<a>').html(item.label))
201       .appendTo(ul);
202   }
203
204   /**
205    * Attaches the autocomplete behavior to all required fields.
206    *
207    * @type {Drupal~behavior}
208    *
209    * @prop {Drupal~behaviorAttach} attach
210    *   Attaches the autocomplete behaviors.
211    * @prop {Drupal~behaviorDetach} detach
212    *   Detaches the autocomplete behaviors.
213    */
214   Drupal.behaviors.autocomplete = {
215     attach: function (context) {
216       // Act on textfields with the "form-autocomplete" class.
217       var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
218       if ($autocomplete.length) {
219         // Allow options to be overriden per instance.
220         var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
221         $.extend(autocomplete.options, {
222           firstCharacterBlacklist: (blacklist) ? blacklist : ''
223         });
224         // Use jQuery UI Autocomplete on the textfield.
225         $autocomplete.autocomplete(autocomplete.options)
226           .each(function () {
227             $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
228           });
229
230         // Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
231         $autocomplete.on('compositionstart.autocomplete', function () {
232           autocomplete.options.isComposing = true;
233         });
234         $autocomplete.on('compositionend.autocomplete', function () {
235           autocomplete.options.isComposing = false;
236         });
237       }
238     },
239     detach: function (context, settings, trigger) {
240       if (trigger === 'unload') {
241         $(context).find('input.form-autocomplete')
242           .removeOnce('autocomplete')
243           .autocomplete('destroy');
244       }
245     }
246   };
247
248   /**
249    * Autocomplete object implementation.
250    *
251    * @namespace Drupal.autocomplete
252    */
253   autocomplete = {
254     cache: {},
255     // Exposes options to allow overriding by contrib.
256     splitValues: autocompleteSplitValues,
257     extractLastTerm: extractLastTerm,
258     // jQuery UI autocomplete options.
259
260     /**
261      * JQuery UI option object.
262      *
263      * @name Drupal.autocomplete.options
264      */
265     options: {
266       source: sourceData,
267       focus: focusHandler,
268       search: searchHandler,
269       select: selectHandler,
270       renderItem: renderItem,
271       minLength: 1,
272       // Custom options, used by Drupal.autocomplete.
273       firstCharacterBlacklist: '',
274       // Custom options, indicate IME usage status.
275       isComposing: false
276     },
277     ajax: {
278       dataType: 'json'
279     }
280   };
281
282   Drupal.autocomplete = autocomplete;
283
284 })(jQuery, Drupal);