Skip to content

Commit 6b10ea1

Browse files
AutoComplete owns a mapping manager to easy add mapping keyboald
1 parent 08be1d5 commit 6b10ea1

File tree

1 file changed

+187
-97
lines changed

1 file changed

+187
-97
lines changed

src/autocomplete.ts

Lines changed: 187 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,158 @@ interface Params {
1414
Headers: Object;
1515
Limit: number;
1616
Method: string;
17-
ParamName: string;
17+
QueryArg: string;
1818
Url: string;
1919

20+
// Keyboard mapping event
21+
KeyboardMappings: { [name: string]: MappingEvent; };
22+
2023
// Workable elements
2124
DOMResults: Element;
2225
Request: XMLHttpRequest;
2326
Input: Element;
2427

2528
// Workflow methods
26-
_Blur: any;
27-
_EmptyMessage: any;
28-
_Focus: any;
29-
_Limit: any;
30-
_Method: any;
31-
_OnKeyUp: any;
32-
_Open: any;
33-
_ParamName: any;
34-
_Position: any;
35-
_Post: any;
36-
_Pre: any;
37-
_Select: any;
38-
_Url: any;
29+
_Blur: any;
30+
_EmptyMessage: any;
31+
_Focus: any;
32+
_Limit: any;
33+
_Method: any;
34+
_Open: any;
35+
_QueryArg: any;
36+
_Position: any;
37+
_Post: any;
38+
_Pre: any;
39+
_Select: any;
40+
_Url: any;
41+
}
42+
43+
interface MappingCondition {
44+
Not: boolean;
45+
}
46+
47+
interface MappingConditionIs extends MappingCondition {
48+
Is: number;
49+
}
50+
51+
interface MappingConditionRange extends MappingCondition {
52+
From: number;
53+
To: number;
54+
}
55+
56+
enum ConditionOperator { AND, OR };
57+
58+
interface MappingEvent {
59+
Conditions: MappingCondition[];
60+
Callback: any;
61+
Operator: ConditionOperator;
3962
}
4063

