]>
git.immae.eu Git - github/shaarli/Shaarli.git/blob - inc/awesomplete.js
2 * Simple, lightweight, usable local autocomplete library for modern browsers
3 * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P
4 * @author Lea Verou http://leaverou.github.io/awesomplete
10 var _ = function (input
, o
) {
15 this.input
= $(input
);
16 this.input
.setAttribute("aria-autocomplete", "list");
20 configure
.call(this, {
24 filter: _
.FILTER_CONTAINS
,
25 sort: _
.SORT_BYLENGTH
,
26 item: function (text
, input
) {
27 return $.create("li", {
28 innerHTML: text
.replace(RegExp($.regExpEscape(input
.trim()), "gi"), "<mark>$&</mark>"),
29 "aria-selected": "false"
32 replace: function (text
) {
33 this.input
.value
= text
;
39 // Create necessary elements
41 this.container
= $.create("div", {
42 className: "awesomplete",
46 this.ul
= $.create("ul", {
48 inside: this.container
51 this.status
= $.create("span", {
52 className: "visually-hidden",
54 "aria-live": "assertive",
55 "aria-relevant": "additions",
56 inside: this.container
62 "input": this.evaluate
.bind(this),
63 "blur": this.close
.bind(this),
64 "keydown": function(evt
) {
67 // If the dropdown `ul` is in view, then act on keydown for the following keys:
68 // Enter / Esc / Up / Down
70 if (c
=== 13 && me
.selected
) { // Enter
74 else if (c
=== 27) { // Esc
77 else if (c
=== 38 || c
=== 40) { // Down/Up arrow
79 me
[c
=== 38? "previous" : "next"]();
85 $.bind(this.input
.form
, {"submit": this.close
.bind(this)});
87 $.bind(this.ul
, {"mousedown": function(evt
) {
92 while (li
&& !/li/i.test(li
.nodeName
)) {
102 if (this.input
.hasAttribute("list")) {
103 this.list
= "#" + input
.getAttribute("list");
104 input
.removeAttribute("list");
107 this.list
= this.input
.getAttribute("data-list") || o
.list
|| [];
115 if (Array
.isArray(list
)) {
118 else if (typeof list
=== "string" && list
.indexOf(",") > -1) {
119 this._list
= list
.split(/\s*,\s*/);
121 else { // Element or CSS selector
124 if (list
&& list
.children
) {
125 this._list
= slice
.apply(list
.children
).map(function (el
) {
126 return el
.innerHTML
.trim();
131 if (document
.activeElement
=== this.input
) {
137 return this.index
> -1;
141 return this.ul
&& this.ul
.getAttribute("hidden") == null;
145 this.ul
.setAttribute("hidden", "");
148 $.fire(this.input
, "awesomplete-close");
152 this.ul
.removeAttribute("hidden");
154 if (this.autoFirst
&& this.index
=== -1) {
158 $.fire(this.input
, "awesomplete-open");
162 var count
= this.ul
.children
.length
;
164 this.goto(this.index
< count
- 1? this.index
+ 1 : -1);
167 previous: function () {
168 var count
= this.ul
.children
.length
;
170 this.goto(this.selected
? this.index
- 1 : count
- 1);
173 // Should not be used, highlights specific item without any checks!
175 var lis
= this.ul
.children
;
178 lis
[this.index
].setAttribute("aria-selected", "false");
183 if (i
> -1 && lis
.length
> 0) {
184 lis
[i
].setAttribute("aria-selected", "true");
185 this.status
.textContent
= lis
[i
].textContent
;
188 $.fire(this.input
, "awesomplete-highlight");
191 select: function (selected
) {
192 selected
= selected
|| this.ul
.children
[this.index
];
197 $.fire(this.input
, "awesomplete-select", {
198 text: selected
.textContent
,
199 preventDefault: function () {
205 this.replace(selected
.textContent
);
207 $.fire(this.input
, "awesomplete-selectcomplete");
212 evaluate: function() {
214 var value
= this.input
.value
;
216 if (value
.length
>= this.minChars
&& this._list
.length
> 0) {
218 // Populate list with options that match
219 this.ul
.innerHTML
= "";
222 .filter(function(item
) {
223 return me
.filter(item
, value
);
226 .every(function(text
, i
) {
227 me
.ul
.appendChild(me
.item(text
, value
));
229 return i
< me
.maxItems
- 1;
232 if (this.ul
.children
.length
=== 0) {
244 // Static methods/properties
248 _
.FILTER_CONTAINS = function (text
, input
) {
249 return RegExp($.regExpEscape(input
.trim()), "i").test(text
);
252 _
.FILTER_STARTSWITH = function (text
, input
) {
253 return RegExp("^" + $.regExpEscape(input
.trim()), "i").test(text
);
256 _
.SORT_BYLENGTH = function (a
, b
) {
257 if (a
.length
!== b
.length
) {
258 return a
.length
- b
.length
;
261 return a
< b
? -1 : 1;
266 function configure(properties
, o
) {
267 for (var i
in properties
) {
268 var initial
= properties
[i
],
269 attrValue
= this.input
.getAttribute("data-" + i
.toLowerCase());
271 if (typeof initial
=== "number") {
272 this[i
] = +attrValue
;
274 else if (initial
=== false) { // Boolean options must be false by default anyway
275 this[i
] = attrValue
!== null;
277 else if (initial
instanceof Function
) {
284 this[i
] = this[i
] || o
[i
] || initial
;
290 var slice
= Array
.prototype.slice
;
292 function $(expr
, con
) {
293 return typeof expr
=== "string"? (con
|| document
).querySelector(expr
) : expr
|| null;
296 function $$(expr
, con
) {
297 return slice
.call((con
|| document
).querySelectorAll(expr
));
300 $.create = function(tag
, o
) {
301 var element
= document
.createElement(tag
);
306 if (i
=== "inside") {
307 $(val
).appendChild(element
);
309 else if (i
=== "around") {
311 ref
.parentNode
.insertBefore(element
, ref
);
312 element
.appendChild(ref
);
314 else if (i
in element
) {
318 element
.setAttribute(i
, val
);
325 $.bind = function(element
, o
) {
327 for (var event
in o
) {
328 var callback
= o
[event
];
330 event
.split(/\s+/).forEach(function (event
) {
331 element
.addEventListener(event
, callback
);
337 $.fire = function(target
, type
, properties
) {
338 var evt
= document
.createEvent("HTMLEvents");
340 evt
.initEvent(type
, true, true );
342 for (var j
in properties
) {
343 evt
[j
] = properties
[j
];
346 target
.dispatchEvent(evt
);
349 $.regExpEscape = function (s
) {
350 return s
.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
356 $$("input.awesomplete").forEach(function (input
) {
357 new Awesomplete(input
);
361 // Are we in a browser? Check for Document constructor
362 if (typeof Document
!== 'undefined') {
363 // DOM already loaded?
364 if (document
.readyState
!== "loading") {
369 document
.addEventListener("DOMContentLoaded", init
);
376 // Make sure to export Awesomplete on self when in a browser
377 if (typeof self
!== 'undefined') {
378 self
.Awesomplete
= _
;
381 // Expose Awesomplete as a CJS module
382 if (typeof exports
=== 'object') {