Projet

Général

Profil

Paste
Télécharger (7,98 ko) Statistiques
| Branche: | Révision:

root / htmltest / misc / autocomplete.js @ 85ad3d82

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);