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