danielebarchiesi@0
|
1 (function ($) {
|
danielebarchiesi@0
|
2
|
danielebarchiesi@0
|
3 /**
|
danielebarchiesi@0
|
4 * Attaches the autocomplete behavior to all required fields.
|
danielebarchiesi@0
|
5 */
|
danielebarchiesi@0
|
6 Drupal.behaviors.autocomplete = {
|
danielebarchiesi@0
|
7 attach: function (context, settings) {
|
danielebarchiesi@0
|
8 var acdb = [];
|
danielebarchiesi@0
|
9 $('input.autocomplete', context).once('autocomplete', function () {
|
danielebarchiesi@0
|
10 var uri = this.value;
|
danielebarchiesi@0
|
11 if (!acdb[uri]) {
|
danielebarchiesi@0
|
12 acdb[uri] = new Drupal.ACDB(uri);
|
danielebarchiesi@0
|
13 }
|
danielebarchiesi@0
|
14 var $input = $('#' + this.id.substr(0, this.id.length - 13))
|
danielebarchiesi@0
|
15 .attr('autocomplete', 'OFF')
|
danielebarchiesi@0
|
16 .attr('aria-autocomplete', 'list');
|
danielebarchiesi@0
|
17 $($input[0].form).submit(Drupal.autocompleteSubmit);
|
danielebarchiesi@0
|
18 $input.parent()
|
danielebarchiesi@0
|
19 .attr('role', 'application')
|
danielebarchiesi@0
|
20 .append($('<span class="element-invisible" aria-live="assertive"></span>')
|
danielebarchiesi@0
|
21 .attr('id', $input.attr('id') + '-autocomplete-aria-live')
|
danielebarchiesi@0
|
22 );
|
danielebarchiesi@0
|
23 new Drupal.jsAC($input, acdb[uri]);
|
danielebarchiesi@0
|
24 });
|
danielebarchiesi@0
|
25 }
|
danielebarchiesi@0
|
26 };
|
danielebarchiesi@0
|
27
|
danielebarchiesi@0
|
28 /**
|
danielebarchiesi@0
|
29 * Prevents the form from submitting if the suggestions popup is open
|
danielebarchiesi@0
|
30 * and closes the suggestions popup when doing so.
|
danielebarchiesi@0
|
31 */
|
danielebarchiesi@0
|
32 Drupal.autocompleteSubmit = function () {
|
danielebarchiesi@0
|
33 return $('#autocomplete').each(function () {
|
danielebarchiesi@0
|
34 this.owner.hidePopup();
|
danielebarchiesi@0
|
35 }).length == 0;
|
danielebarchiesi@0
|
36 };
|
danielebarchiesi@0
|
37
|
danielebarchiesi@0
|
38 /**
|
danielebarchiesi@0
|
39 * An AutoComplete object.
|
danielebarchiesi@0
|
40 */
|
danielebarchiesi@0
|
41 Drupal.jsAC = function ($input, db) {
|
danielebarchiesi@0
|
42 var ac = this;
|
danielebarchiesi@0
|
43 this.input = $input[0];
|
danielebarchiesi@0
|
44 this.ariaLive = $('#' + this.input.id + '-autocomplete-aria-live');
|
danielebarchiesi@0
|
45 this.db = db;
|
danielebarchiesi@0
|
46
|
danielebarchiesi@0
|
47 $input
|
danielebarchiesi@0
|
48 .keydown(function (event) { return ac.onkeydown(this, event); })
|
danielebarchiesi@0
|
49 .keyup(function (event) { ac.onkeyup(this, event); })
|
danielebarchiesi@0
|
50 .blur(function () { ac.hidePopup(); ac.db.cancel(); });
|
danielebarchiesi@0
|
51
|
danielebarchiesi@0
|
52 };
|
danielebarchiesi@0
|
53
|
danielebarchiesi@0
|
54 /**
|
danielebarchiesi@0
|
55 * Handler for the "keydown" event.
|
danielebarchiesi@0
|
56 */
|
danielebarchiesi@0
|
57 Drupal.jsAC.prototype.onkeydown = function (input, e) {
|
danielebarchiesi@0
|
58 if (!e) {
|
danielebarchiesi@0
|
59 e = window.event;
|
danielebarchiesi@0
|
60 }
|
danielebarchiesi@0
|
61 switch (e.keyCode) {
|
danielebarchiesi@0
|
62 case 40: // down arrow.
|
danielebarchiesi@0
|
63 this.selectDown();
|
danielebarchiesi@0
|
64 return false;
|
danielebarchiesi@0
|
65 case 38: // up arrow.
|
danielebarchiesi@0
|
66 this.selectUp();
|
danielebarchiesi@0
|
67 return false;
|
danielebarchiesi@0
|
68 default: // All other keys.
|
danielebarchiesi@0
|
69 return true;
|
danielebarchiesi@0
|
70 }
|
danielebarchiesi@0
|
71 };
|
danielebarchiesi@0
|
72
|
danielebarchiesi@0
|
73 /**
|
danielebarchiesi@0
|
74 * Handler for the "keyup" event.
|
danielebarchiesi@0
|
75 */
|
danielebarchiesi@0
|
76 Drupal.jsAC.prototype.onkeyup = function (input, e) {
|
danielebarchiesi@0
|
77 if (!e) {
|
danielebarchiesi@0
|
78 e = window.event;
|
danielebarchiesi@0
|
79 }
|
danielebarchiesi@0
|
80 switch (e.keyCode) {
|
danielebarchiesi@0
|
81 case 16: // Shift.
|
danielebarchiesi@0
|
82 case 17: // Ctrl.
|
danielebarchiesi@0
|
83 case 18: // Alt.
|
danielebarchiesi@0
|
84 case 20: // Caps lock.
|
danielebarchiesi@0
|
85 case 33: // Page up.
|
danielebarchiesi@0
|
86 case 34: // Page down.
|
danielebarchiesi@0
|
87 case 35: // End.
|
danielebarchiesi@0
|
88 case 36: // Home.
|
danielebarchiesi@0
|
89 case 37: // Left arrow.
|
danielebarchiesi@0
|
90 case 38: // Up arrow.
|
danielebarchiesi@0
|
91 case 39: // Right arrow.
|
danielebarchiesi@0
|
92 case 40: // Down arrow.
|
danielebarchiesi@0
|
93 return true;
|
danielebarchiesi@0
|
94
|
danielebarchiesi@0
|
95 case 9: // Tab.
|
danielebarchiesi@0
|
96 case 13: // Enter.
|
danielebarchiesi@0
|
97 case 27: // Esc.
|
danielebarchiesi@0
|
98 this.hidePopup(e.keyCode);
|
danielebarchiesi@0
|
99 return true;
|
danielebarchiesi@0
|
100
|
danielebarchiesi@0
|
101 default: // All other keys.
|
danielebarchiesi@0
|
102 if (input.value.length > 0 && !input.readOnly) {
|
danielebarchiesi@0
|
103 this.populatePopup();
|
danielebarchiesi@0
|
104 }
|
danielebarchiesi@0
|
105 else {
|
danielebarchiesi@0
|
106 this.hidePopup(e.keyCode);
|
danielebarchiesi@0
|
107 }
|
danielebarchiesi@0
|
108 return true;
|
danielebarchiesi@0
|
109 }
|
danielebarchiesi@0
|
110 };
|
danielebarchiesi@0
|
111
|
danielebarchiesi@0
|
112 /**
|
danielebarchiesi@0
|
113 * Puts the currently highlighted suggestion into the autocomplete field.
|
danielebarchiesi@0
|
114 */
|
danielebarchiesi@0
|
115 Drupal.jsAC.prototype.select = function (node) {
|
danielebarchiesi@0
|
116 this.input.value = $(node).data('autocompleteValue');
|
danielebarchiesi@0
|
117 };
|
danielebarchiesi@0
|
118
|
danielebarchiesi@0
|
119 /**
|
danielebarchiesi@0
|
120 * Highlights the next suggestion.
|
danielebarchiesi@0
|
121 */
|
danielebarchiesi@0
|
122 Drupal.jsAC.prototype.selectDown = function () {
|
danielebarchiesi@0
|
123 if (this.selected && this.selected.nextSibling) {
|
danielebarchiesi@0
|
124 this.highlight(this.selected.nextSibling);
|
danielebarchiesi@0
|
125 }
|
danielebarchiesi@0
|
126 else if (this.popup) {
|
danielebarchiesi@0
|
127 var lis = $('li', this.popup);
|
danielebarchiesi@0
|
128 if (lis.length > 0) {
|
danielebarchiesi@0
|
129 this.highlight(lis.get(0));
|
danielebarchiesi@0
|
130 }
|
danielebarchiesi@0
|
131 }
|
danielebarchiesi@0
|
132 };
|
danielebarchiesi@0
|
133
|
danielebarchiesi@0
|
134 /**
|
danielebarchiesi@0
|
135 * Highlights the previous suggestion.
|
danielebarchiesi@0
|
136 */
|
danielebarchiesi@0
|
137 Drupal.jsAC.prototype.selectUp = function () {
|
danielebarchiesi@0
|
138 if (this.selected && this.selected.previousSibling) {
|
danielebarchiesi@0
|
139 this.highlight(this.selected.previousSibling);
|
danielebarchiesi@0
|
140 }
|
danielebarchiesi@0
|
141 };
|
danielebarchiesi@0
|
142
|
danielebarchiesi@0
|
143 /**
|
danielebarchiesi@0
|
144 * Highlights a suggestion.
|
danielebarchiesi@0
|
145 */
|
danielebarchiesi@0
|
146 Drupal.jsAC.prototype.highlight = function (node) {
|
danielebarchiesi@0
|
147 if (this.selected) {
|
danielebarchiesi@0
|
148 $(this.selected).removeClass('selected');
|
danielebarchiesi@0
|
149 }
|
danielebarchiesi@0
|
150 $(node).addClass('selected');
|
danielebarchiesi@0
|
151 this.selected = node;
|
danielebarchiesi@0
|
152 $(this.ariaLive).html($(this.selected).html());
|
danielebarchiesi@0
|
153 };
|
danielebarchiesi@0
|
154
|
danielebarchiesi@0
|
155 /**
|
danielebarchiesi@0
|
156 * Unhighlights a suggestion.
|
danielebarchiesi@0
|
157 */
|
danielebarchiesi@0
|
158 Drupal.jsAC.prototype.unhighlight = function (node) {
|
danielebarchiesi@0
|
159 $(node).removeClass('selected');
|
danielebarchiesi@0
|
160 this.selected = false;
|
danielebarchiesi@0
|
161 $(this.ariaLive).empty();
|
danielebarchiesi@0
|
162 };
|
danielebarchiesi@0
|
163
|
danielebarchiesi@0
|
164 /**
|
danielebarchiesi@0
|
165 * Hides the autocomplete suggestions.
|
danielebarchiesi@0
|
166 */
|
danielebarchiesi@0
|
167 Drupal.jsAC.prototype.hidePopup = function (keycode) {
|
danielebarchiesi@0
|
168 // Select item if the right key or mousebutton was pressed.
|
danielebarchiesi@0
|
169 if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
|
danielebarchiesi@0
|
170 this.input.value = $(this.selected).data('autocompleteValue');
|
danielebarchiesi@0
|
171 }
|
danielebarchiesi@0
|
172 // Hide popup.
|
danielebarchiesi@0
|
173 var popup = this.popup;
|
danielebarchiesi@0
|
174 if (popup) {
|
danielebarchiesi@0
|
175 this.popup = null;
|
danielebarchiesi@0
|
176 $(popup).fadeOut('fast', function () { $(popup).remove(); });
|
danielebarchiesi@0
|
177 }
|
danielebarchiesi@0
|
178 this.selected = false;
|
danielebarchiesi@0
|
179 $(this.ariaLive).empty();
|
danielebarchiesi@0
|
180 };
|
danielebarchiesi@0
|
181
|
danielebarchiesi@0
|
182 /**
|
danielebarchiesi@0
|
183 * Positions the suggestions popup and starts a search.
|
danielebarchiesi@0
|
184 */
|
danielebarchiesi@0
|
185 Drupal.jsAC.prototype.populatePopup = function () {
|
danielebarchiesi@0
|
186 var $input = $(this.input);
|
danielebarchiesi@0
|
187 var position = $input.position();
|
danielebarchiesi@0
|
188 // Show popup.
|
danielebarchiesi@0
|
189 if (this.popup) {
|
danielebarchiesi@0
|
190 $(this.popup).remove();
|
danielebarchiesi@0
|
191 }
|
danielebarchiesi@0
|
192 this.selected = false;
|
danielebarchiesi@0
|
193 this.popup = $('<div id="autocomplete"></div>')[0];
|
danielebarchiesi@0
|
194 this.popup.owner = this;
|
danielebarchiesi@0
|
195 $(this.popup).css({
|
danielebarchiesi@0
|
196 top: parseInt(position.top + this.input.offsetHeight, 10) + 'px',
|
danielebarchiesi@0
|
197 left: parseInt(position.left, 10) + 'px',
|
danielebarchiesi@0
|
198 width: $input.innerWidth() + 'px',
|
danielebarchiesi@0
|
199 display: 'none'
|
danielebarchiesi@0
|
200 });
|
danielebarchiesi@0
|
201 $input.before(this.popup);
|
danielebarchiesi@0
|
202
|
danielebarchiesi@0
|
203 // Do search.
|
danielebarchiesi@0
|
204 this.db.owner = this;
|
danielebarchiesi@0
|
205 this.db.search(this.input.value);
|
danielebarchiesi@0
|
206 };
|
danielebarchiesi@0
|
207
|
danielebarchiesi@0
|
208 /**
|
danielebarchiesi@0
|
209 * Fills the suggestion popup with any matches received.
|
danielebarchiesi@0
|
210 */
|
danielebarchiesi@0
|
211 Drupal.jsAC.prototype.found = function (matches) {
|
danielebarchiesi@0
|
212 // If no value in the textfield, do not show the popup.
|
danielebarchiesi@0
|
213 if (!this.input.value.length) {
|
danielebarchiesi@0
|
214 return false;
|
danielebarchiesi@0
|
215 }
|
danielebarchiesi@0
|
216
|
danielebarchiesi@0
|
217 // Prepare matches.
|
danielebarchiesi@0
|
218 var ul = $('<ul></ul>');
|
danielebarchiesi@0
|
219 var ac = this;
|
danielebarchiesi@0
|
220 for (key in matches) {
|
danielebarchiesi@0
|
221 $('<li></li>')
|
danielebarchiesi@0
|
222 .html($('<div></div>').html(matches[key]))
|
danielebarchiesi@0
|
223 .mousedown(function () { ac.select(this); })
|
danielebarchiesi@0
|
224 .mouseover(function () { ac.highlight(this); })
|
danielebarchiesi@0
|
225 .mouseout(function () { ac.unhighlight(this); })
|
danielebarchiesi@0
|
226 .data('autocompleteValue', key)
|
danielebarchiesi@0
|
227 .appendTo(ul);
|
danielebarchiesi@0
|
228 }
|
danielebarchiesi@0
|
229
|
danielebarchiesi@0
|
230 // Show popup with matches, if any.
|
danielebarchiesi@0
|
231 if (this.popup) {
|
danielebarchiesi@0
|
232 if (ul.children().length) {
|
danielebarchiesi@0
|
233 $(this.popup).empty().append(ul).show();
|
danielebarchiesi@0
|
234 $(this.ariaLive).html(Drupal.t('Autocomplete popup'));
|
danielebarchiesi@0
|
235 }
|
danielebarchiesi@0
|
236 else {
|
danielebarchiesi@0
|
237 $(this.popup).css({ visibility: 'hidden' });
|
danielebarchiesi@0
|
238 this.hidePopup();
|
danielebarchiesi@0
|
239 }
|
danielebarchiesi@0
|
240 }
|
danielebarchiesi@0
|
241 };
|
danielebarchiesi@0
|
242
|
danielebarchiesi@0
|
243 Drupal.jsAC.prototype.setStatus = function (status) {
|
danielebarchiesi@0
|
244 switch (status) {
|
danielebarchiesi@0
|
245 case 'begin':
|
danielebarchiesi@0
|
246 $(this.input).addClass('throbbing');
|
danielebarchiesi@0
|
247 $(this.ariaLive).html(Drupal.t('Searching for matches...'));
|
danielebarchiesi@0
|
248 break;
|
danielebarchiesi@0
|
249 case 'cancel':
|
danielebarchiesi@0
|
250 case 'error':
|
danielebarchiesi@0
|
251 case 'found':
|
danielebarchiesi@0
|
252 $(this.input).removeClass('throbbing');
|
danielebarchiesi@0
|
253 break;
|
danielebarchiesi@0
|
254 }
|
danielebarchiesi@0
|
255 };
|
danielebarchiesi@0
|
256
|
danielebarchiesi@0
|
257 /**
|
danielebarchiesi@0
|
258 * An AutoComplete DataBase object.
|
danielebarchiesi@0
|
259 */
|
danielebarchiesi@0
|
260 Drupal.ACDB = function (uri) {
|
danielebarchiesi@0
|
261 this.uri = uri;
|
danielebarchiesi@0
|
262 this.delay = 300;
|
danielebarchiesi@0
|
263 this.cache = {};
|
danielebarchiesi@0
|
264 };
|
danielebarchiesi@0
|
265
|
danielebarchiesi@0
|
266 /**
|
danielebarchiesi@0
|
267 * Performs a cached and delayed search.
|
danielebarchiesi@0
|
268 */
|
danielebarchiesi@0
|
269 Drupal.ACDB.prototype.search = function (searchString) {
|
danielebarchiesi@0
|
270 var db = this;
|
danielebarchiesi@0
|
271 this.searchString = searchString;
|
danielebarchiesi@0
|
272
|
danielebarchiesi@0
|
273 // See if this string needs to be searched for anyway.
|
danielebarchiesi@0
|
274 searchString = searchString.replace(/^\s+|\s+$/, '');
|
danielebarchiesi@0
|
275 if (searchString.length <= 0 ||
|
danielebarchiesi@0
|
276 searchString.charAt(searchString.length - 1) == ',') {
|
danielebarchiesi@0
|
277 return;
|
danielebarchiesi@0
|
278 }
|
danielebarchiesi@0
|
279
|
danielebarchiesi@0
|
280 // See if this key has been searched for before.
|
danielebarchiesi@0
|
281 if (this.cache[searchString]) {
|
danielebarchiesi@0
|
282 return this.owner.found(this.cache[searchString]);
|
danielebarchiesi@0
|
283 }
|
danielebarchiesi@0
|
284
|
danielebarchiesi@0
|
285 // Initiate delayed search.
|
danielebarchiesi@0
|
286 if (this.timer) {
|
danielebarchiesi@0
|
287 clearTimeout(this.timer);
|
danielebarchiesi@0
|
288 }
|
danielebarchiesi@0
|
289 this.timer = setTimeout(function () {
|
danielebarchiesi@0
|
290 db.owner.setStatus('begin');
|
danielebarchiesi@0
|
291
|
danielebarchiesi@0
|
292 // Ajax GET request for autocompletion. We use Drupal.encodePath instead of
|
danielebarchiesi@0
|
293 // encodeURIComponent to allow autocomplete search terms to contain slashes.
|
danielebarchiesi@0
|
294 $.ajax({
|
danielebarchiesi@0
|
295 type: 'GET',
|
danielebarchiesi@0
|
296 url: db.uri + '/' + Drupal.encodePath(searchString),
|
danielebarchiesi@0
|
297 dataType: 'json',
|
danielebarchiesi@0
|
298 success: function (matches) {
|
danielebarchiesi@0
|
299 if (typeof matches.status == 'undefined' || matches.status != 0) {
|
danielebarchiesi@0
|
300 db.cache[searchString] = matches;
|
danielebarchiesi@0
|
301 // Verify if these are still the matches the user wants to see.
|
danielebarchiesi@0
|
302 if (db.searchString == searchString) {
|
danielebarchiesi@0
|
303 db.owner.found(matches);
|
danielebarchiesi@0
|
304 }
|
danielebarchiesi@0
|
305 db.owner.setStatus('found');
|
danielebarchiesi@0
|
306 }
|
danielebarchiesi@0
|
307 },
|
danielebarchiesi@0
|
308 error: function (xmlhttp) {
|
danielebarchiesi@0
|
309 alert(Drupal.ajaxError(xmlhttp, db.uri));
|
danielebarchiesi@0
|
310 }
|
danielebarchiesi@0
|
311 });
|
danielebarchiesi@0
|
312 }, this.delay);
|
danielebarchiesi@0
|
313 };
|
danielebarchiesi@0
|
314
|
danielebarchiesi@0
|
315 /**
|
danielebarchiesi@0
|
316 * Cancels the current autocomplete request.
|
danielebarchiesi@0
|
317 */
|
danielebarchiesi@0
|
318 Drupal.ACDB.prototype.cancel = function () {
|
danielebarchiesi@0
|
319 if (this.owner) this.owner.setStatus('cancel');
|
danielebarchiesi@0
|
320 if (this.timer) clearTimeout(this.timer);
|
danielebarchiesi@0
|
321 this.searchString = '';
|
danielebarchiesi@0
|
322 };
|
danielebarchiesi@0
|
323
|
danielebarchiesi@0
|
324 })(jQuery);
|