root/kladr/kladr/auto_suggest.js

Revision 62, 12.5 kB (checked in by teiko, 4 years ago)

Автоподстановка первого варианта из списка предложенных в строку пользователя

Line 
1 REQUEST_INTERVAL = 200;
2 /* Autosuggest Textbox written by Nicholas C. Zakas
3    Taked from http://www.webreference.com/programming/javascript/ncz/ */
4 /*   Функциональность несколько расширена: добавлена фильтрация нажимаемых
5    клавиш, Ajax-обмен с сервером предлагаемых значений, Ajax-проверка не
6    является ли слово заблокированным в базе*/
7 /**
8  * An autosuggest textbox control.
9  * @class
10  * @scope public
11  */
12 function AutoSuggestControl(oTextbox /*:HTMLInputElement*/,
13                             oProvider /*:SuggestionProvider*/) {
14    
15     /**
16      * The currently selected suggestions.
17      * @scope private
18      */   
19     this.cur /*:int*/ = -1;
20
21     /**
22      * The dropdown list layer.
23      * @scope private
24      */
25     this.layer = null;
26    
27     /**
28      * Suggestion provider for the autosuggest feature.
29      * @scope private.
30      */
31     this.provider /*:SuggestionProvider*/ = oProvider;
32    
33     /**
34      * The textbox to capture.
35      * @scope private
36      */
37     this.textbox /*:HTMLInputElement*/ = oTextbox;
38     this.currentNodes = [];
39     this.selectedNode = null;
40     this.pressTimer = null;
41     //initialize the control
42     this.init();
43 }
44
45 /**
46  * Autosuggests one or more suggestions for what the user has typed.
47  * If no suggestions are passed in, then no autosuggest occurs.
48  * @scope private
49  * @param aSuggestions An array of suggestion strings.
50  * @param bTypeAhead If the control should provide a type ahead suggestion.
51  */
52 AutoSuggestControl.prototype.autosuggest = function (aSuggestions /*:Array*/,
53                                                      bTypeAhead /*:boolean*/) {
54     //make sure there's at least one suggestion
55     if (aSuggestions.length > 0) {
56         if (bTypeAhead) {
57            this.typeAhead(aSuggestions[0]);
58         }
59         this.showSuggestions(aSuggestions);
60     } else {
61         this.hideSuggestions();
62     }
63     this.currentNodes = aSuggestions;
64 };
65
66 /**
67  * Creates the dropdown layer to display multiple suggestions.
68  * @scope private
69  */
70 AutoSuggestControl.prototype.createDropDown = function () {
71
72     var oThis = this;
73
74     //create the layer and assign styles
75     this.layer = document.createElement("div");
76     this.layer.className = "suggestions";
77     this.layer.style.visibility = "hidden";
78     this.layer.style.width = this.textbox.offsetWidth;
79    
80     //when the user clicks on the a suggestion, get the text (innerHTML)
81     //and place it into a textbox
82     this.layer.onmousedown =
83     this.layer.onmouseup =
84     this.layer.onmouseover = function (oEvent) {
85         oEvent = oEvent || window.event;
86         oTarget = oEvent.target || oEvent.srcElement;
87
88         if (oEvent.type == "mousedown") {
89             oThis.textbox.value = oTarget.firstChild.nodeValue;
90             var cSuggestionNodes = oThis.layer.childNodes;
91             for (var i=0;i<cSuggestionNodes.length;i++)
92                 if (cSuggestionNodes[i] == oTarget) break;
93             if (i==cSuggestionNodes.length) oThis.selectedNode = null;
94             else oThis.selectedNode = oThis.currentNodes[i];
95             oThis.updateInputs();
96             //oThis.selectedNode = oThis.currentNodes[this.cur];
97             //document.getElementById("kladr_regionType").innerHTML = oThis.selectedNode.id;
98             oThis.hideSuggestions();
99         } else if (oEvent.type == "mouseover") {
100             oThis.highlightSuggestion(oTarget);
101         } else {
102             oThis.textbox.focus();
103         }
104     };
105    
106    
107     document.body.appendChild(this.layer);
108 };
109
110 /**
111  * Gets the left coordinate of the textbox.
112  * @scope private
113  * @return The left coordinate of the textbox in pixels.
114  */
115 AutoSuggestControl.prototype.getLeft = function () /*:int*/ {
116
117     var oNode = this.textbox;
118     var iLeft = 0;
119    
120     while(oNode.tagName != "BODY") {
121         iLeft += oNode.offsetLeft;
122         oNode = oNode.offsetParent;       
123     }
124    
125     return iLeft;
126 };
127
128 /**
129  * Gets the top coordinate of the textbox.
130  * @scope private
131  * @return The top coordinate of the textbox in pixels.
132  */
133 AutoSuggestControl.prototype.getTop = function () /*:int*/ {
134
135     var oNode = this.textbox;
136     var iTop = 0;
137    
138     while(oNode.tagName != "BODY") {
139         iTop += oNode.offsetTop;
140         oNode = oNode.offsetParent;
141     }
142    
143     return iTop;
144 };
145
146 AutoSuggestControl.prototype.getValue = function() {
147     return this.textbox.value;
148 }
149
150 /**
151  * Handles three keydown events.
152  * @scope private
153  * @param oEvent The event object for the keydown event.
154  */
155 AutoSuggestControl.prototype.handleKeyDown = function (oEvent /*:Event*/) {
156     switch(oEvent.keyCode) {
157         case 38: //up arrow
158             this.previousSuggestion();
159             break;
160         case 40: //down arrow
161             this.nextSuggestion();
162             break;
163         case 13: //enter
164             this.group.focusNext(this);
165         case 9:  //tab
166         case 27: //esc
167             this.hideSuggestions();
168             break;
169     }
170
171 };
172
173 /**
174  * Handles keyup events.
175  * @scope private
176  * @param oEvent The event object for the keyup event.
177  */
178 AutoSuggestControl.prototype.handleKeyUp = function (oEvent /*:Event*/) {
179     var oThis = this;
180     function onTimer() {
181         oThis.provider.requestSuggestions(oThis, true);
182     }
183     var iKeyCode = oEvent.keyCode
184     //for backspace (8) and delete (46), shows suggestions without typeahead
185     if (iKeyCode == 8 || iKeyCode == 46) {
186         this.selectedNode = null;
187         clearTimeout(this.requestTimer);
188         this.cur = -1;
189         this.requestTimer = setTimeout(onTimer, REQUEST_INTERVAL);
190     //make sure not to interfere with non-character keys
191     } else if (iKeyCode>0 && iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode < 46) || (iKeyCode >= 112 && iKeyCode <= 123)) {
192         //ignore
193 //      alert(iKeyCode)
194     } else {
195         clearTimeout(this.requestTimer);
196         this.cur = -1;
197         this.requestTimer = setTimeout(onTimer, REQUEST_INTERVAL);
198         //request suggestions from the suggestion provider with typeahead
199         this.selectedNode = null;
200         this.updateInputs();
201     }
202 };
203
204 /**
205  * Hides the suggestion dropdown.
206  * @scope private
207  */
208 AutoSuggestControl.prototype.hideSuggestions = function () {
209     this.layer.style.visibility = "hidden";
210 };
211
212 /**
213  * Highlights the given node in the suggestions dropdown.
214  * @scope private
215  * @param oSuggestionNode The node representing a suggestion in the dropdown.
216  */
217 AutoSuggestControl.prototype.highlightSuggestion = function (oSuggestionNode) {
218    
219     for (var i=0; i < this.layer.childNodes.length; i++) {
220         var oNode = this.layer.childNodes[i];
221         if (oNode == oSuggestionNode) {
222             oNode.className = "current"
223         } else if (oNode.className == "current") {
224             oNode.className = "";
225         }
226     }
227 };
228
229 /**
230  * Initializes the textbox with event handlers for
231  * auto suggest functionality.
232  * @scope private
233  */
234 AutoSuggestControl.prototype.init = function () {
235
236     //save a reference to this object
237     var oThis = this;
238    
239     //assign the onkeyup event handler
240 //    this.textbox.setAttribute("autocomplete", "off");
241     this.textbox.onkeyup = function (oEvent) {
242    
243         //check for the proper location of the event object
244         if (!oEvent) {
245             oEvent = window.event;
246         }   
247        
248         //call the handleKeyUp() method with the event object
249         oThis.handleKeyUp(oEvent);
250     };
251    
252     // filtering
253     this.textbox.onkeypress = function (oEvent) {
254         if (!oEvent) oEvent = window.event;
255         var keyCode = 0;
256         if (oEvent.which) keyCode = oEvent.which;
257         else if (oEvent.keyCode) keyCode = oEvent.keyCode;
258         if (keyCode!=0) {
259             if (oThis.group.filter)
260                 if (!oThis.group.filter(keyCode, oEvent.charCode)) {
261                     if (oEvent.preventDefault) oEvent.preventDefault();
262                     else oEvent.returnValue = false;
263                 }
264         }
265     }
266     //assign onkeydown event handler
267     this.textbox.onkeydown = function (oEvent) {
268         //check for the proper location of the event object
269         if (!oEvent) {
270             oEvent = window.event;
271         }   
272        
273         //call the handleKeyDown() method with the event object
274         oThis.handleKeyDown(oEvent);
275     };
276    
277     //create the suggestions dropdown
278     this.createDropDown();
279 };
280
281 /**
282  * Highlights the next suggestion in the dropdown and
283  * places the suggestion into the textbox.
284  * @scope private
285  */
286 AutoSuggestControl.prototype.nextSuggestion = function () {
287     var cSuggestionNodes = this.layer.childNodes;
288
289     if (cSuggestionNodes.length > 0 && this.cur < cSuggestionNodes.length-1) {
290         var oNode = cSuggestionNodes[++this.cur];
291         this.highlightSuggestion(oNode);
292         this.textbox.value = oNode.firstChild.nodeValue;
293         this.selectedNode = this.currentNodes[this.cur];
294         this.updateInputs();
295     }
296 };
297
298 /**
299  * Highlights the previous suggestion in the dropdown and
300  * places the suggestion into the textbox.
301  * @scope private
302  */
303 AutoSuggestControl.prototype.previousSuggestion = function () {
304     var cSuggestionNodes = this.layer.childNodes;
305
306     if (cSuggestionNodes.length > 0 && this.cur > 0) {
307         var oNode = cSuggestionNodes[--this.cur];
308         this.highlightSuggestion(oNode);
309         this.textbox.value = oNode.firstChild.nodeValue;
310         this.selectedNode = this.currentNodes[this.cur];
311         this.updateInputs();
312     }
313 };
314
315 AutoSuggestControl.prototype.updateInputs = function() {
316     if (!this.group) return;
317     if (this.selectedNode) this.group.showNextInput(this);
318     else this.group.hideNextInputs(this);
319 }
320 AutoSuggestControl.prototype.showInputs = function() {
321     if (!this.group) return;
322     this.group.showNextInput(this);
323 }
324 AutoSuggestControl.prototype.needAutocomplete = function() {
325     if (!this.group) return false;
326     return this.group.needAutoComplete(this);
327 }
328
329 /**
330  * Selects a range of text in the textbox.
331  * @scope public
332  * @param iStart The start index (base 0) of the selection.
333  * @param iLength The number of characters to select.
334  */
335 AutoSuggestControl.prototype.selectRange = function (iStart /*:int*/, iLength /*:int*/) {
336
337     //use text ranges for Internet Explorer
338     if (this.textbox.createTextRange) {
339         var oRange = this.textbox.createTextRange();
340         oRange.moveStart("character", iStart);
341         oRange.moveEnd("character", iLength - this.textbox.value.length);     
342         oRange.select();
343        
344     //use setSelectionRange() for Mozilla
345     } else if (this.textbox.setSelectionRange) {
346         this.textbox.setSelectionRange(iStart, iLength);
347     }     
348
349     //set focus back to the textbox
350     this.textbox.focus();     
351 };
352
353 /**
354  * Builds the suggestion layer contents, moves it into position,
355  * and displays the layer.
356  * @scope private
357  * @param aSuggestions An array of suggestions for the control.
358  */
359 AutoSuggestControl.prototype.showSuggestions = function (aSuggestions /*:Array*/) {
360    
361     var oDiv = null;
362     this.layer.innerHTML = "";  //clear contents of the layer
363    
364     for (var i=0; i < aSuggestions.length; i++) {
365         oDiv = document.createElement("div");
366         if (typeof aSuggestions[i] == "string")
367             oDiv.appendChild(document.createTextNode(aSuggestions[i]));
368         else {
369             var s = aSuggestions[i];
370             var text = this.provider.fieldToString(aSuggestions[i]);
371             oDiv.appendChild(document.createTextNode(text));
372         }
373         this.layer.appendChild(oDiv);
374     }
375    
376     this.layer.style.left = this.getLeft() + "px";
377     this.layer.style.top = (this.getTop()+this.textbox.offsetHeight) + "px";
378     this.layer.style.visibility = "visible";
379
380 };
381
382 /**
383  * Inserts a suggestion into the textbox, highlighting the
384  * suggested part of the text.
385  * @scope private
386  * @param sSuggestion The suggestion for the textbox.
387  */
388 AutoSuggestControl.prototype.typeAhead = function (sSuggestion /*:String*/) {
389     //check for support of typeahead functionality
390     if (this.textbox.createTextRange || this.textbox.setSelectionRange){
391         var iLen = this.textbox.value.length;
392         var text = this.provider.fieldToString(sSuggestion);
393         this.textbox.value = text
394         this.selectRange(iLen, text.length);
395         this.selectedNode = sSuggestion;
396         this.showInputs();
397     }
398 };
Note: See TracBrowser for help on using the browser.