Projet

Général

Profil

Paste
Télécharger (8,27 ko) Statistiques
| Branche: | Révision:

root / drupal7 / misc / autocomplete.js @ cee0424c

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
  $(this.input).trigger('autocompleteSelect', [node]);
118
};
119

    
120
/**
121
 * Highlights the next suggestion.
122
 */
123
Drupal.jsAC.prototype.selectDown = function () {
124
  if (this.selected && this.selected.nextSibling) {
125
    this.highlight(this.selected.nextSibling);
126
  }
127
  else if (this.popup) {
128
    var lis = $('li', this.popup);
129
    if (lis.length > 0) {
130
      this.highlight(lis.get(0));
131
    }
132
  }
133
};
134

    
135
/**
136
 * Highlights the previous suggestion.
137
 */
138
Drupal.jsAC.prototype.selectUp = function () {
139
  if (this.selected && this.selected.previousSibling) {
140
    this.highlight(this.selected.previousSibling);
141
  }
142
};
143

    
144
/**
145
 * Highlights a suggestion.
146
 */
147
Drupal.jsAC.prototype.highlight = function (node) {
148
  if (this.selected) {
149
    $(this.selected).removeClass('selected');
150
  }
151
  $(node).addClass('selected');
152
  this.selected = node;
153
  $(this.ariaLive).html($(this.selected).html());
154
};
155

    
156
/**
157
 * Unhighlights a suggestion.
158
 */
159
Drupal.jsAC.prototype.unhighlight = function (node) {
160
  $(node).removeClass('selected');
161
  this.selected = false;
162
  $(this.ariaLive).empty();
163
};
164

    
165
/**
166
 * Hides the autocomplete suggestions.
167
 */
168
Drupal.jsAC.prototype.hidePopup = function (keycode) {
169
  // Select item if the right key or mousebutton was pressed.
170
  if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
171
    this.select(this.selected);
172
  }
173
  // Hide popup.
174
  var popup = this.popup;
175
  if (popup) {
176
    this.popup = null;
177
    $(popup).fadeOut('fast', function () { $(popup).remove(); });
178
  }
179
  this.selected = false;
180
  $(this.ariaLive).empty();
181
};
182

    
183
/**
184
 * Positions the suggestions popup and starts a search.
185
 */
186
Drupal.jsAC.prototype.populatePopup = function () {
187
  var $input = $(this.input);
188
  var position = $input.position();
189
  // Show popup.
190
  if (this.popup) {
191
    $(this.popup).remove();
192
  }
193
  this.selected = false;
194
  this.popup = $('<div id="autocomplete"></div>')[0];
195
  this.popup.owner = this;
196
  $(this.popup).css({
197
    top: parseInt(position.top + this.input.offsetHeight, 10) + 'px',
198
    left: parseInt(position.left, 10) + 'px',
199
    width: $input.innerWidth() + 'px',
200
    display: 'none'
201
  });
202
  $input.before(this.popup);
203

    
204
  // Do search.
205
  this.db.owner = this;
206
  this.db.search(this.input.value);
207
};
208

    
209
/**
210
 * Fills the suggestion popup with any matches received.
211
 */
212
Drupal.jsAC.prototype.found = function (matches) {
213
  // If no value in the textfield, do not show the popup.
214
  if (!this.input.value.length) {
215
    return false;
216
  }
217

    
218
  // Prepare matches.
219
  var ul = $('<ul></ul>');
220
  var ac = this;
221
  for (key in matches) {
222
    $('<li></li>')
223
      .html($('<div></div>').html(matches[key]))
224
      .mousedown(function () { ac.hidePopup(this); })
225
      .mouseover(function () { ac.highlight(this); })
226
      .mouseout(function () { ac.unhighlight(this); })
227
      .data('autocompleteValue', key)
228
      .appendTo(ul);
229
  }
230

    
231
  // Show popup with matches, if any.
232
  if (this.popup) {
233
    if (ul.children().length) {
234
      $(this.popup).empty().append(ul).show();
235
      $(this.ariaLive).html(Drupal.t('Autocomplete popup'));
236
    }
237
    else {
238
      $(this.popup).css({ visibility: 'hidden' });
239
      this.hidePopup();
240
    }
241
  }
242
};
243

    
244
Drupal.jsAC.prototype.setStatus = function (status) {
245
  switch (status) {
246
    case 'begin':
247
      $(this.input).addClass('throbbing');
248
      $(this.ariaLive).html(Drupal.t('Searching for matches...'));
249
      break;
250
    case 'cancel':
251
    case 'error':
252
    case 'found':
253
      $(this.input).removeClass('throbbing');
254
      break;
255
  }
256
};
257

    
258
/**
259
 * An AutoComplete DataBase object.
260
 */
261
Drupal.ACDB = function (uri) {
262
  this.uri = uri;
263
  this.delay = 300;
264
  this.cache = {};
265
};
266

    
267
/**
268
 * Performs a cached and delayed search.
269
 */
270
Drupal.ACDB.prototype.search = function (searchString) {
271
  var db = this;
272
  this.searchString = searchString;
273

    
274
  // See if this string needs to be searched for anyway. The pattern ../ is
275
  // stripped since it may be misinterpreted by the browser.
276
  searchString = searchString.replace(/^\s+|\.{2,}\/|\s+$/g, '');
277
  // Skip empty search strings, or search strings ending with a comma, since
278
  // that is the separator between search terms.
279
  if (searchString.length <= 0 ||
280
    searchString.charAt(searchString.length - 1) == ',') {
281
    return;
282
  }
283

    
284
  // See if this key has been searched for before.
285
  if (this.cache[searchString]) {
286
    return this.owner.found(this.cache[searchString]);
287
  }
288

    
289
  // Initiate delayed search.
290
  if (this.timer) {
291
    clearTimeout(this.timer);
292
  }
293
  this.timer = setTimeout(function () {
294
    db.owner.setStatus('begin');
295

    
296
    // Ajax GET request for autocompletion. We use Drupal.encodePath instead of
297
    // encodeURIComponent to allow autocomplete search terms to contain slashes.
298
    $.ajax({
299
      type: 'GET',
300
      url: Drupal.sanitizeAjaxUrl(db.uri + '/' + Drupal.encodePath(searchString)),
301
      dataType: 'json',
302
      jsonp: false,
303
      success: function (matches) {
304
        if (typeof matches.status == 'undefined' || matches.status != 0) {
305
          db.cache[searchString] = matches;
306
          // Verify if these are still the matches the user wants to see.
307
          if (db.searchString == searchString) {
308
            db.owner.found(matches);
309
          }
310
          db.owner.setStatus('found');
311
        }
312
      },
313
      error: function (xmlhttp) {
314
        Drupal.displayAjaxError(Drupal.ajaxError(xmlhttp, db.uri));
315
      }
316
    });
317
  }, this.delay);
318
};
319

    
320
/**
321
 * Cancels the current autocomplete request.
322
 */
323
Drupal.ACDB.prototype.cancel = function () {
324
  if (this.owner) this.owner.setStatus('cancel');
325
  if (this.timer) clearTimeout(this.timer);
326
  this.searchString = '';
327
};
328

    
329
})(jQuery);