4164
// Core
4265
class AutoComplete {
66+
static merge: any = function(): any {
67+
var merge: any = {},
68+
tmp: any;
69+
70+
for (var i = 0; i < arguments.length; i++) {
71+
for (tmp in arguments[i]) {
72+
merge[tmp] = arguments[i][tmp];
73+
}
74+
}
75+
76+
return merge;
77+
};
4378
static defaults: Params = {
4479
EmptyMessage: "No result here",
4580
Headers: {
4681
"Content-type": "application/x-www-form-urlencoded"
4782
},
4883
Limit: 0,
4984
Method: "GET",
50-
ParamName: "q",
85+
QueryArg: "q",
5186
Url: null,
87+
88+
KeyboardMappings: {
89+
"Enter": {
90+
Conditions: [{
91+
Is: 13,
92+
Not: false
93+
}],
94+
Callback: function(event: KeyboardEvent) {
95+
if (this.DOMResults.getAttribute("class").indexOf("open") != -1) {
96+
var liActive = this.DOMResults.querySelector("li.active");
97+
98+
if (liActive !== null) {
99+
this._Select(liActive);
100+
this.DOMResults.setAttribute("class", "autocomplete");
101+
}
102+
}
103+
},
104+
Operator: ConditionOperator.AND
105+
},
106+
"KeyUpAndDown": {
107+
Conditions: [{
108+
Is: 38,
109+
Not: false
110+
},
111+
{
112+
Is: 40,
113+
Not: false
114+
}],
115+
Callback: function(event: KeyboardEvent) {
116+
var first = this.DOMResults.querySelector("li:first-child:not(.locked)"),
117+
active = this.DOMResults.querySelector("li.active");
118+
119+
if (active) {
120+
var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active),
121+
position = currentIndex + (event.keyCode - 39),
122+
lisCount = this.DOMResults.getElementsByTagName("li").length;
123+
124+
if (position < 0) {
125+
position = lisCount - 1;
126+
} else if (position >= lisCount) {
127+
position = 0;
128+
}
129+
130+
active.setAttribute("class", "");
131+
active.parentElement.childNodes.item(position).setAttribute("class", "active");
132+
} else if (first) {
133+
first.setAttribute("class", "active");
134+
}
135+
},
136+
Not: false,
137+
Operator: ConditionOperator.OR
138+
},
139+
"AlphaNum": {
140+
Conditions: [{
141+
Is: 13,
142+
Not: true
143+
}, {
144+
From: 35,
145+
To: 40,
146+
Not: true
147+
}],
148+
Callback: function(event: KeyboardEvent) {
149+
var oldValue = this.Input.getAttribute("data-autocomplete-old-value"),
150+
currentValue = this._Pre();
151+
152+
if (currentValue !== "") {
153+
if (!oldValue || currentValue != oldValue) {
154+
this.DOMResults.setAttribute("class", "autocomplete open");
155+
}
156+
157+
AutoComplete.prototype.ajax(this, function() {
158+
if (this.Request.readyState == 4 && this.Request.status == 200) {
159+
if (!this._Post(this.Request.response)) {
160+
this._Open();
161+
}
162+
}
163+
}.bind(this));
164+
}
165+
},
166+
Operator: ConditionOperator.AND
167+
}
168+
},
52169

53170
DOMResults: document.createElement("div"),
54171
Request: null,
@@ -83,14 +200,14 @@ class AutoComplete {
83200

84201
return this.Method;
85202
},
86-
_ParamName: function(): string {
87-
console.log("ParamName", this);
203+
_QueryArg: function(): string {
204+
console.log("QueryArg", this);
88205

89206
if (this.Input.hasAttribute("data-autocomplete-param-name")) {
90207
return this.Input.getAttribute("data-autocomplete-param-name");
91208
}
92209

93-
return this.ParamName;
210+
return this.QueryArg;
94211
},
95212
_Url: function(): string {
96213
console.log("Url", this);
@@ -122,63 +239,12 @@ class AutoComplete {
122239
this.DOMResults.setAttribute("class", "autocomplete open");
123240
}
124241
},
125-
_OnKeyUp: function(event: KeyboardEvent): void {
126-
console.log("OnKeyUp", this, "KeyboardEvent", event);
127-
128-
var first = this.DOMResults.querySelector("li:first-child:not(.locked)"),
129-
input = event.target,
130-
inputValue = this.Pre(),
131-
dataAutocompleteOldValue = this.Input.getAttribute("data-autocomplete-old-value"),
132-
keyCode = event.keyCode,
133-
currentIndex,
134-
position,
135-
lisCount,
136-
liActive;
137-
138-
if (keyCode == 13 && this.DOMResults.getAttribute("class").indexOf("open") != -1) {
139-
liActive = this.DOMResults.querySelector("li.active");
140-
if (liActive !== null) {
141-
this.Select(liActive);
142-
this.DOMResults.setAttribute("class", "autocomplete");
143-
}
144-
}
145-
146-
if (keyCode == 38 || keyCode == 40) {
147-
liActive = this.DOMResults.querySelector("li.active");
148-
149-
if (liActive) {
150-
currentIndex = Array.prototype.indexOf.call(liActive.parentNode.children, liActive);
151-
position = currentIndex + (keyCode - 39);
152-
lisCount = this.DOMResults.getElementsByTagName("li").length;
153-
154-
liActive.setAttribute("class", "");
155-
156-
if (position < 0) {
157-
position = lisCount - 1;
158-
} else if (position >= lisCount) {
159-
position = 0;
160-
}
161-
162-
liActive.parentElement.childNodes.item(position).setAttribute("class", "active");
163-
} else if (first) {
164-
first.setAttribute("class", "active");
165-
}
166-
} else if (keyCode != 13 && (keyCode < 35 || keyCode > 40)) {
167-
if (inputValue && this._Url()) {
168-
if (!dataAutocompleteOldValue || inputValue != dataAutocompleteOldValue) {
169-
this.DOMResults.setAttribute("class", "autocomplete open");
170-
}
171-
172-
AutoComplete.prototype.ajax(this);
173-
}
174-
}
175-
},
176242
_Open: function(): void {
177243
console.log("Open", this);
178244
var params = this;
179245
Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function(li) {
180246
li.onclick = function(event) {
181-
params.Select(event.target);
247+
params._Select(event.target);
182248
};
183249
});
184250
},
@@ -187,7 +253,7 @@ class AutoComplete {
187253
this.DOMResults.setAttribute("class", "autocomplete");
188254
this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.clientWidth + "px;");
189255
},
190-
_Post: function(response): void {
256+
_Post: function(response: string): void {
191257
console.log("Post", this);
192258
try {
193259
response = JSON.parse(response);
@@ -255,12 +321,14 @@ class AutoComplete {
255321
},
256322
_Select: function(item): void {
257323
console.log("Select", this);
258-
this.Input.setAttribute("data-autocomplete-old-value", this.Input.value = item.getAttribute("data-autocomplete-value", item.innerHTML));
324+
325+
this.Input.value = item.getAttribute("data-autocomplete-value", item.innerHTML);
326+
this.Input.setAttribute("data-autocomplete-old-value", this.Input.value);
259327
},
260328
};
261329

262330
// Constructor
263-
constructor(params: Object = {}, selector: any = "[data-autocomplete]"): void {
331+
constructor(params: Object = {}, selector: any = "[data-autocomplete]") {
264332
if (Array.isArray(selector)) {
265333
selector.forEach(function(s: string) {
266334
new AutoComplete(params, s);
@@ -280,7 +348,7 @@ class AutoComplete {
280348

281349
console.log("Selector", selector);
282350

283-
AutoComplete.prototype.create(MergeObject(AutoComplete.defaults, params, {
351+
AutoComplete.prototype.create(AutoComplete.merge(AutoComplete.defaults, params, {
284352
Input: selector,
285353
}));
286354
}
@@ -296,7 +364,7 @@ class AutoComplete {
296364

297365
params.Input.addEventListener("focus", params._Focus.bind(params));
298366

299-
params.Input.addEventListener("keyup", params._OnKeyUp.bind(params));
367+
params.Input.addEventListener("keyup", AutoComplete.prototype.event.bind(null, params));
300368

301369
params.Input.addEventListener("blur", params._Blur.bind(params));
302370
params.Input.addEventListener("position", params._Position.bind(params));
@@ -306,7 +374,47 @@ class AutoComplete {
306374
}
307375
}
308376

309-
ajax(params: Params): void {
377+
event(params: Params, event: KeyboardEvent): void {
378+
console.log("Event", params, "KeyboardEvent", event);
379+
380+
for (name in params.KeyboardMappings) {
381+
var mapping: MappingEvent = AutoComplete.merge({
382+
Operator: ConditionOperator.AND
383+
}, params.KeyboardMappings[name]),
384+
match: boolean = ConditionOperator.AND == mapping.Operator;
385+
386+
mapping.Conditions.forEach(function(condition: MappingCondition) {
387+
if ((match == true && mapping.Operator == ConditionOperator.AND) || (match == false && ConditionOperator.OR)) {
388+
condition = AutoComplete.merge({
389+
Not: false
390+
}, condition);
391+
392+
// For MappingConditionIs object
393+
if (condition.hasOwnProperty("Is")) {
394+
if (condition.Is == event.keyCode) {
395+
match = !condition.Not;
396+
} else {
397+
match = condition.Not;
398+
}
399+
}
400+
// For MappingConditionRange object
401+
else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) {
402+
if (event.keyCode >= condition.From && event.keyCode <= condition.To) {
403+
match = !condition.Not;
404+
} else {
405+
match = condition.Not;
406+
}
407+
}
408+
}
409+
});
410+
411+
if (match == true) {
412+
mapping.Callback.bind(params, event)();
413+
}
414+
};
415+
}
416+
417+
ajax(params: Params, callback: any): void {
310418
console.log("AJAX", params);
311419
if (params.Request) {
312420
params.Request.abort();
@@ -315,7 +423,7 @@ class AutoComplete {
315423
var propertyHeaders = Object.getOwnPropertyNames(params.Headers),
316424
method = params._Method(),
317425
url = params._Url(),
318-
queryParams = params.ParamName + "=" + params._Pre();
426+
queryParams = params.QueryArg + "=" + params._Pre();
319427

320428
if (method.match(/^GET$/i)) {
321429
url += "?" + queryParams;
@@ -328,13 +436,7 @@ class AutoComplete {
328436
params.Request.setRequestHeader(propertyHeaders[i], params.Headers[propertyHeaders[i]]);
329437
}
330438

331-
params.Request.onreadystatechange = function () {
332-
if (params.Request.readyState == 4 && params.Request.status == 200) {
333-
if (!params._Post(params.Request.response)) {
334-
params._Open();
335-
}
336-
}
337-
};
439+
params.Request.onreadystatechange = callback;
338440

339441
params.Request.send(queryParams);
340442
}
@@ -345,22 +447,10 @@ class AutoComplete {
345447
params.Input.removeEventListener("position", params._Position);
346448
params.Input.removeEventListener("focus", params._Focus);
347449
params.Input.removeEventListener("blur", params._Blur);
348-
params.Input.removeEventListener("keyup", params._OnKeyUp);
450+
// params.Input.removeEventListener("keyup", AutoComplete.prototype.event);
349451
params.DOMResults.parentNode.removeChild(params.DOMResults);
350452

351453
// delete(params);
352454
}
353455
}
354456

355-
function MergeObject(): any {
356-
var merge: any = {},
357-
tmp: any;
358-
359-
for (var i = 0; i < arguments.length; i++) {
360-
for (tmp in arguments[i]) {
361-
merge[tmp] = arguments[i][tmp];
362-
}
363-
}
364-
365-
return merge;
366-
}

0 commit comments

Comments
 (0)