root / drupal7 / sites / all / themes / bootstrap / js / misc / autocomplete.js @ 388c412d
1 |
(function ($) { |
---|---|
2 |
|
3 |
/**
|
4 |
* Attaches the autocomplete behavior to all required fields.
|
5 |
*/
|
6 |
Drupal.behaviors.autocomplete = { |
7 |
attach: function (context) { |
8 |
var $context = $(context); |
9 |
var acdb = [];
|
10 |
$context.find('input.autocomplete').once('autocomplete', function () { |
11 |
var uri = this.value; |
12 |
if (!acdb[uri]) {
|
13 |
acdb[uri] = new Drupal.ACDB(uri);
|
14 |
} |
15 |
var $input = $context.find('#' + this.id.substr(0, this.id.length - 13)) |
16 |
.attr('autocomplete', 'OFF') |
17 |
.attr('aria-autocomplete', 'list'); |
18 |
$context.find($input[0].form).submit(Drupal.autocompleteSubmit); |
19 |
$input.parents('.form-item') |
20 |
.attr('role', 'application') |
21 |
.append($('<span class="element-invisible" aria-live="assertive"></span>') |
22 |
.attr('id', $input.attr('id') + '-autocomplete-aria-live') |
23 |
); |
24 |
new Drupal.jsAC($input, acdb[uri], $context); |
25 |
}); |
26 |
} |
27 |
}; |
28 |
|
29 |
/**
|
30 |
* Prevents the form from submitting if the suggestions popup is open
|
31 |
* and closes the suggestions popup when doing so.
|
32 |
*/
|
33 |
Drupal.autocompleteSubmit = function () { |
34 |
$('.form-autocomplete > .dropdown').each(function () { |
35 |
this.owner.hidePopup();
|
36 |
}); |
37 |
|
38 |
// Always return true to make it possible to submit even when there was an
|
39 |
// autocomplete suggestion list open.
|
40 |
return true; |
41 |
}; |
42 |
|
43 |
/**
|
44 |
* Highlights a suggestion.
|
45 |
*/
|
46 |
Drupal.jsAC.prototype.highlight = function (node) { |
47 |
if (this.selected) { |
48 |
$(this.selected).removeClass('active'); |
49 |
} |
50 |
$(node).addClass('active'); |
51 |
this.selected = node;
|
52 |
$(this.ariaLive).html($(this.selected).html()); |
53 |
}; |
54 |
|
55 |
/**
|
56 |
* Unhighlights a suggestion.
|
57 |
*/
|
58 |
Drupal.jsAC.prototype.unhighlight = function (node) { |
59 |
$(node).removeClass('active'); |
60 |
this.selected = false; |
61 |
$(this.ariaLive).empty(); |
62 |
}; |
63 |
|
64 |
/**
|
65 |
* Positions the suggestions popup and starts a search.
|
66 |
*/
|
67 |
Drupal.jsAC.prototype.populatePopup = function () { |
68 |
var $input = $(this.input); |
69 |
// Show popup.
|
70 |
if (this.popup) { |
71 |
$(this.popup).remove(); |
72 |
} |
73 |
this.selected = false; |
74 |
this.popup = $('<div class="dropdown"></div>')[0]; |
75 |
this.popup.owner = this; |
76 |
$input.parent().after(this.popup); |
77 |
|
78 |
// Do search.
|
79 |
this.db.owner = this; |
80 |
this.db.search(this.input.value); |
81 |
}; |
82 |
|
83 |
/**
|
84 |
* Fills the suggestion popup with any matches received.
|
85 |
*/
|
86 |
Drupal.jsAC.prototype.found = function (matches) { |
87 |
// If no value in the textfield, do not show the popup.
|
88 |
if (!this.input.value.length) { |
89 |
return false; |
90 |
} |
91 |
|
92 |
// Prepare matches.
|
93 |
var ul = $('<ul class="dropdown-menu"></ul>'); |
94 |
var ac = this; |
95 |
ul.css({ |
96 |
display: 'block', |
97 |
right: 0 |
98 |
}); |
99 |
for (var key in matches) { |
100 |
$('<li></li>') |
101 |
.html($('<a href="#"></a>').html(matches[key]).on('click', function (e) { |
102 |
e.preventDefault(); |
103 |
})) |
104 |
.on('mousedown', function () { |
105 |
ac.hidePopup(this);
|
106 |
}) |
107 |
.on('mouseover', function () { |
108 |
ac.highlight(this);
|
109 |
}) |
110 |
.on('mouseout', function () { |
111 |
ac.unhighlight(this);
|
112 |
}) |
113 |
.data('autocompleteValue', key)
|
114 |
.appendTo(ul); |
115 |
} |
116 |
|
117 |
// Show popup with matches, if any.
|
118 |
if (this.popup) { |
119 |
if (ul.children().length) {
|
120 |
$(this.popup).empty().append(ul).show(); |
121 |
$(this.ariaLive).html(Drupal.t('Autocomplete popup')); |
122 |
} |
123 |
else {
|
124 |
$(this.popup).css({visibility: 'hidden'}); |
125 |
this.hidePopup();
|
126 |
} |
127 |
} |
128 |
}; |
129 |
|
130 |
/**
|
131 |
* Finds the next sibling item.
|
132 |
*/
|
133 |
Drupal.jsAC.prototype.findNextSibling = function (element) { |
134 |
var sibling = element && element.nextSibling;
|
135 |
if (sibling && !this.validItem(sibling)) { |
136 |
return this.findNextSibling(sibling.nextSibling); |
137 |
} |
138 |
return sibling;
|
139 |
}; |
140 |
|
141 |
/**
|
142 |
* Finds the previous sibling item.
|
143 |
*/
|
144 |
Drupal.jsAC.prototype.findPreviousSibling = function (element) { |
145 |
var sibling = element && element.previousSibling;
|
146 |
if (sibling && !this.validItem(sibling)) { |
147 |
return this.findPreviousSibling(sibling.previousSibling); |
148 |
} |
149 |
return sibling;
|
150 |
}; |
151 |
|
152 |
/**
|
153 |
* Highlights the next suggestion.
|
154 |
*/
|
155 |
Drupal.jsAC.prototype.selectDown = function () { |
156 |
var sibling = this.findNextSibling(this.selected); |
157 |
if (sibling) {
|
158 |
this.highlight(sibling);
|
159 |
} |
160 |
else if (this.popup) { |
161 |
var lis = $('li', this.popup); |
162 |
if (lis.length > 0) { |
163 |
if (this.validItem(lis[0])) { |
164 |
this.highlight(lis[0]); |
165 |
} |
166 |
else {
|
167 |
this.highlight(this.findNextSibling(lis[0])); |
168 |
} |
169 |
} |
170 |
} |
171 |
}; |
172 |
|
173 |
/**
|
174 |
* Highlights the previous suggestion.
|
175 |
*/
|
176 |
Drupal.jsAC.prototype.selectUp = function () { |
177 |
var sibling = this.findPreviousSibling(this.selected); |
178 |
if (sibling) {
|
179 |
this.highlight(sibling);
|
180 |
} |
181 |
else if (this.popup) { |
182 |
var lis = $('li', this.popup); |
183 |
if (lis.length > 0) { |
184 |
if (this.validItem(lis[lis.length - 1])) { |
185 |
this.highlight(lis[lis.length - 1]); |
186 |
} |
187 |
else {
|
188 |
this.highlight(this.findPreviousSibling(lis[lis.length - 1])); |
189 |
} |
190 |
} |
191 |
} |
192 |
}; |
193 |
|
194 |
/**
|
195 |
* Ensures the item is valid.
|
196 |
*/
|
197 |
Drupal.jsAC.prototype.validItem = function (element) { |
198 |
return !$(element).is('.dropdown-header, .divider, .disabled'); |
199 |
}; |
200 |
|
201 |
Drupal.jsAC.prototype.setStatus = function (status) { |
202 |
var $throbber = $(this.input).parent().find('.glyphicon-refresh, .autocomplete-throbber').first(); |
203 |
var throbbingClass = $throbber.is('.autocomplete-throbber') ? 'throbbing' : 'glyphicon-spin'; |
204 |
switch (status) {
|
205 |
case 'begin': |
206 |
$throbber.addClass(throbbingClass);
|
207 |
$(this.ariaLive).html(Drupal.t('Searching for matches...')); |
208 |
break;
|
209 |
case 'cancel': |
210 |
case 'error': |
211 |
case 'found': |
212 |
$throbber.removeClass(throbbingClass);
|
213 |
break;
|
214 |
} |
215 |
}; |
216 |
|
217 |
// Save the previous autocomplete prototype.
|
218 |
var oldPrototype = Drupal.jsAC.prototype;
|
219 |
|
220 |
/**
|
221 |
* Override the autocomplete constructor.
|
222 |
*/
|
223 |
Drupal.jsAC = function ($input, db, context) { |
224 |
var ac = this; |
225 |
|
226 |
// Context is normally passed by Drupal.behaviors.autocomplete above. However,
|
227 |
// if a module has manually invoked this method they will likely not know
|
228 |
// about this feature and a global fallback context to document must be used.
|
229 |
// @see https://www.drupal.org/node/2594243
|
230 |
// @see https://www.drupal.org/node/2315295
|
231 |
this.$context = context && $(context) || $(document); |
232 |
|
233 |
this.input = $input[0]; |
234 |
this.ariaLive = this.$context.find('#' + this.input.id + '-autocomplete-aria-live'); |
235 |
this.db = db;
|
236 |
$input
|
237 |
.keydown(function (event) {
|
238 |
return ac.onkeydown(this, event); |
239 |
}) |
240 |
.keyup(function (event) {
|
241 |
ac.onkeyup(this, event);
|
242 |
}) |
243 |
.blur(function () {
|
244 |
ac.hidePopup(); |
245 |
ac.db.cancel(); |
246 |
}); |
247 |
}; |
248 |
|
249 |
// Restore the previous prototype.
|
250 |
Drupal.jsAC.prototype = oldPrototype; |
251 |
|
252 |
})(jQuery); |