Diffstat (limited to 'frontend/beta/js/YUI/autocomplete.js') (more/less context) (ignore whitespace changes)
-rw-r--r-- | frontend/beta/js/YUI/autocomplete.js | 3066 |
1 files changed, 3066 insertions, 0 deletions
diff --git a/frontend/beta/js/YUI/autocomplete.js b/frontend/beta/js/YUI/autocomplete.js new file mode 100644 index 0000000..a5722cc --- a/dev/null +++ b/frontend/beta/js/YUI/autocomplete.js | |||
@@ -0,0 +1,3066 @@ | |||
1 | /* | ||
2 | Copyright (c) 2006, Yahoo! Inc. All rights reserved. | ||
3 | Code licensed under the BSD License: | ||
4 | http://developer.yahoo.com/yui/license.txt | ||
5 | version: 0.12.0 | ||
6 | */ | ||
7 | |||
8 | /** | ||
9 | * The AutoComplete control provides the front-end logic for text-entry suggestion and | ||
10 | * completion functionality. | ||
11 | * | ||
12 | * @module autocomplete | ||
13 | * @requires yahoo, dom, event, datasource | ||
14 | * @optional animation, connection, json | ||
15 | * @namespace YAHOO.widget | ||
16 | * @title AutoComplete Widget | ||
17 | */ | ||
18 | |||
19 | /****************************************************************************/ | ||
20 | /****************************************************************************/ | ||
21 | /****************************************************************************/ | ||
22 | |||
23 | /** | ||
24 | * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML | ||
25 | * auto completion widget. Some key features: | ||
26 | * <ul> | ||
27 | * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li> | ||
28 | * <li>The drop down container can "roll down" or "fly out" via configurable | ||
29 | * animation</li> | ||
30 | * <li>UI look-and-feel customizable through CSS, including container | ||
31 | * attributes, borders, position, fonts, etc</li> | ||
32 | * </ul> | ||
33 | * | ||
34 | * @class AutoComplete | ||
35 | * @constructor | ||
36 | * @param elInput {HTMLElement} DOM element reference of an input field | ||
37 | * @param elInput {String} String ID of an input field | ||
38 | * @param elContainer {HTMLElement} DOM element reference of an existing DIV | ||
39 | * @param elContainer {String} String ID of an existing DIV | ||
40 | * @param oDataSource {Object} Instance of YAHOO.widget.DataSource for query/results | ||
41 | * @param oConfigs {Object} (optional) Object literal of configuration params | ||
42 | */ | ||
43 | YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) { | ||
44 | if(elInput && elContainer && oDataSource) { | ||
45 | // Validate DataSource | ||
46 | if (oDataSource && (oDataSource instanceof YAHOO.widget.DataSource)) { | ||
47 | this.dataSource = oDataSource; | ||
48 | } | ||
49 | else { | ||
50 | return; | ||
51 | } | ||
52 | |||
53 | // Validate input element | ||
54 | if(YAHOO.util.Dom.inDocument(elInput)) { | ||
55 | if(typeof elInput == "string") { | ||
56 | this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput; | ||
57 | this._oTextbox = document.getElementById(elInput); | ||
58 | } | ||
59 | else { | ||
60 | this._sName = (elInput.id) ? | ||
61 | "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id: | ||
62 | "instance" + YAHOO.widget.AutoComplete._nIndex; | ||
63 | this._oTextbox = elInput; | ||
64 | } | ||
65 | } | ||
66 | else { | ||
67 | return; | ||
68 | } | ||
69 | |||
70 | // Validate container element | ||
71 | if(YAHOO.util.Dom.inDocument(elContainer)) { | ||
72 | if(typeof elContainer == "string") { | ||
73 | this._oContainer = document.getElementById(elContainer); | ||
74 | } | ||
75 | else { | ||
76 | this._oContainer = elContainer; | ||
77 | } | ||
78 | if(this._oContainer.style.display == "none") { | ||
79 | } | ||
80 | } | ||
81 | else { | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | // Set any config params passed in to override defaults | ||
86 | if (typeof oConfigs == "object") { | ||
87 | for(var sConfig in oConfigs) { | ||
88 | if (sConfig) { | ||
89 | this[sConfig] = oConfigs[sConfig]; | ||
90 | } | ||
91 | } | ||
92 | } | ||
93 | |||
94 | // Initialization sequence | ||
95 | this._initContainer(); | ||
96 | this._initProps(); | ||
97 | this._initList(); | ||
98 | this._initContainerHelpers(); | ||
99 | |||
100 | // Set up events | ||
101 | var oSelf = this; | ||
102 | var oTextbox = this._oTextbox; | ||
103 | // Events are actually for the content module within the container | ||
104 | var oContent = this._oContainer._oContent; | ||
105 | |||
106 | // Dom events | ||
107 | YAHOO.util.Event.addListener(oTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf); | ||
108 | YAHOO.util.Event.addListener(oTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf); | ||
109 | YAHOO.util.Event.addListener(oTextbox,"focus",oSelf._onTextboxFocus,oSelf); | ||
110 | YAHOO.util.Event.addListener(oTextbox,"blur",oSelf._onTextboxBlur,oSelf); | ||
111 | YAHOO.util.Event.addListener(oContent,"mouseover",oSelf._onContainerMouseover,oSelf); | ||
112 | YAHOO.util.Event.addListener(oContent,"mouseout",oSelf._onContainerMouseout,oSelf); | ||
113 | YAHOO.util.Event.addListener(oContent,"scroll",oSelf._onContainerScroll,oSelf); | ||
114 | YAHOO.util.Event.addListener(oContent,"resize",oSelf._onContainerResize,oSelf); | ||
115 | if(oTextbox.form) { | ||
116 | YAHOO.util.Event.addListener(oTextbox.form,"submit",oSelf._onFormSubmit,oSelf); | ||
117 | } | ||
118 | YAHOO.util.Event.addListener(oTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf); | ||
119 | |||
120 | // Custom events | ||
121 | this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this); | ||
122 | this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this); | ||
123 | this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this); | ||
124 | this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this); | ||
125 | this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this); | ||
126 | this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this); | ||
127 | this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this); | ||
128 | this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this); | ||
129 | this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this); | ||
130 | this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this); | ||
131 | this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this); | ||
132 | this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this); | ||
133 | this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this); | ||
134 | this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this); | ||
135 | this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this); | ||
136 | this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this); | ||
137 | |||
138 | // Finish up | ||
139 | oTextbox.setAttribute("autocomplete","off"); | ||
140 | YAHOO.widget.AutoComplete._nIndex++; | ||
141 | } | ||
142 | // Required arguments were not found | ||
143 | else { | ||
144 | } | ||
145 | }; | ||
146 | |||
147 | ///////////////////////////////////////////////////////////////////////////// | ||
148 | // | ||
149 | // Public member variables | ||
150 | // | ||
151 | ///////////////////////////////////////////////////////////////////////////// | ||
152 | |||
153 | /** | ||
154 | * The DataSource object that encapsulates the data used for auto completion. | ||
155 | * This object should be an inherited object from YAHOO.widget.DataSource. | ||
156 | * | ||
157 | * @property dataSource | ||
158 | * @type Object | ||
159 | */ | ||
160 | YAHOO.widget.AutoComplete.prototype.dataSource = null; | ||
161 | |||
162 | /** | ||
163 | * Number of characters that must be entered before querying for results. A negative value | ||
164 | * effectively turns off the widget. A value of 0 allows queries of null or empty string | ||
165 | * values. | ||
166 | * | ||
167 | * @property minQueryLength | ||
168 | * @type Number | ||
169 | * @default 1 | ||
170 | */ | ||
171 | YAHOO.widget.AutoComplete.prototype.minQueryLength = 1; | ||
172 | |||
173 | /** | ||
174 | * Maximum number of results to display in results container. | ||
175 | * | ||
176 | * @property maxResultsDisplayed | ||
177 | * @type Number | ||
178 | * @default 10 | ||
179 | */ | ||
180 | YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10; | ||
181 | |||
182 | /** | ||
183 | * Number of seconds to delay before submitting a query request. If a query | ||
184 | * request is received before a previous one has completed its delay, the | ||
185 | * previous request is cancelled and the new request is set to the delay. | ||
186 | * | ||
187 | * @property queryDelay | ||
188 | * @type Number | ||
189 | * @default 0.5 | ||
190 | */ | ||
191 | YAHOO.widget.AutoComplete.prototype.queryDelay = 0.5; | ||
192 | |||
193 | /** | ||
194 | * Class name of a highlighted item within results container. | ||
195 | * | ||
196 | * @property highlighClassName | ||
197 | * @type String | ||
198 | * @default "yui-ac-highlight" | ||
199 | */ | ||
200 | YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight"; | ||
201 | |||
202 | /** | ||
203 | * Class name of a pre-highlighted item within results container. | ||
204 | * | ||
205 | * @property prehighlightClassName | ||
206 | * @type String | ||
207 | */ | ||
208 | YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null; | ||
209 | |||
210 | /** | ||
211 | * Query delimiter. A single character separator for multiple delimited | ||
212 | * selections. Multiple delimiter characteres may be defined as an array of | ||
213 | * strings. A null value or empty string indicates that query results cannot | ||
214 | * be delimited. This feature is not recommended if you need forceSelection to | ||
215 | * be true. | ||
216 | * | ||
217 | * @property delimChar | ||
218 | * @type String | String[] | ||
219 | */ | ||
220 | YAHOO.widget.AutoComplete.prototype.delimChar = null; | ||
221 | |||
222 | /** | ||
223 | * Whether or not the first item in results container should be automatically highlighted | ||
224 | * on expand. | ||
225 | * | ||
226 | * @property autoHighlight | ||
227 | * @type Boolean | ||
228 | * @default true | ||
229 | */ | ||
230 | YAHOO.widget.AutoComplete.prototype.autoHighlight = true; | ||
231 | |||
232 | /** | ||
233 | * Whether or not the input field should be automatically updated | ||
234 | * with the first query result as the user types, auto-selecting the substring | ||
235 | * that the user has not typed. | ||
236 | * | ||
237 | * @property typeAhead | ||
238 | * @type Boolean | ||
239 | * @default false | ||
240 | */ | ||
241 | YAHOO.widget.AutoComplete.prototype.typeAhead = false; | ||
242 | |||
243 | /** | ||
244 | * Whether or not to animate the expansion/collapse of the results container in the | ||
245 | * horizontal direction. | ||
246 | * | ||
247 | * @property animHoriz | ||
248 | * @type Boolean | ||
249 | * @default false | ||
250 | */ | ||
251 | YAHOO.widget.AutoComplete.prototype.animHoriz = false; | ||
252 | |||
253 | /** | ||
254 | * Whether or not to animate the expansion/collapse of the results container in the | ||
255 | * vertical direction. | ||
256 | * | ||
257 | * @property animVert | ||
258 | * @type Boolean | ||
259 | * @default true | ||
260 | */ | ||
261 | YAHOO.widget.AutoComplete.prototype.animVert = true; | ||
262 | |||
263 | /** | ||
264 | * Speed of container expand/collapse animation, in seconds.. | ||
265 | * | ||
266 | * @property animSpeed | ||
267 | * @type Number | ||
268 | * @default 0.3 | ||
269 | */ | ||
270 | YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3; | ||
271 | |||
272 | /** | ||
273 | * Whether or not to force the user's selection to match one of the query | ||
274 | * results. Enabling this feature essentially transforms the input field into a | ||
275 | * <select> field. This feature is not recommended with delimiter character(s) | ||
276 | * defined. | ||
277 | * | ||
278 | * @property forceSelection | ||
279 | * @type Boolean | ||
280 | * @default false | ||
281 | */ | ||
282 | YAHOO.widget.AutoComplete.prototype.forceSelection = false; | ||
283 | |||
284 | /** | ||
285 | * Whether or not to allow browsers to cache user-typed input in the input | ||
286 | * field. Disabling this feature will prevent the widget from setting the | ||
287 | * autocomplete="off" on the input field. When autocomplete="off" | ||
288 | * and users click the back button after form submission, user-typed input can | ||
289 | * be prefilled by the browser from its cache. This caching of user input may | ||
290 | * not be desired for sensitive data, such as credit card numbers, in which | ||
291 | * case, implementers should consider setting allowBrowserAutocomplete to false. | ||
292 | * | ||
293 | * @property allowBrowserAutocomplete | ||
294 | * @type Boolean | ||
295 | * @default true | ||
296 | */ | ||
297 | YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true; | ||
298 | |||
299 | /** | ||
300 | * Whether or not the results container should always be displayed. | ||
301 | * Enabling this feature displays the container when the widget is instantiated | ||
302 | * and prevents the toggling of the container to a collapsed state. | ||
303 | * | ||
304 | * @property alwaysShowContainer | ||
305 | * @type Boolean | ||
306 | * @default false | ||
307 | */ | ||
308 | YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false; | ||
309 | |||
310 | /** | ||
311 | * Whether or not to use an iFrame to layer over Windows form elements in | ||
312 | * IE. Set to true only when the results container will be on top of a | ||
313 | * <select> field in IE and thus exposed to the IE z-index bug (i.e., | ||
314 | * 5.5 < IE < 7). | ||
315 | * | ||
316 | * @property useIFrame | ||
317 | * @type Boolean | ||
318 | * @default false | ||
319 | */ | ||
320 | YAHOO.widget.AutoComplete.prototype.useIFrame = false; | ||
321 | |||
322 | /** | ||
323 | * Whether or not the results container should have a shadow. | ||
324 | * | ||
325 | * @property useShadow | ||
326 | * @type Boolean | ||
327 | * @default false | ||
328 | */ | ||
329 | YAHOO.widget.AutoComplete.prototype.useShadow = false; | ||
330 | |||
331 | ///////////////////////////////////////////////////////////////////////////// | ||
332 | // | ||
333 | // Public methods | ||
334 | // | ||
335 | ///////////////////////////////////////////////////////////////////////////// | ||
336 | |||
337 | /** | ||
338 | * Public accessor to the unique name of the AutoComplete instance. | ||
339 | * | ||
340 | * @method toString | ||
341 | * @return {String} Unique name of the AutoComplete instance. | ||
342 | */ | ||
343 | YAHOO.widget.AutoComplete.prototype.toString = function() { | ||
344 | return "AutoComplete " + this._sName; | ||
345 | }; | ||
346 | |||
347 | /** | ||
348 | * Returns true if container is in an expanded state, false otherwise. | ||
349 | * | ||
350 | * @method isContainerOpen | ||
351 | * @return {Boolean} Returns true if container is in an expanded state, false otherwise. | ||
352 | */ | ||
353 | YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() { | ||
354 | return this._bContainerOpen; | ||
355 | }; | ||
356 | |||
357 | /** | ||
358 | * Public accessor to the internal array of DOM <li> elements that | ||
359 | * display query results within the results container. | ||
360 | * | ||
361 | * @method getListItems | ||
362 | * @return {HTMLElement[]} Array of <li> elements within the results container. | ||
363 | */ | ||
364 | YAHOO.widget.AutoComplete.prototype.getListItems = function() { | ||
365 | return this._aListItems; | ||
366 | }; | ||
367 | |||
368 | /** | ||
369 | * Public accessor to the data held in an <li> element of the | ||
370 | * results container. | ||
371 | * | ||
372 | * @method getListItemData | ||
373 | * @return {Object | Array} Object or array of result data or null | ||
374 | */ | ||
375 | YAHOO.widget.AutoComplete.prototype.getListItemData = function(oListItem) { | ||
376 | if(oListItem._oResultData) { | ||
377 | return oListItem._oResultData; | ||
378 | } | ||
379 | else { | ||
380 | return false; | ||
381 | } | ||
382 | }; | ||
383 | |||
384 | /** | ||
385 | * Sets HTML markup for the results container header. This markup will be | ||
386 | * inserted within a <div> tag with a class of "ac_hd". | ||
387 | * | ||
388 | * @method setHeader | ||
389 | * @param sHeader {String} HTML markup for results container header. | ||
390 | */ | ||
391 | YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) { | ||
392 | if(sHeader) { | ||
393 | if(this._oContainer._oContent._oHeader) { | ||
394 | this._oContainer._oContent._oHeader.innerHTML = sHeader; | ||
395 | this._oContainer._oContent._oHeader.style.display = "block"; | ||
396 | } | ||
397 | } | ||
398 | else { | ||
399 | this._oContainer._oContent._oHeader.innerHTML = ""; | ||
400 | this._oContainer._oContent._oHeader.style.display = "none"; | ||
401 | } | ||
402 | }; | ||
403 | |||
404 | /** | ||
405 | * Sets HTML markup for the results container footer. This markup will be | ||
406 | * inserted within a <div> tag with a class of "ac_ft". | ||
407 | * | ||
408 | * @method setFooter | ||
409 | * @param sFooter {String} HTML markup for results container footer. | ||
410 | */ | ||
411 | YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) { | ||
412 | if(sFooter) { | ||
413 | if(this._oContainer._oContent._oFooter) { | ||
414 | this._oContainer._oContent._oFooter.innerHTML = sFooter; | ||
415 | this._oContainer._oContent._oFooter.style.display = "block"; | ||
416 | } | ||
417 | } | ||
418 | else { | ||
419 | this._oContainer._oContent._oFooter.innerHTML = ""; | ||
420 | this._oContainer._oContent._oFooter.style.display = "none"; | ||
421 | } | ||
422 | }; | ||
423 | |||
424 | /** | ||
425 | * Sets HTML markup for the results container body. This markup will be | ||
426 | * inserted within a <div> tag with a class of "ac_bd". | ||
427 | * | ||
428 | * @method setBody | ||
429 | * @param sHeader {String} HTML markup for results container body. | ||
430 | */ | ||
431 | YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) { | ||
432 | if(sBody) { | ||
433 | if(this._oContainer._oContent._oBody) { | ||
434 | this._oContainer._oContent._oBody.innerHTML = sBody; | ||
435 | this._oContainer._oContent._oBody.style.display = "block"; | ||
436 | this._oContainer._oContent.style.display = "block"; | ||
437 | } | ||
438 | } | ||
439 | else { | ||
440 | this._oContainer._oContent._oBody.innerHTML = ""; | ||
441 | this._oContainer._oContent.style.display = "none"; | ||
442 | } | ||
443 | this._maxResultsDisplayed = 0; | ||
444 | }; | ||
445 | |||
446 | /** | ||
447 | * Overridable method that converts a result item object into HTML markup | ||
448 | * for display. Return data values are accessible via the oResultItem object, | ||
449 | * and the key return value will always be oResultItem[0]. Markup will be | ||
450 | * displayed within <li> element tags in the container. | ||
451 | * | ||
452 | * @method formatResult | ||
453 | * @param oResultItem {Object} Result item representing one query result. Data is held in an array. | ||
454 | * @param sQuery {String} The current query string. | ||
455 | * @return {String} HTML markup of formatted result data. | ||
456 | */ | ||
457 | YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultItem, sQuery) { | ||
458 | var sResult = oResultItem[0]; | ||
459 | if(sResult) { | ||
460 | return sResult; | ||
461 | } | ||
462 | else { | ||
463 | return ""; | ||
464 | } | ||
465 | }; | ||
466 | |||
467 | /** | ||
468 | * Overridable method called before container expands allows implementers to access data | ||
469 | * and DOM elements. | ||
470 | * | ||
471 | * @method doBeforeExpandContainer | ||
472 | * @return {Boolean} Return true to continue expanding container, false to cancel the expand. | ||
473 | */ | ||
474 | YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(oResultItem, sQuery) { | ||
475 | return true; | ||
476 | }; | ||
477 | |||
478 | /** | ||
479 | * Makes query request to the DataSource. | ||
480 | * | ||
481 | * @method sendQuery | ||
482 | * @param sQuery {String} Query string. | ||
483 | */ | ||
484 | YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) { | ||
485 | this._sendQuery(sQuery); | ||
486 | }; | ||
487 | |||
488 | ///////////////////////////////////////////////////////////////////////////// | ||
489 | // | ||
490 | // Public events | ||
491 | // | ||
492 | ///////////////////////////////////////////////////////////////////////////// | ||
493 | |||
494 | /** | ||
495 | * Fired when the input field receives focus. | ||
496 | * | ||
497 | * @event textboxFocusEvent | ||
498 | * @param oSelf {Object} The AutoComplete instance. | ||
499 | */ | ||
500 | YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null; | ||
501 | |||
502 | /** | ||
503 | * Fired when the input field receives key input. | ||
504 | * | ||
505 | * @event textboxKeyEvent | ||
506 | * @param oSelf {Object} The AutoComplete instance. | ||
507 | * @param nKeycode {Number} The keycode number. | ||
508 | */ | ||
509 | YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null; | ||
510 | |||
511 | /** | ||
512 | * Fired when the AutoComplete instance makes a query to the DataSource. | ||
513 | * | ||
514 | * @event dataRequestEvent | ||
515 | * @param oSelf {Object} The AutoComplete instance. | ||
516 | * @param sQuery {String} The query string. | ||
517 | */ | ||
518 | YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null; | ||
519 | |||
520 | /** | ||
521 | * Fired when the AutoComplete instance receives query results from the data | ||
522 | * source. | ||
523 | * | ||
524 | * @event dataReturnEvent | ||
525 | * @param oSelf {Object} The AutoComplete instance. | ||
526 | * @param sQuery {String} The query string. | ||
527 | * @param aResults {Array} Results array. | ||
528 | */ | ||
529 | YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null; | ||
530 | |||
531 | /** | ||
532 | * Fired when the AutoComplete instance does not receive query results from the | ||
533 | * DataSource due to an error. | ||
534 | * | ||
535 | * @event dataErrorEvent | ||
536 | * @param oSelf {Object} The AutoComplete instance. | ||
537 | * @param sQuery {String} The query string. | ||
538 | */ | ||
539 | YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null; | ||
540 | |||
541 | /** | ||
542 | * Fired when the results container is expanded. | ||
543 | * | ||
544 | * @event containerExpandEvent | ||
545 | * @param oSelf {Object} The AutoComplete instance. | ||
546 | */ | ||
547 | YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null; | ||
548 | |||
549 | /** | ||
550 | * Fired when the input field has been prefilled by the type-ahead | ||
551 | * feature. | ||
552 | * | ||
553 | * @event typeAheadEvent | ||
554 | * @param oSelf {Object} The AutoComplete instance. | ||
555 | * @param sQuery {String} The query string. | ||
556 | * @param sPrefill {String} The prefill string. | ||
557 | */ | ||
558 | YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null; | ||
559 | |||
560 | /** | ||
561 | * Fired when result item has been moused over. | ||
562 | * | ||
563 | * @event itemMouseOverEvent | ||
564 | * @param oSelf {Object} The AutoComplete instance. | ||
565 | * @param elItem {HTMLElement} The <li> element item moused to. | ||
566 | */ | ||
567 | YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null; | ||
568 | |||
569 | /** | ||
570 | * Fired when result item has been moused out. | ||
571 | * | ||
572 | * @event itemMouseOutEvent | ||
573 | * @param oSelf {Object} The AutoComplete instance. | ||
574 | * @param elItem {HTMLElement} The <li> element item moused from. | ||
575 | */ | ||
576 | YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null; | ||
577 | |||
578 | /** | ||
579 | * Fired when result item has been arrowed to. | ||
580 | * | ||
581 | * @event itemArrowToEvent | ||
582 | * @param oSelf {Object} The AutoComplete instance. | ||
583 | * @param elItem {HTMLElement} The <li> element item arrowed to. | ||
584 | */ | ||
585 | YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null; | ||
586 | |||
587 | /** | ||
588 | * Fired when result item has been arrowed away from. | ||
589 | * | ||
590 | * @event itemArrowFromEvent | ||
591 | * @param oSelf {Object} The AutoComplete instance. | ||
592 | * @param elItem {HTMLElement} The <li> element item arrowed from. | ||
593 | */ | ||
594 | YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null; | ||
595 | |||
596 | /** | ||
597 | * Fired when an item is selected via mouse click, ENTER key, or TAB key. | ||
598 | * | ||
599 | * @event itemSelectEvent | ||
600 | * @param oSelf {Object} The AutoComplete instance. | ||
601 | * @param elItem {HTMLElement} The selected <li> element item. | ||
602 | * @param oData {Object} The data returned for the item, either as an object, | ||
603 | * or mapped from the schema into an array. | ||
604 | */ | ||
605 | YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null; | ||
606 | |||
607 | /** | ||
608 | * Fired when a user selection does not match any of the displayed result items. | ||
609 | * Note that this event may not behave as expected when delimiter characters | ||
610 | * have been defined. | ||
611 | * | ||
612 | * @event unmatchedItemSelectEvent | ||
613 | * @param oSelf {Object} The AutoComplete instance. | ||
614 | * @param sQuery {String} The user-typed query string. | ||
615 | */ | ||
616 | YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null; | ||
617 | |||
618 | /** | ||
619 | * Fired if forceSelection is enabled and the user's input has been cleared | ||
620 | * because it did not match one of the returned query results. | ||
621 | * | ||
622 | * @event selectionEnforceEvent | ||
623 | * @param oSelf {Object} The AutoComplete instance. | ||
624 | */ | ||
625 | YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null; | ||
626 | |||
627 | /** | ||
628 | * Fired when the results container is collapsed. | ||
629 | * | ||
630 | * @event containerCollapseEvent | ||
631 | * @param oSelf {Object} The AutoComplete instance. | ||
632 | */ | ||
633 | YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null; | ||
634 | |||
635 | /** | ||
636 | * Fired when the input field loses focus. | ||
637 | * | ||
638 | * @event textboxBlurEvent | ||
639 | * @param oSelf {Object} The AutoComplete instance. | ||
640 | */ | ||
641 | YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null; | ||
642 | |||
643 | ///////////////////////////////////////////////////////////////////////////// | ||
644 | // | ||
645 | // Private member variables | ||
646 | // | ||
647 | ///////////////////////////////////////////////////////////////////////////// | ||
648 | |||
649 | /** | ||
650 | * Internal class variable to index multiple AutoComplete instances. | ||
651 | * | ||
652 | * @property _nIndex | ||
653 | * @type Number | ||
654 | * @default 0 | ||
655 | * @private | ||
656 | */ | ||
657 | YAHOO.widget.AutoComplete._nIndex = 0; | ||
658 | |||
659 | /** | ||
660 | * Name of AutoComplete instance. | ||
661 | * | ||
662 | * @property _sName | ||
663 | * @type String | ||
664 | * @private | ||
665 | */ | ||
666 | YAHOO.widget.AutoComplete.prototype._sName = null; | ||
667 | |||
668 | /** | ||
669 | * Text input field DOM element. | ||
670 | * | ||
671 | * @property _oTextbox | ||
672 | * @type HTMLElement | ||
673 | * @private | ||
674 | */ | ||
675 | YAHOO.widget.AutoComplete.prototype._oTextbox = null; | ||
676 | |||
677 | /** | ||
678 | * Whether or not the input field is currently in focus. If query results come back | ||
679 | * but the user has already moved on, do not proceed with auto complete behavior. | ||
680 | * | ||
681 | * @property _bFocused | ||
682 | * @type Boolean | ||
683 | * @private | ||
684 | */ | ||
685 | YAHOO.widget.AutoComplete.prototype._bFocused = true; | ||
686 | |||
687 | /** | ||
688 | * Animation instance for container expand/collapse. | ||
689 | * | ||
690 | * @property _oAnim | ||
691 | * @type Boolean | ||
692 | * @private | ||
693 | */ | ||
694 | YAHOO.widget.AutoComplete.prototype._oAnim = null; | ||
695 | |||
696 | /** | ||
697 | * Container DOM element. | ||
698 | * | ||
699 | * @property _oContainer | ||
700 | * @type HTMLElement | ||
701 | * @private | ||
702 | */ | ||
703 | YAHOO.widget.AutoComplete.prototype._oContainer = null; | ||
704 | |||
705 | /** | ||
706 | * Whether or not the results container is currently open. | ||
707 | * | ||
708 | * @property _bContainerOpen | ||
709 | * @type Boolean | ||
710 | * @private | ||
711 | */ | ||
712 | YAHOO.widget.AutoComplete.prototype._bContainerOpen = false; | ||
713 | |||
714 | /** | ||
715 | * Whether or not the mouse is currently over the results | ||
716 | * container. This is necessary in order to prevent clicks on container items | ||
717 | * from being text input field blur events. | ||
718 | * | ||
719 | * @property _bOverContainer | ||
720 | * @type Boolean | ||
721 | * @private | ||
722 | */ | ||
723 | YAHOO.widget.AutoComplete.prototype._bOverContainer = false; | ||
724 | |||
725 | /** | ||
726 | * Array of <li> elements references that contain query results within the | ||
727 | * results container. | ||
728 | * | ||
729 | * @property _aListItems | ||
730 | * @type Array | ||
731 | * @private | ||
732 | */ | ||
733 | YAHOO.widget.AutoComplete.prototype._aListItems = null; | ||
734 | |||
735 | /** | ||
736 | * Number of <li> elements currently displayed in results container. | ||
737 | * | ||
738 | * @property _nDisplayedItems | ||
739 | * @type Number | ||
740 | * @private | ||
741 | */ | ||
742 | YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0; | ||
743 | |||
744 | /** | ||
745 | * Internal count of <li> elements displayed and hidden in results container. | ||
746 | * | ||
747 | * @property _maxResultsDisplayed | ||
748 | * @type Number | ||
749 | * @private | ||
750 | */ | ||
751 | YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0; | ||
752 | |||
753 | /** | ||
754 | * Current query string | ||
755 | * | ||
756 | * @property _sCurQuery | ||
757 | * @type String | ||
758 | * @private | ||
759 | */ | ||
760 | YAHOO.widget.AutoComplete.prototype._sCurQuery = null; | ||
761 | |||
762 | /** | ||
763 | * Past queries this session (for saving delimited queries). | ||
764 | * | ||
765 | * @property _sSavedQuery | ||
766 | * @type String | ||
767 | * @private | ||
768 | */ | ||
769 | YAHOO.widget.AutoComplete.prototype._sSavedQuery = null; | ||
770 | |||
771 | /** | ||
772 | * Pointer to the currently highlighted <li> element in the container. | ||
773 | * | ||
774 | * @property _oCurItem | ||
775 | * @type HTMLElement | ||
776 | * @private | ||
777 | */ | ||
778 | YAHOO.widget.AutoComplete.prototype._oCurItem = null; | ||
779 | |||
780 | /** | ||
781 | * Whether or not an item has been selected since the container was populated | ||
782 | * with results. Reset to false by _populateList, and set to true when item is | ||
783 | * selected. | ||
784 | * | ||
785 | * @property _bItemSelected | ||
786 | * @type Boolean | ||
787 | * @private | ||
788 | */ | ||
789 | YAHOO.widget.AutoComplete.prototype._bItemSelected = false; | ||
790 | |||
791 | /** | ||
792 | * Key code of the last key pressed in textbox. | ||
793 | * | ||
794 | * @property _nKeyCode | ||
795 | * @type Number | ||
796 | * @private | ||
797 | */ | ||
798 | YAHOO.widget.AutoComplete.prototype._nKeyCode = null; | ||
799 | |||
800 | /** | ||
801 | * Delay timeout ID. | ||
802 | * | ||
803 | * @property _nDelayID | ||
804 | * @type Number | ||
805 | * @private | ||
806 | */ | ||
807 | YAHOO.widget.AutoComplete.prototype._nDelayID = -1; | ||
808 | |||
809 | /** | ||
810 | * Src to iFrame used when useIFrame = true. Supports implementations over SSL | ||
811 | * as well. | ||
812 | * | ||
813 | * @property _iFrameSrc | ||
814 | * @type String | ||
815 | * @private | ||
816 | */ | ||
817 | YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;"; | ||
818 | |||
819 | /** | ||
820 | * For users typing via certain IMEs, queries must be triggered by intervals, | ||
821 | * since key events yet supported across all browsers for all IMEs. | ||
822 | * | ||
823 | * @property _queryInterval | ||
824 | * @type Object | ||
825 | * @private | ||
826 | */ | ||
827 | YAHOO.widget.AutoComplete.prototype._queryInterval = null; | ||
828 | |||
829 | /** | ||
830 | * Internal tracker to last known textbox value, used to determine whether or not | ||
831 | * to trigger a query via interval for certain IME users. | ||
832 | * | ||
833 | * @event _sLastTextboxValue | ||
834 | * @type String | ||
835 | * @private | ||
836 | */ | ||
837 | YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null; | ||
838 | |||
839 | ///////////////////////////////////////////////////////////////////////////// | ||
840 | // | ||
841 | // Private methods | ||
842 | // | ||
843 | ///////////////////////////////////////////////////////////////////////////// | ||
844 | |||
845 | /** | ||
846 | * Updates and validates latest public config properties. | ||
847 | * | ||
848 | * @method __initProps | ||
849 | * @private | ||
850 | */ | ||
851 | YAHOO.widget.AutoComplete.prototype._initProps = function() { | ||
852 | // Correct any invalid values | ||
853 | var minQueryLength = this.minQueryLength; | ||
854 | if(isNaN(minQueryLength) || (minQueryLength < 1)) { | ||
855 | minQueryLength = 1; | ||
856 | } | ||
857 | var maxResultsDisplayed = this.maxResultsDisplayed; | ||
858 | if(isNaN(this.maxResultsDisplayed) || (this.maxResultsDisplayed < 1)) { | ||
859 | this.maxResultsDisplayed = 10; | ||
860 | } | ||
861 | var queryDelay = this.queryDelay; | ||
862 | if(isNaN(this.queryDelay) || (this.queryDelay < 0)) { | ||
863 | this.queryDelay = 0.5; | ||
864 | } | ||
865 | var aDelimChar = (this.delimChar) ? this.delimChar : null; | ||
866 | if(aDelimChar) { | ||
867 | if(typeof aDelimChar == "string") { | ||
868 | this.delimChar = [aDelimChar]; | ||
869 | } | ||
870 | else if(aDelimChar.constructor != Array) { | ||
871 | this.delimChar = null; | ||
872 | } | ||
873 | } | ||
874 | var animSpeed = this.animSpeed; | ||
875 | if((this.animHoriz || this.animVert) && YAHOO.util.Anim) { | ||
876 | if(isNaN(animSpeed) || (animSpeed < 0)) { | ||
877 | animSpeed = 0.3; | ||
878 | } | ||
879 | if(!this._oAnim ) { | ||
880 | oAnim = new YAHOO.util.Anim(this._oContainer._oContent, {}, this.animSpeed); | ||
881 | this._oAnim = oAnim; | ||
882 | } | ||
883 | else { | ||
884 | this._oAnim.duration = animSpeed; | ||
885 | } | ||
886 | } | ||
887 | if(this.forceSelection && this.delimChar) { | ||
888 | } | ||
889 | }; | ||
890 | |||
891 | /** | ||
892 | * Initializes the results container helpers if they are enabled and do | ||
893 | * not exist | ||
894 | * | ||
895 | * @method _initContainerHelpers | ||
896 | * @private | ||
897 | */ | ||
898 | YAHOO.widget.AutoComplete.prototype._initContainerHelpers = function() { | ||
899 | if(this.useShadow && !this._oContainer._oShadow) { | ||
900 | var oShadow = document.createElement("div"); | ||
901 | oShadow.className = "yui-ac-shadow"; | ||
902 | this._oContainer._oShadow = this._oContainer.appendChild(oShadow); | ||
903 | } | ||
904 | if(this.useIFrame && !this._oContainer._oIFrame) { | ||
905 | var oIFrame = document.createElement("iframe"); | ||
906 | oIFrame.src = this._iFrameSrc; | ||
907 | oIFrame.frameBorder = 0; | ||
908 | oIFrame.scrolling = "no"; | ||
909 | oIFrame.style.position = "absolute"; | ||
910 | oIFrame.style.width = "100%"; | ||
911 | oIFrame.style.height = "100%"; | ||
912 | oIFrame.tabIndex = -1; | ||
913 | this._oContainer._oIFrame = this._oContainer.appendChild(oIFrame); | ||
914 | } | ||
915 | }; | ||
916 | |||
917 | /** | ||
918 | * Initializes the results container once at object creation | ||
919 | * | ||
920 | * @method _initContainer | ||
921 | * @private | ||
922 | */ | ||
923 | YAHOO.widget.AutoComplete.prototype._initContainer = function() { | ||
924 | if(!this._oContainer._oContent) { | ||
925 | // The oContent div helps size the iframe and shadow properly | ||
926 | var oContent = document.createElement("div"); | ||
927 | oContent.className = "yui-ac-content"; | ||
928 | oContent.style.display = "none"; | ||
929 | this._oContainer._oContent = this._oContainer.appendChild(oContent); | ||
930 | |||
931 | var oHeader = document.createElement("div"); | ||
932 | oHeader.className = "yui-ac-hd"; | ||
933 | oHeader.style.display = "none"; | ||
934 | this._oContainer._oContent._oHeader = this._oContainer._oContent.appendChild(oHeader); | ||
935 | |||
936 | var oBody = document.createElement("div"); | ||
937 | oBody.className = "yui-ac-bd"; | ||
938 | this._oContainer._oContent._oBody = this._oContainer._oContent.appendChild(oBody); | ||
939 | |||
940 | var oFooter = document.createElement("div"); | ||
941 | oFooter.className = "yui-ac-ft"; | ||
942 | oFooter.style.display = "none"; | ||
943 | this._oContainer._oContent._oFooter = this._oContainer._oContent.appendChild(oFooter); | ||
944 | } | ||
945 | else { | ||
946 | } | ||
947 | }; | ||
948 | |||
949 | /** | ||
950 | * Clears out contents of container body and creates up to | ||
951 | * YAHOO.widget.AutoComplete#maxResultsDisplayed <li> elements in an | ||
952 | * <ul> element. | ||
953 | * | ||
954 | * @method _initList | ||
955 | * @private | ||
956 | */ | ||
957 | YAHOO.widget.AutoComplete.prototype._initList = function() { | ||
958 | this._aListItems = []; | ||
959 | while(this._oContainer._oContent._oBody.hasChildNodes()) { | ||
960 | var oldListItems = this.getListItems(); | ||
961 | if(oldListItems) { | ||
962 | for(var oldi = oldListItems.length-1; oldi >= 0; i--) { | ||
963 | oldListItems[oldi] = null; | ||
964 | } | ||
965 | } | ||
966 | this._oContainer._oContent._oBody.innerHTML = ""; | ||
967 | } | ||
968 | |||
969 | var oList = document.createElement("ul"); | ||
970 | oList = this._oContainer._oContent._oBody.appendChild(oList); | ||
971 | for(var i=0; i<this.maxResultsDisplayed; i++) { | ||
972 | var oItem = document.createElement("li"); | ||
973 | oItem = oList.appendChild(oItem); | ||
974 | this._aListItems[i] = oItem; | ||
975 | this._initListItem(oItem, i); | ||
976 | } | ||
977 | this._maxResultsDisplayed = this.maxResultsDisplayed; | ||
978 | }; | ||
979 | |||
980 | /** | ||
981 | * Initializes each <li> element in the container list. | ||
982 | * | ||
983 | * @method _initListItem | ||
984 | * @param oItem {HTMLElement} The <li> DOM element. | ||
985 | * @param nItemIndex {Number} The index of the element. | ||
986 | * @private | ||
987 | */ | ||
988 | YAHOO.widget.AutoComplete.prototype._initListItem = function(oItem, nItemIndex) { | ||
989 | var oSelf = this; | ||
990 | oItem.style.display = "none"; | ||
991 | oItem._nItemIndex = nItemIndex; | ||
992 | |||
993 | oItem.mouseover = oItem.mouseout = oItem.onclick = null; | ||
994 | YAHOO.util.Event.addListener(oItem,"mouseover",oSelf._onItemMouseover,oSelf); | ||
995 | YAHOO.util.Event.addListener(oItem,"mouseout",oSelf._onItemMouseout,oSelf); | ||
996 | YAHOO.util.Event.addListener(oItem,"click",oSelf._onItemMouseclick,oSelf); | ||
997 | }; | ||
998 | |||
999 | /** | ||
1000 | * Enables interval detection for Korean IME support. | ||
1001 | * | ||
1002 | * @method _onIMEDetected | ||
1003 | * @param oSelf {Object} The AutoComplete instance. | ||
1004 | * @private | ||
1005 | */ | ||
1006 | YAHOO.widget.AutoComplete.prototype._onIMEDetected = function(oSelf) { | ||
1007 | oSelf._enableIntervalDetection(); | ||
1008 | }; | ||
1009 | |||
1010 | /** | ||
1011 | * Enables query triggers based on text input detection by intervals (rather | ||
1012 | * than by key events). | ||
1013 | * | ||
1014 | * @method _enableIntervalDetection | ||
1015 | * @private | ||
1016 | */ | ||
1017 | YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() { | ||
1018 | var currValue = this._oTextbox.value; | ||
1019 | var lastValue = this._sLastTextboxValue; | ||
1020 | if(currValue != lastValue) { | ||
1021 | this._sLastTextboxValue = currValue; | ||
1022 | this._sendQuery(currValue); | ||
1023 | } | ||
1024 | }; | ||
1025 | |||
1026 | /** | ||
1027 | * Cancels text input detection by intervals. | ||
1028 | * | ||
1029 | * @method _cancelIntervalDetection | ||
1030 | * @param oSelf {Object} The AutoComplete instance. | ||
1031 | * @private | ||
1032 | */ | ||
1033 | YAHOO.widget.AutoComplete.prototype._cancelIntervalDetection = function(oSelf) { | ||
1034 | if(oSelf._queryInterval) { | ||
1035 | clearInterval(oSelf._queryInterval); | ||
1036 | } | ||
1037 | }; | ||
1038 | |||
1039 | /** | ||
1040 | * Whether or not key is functional or should be ignored. Note that the right | ||
1041 | * arrow key is NOT an ignored key since it triggers queries for certain intl | ||
1042 | * charsets. | ||
1043 | * | ||
1044 | * @method _isIgnoreKey | ||
1045 | * @param nKeycode {Number} Code of key pressed. | ||
1046 | * @return {Boolean} True if key should be ignored, false otherwise. | ||
1047 | * @private | ||
1048 | */ | ||
1049 | YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) { | ||
1050 | if ((nKeyCode == 9) || (nKeyCode == 13) || // tab, enter | ||
1051 | (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl | ||
1052 | (nKeyCode >= 18 && nKeyCode <= 20) || // alt,pause/break,caps lock | ||
1053 | (nKeyCode == 27) || // esc | ||
1054 | (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end | ||
1055 | (nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up | ||
1056 | (nKeyCode == 40) || // down | ||
1057 | (nKeyCode >= 44 && nKeyCode <= 45)) { // print screen,insert | ||
1058 | return true; | ||
1059 | } | ||
1060 | return false; | ||
1061 | }; | ||
1062 | |||
1063 | /** | ||
1064 | * Makes query request to the DataSource. | ||
1065 | * | ||
1066 | * @method _sendQuery | ||
1067 | * @param sQuery {String} Query string. | ||
1068 | * @private | ||
1069 | */ | ||
1070 | YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) { | ||
1071 | // Widget has been effectively turned off | ||
1072 | if(this.minQueryLength == -1) { | ||
1073 | this._toggleContainer(false); | ||
1074 | return; | ||
1075 | } | ||
1076 | // Delimiter has been enabled | ||
1077 | var aDelimChar = (this.delimChar) ? this.delimChar : null; | ||
1078 | if(aDelimChar) { | ||
1079 | // Loop through all possible delimiters and find the latest one | ||
1080 | // A " " may be a false positive if they are defined as delimiters AND | ||
1081 | // are used to separate delimited queries | ||
1082 | var nDelimIndex = -1; | ||
1083 | for(var i = aDelimChar.length-1; i >= 0; i--) { | ||
1084 | var nNewIndex = sQuery.lastIndexOf(aDelimChar[i]); | ||
1085 | if(nNewIndex > nDelimIndex) { | ||
1086 | nDelimIndex = nNewIndex; | ||
1087 | } | ||
1088 | } | ||
1089 | // If we think the last delimiter is a space (" "), make sure it is NOT | ||
1090 | // a false positive by also checking the char directly before it | ||
1091 | if(aDelimChar[i] == " ") { | ||
1092 | for (var j = aDelimChar.length-1; j >= 0; j--) { | ||
1093 | if(sQuery[nDelimIndex - 1] == aDelimChar[j]) { | ||
1094 | nDelimIndex--; | ||
1095 | break; | ||
1096 | } | ||
1097 | } | ||
1098 | } | ||
1099 | // A delimiter has been found so extract the latest query | ||
1100 | if (nDelimIndex > -1) { | ||
1101 | var nQueryStart = nDelimIndex + 1; | ||
1102 | // Trim any white space from the beginning... | ||
1103 | while(sQuery.charAt(nQueryStart) == " ") { | ||
1104 | nQueryStart += 1; | ||
1105 | } | ||
1106 | // ...and save the rest of the string for later | ||
1107 | this._sSavedQuery = sQuery.substring(0,nQueryStart); | ||
1108 | // Here is the query itself | ||
1109 | sQuery = sQuery.substr(nQueryStart); | ||
1110 | } | ||
1111 | else if(sQuery.indexOf(this._sSavedQuery) < 0){ | ||
1112 | this._sSavedQuery = null; | ||
1113 | } | ||
1114 | } | ||
1115 | |||
1116 | // Don't search queries that are too short | ||
1117 | if (sQuery && (sQuery.length < this.minQueryLength) || (!sQuery && this.minQueryLength > 0)) { | ||
1118 | if (this._nDelayID != -1) { | ||
1119 | clearTimeout(this._nDelayID); | ||
1120 | } | ||
1121 | this._toggleContainer(false); | ||
1122 | return; | ||
1123 | } | ||
1124 | |||
1125 | sQuery = encodeURIComponent(sQuery); | ||
1126 | this._nDelayID = -1; // Reset timeout ID because request has been made | ||
1127 | this.dataRequestEvent.fire(this, sQuery); | ||
1128 | this.dataSource.getResults(this._populateList, sQuery, this); | ||
1129 | }; | ||
1130 | |||
1131 | /** | ||
1132 | * Populates the array of <li> elements in the container with query | ||
1133 | * results. This method is passed to YAHOO.widget.DataSource#getResults as a | ||
1134 | * callback function so results from the DataSource instance are returned to the | ||
1135 | * AutoComplete instance. | ||
1136 | * | ||
1137 | * @method _populateList | ||
1138 | * @param sQuery {String} The query string. | ||
1139 | * @param aResults {Array} An array of query result objects from the DataSource. | ||
1140 | * @param oSelf {Object} The AutoComplete instance. | ||
1141 | * @private | ||
1142 | */ | ||
1143 | YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, aResults, oSelf) { | ||
1144 | if(aResults === null) { | ||
1145 | oSelf.dataErrorEvent.fire(oSelf, sQuery); | ||
1146 | } | ||
1147 | if (!oSelf._bFocused || !aResults) { | ||
1148 | return; | ||
1149 | } | ||
1150 | |||
1151 | var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); | ||
1152 | var contentStyle = oSelf._oContainer._oContent.style; | ||
1153 | contentStyle.width = (!isOpera) ? null : ""; | ||
1154 | contentStyle.height = (!isOpera) ? null : ""; | ||
1155 | |||
1156 | var sCurQuery = decodeURIComponent(sQuery); | ||
1157 | oSelf._sCurQuery = sCurQuery; | ||
1158 | oSelf._bItemSelected = false; | ||
1159 | |||
1160 | if(oSelf._maxResultsDisplayed != oSelf.maxResultsDisplayed) { | ||
1161 | oSelf._initList(); | ||
1162 | } | ||
1163 | |||
1164 | var nItems = Math.min(aResults.length,oSelf.maxResultsDisplayed); | ||
1165 | oSelf._nDisplayedItems = nItems; | ||
1166 | if (nItems > 0) { | ||
1167 | oSelf._initContainerHelpers(); | ||
1168 | var aItems = oSelf._aListItems; | ||
1169 | |||
1170 | // Fill items with data | ||
1171 | for(var i = nItems-1; i >= 0; i--) { | ||
1172 | var oItemi = aItems[i]; | ||
1173 | var oResultItemi = aResults[i]; | ||
1174 | oItemi.innerHTML = oSelf.formatResult(oResultItemi, sCurQuery); | ||
1175 | oItemi.style.display = "list-item"; | ||
1176 | oItemi._sResultKey = oResultItemi[0]; | ||
1177 | oItemi._oResultData = oResultItemi; | ||
1178 | |||
1179 | } | ||
1180 | |||
1181 | // Empty out remaining items if any | ||
1182 | for(var j = aItems.length-1; j >= nItems ; j--) { | ||
1183 | var oItemj = aItems[j]; | ||
1184 | oItemj.innerHTML = null; | ||
1185 | oItemj.style.display = "none"; | ||
1186 | oItemj._sResultKey = null; | ||
1187 | oItemj._oResultData = null; | ||
1188 | } | ||
1189 | |||
1190 | if(oSelf.autoHighlight) { | ||
1191 | // Go to the first item | ||
1192 | var oFirstItem = aItems[0]; | ||
1193 | oSelf._toggleHighlight(oFirstItem,"to"); | ||
1194 | oSelf.itemArrowToEvent.fire(oSelf, oFirstItem); | ||
1195 | oSelf._typeAhead(oFirstItem,sQuery); | ||
1196 | } | ||
1197 | else { | ||
1198 | oSelf._oCurItem = null; | ||
1199 | } | ||
1200 | |||
1201 | // Expand the container | ||
1202 | var ok = oSelf.doBeforeExpandContainer(oSelf._oTextbox, oSelf._oContainer, sQuery, aResults); | ||
1203 | oSelf._toggleContainer(ok); | ||
1204 | } | ||
1205 | else { | ||
1206 | oSelf._toggleContainer(false); | ||
1207 | } | ||
1208 | oSelf.dataReturnEvent.fire(oSelf, sQuery, aResults); | ||
1209 | }; | ||
1210 | |||
1211 | /** | ||
1212 | * When forceSelection is true and the user attempts | ||
1213 | * leave the text input box without selecting an item from the query results, | ||
1214 | * the user selection is cleared. | ||
1215 | * | ||
1216 | * @method _clearSelection | ||
1217 | * @private | ||
1218 | */ | ||
1219 | YAHOO.widget.AutoComplete.prototype._clearSelection = function() { | ||
1220 | var sValue = this._oTextbox.value; | ||
1221 | var sChar = (this.delimChar) ? this.delimChar[0] : null; | ||
1222 | var nIndex = (sChar) ? sValue.lastIndexOf(sChar, sValue.length-2) : -1; | ||
1223 | if(nIndex > -1) { | ||
1224 | this._oTextbox.value = sValue.substring(0,nIndex); | ||
1225 | } | ||
1226 | else { | ||
1227 | this._oTextbox.value = ""; | ||
1228 | } | ||
1229 | this._sSavedQuery = this._oTextbox.value; | ||
1230 | |||
1231 | // Fire custom event | ||
1232 | this.selectionEnforceEvent.fire(this); | ||
1233 | }; | ||
1234 | |||
1235 | /** | ||
1236 | * Whether or not user-typed value in the text input box matches any of the | ||
1237 | * query results. | ||
1238 | * | ||
1239 | * @method _textMatchesOption | ||
1240 | * @return {Boolean} True if user-input text matches a result, false otherwise. | ||
1241 | * @private | ||
1242 | */ | ||
1243 | YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() { | ||
1244 | var foundMatch = false; | ||
1245 | |||
1246 | for(var i = this._nDisplayedItems-1; i >= 0 ; i--) { | ||
1247 | var oItem = this._aListItems[i]; | ||
1248 | var sMatch = oItem._sResultKey.toLowerCase(); | ||
1249 | if (sMatch == this._sCurQuery.toLowerCase()) { | ||
1250 | foundMatch = true; | ||
1251 | break; | ||
1252 | } | ||
1253 | } | ||
1254 | return(foundMatch); | ||
1255 | }; | ||
1256 | |||
1257 | /** | ||
1258 | * Updates in the text input box with the first query result as the user types, | ||
1259 | * selecting the substring that the user has not typed. | ||
1260 | * | ||
1261 | * @method _typeAhead | ||
1262 | * @param oItem {HTMLElement} The <li> element item whose data populates the input field. | ||
1263 | * @param sQuery {String} Query string. | ||
1264 | * @private | ||
1265 | */ | ||
1266 | YAHOO.widget.AutoComplete.prototype._typeAhead = function(oItem, sQuery) { | ||
1267 | // Don't update if turned off | ||
1268 | if (!this.typeAhead) { | ||
1269 | return; | ||
1270 | } | ||
1271 | |||
1272 | var oTextbox = this._oTextbox; | ||
1273 | var sValue = this._oTextbox.value; // any saved queries plus what user has typed | ||
1274 | |||
1275 | // Don't update with type-ahead if text selection is not supported | ||
1276 | if(!oTextbox.setSelectionRange && !oTextbox.createTextRange) { | ||
1277 | return; | ||
1278 | } | ||
1279 | |||
1280 | // Select the portion of text that the user has not typed | ||
1281 | var nStart = sValue.length; | ||
1282 | this._updateValue(oItem); | ||
1283 | var nEnd = oTextbox.value.length; | ||
1284 | this._selectText(oTextbox,nStart,nEnd); | ||
1285 | var sPrefill = oTextbox.value.substr(nStart,nEnd); | ||
1286 | this.typeAheadEvent.fire(this,sQuery,sPrefill); | ||
1287 | }; | ||
1288 | |||
1289 | /** | ||
1290 | * Selects text in the input field. | ||
1291 | * | ||
1292 | * @method _selectText | ||
1293 | * @param oTextbox {HTMLElement} Text input box element in which to select text. | ||
1294 | * @param nStart {Number} Starting index of text string to select. | ||
1295 | * @param nEnd {Number} Ending index of text selection. | ||
1296 | * @private | ||
1297 | */ | ||
1298 | YAHOO.widget.AutoComplete.prototype._selectText = function(oTextbox, nStart, nEnd) { | ||
1299 | if (oTextbox.setSelectionRange) { // For Mozilla | ||
1300 | oTextbox.setSelectionRange(nStart,nEnd); | ||
1301 | } | ||
1302 | else if (oTextbox.createTextRange) { // For IE | ||
1303 | var oTextRange = oTextbox.createTextRange(); | ||
1304 | oTextRange.moveStart("character", nStart); | ||
1305 | oTextRange.moveEnd("character", nEnd-oTextbox.value.length); | ||
1306 | oTextRange.select(); | ||
1307 | } | ||
1308 | else { | ||
1309 | oTextbox.select(); | ||
1310 | } | ||
1311 | }; | ||
1312 | |||
1313 | /** | ||
1314 | * Syncs results container with its helpers. | ||
1315 | * | ||
1316 | * @method _toggleContainerHelpers | ||
1317 | * @param bShow {Boolean} True if container is expanded, false if collapsed | ||
1318 | * @private | ||
1319 | */ | ||
1320 | YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) { | ||
1321 | var bFireEvent = false; | ||
1322 | var width = this._oContainer._oContent.offsetWidth + "px"; | ||
1323 | var height = this._oContainer._oContent.offsetHeight + "px"; | ||
1324 | |||
1325 | if(this.useIFrame && this._oContainer._oIFrame) { | ||
1326 | bFireEvent = true; | ||
1327 | if(bShow) { | ||
1328 | this._oContainer._oIFrame.style.width = width; | ||
1329 | this._oContainer._oIFrame.style.height = height; | ||
1330 | } | ||
1331 | else { | ||
1332 | this._oContainer._oIFrame.style.width = 0; | ||
1333 | this._oContainer._oIFrame.style.height = 0; | ||
1334 | } | ||
1335 | } | ||
1336 | if(this.useShadow && this._oContainer._oShadow) { | ||
1337 | bFireEvent = true; | ||
1338 | if(bShow) { | ||
1339 | this._oContainer._oShadow.style.width = width; | ||
1340 | this._oContainer._oShadow.style.height = height; | ||
1341 | } | ||
1342 | else { | ||
1343 | this._oContainer._oShadow.style.width = 0; | ||
1344 | this._oContainer._oShadow.style.height = 0; | ||
1345 | } | ||
1346 | } | ||
1347 | }; | ||
1348 | |||
1349 | /** | ||
1350 | * Animates expansion or collapse of the container. | ||
1351 | * | ||
1352 | * @method _toggleContainer | ||
1353 | * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed | ||
1354 | * @private | ||
1355 | */ | ||
1356 | YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) { | ||
1357 | var oContainer = this._oContainer; | ||
1358 | |||
1359 | // Implementer has container always open so don't mess with it | ||
1360 | if(this.alwaysShowContainer && this._bContainerOpen) { | ||
1361 | return; | ||
1362 | } | ||
1363 | |||
1364 | // Clear contents of container | ||
1365 | if(!bShow) { | ||
1366 | this._oContainer._oContent.scrollTop = 0; | ||
1367 | var aItems = this._aListItems; | ||
1368 | |||
1369 | if(aItems && (aItems.length > 0)) { | ||
1370 | for(var i = aItems.length-1; i >= 0 ; i--) { | ||
1371 | aItems[i].style.display = "none"; | ||
1372 | } | ||
1373 | } | ||
1374 | |||
1375 | if (this._oCurItem) { | ||
1376 | this._toggleHighlight(this._oCurItem,"from"); | ||
1377 | } | ||
1378 | |||
1379 | this._oCurItem = null; | ||
1380 | this._nDisplayedItems = 0; | ||
1381 | this._sCurQuery = null; | ||
1382 | } | ||
1383 | |||
1384 | // Container is already closed | ||
1385 | if (!bShow && !this._bContainerOpen) { | ||
1386 | oContainer._oContent.style.display = "none"; | ||
1387 | return; | ||
1388 | } | ||
1389 | |||
1390 | // If animation is enabled... | ||
1391 | var oAnim = this._oAnim; | ||
1392 | if (oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) { | ||
1393 | // If helpers need to be collapsed, do it right away... | ||
1394 | // but if helpers need to be expanded, wait until after the container expands | ||
1395 | if(!bShow) { | ||
1396 | this._toggleContainerHelpers(bShow); | ||
1397 | } | ||
1398 | |||
1399 | if(oAnim.isAnimated()) { | ||
1400 | oAnim.stop(); | ||
1401 | } | ||
1402 | |||
1403 | // Clone container to grab current size offscreen | ||
1404 | var oClone = oContainer._oContent.cloneNode(true); | ||
1405 | oContainer.appendChild(oClone); | ||
1406 | oClone.style.top = "-9000px"; | ||
1407 | oClone.style.display = "block"; | ||
1408 | |||
1409 | // Current size of the container is the EXPANDED size | ||
1410 | var wExp = oClone.offsetWidth; | ||
1411 | var hExp = oClone.offsetHeight; | ||
1412 | |||
1413 | // Calculate COLLAPSED sizes based on horiz and vert anim | ||
1414 | var wColl = (this.animHoriz) ? 0 : wExp; | ||
1415 | var hColl = (this.animVert) ? 0 : hExp; | ||
1416 | |||
1417 | // Set animation sizes | ||
1418 | oAnim.attributes = (bShow) ? | ||
1419 | {width: { to: wExp }, height: { to: hExp }} : | ||
1420 | {width: { to: wColl}, height: { to: hColl }}; | ||
1421 | |||
1422 | // If opening anew, set to a collapsed size... | ||
1423 | if(bShow && !this._bContainerOpen) { | ||
1424 | oContainer._oContent.style.width = wColl+"px"; | ||
1425 | oContainer._oContent.style.height = hColl+"px"; | ||
1426 | } | ||
1427 | // Else, set it to its last known size. | ||
1428 | else { | ||
1429 | oContainer._oContent.style.width = wExp+"px"; | ||
1430 | oContainer._oContent.style.height = hExp+"px"; | ||
1431 | } | ||
1432 | |||
1433 | oContainer.removeChild(oClone); | ||
1434 | oClone = null; | ||
1435 | |||
1436 | var oSelf = this; | ||
1437 | var onAnimComplete = function() { | ||
1438 | // Finish the collapse | ||
1439 | oAnim.onComplete.unsubscribeAll(); | ||
1440 | |||
1441 | if(bShow) { | ||
1442 | oSelf.containerExpandEvent.fire(oSelf); | ||
1443 | } | ||
1444 | else { | ||
1445 | oContainer._oContent.style.display = "none"; | ||
1446 | oSelf.containerCollapseEvent.fire(oSelf); | ||
1447 | } | ||
1448 | oSelf._toggleContainerHelpers(bShow); | ||
1449 | }; | ||
1450 | |||
1451 | // Display container and animate it | ||
1452 | oContainer._oContent.style.display = "block"; | ||
1453 | oAnim.onComplete.subscribe(onAnimComplete); | ||
1454 | oAnim.animate(); | ||
1455 | this._bContainerOpen = bShow; | ||
1456 | } | ||
1457 | // Else don't animate, just show or hide | ||
1458 | else { | ||
1459 | if(bShow) { | ||
1460 | oContainer._oContent.style.display = "block"; | ||
1461 | this.containerExpandEvent.fire(this); | ||
1462 | } | ||
1463 | else { | ||
1464 | oContainer._oContent.style.display = "none"; | ||
1465 | this.containerCollapseEvent.fire(this); | ||
1466 | } | ||
1467 | this._toggleContainerHelpers(bShow); | ||
1468 | this._bContainerOpen = bShow; | ||
1469 | } | ||
1470 | |||
1471 | }; | ||
1472 | |||
1473 | /** | ||
1474 | * Toggles the highlight on or off for an item in the container, and also cleans | ||
1475 | * up highlighting of any previous item. | ||
1476 | * | ||
1477 | * @method _toggleHighlight | ||
1478 | * @param oNewItem {HTMLElement} The <li> element item to receive highlight behavior. | ||
1479 | * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off. | ||
1480 | * @private | ||
1481 | */ | ||
1482 | YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(oNewItem, sType) { | ||
1483 | var sHighlight = this.highlightClassName; | ||
1484 | if(this._oCurItem) { | ||
1485 | // Remove highlight from old item | ||
1486 | YAHOO.util.Dom.removeClass(this._oCurItem, sHighlight); | ||
1487 | } | ||
1488 | |||
1489 | if((sType == "to") && sHighlight) { | ||
1490 | // Apply highlight to new item | ||
1491 | YAHOO.util.Dom.addClass(oNewItem, sHighlight); | ||
1492 | this._oCurItem = oNewItem; | ||
1493 | } | ||
1494 | }; | ||
1495 | |||
1496 | /** | ||
1497 | * Toggles the pre-highlight on or off for an item in the container. | ||
1498 | * | ||
1499 | * @method _togglePrehighlight | ||
1500 | * @param oNewItem {HTMLElement} The <li> element item to receive highlight behavior. | ||
1501 | * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off. | ||
1502 | * @private | ||
1503 | */ | ||
1504 | YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(oNewItem, sType) { | ||
1505 | if(oNewItem == this._oCurItem) { | ||
1506 | return; | ||
1507 | } | ||
1508 | |||
1509 | var sPrehighlight = this.prehighlightClassName; | ||
1510 | if((sType == "mouseover") && sPrehighlight) { | ||
1511 | // Apply prehighlight to new item | ||
1512 | YAHOO.util.Dom.addClass(oNewItem, sPrehighlight); | ||
1513 | } | ||
1514 | else { | ||
1515 | // Remove prehighlight from old item | ||
1516 | YAHOO.util.Dom.removeClass(oNewItem, sPrehighlight); | ||
1517 | } | ||
1518 | }; | ||
1519 | |||
1520 | /** | ||
1521 | * Updates the text input box value with selected query result. If a delimiter | ||
1522 | * has been defined, then the value gets appended with the delimiter. | ||
1523 | * | ||
1524 | * @method _updateValue | ||
1525 | * @param oItem {HTMLElement} The <li> element item with which to update the value. | ||
1526 | * @private | ||
1527 | */ | ||
1528 | YAHOO.widget.AutoComplete.prototype._updateValue = function(oItem) { | ||
1529 | var oTextbox = this._oTextbox; | ||
1530 | var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null; | ||
1531 | var sSavedQuery = this._sSavedQuery; | ||
1532 | var sResultKey = oItem._sResultKey; | ||
1533 | oTextbox.focus(); | ||
1534 | |||
1535 | // First clear text field | ||
1536 | oTextbox.value = ""; | ||
1537 | // Grab data to put into text field | ||
1538 | if(sDelimChar) { | ||
1539 | if(sSavedQuery) { | ||
1540 | oTextbox.value = sSavedQuery; | ||
1541 | } | ||
1542 | oTextbox.value += sResultKey + sDelimChar; | ||
1543 | if(sDelimChar != " ") { | ||
1544 | oTextbox.value += " "; | ||
1545 | } | ||
1546 | } | ||
1547 | else { oTextbox.value = sResultKey; } | ||
1548 | |||
1549 | // scroll to bottom of textarea if necessary | ||
1550 | if(oTextbox.type == "textarea") { | ||
1551 | oTextbox.scrollTop = oTextbox.scrollHeight; | ||
1552 | } | ||
1553 | |||
1554 | // move cursor to end | ||
1555 | var end = oTextbox.value.length; | ||
1556 | this._selectText(oTextbox,end,end); | ||
1557 | |||
1558 | this._oCurItem = oItem; | ||
1559 | }; | ||
1560 | |||
1561 | /** | ||
1562 | * Selects a result item from the container | ||
1563 | * | ||
1564 | * @method _selectItem | ||
1565 | * @param oItem {HTMLElement} The selected <li> element item. | ||
1566 | * @private | ||
1567 | */ | ||
1568 | YAHOO.widget.AutoComplete.prototype._selectItem = function(oItem) { | ||
1569 | this._bItemSelected = true; | ||
1570 | this._updateValue(oItem); | ||
1571 | this._cancelIntervalDetection(this); | ||
1572 | this.itemSelectEvent.fire(this, oItem, oItem._oResultData); | ||
1573 | this._toggleContainer(false); | ||
1574 | }; | ||
1575 | |||
1576 | /** | ||
1577 | * For values updated by type-ahead, the right arrow key jumps to the end | ||
1578 | * of the textbox, otherwise the container is closed. | ||
1579 | * | ||
1580 | * @method _jumpSelection | ||
1581 | * @private | ||
1582 | */ | ||
1583 | YAHOO.widget.AutoComplete.prototype._jumpSelection = function() { | ||
1584 | if(!this.typeAhead) { | ||
1585 | return; | ||
1586 | } | ||
1587 | else { | ||
1588 | this._toggleContainer(false); | ||
1589 | } | ||
1590 | }; | ||
1591 | |||
1592 | /** | ||
1593 | * Triggered by up and down arrow keys, changes the current highlighted | ||
1594 | * <li> element item. Scrolls container if necessary. | ||
1595 | * | ||
1596 | * @method _moveSelection | ||
1597 | * @param nKeyCode {Number} Code of key pressed. | ||
1598 | * @private | ||
1599 | */ | ||
1600 | YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) { | ||
1601 | if(this._bContainerOpen) { | ||
1602 | // Determine current item's id number | ||
1603 | var oCurItem = this._oCurItem; | ||
1604 | var nCurItemIndex = -1; | ||
1605 | |||
1606 | if (oCurItem) { | ||
1607 | nCurItemIndex = oCurItem._nItemIndex; | ||
1608 | } | ||
1609 | |||
1610 | var nNewItemIndex = (nKeyCode == 40) ? | ||
1611 | (nCurItemIndex + 1) : (nCurItemIndex - 1); | ||
1612 | |||
1613 | // Out of bounds | ||
1614 | if (nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) { | ||
1615 | return; | ||
1616 | } | ||
1617 | |||
1618 | if (oCurItem) { | ||
1619 | // Unhighlight current item | ||
1620 | this._toggleHighlight(oCurItem, "from"); | ||
1621 | this.itemArrowFromEvent.fire(this, oCurItem); | ||
1622 | } | ||
1623 | if (nNewItemIndex == -1) { | ||
1624 | // Go back to query (remove type-ahead string) | ||
1625 | if(this.delimChar && this._sSavedQuery) { | ||
1626 | if (!this._textMatchesOption()) { | ||
1627 | this._oTextbox.value = this._sSavedQuery; | ||
1628 | } | ||
1629 | else { | ||
1630 | this._oTextbox.value = this._sSavedQuery + this._sCurQuery; | ||
1631 | } | ||
1632 | } | ||
1633 | else { | ||
1634 | this._oTextbox.value = this._sCurQuery; | ||
1635 | } | ||
1636 | this._oCurItem = null; | ||
1637 | return; | ||
1638 | } | ||
1639 | if (nNewItemIndex == -2) { | ||
1640 | // Close container | ||
1641 | this._toggleContainer(false); | ||
1642 | return; | ||
1643 | } | ||
1644 | |||
1645 | var oNewItem = this._aListItems[nNewItemIndex]; | ||
1646 | |||
1647 | // Scroll the container if necessary | ||
1648 | var oContent = this._oContainer._oContent; | ||
1649 | var scrollOn = ((YAHOO.util.Dom.getStyle(oContent,"overflow") == "auto") || | ||
1650 | (YAHOO.util.Dom.getStyle(oContent,"overflowY") == "auto")); | ||
1651 | if(scrollOn && (nNewItemIndex > -1) && | ||
1652 | (nNewItemIndex < this._nDisplayedItems)) { | ||
1653 | // User is keying down | ||
1654 | if(nKeyCode == 40) { | ||
1655 | // Bottom of selected item is below scroll area... | ||
1656 | if((oNewItem.offsetTop+oNewItem.offsetHeight) > (oContent.scrollTop + oContent.offsetHeight)) { | ||
1657 | // Set bottom of scroll area to bottom of selected item | ||
1658 | oContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - oContent.offsetHeight; | ||
1659 | } | ||
1660 | // Bottom of selected item is above scroll area... | ||
1661 | else if((oNewItem.offsetTop+oNewItem.offsetHeight) < oContent.scrollTop) { | ||
1662 | // Set top of selected item to top of scroll area | ||
1663 | oContent.scrollTop = oNewItem.offsetTop; | ||
1664 | |||
1665 | } | ||
1666 | } | ||
1667 | // User is keying up | ||
1668 | else { | ||
1669 | // Top of selected item is above scroll area | ||
1670 | if(oNewItem.offsetTop < oContent.scrollTop) { | ||
1671 | // Set top of scroll area to top of selected item | ||
1672 | this._oContainer._oContent.scrollTop = oNewItem.offsetTop; | ||
1673 | } | ||
1674 | // Top of selected item is below scroll area | ||
1675 | else if(oNewItem.offsetTop > (oContent.scrollTop + oContent.offsetHeight)) { | ||
1676 | // Set bottom of selected item to bottom of scroll area | ||
1677 | this._oContainer._oContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - oContent.offsetHeight; | ||
1678 | } | ||
1679 | } | ||
1680 | } | ||
1681 | |||
1682 | this._toggleHighlight(oNewItem, "to"); | ||
1683 | this.itemArrowToEvent.fire(this, oNewItem); | ||
1684 | if(this.typeAhead) { | ||
1685 | this._updateValue(oNewItem); | ||
1686 | } | ||
1687 | } | ||
1688 | }; | ||
1689 | |||
1690 | ///////////////////////////////////////////////////////////////////////////// | ||
1691 | // | ||
1692 | // Private event handlers | ||
1693 | // | ||
1694 | ///////////////////////////////////////////////////////////////////////////// | ||
1695 | |||
1696 | /** | ||
1697 | * Handles <li> element mouseover events in the container. | ||
1698 | * | ||
1699 | * @method _onItemMouseover | ||
1700 | * @param v {HTMLEvent} The mouseover event. | ||
1701 | * @param oSelf {Object} The AutoComplete instance. | ||
1702 | * @private | ||
1703 | */ | ||
1704 | YAHOO.widget.AutoComplete.prototype._onItemMouseover = function(v,oSelf) { | ||
1705 | if(oSelf.prehighlightClassName) { | ||
1706 | oSelf._togglePrehighlight(this,"mouseover"); | ||
1707 | } | ||
1708 | else { | ||
1709 | oSelf._toggleHighlight(this,"to"); | ||
1710 | } | ||
1711 | |||
1712 | oSelf.itemMouseOverEvent.fire(oSelf, this); | ||
1713 | }; | ||
1714 | |||
1715 | /** | ||
1716 | * Handles <li> element mouseout events in the container. | ||
1717 | * | ||
1718 | * @method _onItemMouseout | ||
1719 | * @param v {HTMLEvent} The mouseout event. | ||
1720 | * @param oSelf {Object} The AutoComplete instance. | ||
1721 | * @private | ||
1722 | */ | ||
1723 | YAHOO.widget.AutoComplete.prototype._onItemMouseout = function(v,oSelf) { | ||
1724 | if(oSelf.prehighlightClassName) { | ||
1725 | oSelf._togglePrehighlight(this,"mouseout"); | ||
1726 | } | ||
1727 | else { | ||
1728 | oSelf._toggleHighlight(this,"from"); | ||
1729 | } | ||
1730 | |||
1731 | oSelf.itemMouseOutEvent.fire(oSelf, this); | ||
1732 | }; | ||
1733 | |||
1734 | /** | ||
1735 | * Handles <li> element click events in the container. | ||
1736 | * | ||
1737 | * @method _onItemMouseclick | ||
1738 | * @param v {HTMLEvent} The click event. | ||
1739 | * @param oSelf {Object} The AutoComplete instance. | ||
1740 | * @private | ||
1741 | */ | ||
1742 | YAHOO.widget.AutoComplete.prototype._onItemMouseclick = function(v,oSelf) { | ||
1743 | // In case item has not been moused over | ||
1744 | oSelf._toggleHighlight(this,"to"); | ||
1745 | oSelf._selectItem(this); | ||
1746 | }; | ||
1747 | |||
1748 | /** | ||
1749 | * Handles container mouseover events. | ||
1750 | * | ||
1751 | * @method _onContainerMouseover | ||
1752 | * @param v {HTMLEvent} The mouseover event. | ||
1753 | * @param oSelf {Object} The AutoComplete instance. | ||
1754 | * @private | ||
1755 | */ | ||
1756 | YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) { | ||
1757 | oSelf._bOverContainer = true; | ||
1758 | }; | ||
1759 | |||
1760 | /** | ||
1761 | * Handles container mouseout events. | ||
1762 | * | ||
1763 | * @method _onContainerMouseout | ||
1764 | * @param v {HTMLEvent} The mouseout event. | ||
1765 | * @param oSelf {Object} The AutoComplete instance. | ||
1766 | * @private | ||
1767 | */ | ||
1768 | YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) { | ||
1769 | oSelf._bOverContainer = false; | ||
1770 | // If container is still active | ||
1771 | if(oSelf._oCurItem) { | ||
1772 | oSelf._toggleHighlight(oSelf._oCurItem,"to"); | ||
1773 | } | ||
1774 | }; | ||
1775 | |||
1776 | /** | ||
1777 | * Handles container scroll events. | ||
1778 | * | ||
1779 | * @method _onContainerScroll | ||
1780 | * @param v {HTMLEvent} The scroll event. | ||
1781 | * @param oSelf {Object} The AutoComplete instance. | ||
1782 | * @private | ||
1783 | */ | ||
1784 | YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) { | ||
1785 | oSelf._oTextbox.focus(); | ||
1786 | }; | ||
1787 | |||
1788 | /** | ||
1789 | * Handles container resize events. | ||
1790 | * | ||
1791 | * @method _onContainerResize | ||
1792 | * @param v {HTMLEvent} The resize event. | ||
1793 | * @param oSelf {Object} The AutoComplete instance. | ||
1794 | * @private | ||
1795 | */ | ||
1796 | YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) { | ||
1797 | oSelf._toggleContainerHelpers(oSelf._bContainerOpen); | ||
1798 | }; | ||
1799 | |||
1800 | /** | ||
1801 | * Handles textbox keydown events of functional keys, mainly for UI behavior. | ||
1802 | * | ||
1803 | * @method _onTextboxKeyDown | ||
1804 | * @param v {HTMLEvent} The keydown event. | ||
1805 | * @param oSelf {object} The AutoComplete instance. | ||
1806 | * @private | ||
1807 | */ | ||
1808 | YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) { | ||
1809 | var nKeyCode = v.keyCode; | ||
1810 | |||
1811 | switch (nKeyCode) { | ||
1812 | case 9: // tab | ||
1813 | if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) { | ||
1814 | if(oSelf._bContainerOpen) { | ||
1815 | YAHOO.util.Event.stopEvent(v); | ||
1816 | } | ||
1817 | } | ||
1818 | // select an item or clear out | ||
1819 | if(oSelf._oCurItem) { | ||
1820 | oSelf._selectItem(oSelf._oCurItem); | ||
1821 | } | ||
1822 | else { | ||
1823 | oSelf._toggleContainer(false); | ||
1824 | } | ||
1825 | break; | ||
1826 | case 13: // enter | ||
1827 | if(oSelf._nKeyCode != nKeyCode) { | ||
1828 | if(oSelf._bContainerOpen) { | ||
1829 | YAHOO.util.Event.stopEvent(v); | ||
1830 | } | ||
1831 | } | ||
1832 | if(oSelf._oCurItem) { | ||
1833 | oSelf._selectItem(oSelf._oCurItem); | ||
1834 | } | ||
1835 | else { | ||
1836 | oSelf._toggleContainer(false); | ||
1837 | } | ||
1838 | break; | ||
1839 | case 27: // esc | ||
1840 | oSelf._toggleContainer(false); | ||
1841 | return; | ||
1842 | case 39: // right | ||
1843 | oSelf._jumpSelection(); | ||
1844 | break; | ||
1845 | case 38: // up | ||
1846 | YAHOO.util.Event.stopEvent(v); | ||
1847 | oSelf._moveSelection(nKeyCode); | ||
1848 | break; | ||
1849 | case 40: // down | ||
1850 | YAHOO.util.Event.stopEvent(v); | ||
1851 | oSelf._moveSelection(nKeyCode); | ||
1852 | break; | ||
1853 | default: | ||
1854 | break; | ||
1855 | } | ||
1856 | }; | ||
1857 | |||
1858 | /** | ||
1859 | * Handles textbox keypress events. | ||
1860 | * @method _onTextboxKeyPress | ||
1861 | * @param v {HTMLEvent} The keypress event. | ||
1862 | * @param oSelf {Object} The AutoComplete instance. | ||
1863 | * @private | ||
1864 | */ | ||
1865 | YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) { | ||
1866 | var nKeyCode = v.keyCode; | ||
1867 | |||
1868 | //Expose only to Mac browsers, where stopEvent is ineffective on keydown events (bug 790337) | ||
1869 | var isMac = (navigator.userAgent.toLowerCase().indexOf("mac") != -1); | ||
1870 | if(isMac) { | ||
1871 | switch (nKeyCode) { | ||
1872 | case 9: // tab | ||
1873 | if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) { | ||
1874 | if(oSelf._bContainerOpen) { | ||
1875 | YAHOO.util.Event.stopEvent(v); | ||
1876 | } | ||
1877 | } | ||
1878 | break; | ||
1879 | case 13: // enter | ||
1880 | if(oSelf._nKeyCode != nKeyCode) { | ||
1881 | if(oSelf._bContainerOpen) { | ||
1882 | YAHOO.util.Event.stopEvent(v); | ||
1883 | } | ||
1884 | } | ||
1885 | break; | ||
1886 | case 38: // up | ||
1887 | case 40: // down | ||
1888 | YAHOO.util.Event.stopEvent(v); | ||
1889 | break; | ||
1890 | default: | ||
1891 | break; | ||
1892 | } | ||
1893 | } | ||
1894 | |||
1895 | //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948) | ||
1896 | // Korean IME detected | ||
1897 | else if(nKeyCode == 229) { | ||
1898 | oSelf._queryInterval = setInterval(function() { oSelf._onIMEDetected(oSelf); },500); | ||
1899 | } | ||
1900 | }; | ||
1901 | |||
1902 | /** | ||
1903 | * Handles textbox keyup events that trigger queries. | ||
1904 | * | ||
1905 | * @method _onTextboxKeyUp | ||
1906 | * @param v {HTMLEvent} The keyup event. | ||
1907 | * @param oSelf {Object} The AutoComplete instance. | ||
1908 | * @private | ||
1909 | */ | ||
1910 | YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) { | ||
1911 | // Check to see if any of the public properties have been updated | ||
1912 | oSelf._initProps(); | ||
1913 | |||
1914 | var nKeyCode = v.keyCode; | ||
1915 | oSelf._nKeyCode = nKeyCode; | ||
1916 | var sText = this.value; //string in textbox | ||
1917 | |||
1918 | // Filter out chars that don't trigger queries | ||
1919 | if (oSelf._isIgnoreKey(nKeyCode) || (sText.toLowerCase() == oSelf._sCurQuery)) { | ||
1920 | return; | ||
1921 | } | ||
1922 | else { | ||
1923 | oSelf.textboxKeyEvent.fire(oSelf, nKeyCode); | ||
1924 | } | ||
1925 | |||
1926 | // Set timeout on the request | ||
1927 | if (oSelf.queryDelay > 0) { | ||
1928 | var nDelayID = | ||
1929 | setTimeout(function(){oSelf._sendQuery(sText);},(oSelf.queryDelay * 1000)); | ||
1930 | |||
1931 | if (oSelf._nDelayID != -1) { | ||
1932 | clearTimeout(oSelf._nDelayID); | ||
1933 | } | ||
1934 | |||
1935 | oSelf._nDelayID = nDelayID; | ||
1936 | } | ||
1937 | else { | ||
1938 | // No delay so send request immediately | ||
1939 | oSelf._sendQuery(sText); | ||
1940 | } | ||
1941 | }; | ||
1942 | |||
1943 | /** | ||
1944 | * Handles text input box receiving focus. | ||
1945 | * | ||
1946 | * @method _onTextboxFocus | ||
1947 | * @param v {HTMLEvent} The focus event. | ||
1948 | * @param oSelf {Object} The AutoComplete instance. | ||
1949 | * @private | ||
1950 | */ | ||
1951 | YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) { | ||
1952 | oSelf._oTextbox.setAttribute("autocomplete","off"); | ||
1953 | oSelf._bFocused = true; | ||
1954 | oSelf.textboxFocusEvent.fire(oSelf); | ||
1955 | }; | ||
1956 | |||
1957 | /** | ||
1958 | * Handles text input box losing focus. | ||
1959 | * | ||
1960 | * @method _onTextboxBlur | ||
1961 | * @param v {HTMLEvent} The focus event. | ||
1962 | * @param oSelf {Object} The AutoComplete instance. | ||
1963 | * @private | ||
1964 | */ | ||
1965 | YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) { | ||
1966 | // Don't treat as a blur if it was a selection via mouse click | ||
1967 | if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) { | ||
1968 | // Current query needs to be validated | ||
1969 | if(!oSelf._bItemSelected) { | ||
1970 | if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && !oSelf._textMatchesOption())) { | ||
1971 | if(oSelf.forceSelection) { | ||
1972 | oSelf._clearSelection(); | ||
1973 | } | ||
1974 | else { | ||
1975 | oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery); | ||
1976 | } | ||
1977 | } | ||
1978 | } | ||
1979 | |||
1980 | if(oSelf._bContainerOpen) { | ||
1981 | oSelf._toggleContainer(false); | ||
1982 | } | ||
1983 | oSelf._cancelIntervalDetection(oSelf); | ||
1984 | oSelf._bFocused = false; | ||
1985 | oSelf.textboxBlurEvent.fire(oSelf); | ||
1986 | } | ||
1987 | }; | ||
1988 | |||
1989 | /** | ||
1990 | * Handles form submission event. | ||
1991 | * | ||
1992 | * @method _onFormSubmit | ||
1993 | * @param v {HTMLEvent} The submit event. | ||
1994 | * @param oSelf {Object} The AutoComplete instance. | ||
1995 | * @private | ||
1996 | */ | ||
1997 | YAHOO.widget.AutoComplete.prototype._onFormSubmit = function(v,oSelf) { | ||
1998 | if(oSelf.allowBrowserAutocomplete) { | ||
1999 | oSelf._oTextbox.setAttribute("autocomplete","on"); | ||
2000 | } | ||
2001 | else { | ||
2002 | oSelf._oTextbox.setAttribute("autocomplete","off"); | ||
2003 | } | ||
2004 | }; | ||
2005 | /****************************************************************************/ | ||
2006 | /****************************************************************************/ | ||
2007 | /****************************************************************************/ | ||
2008 | |||
2009 | /** | ||
2010 | * The DataSource classes manages sending a request and returning response from a live | ||
2011 | * database. Supported data include local JavaScript arrays and objects and databases | ||
2012 | * accessible via XHR connections. Supported response formats include JavaScript arrays, | ||
2013 | * JSON, XML, and flat-file textual data. | ||
2014 | * | ||
2015 | * @class DataSource | ||
2016 | * @constructor | ||
2017 | */ | ||
2018 | YAHOO.widget.DataSource = function() { | ||
2019 | /* abstract class */ | ||
2020 | }; | ||
2021 | |||
2022 | |||
2023 | ///////////////////////////////////////////////////////////////////////////// | ||
2024 | // | ||
2025 | // Public constants | ||
2026 | // | ||
2027 | ///////////////////////////////////////////////////////////////////////////// | ||
2028 | |||
2029 | /** | ||
2030 | * Error message for null data responses. | ||
2031 | * | ||
2032 | * @property ERROR_DATANULL | ||
2033 | * @type String | ||
2034 | * @static | ||
2035 | * @final | ||
2036 | */ | ||
2037 | YAHOO.widget.DataSource.ERROR_DATANULL = "Response data was null"; | ||
2038 | |||
2039 | /** | ||
2040 | * Error message for data responses with parsing errors. | ||
2041 | * | ||
2042 | * @property ERROR_DATAPARSE | ||
2043 | * @type String | ||
2044 | * @static | ||
2045 | * @final | ||
2046 | */ | ||
2047 | YAHOO.widget.DataSource.ERROR_DATAPARSE = "Response data could not be parsed"; | ||
2048 | |||
2049 | |||
2050 | ///////////////////////////////////////////////////////////////////////////// | ||
2051 | // | ||
2052 | // Public member variables | ||
2053 | // | ||
2054 | ///////////////////////////////////////////////////////////////////////////// | ||
2055 | |||
2056 | /** | ||
2057 | * Max size of the local cache. Set to 0 to turn off caching. Caching is | ||
2058 | * useful to reduce the number of server connections. Recommended only for data | ||
2059 | * sources that return comprehensive results for queries or when stale data is | ||
2060 | * not an issue. | ||
2061 | * | ||
2062 | * @property maxCacheEntries | ||
2063 | * @type Number | ||
2064 | * @default 15 | ||
2065 | */ | ||
2066 | YAHOO.widget.DataSource.prototype.maxCacheEntries = 15; | ||
2067 | |||
2068 | /** | ||
2069 | * Use this to equate cache matching with the type of matching done by your live | ||
2070 | * data source. If caching is on and queryMatchContains is true, the cache | ||
2071 | * returns results that "contain" the query string. By default, | ||
2072 | * queryMatchContains is set to false, meaning the cache only returns results | ||
2073 | * that "start with" the query string. | ||
2074 | * | ||
2075 | * @property queryMatchContains | ||
2076 | * @type Boolean | ||
2077 | * @default false | ||
2078 | */ | ||
2079 | YAHOO.widget.DataSource.prototype.queryMatchContains = false; | ||
2080 | |||
2081 | /** | ||
2082 | * Enables query subset matching. If caching is on and queryMatchSubset is | ||
2083 | * true, substrings of queries will return matching cached results. For | ||
2084 | * instance, if the first query is for "abc" susequent queries that start with | ||
2085 | * "abc", like "abcd", will be queried against the cache, and not the live data | ||
2086 | * source. Recommended only for DataSources that return comprehensive results | ||
2087 | * for queries with very few characters. | ||
2088 | * | ||
2089 | * @property queryMatchSubset | ||
2090 | * @type Boolean | ||
2091 | * @default false | ||
2092 | * | ||
2093 | */ | ||
2094 | YAHOO.widget.DataSource.prototype.queryMatchSubset = false; | ||
2095 | |||
2096 | /** | ||
2097 | * Enables query case-sensitivity matching. If caching is on and | ||
2098 | * queryMatchCase is true, queries will only return results for case-sensitive | ||
2099 | * matches. | ||
2100 | * | ||
2101 | * @property queryMatchCase | ||
2102 | * @type Boolean | ||
2103 | * @default false | ||
2104 | */ | ||
2105 | YAHOO.widget.DataSource.prototype.queryMatchCase = false; | ||
2106 | |||
2107 | |||
2108 | ///////////////////////////////////////////////////////////////////////////// | ||
2109 | // | ||
2110 | // Public methods | ||
2111 | // | ||
2112 | ///////////////////////////////////////////////////////////////////////////// | ||
2113 | |||
2114 | /** | ||
2115 | * Public accessor to the unique name of the DataSource instance. | ||
2116 | * | ||
2117 | * @method toString | ||
2118 | * @return {String} Unique name of the DataSource instance | ||
2119 | */ | ||
2120 | YAHOO.widget.DataSource.prototype.toString = function() { | ||
2121 | return "DataSource " + this._sName; | ||
2122 | }; | ||
2123 | |||
2124 | /** | ||
2125 | * Retrieves query results, first checking the local cache, then making the | ||
2126 | * query request to the live data source as defined by the function doQuery. | ||
2127 | * | ||
2128 | * @method getResults | ||
2129 | * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results. | ||
2130 | * @param sQuery {String} Query string. | ||
2131 | * @param oParent {Object} The object instance that has requested data. | ||
2132 | */ | ||
2133 | YAHOO.widget.DataSource.prototype.getResults = function(oCallbackFn, sQuery, oParent) { | ||
2134 | |||
2135 | // First look in cache | ||
2136 | var aResults = this._doQueryCache(oCallbackFn,sQuery,oParent); | ||
2137 | |||
2138 | // Not in cache, so get results from server | ||
2139 | if(aResults.length === 0) { | ||
2140 | this.queryEvent.fire(this, oParent, sQuery); | ||
2141 | this.doQuery(oCallbackFn, sQuery, oParent); | ||
2142 | } | ||
2143 | }; | ||
2144 | |||
2145 | /** | ||
2146 | * Abstract method implemented by subclasses to make a query to the live data | ||
2147 | * source. Must call the callback function with the response returned from the | ||
2148 | * query. Populates cache (if enabled). | ||
2149 | * | ||
2150 | * @method doQuery | ||
2151 | * @param oCallbackFn {HTMLFunction} Callback function implemented by oParent to which to return results. | ||
2152 | * @param sQuery {String} Query string. | ||
2153 | * @param oParent {Object} The object instance that has requested data. | ||
2154 | */ | ||
2155 | YAHOO.widget.DataSource.prototype.doQuery = function(oCallbackFn, sQuery, oParent) { | ||
2156 | /* override this */ | ||
2157 | }; | ||
2158 | |||
2159 | /** | ||
2160 | * Flushes cache. | ||
2161 | * | ||
2162 | * @method flushCache | ||
2163 | */ | ||
2164 | YAHOO.widget.DataSource.prototype.flushCache = function() { | ||
2165 | if(this._aCache) { | ||
2166 | this._aCache = []; | ||
2167 | } | ||
2168 | if(this._aCacheHelper) { | ||
2169 | this._aCacheHelper = []; | ||
2170 | } | ||
2171 | this.cacheFlushEvent.fire(this); | ||
2172 | }; | ||
2173 | |||
2174 | ///////////////////////////////////////////////////////////////////////////// | ||
2175 | // | ||
2176 | // Public events | ||
2177 | // | ||
2178 | ///////////////////////////////////////////////////////////////////////////// | ||
2179 | |||
2180 | /** | ||
2181 | * Fired when a query is made to the live data source. | ||
2182 | * | ||
2183 | * @event queryEvent | ||
2184 | * @param oSelf {Object} The DataSource instance. | ||
2185 | * @param oParent {Object} The requesting object. | ||
2186 | * @param sQuery {String} The query string. | ||
2187 | */ | ||
2188 | YAHOO.widget.DataSource.prototype.queryEvent = null; | ||
2189 | |||
2190 | /** | ||
2191 | * Fired when a query is made to the local cache. | ||
2192 | * | ||
2193 | * @event cacheQueryEvent | ||
2194 | * @param oSelf {Object} The DataSource instance. | ||
2195 | * @param oParent {Object} The requesting object. | ||
2196 | * @param sQuery {String} The query string. | ||
2197 | */ | ||
2198 | YAHOO.widget.DataSource.prototype.cacheQueryEvent = null; | ||
2199 | |||
2200 | /** | ||
2201 | * Fired when data is retrieved from the live data source. | ||
2202 | * | ||
2203 | * @event getResultsEvent | ||
2204 | * @param oSelf {Object} The DataSource instance. | ||
2205 | * @param oParent {Object} The requesting object. | ||
2206 | * @param sQuery {String} The query string. | ||
2207 | * @param aResults {Object[]} Array of result objects. | ||
2208 | */ | ||
2209 | YAHOO.widget.DataSource.prototype.getResultsEvent = null; | ||
2210 | |||
2211 | /** | ||
2212 | * Fired when data is retrieved from the local cache. | ||
2213 | * | ||
2214 | * @event getCachedResultsEvent | ||
2215 | * @param oSelf {Object} The DataSource instance. | ||
2216 | * @param oParent {Object} The requesting object. | ||
2217 | * @param sQuery {String} The query string. | ||
2218 | * @param aResults {Object[]} Array of result objects. | ||
2219 | */ | ||
2220 | YAHOO.widget.DataSource.prototype.getCachedResultsEvent = null; | ||
2221 | |||
2222 | /** | ||
2223 | * Fired when an error is encountered with the live data source. | ||
2224 | * | ||
2225 | * @event dataErrorEvent | ||
2226 | * @param oSelf {Object} The DataSource instance. | ||
2227 | * @param oParent {Object} The requesting object. | ||
2228 | * @param sQuery {String} The query string. | ||
2229 | * @param sMsg {String} Error message string | ||
2230 | */ | ||
2231 | YAHOO.widget.DataSource.prototype.dataErrorEvent = null; | ||
2232 | |||
2233 | /** | ||
2234 | * Fired when the local cache is flushed. | ||
2235 | * | ||
2236 | * @event cacheFlushEvent | ||
2237 | * @param oSelf {Object} The DataSource instance | ||
2238 | */ | ||
2239 | YAHOO.widget.DataSource.prototype.cacheFlushEvent = null; | ||
2240 | |||
2241 | ///////////////////////////////////////////////////////////////////////////// | ||
2242 | // | ||
2243 | // Private member variables | ||
2244 | // | ||
2245 | ///////////////////////////////////////////////////////////////////////////// | ||
2246 | |||
2247 | /** | ||
2248 | * Internal class variable to index multiple DataSource instances. | ||
2249 | * | ||
2250 | * @property _nIndex | ||
2251 | * @type Number | ||
2252 | * @private | ||
2253 | * @static | ||
2254 | */ | ||
2255 | YAHOO.widget.DataSource._nIndex = 0; | ||
2256 | |||
2257 | /** | ||
2258 | * Name of DataSource instance. | ||
2259 | * | ||
2260 | * @property _sName | ||
2261 | * @type String | ||
2262 | * @private | ||
2263 | */ | ||
2264 | YAHOO.widget.DataSource.prototype._sName = null; | ||
2265 | |||
2266 | /** | ||
2267 | * Local cache of data result objects indexed chronologically. | ||
2268 | * | ||
2269 | * @property _aCache | ||
2270 | * @type Object[] | ||
2271 | * @private | ||
2272 | */ | ||
2273 | YAHOO.widget.DataSource.prototype._aCache = null; | ||
2274 | |||
2275 | |||
2276 | ///////////////////////////////////////////////////////////////////////////// | ||
2277 | // | ||
2278 | // Private methods | ||
2279 | // | ||
2280 | ///////////////////////////////////////////////////////////////////////////// | ||
2281 | |||
2282 | /** | ||
2283 | * Initializes DataSource instance. | ||
2284 | * | ||
2285 | * @method _init | ||
2286 | * @private | ||
2287 | */ | ||
2288 | YAHOO.widget.DataSource.prototype._init = function() { | ||
2289 | // Validate and initialize public configs | ||
2290 | var maxCacheEntries = this.maxCacheEntries; | ||
2291 | if(isNaN(maxCacheEntries) || (maxCacheEntries < 0)) { | ||
2292 | maxCacheEntries = 0; | ||
2293 | } | ||
2294 | // Initialize local cache | ||
2295 | if(maxCacheEntries > 0 && !this._aCache) { | ||
2296 | this._aCache = []; | ||
2297 | } | ||
2298 | |||
2299 | this._sName = "instance" + YAHOO.widget.DataSource._nIndex; | ||
2300 | YAHOO.widget.DataSource._nIndex++; | ||
2301 | |||
2302 | this.queryEvent = new YAHOO.util.CustomEvent("query", this); | ||
2303 | this.cacheQueryEvent = new YAHOO.util.CustomEvent("cacheQuery", this); | ||
2304 | this.getResultsEvent = new YAHOO.util.CustomEvent("getResults", this); | ||
2305 | this.getCachedResultsEvent = new YAHOO.util.CustomEvent("getCachedResults", this); | ||
2306 | this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this); | ||
2307 | this.cacheFlushEvent = new YAHOO.util.CustomEvent("cacheFlush", this); | ||
2308 | }; | ||
2309 | |||
2310 | /** | ||
2311 | * Adds a result object to the local cache, evicting the oldest element if the | ||
2312 | * cache is full. Newer items will have higher indexes, the oldest item will have | ||
2313 | * index of 0. | ||
2314 | * | ||
2315 | * @method _addCacheElem | ||
2316 | * @param oResult {Object} Data result object, including array of results. | ||
2317 | * @private | ||
2318 | */ | ||
2319 | YAHOO.widget.DataSource.prototype._addCacheElem = function(oResult) { | ||
2320 | var aCache = this._aCache; | ||
2321 | // Don't add if anything important is missing. | ||
2322 | if(!aCache || !oResult || !oResult.query || !oResult.results) { | ||
2323 | return; | ||
2324 | } | ||
2325 | |||
2326 | // If the cache is full, make room by removing from index=0 | ||
2327 | if(aCache.length >= this.maxCacheEntries) { | ||
2328 | aCache.shift(); | ||
2329 | } | ||
2330 | |||
2331 | // Add to cache, at the end of the array | ||
2332 | aCache.push(oResult); | ||
2333 | }; | ||
2334 | |||
2335 | /** | ||
2336 | * Queries the local cache for results. If query has been cached, the callback | ||
2337 | * function is called with the results, and the cached is refreshed so that it | ||
2338 | * is now the newest element. | ||
2339 | * | ||
2340 | * @method _doQueryCache | ||
2341 | * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results. | ||
2342 | * @param sQuery {String} Query string. | ||
2343 | * @param oParent {Object} The object instance that has requested data. | ||
2344 | * @return aResults {Object[]} Array of results from local cache if found, otherwise null. | ||
2345 | * @private | ||
2346 | */ | ||
2347 | YAHOO.widget.DataSource.prototype._doQueryCache = function(oCallbackFn, sQuery, oParent) { | ||
2348 | var aResults = []; | ||
2349 | var bMatchFound = false; | ||
2350 | var aCache = this._aCache; | ||
2351 | var nCacheLength = (aCache) ? aCache.length : 0; | ||
2352 | var bMatchContains = this.queryMatchContains; | ||
2353 | |||
2354 | // If cache is enabled... | ||
2355 | if((this.maxCacheEntries > 0) && aCache && (nCacheLength > 0)) { | ||
2356 | this.cacheQueryEvent.fire(this, oParent, sQuery); | ||
2357 | // If case is unimportant, normalize query now instead of in loops | ||
2358 | if(!this.queryMatchCase) { | ||
2359 | var sOrigQuery = sQuery; | ||
2360 | sQuery = sQuery.toLowerCase(); | ||
2361 | } | ||
2362 | |||
2363 | // Loop through each cached element's query property... | ||
2364 | for(var i = nCacheLength-1; i >= 0; i--) { | ||
2365 | var resultObj = aCache[i]; | ||
2366 | var aAllResultItems = resultObj.results; | ||
2367 | // If case is unimportant, normalize match key for comparison | ||
2368 | var matchKey = (!this.queryMatchCase) ? | ||
2369 | encodeURIComponent(resultObj.query).toLowerCase(): | ||
2370 | encodeURIComponent(resultObj.query); | ||
2371 | |||
2372 | // If a cached match key exactly matches the query... | ||
2373 | if(matchKey == sQuery) { | ||
2374 | // Stash all result objects into aResult[] and stop looping through the cache. | ||
2375 | bMatchFound = true; | ||
2376 | aResults = aAllResultItems; | ||
2377 | |||
2378 | // The matching cache element was not the most recent, | ||
2379 | // so now we need to refresh the cache. | ||
2380 | if(i != nCacheLength-1) { | ||
2381 | // Remove element from its original location | ||
2382 | aCache.splice(i,1); | ||
2383 | // Add element as newest | ||
2384 | this._addCacheElem(resultObj); | ||
2385 | } | ||
2386 | break; | ||
2387 | } | ||
2388 | // Else if this query is not an exact match and subset matching is enabled... | ||
2389 | else if(this.queryMatchSubset) { | ||
2390 | // Loop through substrings of each cached element's query property... | ||
2391 | for(var j = sQuery.length-1; j >= 0 ; j--) { | ||
2392 | var subQuery = sQuery.substr(0,j); | ||
2393 | |||
2394 | // If a substring of a cached sQuery exactly matches the query... | ||
2395 | if(matchKey == subQuery) { | ||
2396 | bMatchFound = true; | ||
2397 | |||
2398 | // Go through each cached result object to match against the query... | ||
2399 | for(var k = aAllResultItems.length-1; k >= 0; k--) { | ||
2400 | var aRecord = aAllResultItems[k]; | ||
2401 | var sKeyIndex = (this.queryMatchCase) ? | ||
2402 | encodeURIComponent(aRecord[0]).indexOf(sQuery): | ||
2403 | encodeURIComponent(aRecord[0]).toLowerCase().indexOf(sQuery); | ||
2404 | |||
2405 | // A STARTSWITH match is when the query is found at the beginning of the key string... | ||
2406 | if((!bMatchContains && (sKeyIndex === 0)) || | ||
2407 | // A CONTAINS match is when the query is found anywhere within the key string... | ||
2408 | (bMatchContains && (sKeyIndex > -1))) { | ||
2409 | // Stash a match into aResults[]. | ||
2410 | aResults.unshift(aRecord); | ||
2411 | } | ||
2412 | } | ||
2413 | |||
2414 | // Add the subset match result set object as the newest element to cache, | ||
2415 | // and stop looping through the cache. | ||
2416 | resultObj = {}; | ||
2417 | resultObj.query = sQuery; | ||
2418 | resultObj.results = aResults; | ||
2419 | this._addCacheElem(resultObj); | ||
2420 | break; | ||
2421 | } | ||
2422 | } | ||
2423 | if(bMatchFound) { | ||
2424 | break; | ||
2425 | } | ||
2426 | } | ||
2427 | } | ||
2428 | |||
2429 | // If there was a match, send along the results. | ||
2430 | if(bMatchFound) { | ||
2431 | this.getCachedResultsEvent.fire(this, oParent, sOrigQuery, aResults); | ||
2432 | oCallbackFn(sOrigQuery, aResults, oParent); | ||
2433 | } | ||
2434 | } | ||
2435 | return aResults; | ||
2436 | }; | ||
2437 | |||
2438 | |||
2439 | /****************************************************************************/ | ||
2440 | /****************************************************************************/ | ||
2441 | /****************************************************************************/ | ||
2442 | |||
2443 | /** | ||
2444 | * Implementation of YAHOO.widget.DataSource using XML HTTP requests that return | ||
2445 | * query results. | ||
2446 | * | ||
2447 | * @class DS_XHR | ||
2448 | * @extends YAHOO.widget.DataSource | ||
2449 | * @requires connection | ||
2450 | * @constructor | ||
2451 | * @param sScriptURI {String} Absolute or relative URI to script that returns query | ||
2452 | * results as JSON, XML, or delimited flat-file data. | ||
2453 | * @param aSchema {String[]} Data schema definition of results. | ||
2454 | * @param oConfigs {Object} (optional) Object literal of config params. | ||
2455 | */ | ||
2456 | YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) { | ||
2457 | // Set any config params passed in to override defaults | ||
2458 | if(typeof oConfigs == "object") { | ||
2459 | for(var sConfig in oConfigs) { | ||
2460 | this[sConfig] = oConfigs[sConfig]; | ||
2461 | } | ||
2462 | } | ||
2463 | |||
2464 | // Initialization sequence | ||
2465 | if(!aSchema || (aSchema.constructor != Array)) { | ||
2466 | return; | ||
2467 | } | ||
2468 | else { | ||
2469 | this.schema = aSchema; | ||
2470 | } | ||
2471 | this.scriptURI = sScriptURI; | ||
2472 | this._init(); | ||
2473 | }; | ||
2474 | |||
2475 | YAHOO.widget.DS_XHR.prototype = new YAHOO.widget.DataSource(); | ||
2476 | |||
2477 | ///////////////////////////////////////////////////////////////////////////// | ||
2478 | // | ||
2479 | // Public constants | ||
2480 | // | ||
2481 | ///////////////////////////////////////////////////////////////////////////// | ||
2482 | |||
2483 | /** | ||
2484 | * JSON data type. | ||
2485 | * | ||
2486 | * @property TYPE_JSON | ||
2487 | * @type Number | ||
2488 | * @static | ||
2489 | * @final | ||
2490 | */ | ||
2491 | YAHOO.widget.DS_XHR.TYPE_JSON = 0; | ||
2492 | |||
2493 | /** | ||
2494 | * XML data type. | ||
2495 | * | ||
2496 | * @property TYPE_XML | ||
2497 | * @type Number | ||
2498 | * @static | ||
2499 | * @final | ||
2500 | */ | ||
2501 | YAHOO.widget.DS_XHR.TYPE_XML = 1; | ||
2502 | |||
2503 | /** | ||
2504 | * Flat-file data type. | ||
2505 | * | ||
2506 | * @property TYPE_FLAT | ||
2507 | * @type Number | ||
2508 | * @static | ||
2509 | * @final | ||
2510 | */ | ||
2511 | YAHOO.widget.DS_XHR.TYPE_FLAT = 2; | ||
2512 | |||
2513 | /** | ||
2514 | * Error message for XHR failure. | ||
2515 | * | ||
2516 | * @property ERROR_DATAXHR | ||
2517 | * @type String | ||
2518 | * @static | ||
2519 | * @final | ||
2520 | */ | ||
2521 | YAHOO.widget.DS_XHR.ERROR_DATAXHR = "XHR response failed"; | ||
2522 | |||
2523 | ///////////////////////////////////////////////////////////////////////////// | ||
2524 | // | ||
2525 | // Public member variables | ||
2526 | // | ||
2527 | ///////////////////////////////////////////////////////////////////////////// | ||
2528 | |||
2529 | /** | ||
2530 | * Alias to YUI Connection Manager. Allows implementers to specify their own | ||
2531 | * subclasses of the YUI Connection Manager utility. | ||
2532 | * | ||
2533 | * @property connMgr | ||
2534 | * @type Object | ||
2535 | * @default YAHOO.util.Connect | ||
2536 | */ | ||
2537 | YAHOO.widget.DS_XHR.prototype.connMgr = YAHOO.util.Connect; | ||
2538 | |||
2539 | /** | ||
2540 | * Number of milliseconds the XHR connection will wait for a server response. A | ||
2541 | * a value of zero indicates the XHR connection will wait forever. Any value | ||
2542 | * greater than zero will use the Connection utility's Auto-Abort feature. | ||
2543 | * | ||
2544 | * @property connTimeout | ||
2545 | * @type Number | ||
2546 | * @default 0 | ||
2547 | */ | ||
2548 | YAHOO.widget.DS_XHR.prototype.connTimeout = 0; | ||
2549 | |||
2550 | /** | ||
2551 | * Absolute or relative URI to script that returns query results. For instance, | ||
2552 | * queries will be sent to <scriptURI>?<scriptQueryParam>=userinput | ||
2553 | * | ||
2554 | * @property scriptURI | ||
2555 | * @type String | ||
2556 | */ | ||
2557 | YAHOO.widget.DS_XHR.prototype.scriptURI = null; | ||
2558 | |||
2559 | /** | ||
2560 | * Query string parameter name sent to scriptURI. For instance, queries will be | ||
2561 | * sent to <scriptURI>?<scriptQueryParam>=userinput | ||
2562 | * | ||
2563 | * @property scriptQueryParam | ||
2564 | * @type String | ||
2565 | * @default "query" | ||
2566 | */ | ||
2567 | YAHOO.widget.DS_XHR.prototype.scriptQueryParam = "query"; | ||
2568 | |||
2569 | /** | ||
2570 | * String of key/value pairs to append to requests made to scriptURI. Define | ||
2571 | * this string when you want to send additional query parameters to your script. | ||
2572 | * When defined, queries will be sent to | ||
2573 | * <scriptURI>?<scriptQueryParam>=userinput&<scriptQueryAppend> | ||
2574 | * | ||
2575 | * @property scriptQueryAppend | ||
2576 | * @type String | ||
2577 | * @default "" | ||
2578 | */ | ||
2579 | YAHOO.widget.DS_XHR.prototype.scriptQueryAppend = ""; | ||
2580 | |||
2581 | /** | ||
2582 | * XHR response data type. Other types that may be defined are YAHOO.widget.DS_XHR.TYPE_XML | ||
2583 | * and YAHOO.widget.DS_XHR.TYPE_FLAT. | ||
2584 | * | ||
2585 | * @property responseType | ||
2586 | * @type String | ||
2587 | * @default YAHOO.widget.DS_XHR.TYPE_JSON | ||
2588 | */ | ||
2589 | YAHOO.widget.DS_XHR.prototype.responseType = YAHOO.widget.DS_XHR.TYPE_JSON; | ||
2590 | |||
2591 | /** | ||
2592 | * String after which to strip results. If the results from the XHR are sent | ||
2593 | * back as HTML, the gzip HTML comment appears at the end of the data and should | ||
2594 | * be ignored. | ||
2595 | * | ||
2596 | * @property responseStripAfter | ||
2597 | * @type String | ||
2598 | * @default "\n<!-" | ||
2599 | */ | ||
2600 | YAHOO.widget.DS_XHR.prototype.responseStripAfter = "\n<!-"; | ||
2601 | |||
2602 | ///////////////////////////////////////////////////////////////////////////// | ||
2603 | // | ||
2604 | // Public methods | ||
2605 | // | ||
2606 | ///////////////////////////////////////////////////////////////////////////// | ||
2607 | |||
2608 | /** | ||
2609 | * Queries the live data source defined by scriptURI for results. Results are | ||
2610 | * passed back to a callback function. | ||
2611 | * | ||
2612 | * @method doQuery | ||
2613 | * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results. | ||
2614 | * @param sQuery {String} Query string. | ||
2615 | * @param oParent {Object} The object instance that has requested data. | ||
2616 | */ | ||
2617 | YAHOO.widget.DS_XHR.prototype.doQuery = function(oCallbackFn, sQuery, oParent) { | ||
2618 | var isXML = (this.responseType == YAHOO.widget.DS_XHR.TYPE_XML); | ||
2619 | var sUri = this.scriptURI+"?"+this.scriptQueryParam+"="+sQuery; | ||
2620 | if(this.scriptQueryAppend.length > 0) { | ||
2621 | sUri += "&" + this.scriptQueryAppend; | ||
2622 | } | ||
2623 | var oResponse = null; | ||
2624 | |||
2625 | var oSelf = this; | ||
2626 | /* | ||
2627 | * Sets up ajax request callback | ||
2628 | * | ||
2629 | * @param {object} oReq HTTPXMLRequest object | ||
2630 | * @private | ||
2631 | */ | ||
2632 | var responseSuccess = function(oResp) { | ||
2633 | // Response ID does not match last made request ID. | ||
2634 | if(!oSelf._oConn || (oResp.tId != oSelf._oConn.tId)) { | ||
2635 | oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL); | ||
2636 | return; | ||
2637 | } | ||
2638 | //DEBUG | ||
2639 | for(var foo in oResp) { | ||
2640 | } | ||
2641 | if(!isXML) { | ||
2642 | oResp = oResp.responseText; | ||
2643 | } | ||
2644 | else { | ||
2645 | oResp = oResp.responseXML; | ||
2646 | } | ||
2647 | if(oResp === null) { | ||
2648 | oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL); | ||
2649 | return; | ||
2650 | } | ||
2651 | |||
2652 | var aResults = oSelf.parseResponse(sQuery, oResp, oParent); | ||
2653 | var resultObj = {}; | ||
2654 | resultObj.query = decodeURIComponent(sQuery); | ||
2655 | resultObj.results = aResults; | ||
2656 | if(aResults === null) { | ||
2657 | oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATAPARSE); | ||
2658 | aResults = []; | ||
2659 | } | ||
2660 | else { | ||
2661 | oSelf.getResultsEvent.fire(oSelf, oParent, sQuery, aResults); | ||
2662 | oSelf._addCacheElem(resultObj); | ||
2663 | } | ||
2664 | oCallbackFn(sQuery, aResults, oParent); | ||
2665 | }; | ||
2666 | |||
2667 | var responseFailure = function(oResp) { | ||
2668 | oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DS_XHR.ERROR_DATAXHR); | ||
2669 | return; | ||
2670 | }; | ||
2671 | |||
2672 | var oCallback = { | ||
2673 | success:responseSuccess, | ||
2674 | failure:responseFailure | ||
2675 | }; | ||
2676 | |||
2677 | if(!isNaN(this.connTimeout) && this.connTimeout > 0) { | ||
2678 | oCallback.timeout = this.connTimeout; | ||
2679 | } | ||
2680 | |||
2681 | if(this._oConn) { | ||
2682 | this.connMgr.abort(this._oConn); | ||
2683 | } | ||
2684 | |||
2685 | oSelf._oConn = this.connMgr.asyncRequest("GET", sUri, oCallback, null); | ||
2686 | }; | ||
2687 | |||
2688 | /** | ||
2689 | * Parses raw response data into an array of result objects. The result data key | ||
2690 | * is always stashed in the [0] element of each result object. | ||
2691 | * | ||
2692 | * @method parseResponse | ||
2693 | * @param sQuery {String} Query string. | ||
2694 | * @param oResponse {Object} The raw response data to parse. | ||
2695 | * @param oParent {Object} The object instance that has requested data. | ||
2696 | * @returns {Object[]} Array of result objects. | ||
2697 | */ | ||
2698 | YAHOO.widget.DS_XHR.prototype.parseResponse = function(sQuery, oResponse, oParent) { | ||
2699 | var aSchema = this.schema; | ||
2700 | var aResults = []; | ||
2701 | var bError = false; | ||
2702 | |||
2703 | // Strip out comment at the end of results | ||
2704 | var nEnd = ((this.responseStripAfter !== "") && (oResponse.indexOf)) ? | ||
2705 | oResponse.indexOf(this.responseStripAfter) : -1; | ||
2706 | if(nEnd != -1) { | ||
2707 | oResponse = oResponse.substring(0,nEnd); | ||
2708 | } | ||
2709 | |||
2710 | switch (this.responseType) { | ||
2711 | case YAHOO.widget.DS_XHR.TYPE_JSON: | ||
2712 | var jsonList; | ||
2713 | // Divert KHTML clients from JSON lib | ||
2714 | if(window.JSON && (navigator.userAgent.toLowerCase().indexOf('khtml')== -1)) { | ||
2715 | // Use the JSON utility if available | ||
2716 | var jsonObjParsed = JSON.parse(oResponse); | ||
2717 | if(!jsonObjParsed) { | ||
2718 | bError = true; | ||
2719 | break; | ||
2720 | } | ||
2721 | else { | ||
2722 | try { | ||
2723 | // eval is necessary here since aSchema[0] is of unknown depth | ||
2724 | jsonList = eval("jsonObjParsed." + aSchema[0]); | ||
2725 | } | ||
2726 | catch(e) { | ||
2727 | bError = true; | ||
2728 | break; | ||
2729 | } | ||
2730 | } | ||
2731 | } | ||
2732 | else { | ||
2733 | // Parse the JSON response as a string | ||
2734 | try { | ||
2735 | // Trim leading spaces | ||
2736 | while (oResponse.substring(0,1) == " ") { | ||
2737 | oResponse = oResponse.substring(1, oResponse.length); | ||
2738 | } | ||
2739 | |||
2740 | // Invalid JSON response | ||
2741 | if(oResponse.indexOf("{") < 0) { | ||
2742 | bError = true; | ||
2743 | break; | ||
2744 | } | ||
2745 | |||
2746 | // Empty (but not invalid) JSON response | ||
2747 | if(oResponse.indexOf("{}") === 0) { | ||
2748 | break; | ||
2749 | } | ||
2750 | |||
2751 | // Turn the string into an object literal... | ||
2752 | // ...eval is necessary here | ||
2753 | var jsonObjRaw = eval("(" + oResponse + ")"); | ||
2754 | if(!jsonObjRaw) { | ||
2755 | bError = true; | ||
2756 | break; | ||
2757 | } | ||
2758 | |||
2759 | // Grab the object member that contains an array of all reponses... | ||
2760 | // ...eval is necessary here since aSchema[0] is of unknown depth | ||
2761 | jsonList = eval("(jsonObjRaw." + aSchema[0]+")"); | ||
2762 | } | ||
2763 | catch(e) { | ||
2764 | bError = true; | ||
2765 | break; | ||
2766 | } | ||
2767 | } | ||
2768 | |||
2769 | if(!jsonList) { | ||
2770 | bError = true; | ||
2771 | break; | ||
2772 | } | ||
2773 | |||
2774 | if(jsonList.constructor != Array) { | ||
2775 | jsonList = [jsonList]; | ||
2776 | } | ||
2777 | |||
2778 | // Loop through the array of all responses... | ||
2779 | for(var i = jsonList.length-1; i >= 0 ; i--) { | ||
2780 | var aResultItem = []; | ||
2781 | var jsonResult = jsonList[i]; | ||
2782 | // ...and loop through each data field value of each response | ||
2783 | for(var j = aSchema.length-1; j >= 1 ; j--) { | ||
2784 | // ...and capture data into an array mapped according to the schema... | ||
2785 | var dataFieldValue = jsonResult[aSchema[j]]; | ||
2786 | if(!dataFieldValue) { | ||
2787 | dataFieldValue = ""; | ||
2788 | } | ||
2789 | aResultItem.unshift(dataFieldValue); | ||
2790 | } | ||
2791 | // If schema isn't well defined, pass along the entire result object | ||
2792 | if(aResultItem.length == 1) { | ||
2793 | aResultItem.push(jsonResult); | ||
2794 | } | ||
2795 | // Capture the array of data field values in an array of results | ||
2796 | aResults.unshift(aResultItem); | ||
2797 | } | ||
2798 | break; | ||
2799 | case YAHOO.widget.DS_XHR.TYPE_XML: | ||
2800 | // Get the collection of results | ||
2801 | var xmlList = oResponse.getElementsByTagName(aSchema[0]); | ||
2802 | if(!xmlList) { | ||
2803 | bError = true; | ||
2804 | break; | ||
2805 | } | ||
2806 | // Loop through each result | ||
2807 | for(var k = xmlList.length-1; k >= 0 ; k--) { | ||
2808 | var result = xmlList.item(k); | ||
2809 | var aFieldSet = []; | ||
2810 | // Loop through each data field in each result using the schema | ||
2811 | for(var m = aSchema.length-1; m >= 1 ; m--) { | ||
2812 | var sValue = null; | ||
2813 | // Values may be held in an attribute... | ||
2814 | var xmlAttr = result.attributes.getNamedItem(aSchema[m]); | ||
2815 | if(xmlAttr) { | ||
2816 | sValue = xmlAttr.value; | ||
2817 | } | ||
2818 | // ...or in a node | ||
2819 | else{ | ||
2820 | var xmlNode = result.getElementsByTagName(aSchema[m]); | ||
2821 | if(xmlNode && xmlNode.item(0) && xmlNode.item(0).firstChild) { | ||
2822 | sValue = xmlNode.item(0).firstChild.nodeValue; | ||
2823 | } | ||
2824 | else { | ||
2825 | sValue = ""; | ||
2826 | } | ||
2827 | } | ||
2828 | // Capture the schema-mapped data field values into an array | ||
2829 | aFieldSet.unshift(sValue); | ||
2830 | } | ||
2831 | // Capture each array of values into an array of results | ||
2832 | aResults.unshift(aFieldSet); | ||
2833 | } | ||
2834 | break; | ||
2835 | case YAHOO.widget.DS_XHR.TYPE_FLAT: | ||
2836 | if(oResponse.length > 0) { | ||
2837 | // Delete the last line delimiter at the end of the data if it exists | ||
2838 | var newLength = oResponse.length-aSchema[0].length; | ||
2839 | if(oResponse.substr(newLength) == aSchema[0]) { | ||
2840 | oResponse = oResponse.substr(0, newLength); | ||
2841 | } | ||
2842 | var aRecords = oResponse.split(aSchema[0]); | ||
2843 | for(var n = aRecords.length-1; n >= 0; n--) { | ||
2844 | aResults[n] = aRecords[n].split(aSchema[1]); | ||
2845 | } | ||
2846 | } | ||
2847 | break; | ||
2848 | default: | ||
2849 | break; | ||
2850 | } | ||
2851 | sQuery = null; | ||
2852 | oResponse = null; | ||
2853 | oParent = null; | ||
2854 | if(bError) { | ||
2855 | return null; | ||
2856 | } | ||
2857 | else { | ||
2858 | return aResults; | ||
2859 | } | ||
2860 | }; | ||
2861 | |||
2862 | ///////////////////////////////////////////////////////////////////////////// | ||
2863 | // | ||
2864 | // Private member variables | ||
2865 | // | ||
2866 | ///////////////////////////////////////////////////////////////////////////// | ||
2867 | |||
2868 | /** | ||
2869 | * XHR connection object. | ||
2870 | * | ||
2871 | * @property _oConn | ||
2872 | * @type Object | ||
2873 | * @private | ||
2874 | */ | ||
2875 | YAHOO.widget.DS_XHR.prototype._oConn = null; | ||
2876 | |||
2877 | |||
2878 | /****************************************************************************/ | ||
2879 | /****************************************************************************/ | ||
2880 | /****************************************************************************/ | ||
2881 | |||
2882 | /** | ||
2883 | * Implementation of YAHOO.widget.DataSource using a native Javascript function as | ||
2884 | * its live data source. | ||
2885 | * | ||
2886 | * @class DS_JSFunction | ||
2887 | * @constructor | ||
2888 | * @extends YAHOO.widget.DataSource | ||
2889 | * @param oFunction {String} In-memory Javascript function that returns query results as an array of objects. | ||
2890 | * @param oConfigs {Object} (optional) Object literal of config params. | ||
2891 | */ | ||
2892 | YAHOO.widget.DS_JSFunction = function(oFunction, oConfigs) { | ||
2893 | // Set any config params passed in to override defaults | ||
2894 | if(typeof oConfigs == "object") { | ||
2895 | for(var sConfig in oConfigs) { | ||
2896 | this[sConfig] = oConfigs[sConfig]; | ||
2897 | } | ||
2898 | } | ||
2899 | |||
2900 | // Initialization sequence | ||
2901 | if(!oFunction || (oFunction.constructor != Function)) { | ||
2902 | return; | ||
2903 | } | ||
2904 | else { | ||
2905 | this.dataFunction = oFunction; | ||
2906 | this._init(); | ||
2907 | } | ||
2908 | }; | ||
2909 | |||
2910 | YAHOO.widget.DS_JSFunction.prototype = new YAHOO.widget.DataSource(); | ||
2911 | |||
2912 | ///////////////////////////////////////////////////////////////////////////// | ||
2913 | // | ||
2914 | // Public member variables | ||
2915 | // | ||
2916 | ///////////////////////////////////////////////////////////////////////////// | ||
2917 | |||
2918 | /** | ||
2919 | * In-memory Javascript function that returns query results. | ||
2920 | * | ||
2921 | * @property dataFunction | ||
2922 | * @type HTMLFunction | ||
2923 | */ | ||
2924 | YAHOO.widget.DS_JSFunction.prototype.dataFunction = null; | ||
2925 | |||
2926 | ///////////////////////////////////////////////////////////////////////////// | ||
2927 | // | ||
2928 | // Public methods | ||
2929 | // | ||
2930 | ///////////////////////////////////////////////////////////////////////////// | ||
2931 | |||
2932 | /** | ||
2933 | * Queries the live data source defined by function for results. Results are | ||
2934 | * passed back to a callback function. | ||
2935 | * | ||
2936 | * @method doQuery | ||
2937 | * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results. | ||
2938 | * @param sQuery {String} Query string. | ||
2939 | * @param oParent {Object} The object instance that has requested data. | ||
2940 | */ | ||
2941 | YAHOO.widget.DS_JSFunction.prototype.doQuery = function(oCallbackFn, sQuery, oParent) { | ||
2942 | var oFunction = this.dataFunction; | ||
2943 | var aResults = []; | ||
2944 | |||
2945 | aResults = oFunction(sQuery); | ||
2946 | if(aResults === null) { | ||
2947 | this.dataErrorEvent.fire(this, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL); | ||
2948 | return; | ||
2949 | } | ||
2950 | |||
2951 | var resultObj = {}; | ||
2952 | resultObj.query = decodeURIComponent(sQuery); | ||
2953 | resultObj.results = aResults; | ||
2954 | this._addCacheElem(resultObj); | ||
2955 | |||
2956 | this.getResultsEvent.fire(this, oParent, sQuery, aResults); | ||
2957 | oCallbackFn(sQuery, aResults, oParent); | ||
2958 | return; | ||
2959 | }; | ||
2960 | |||
2961 | /****************************************************************************/ | ||
2962 | /****************************************************************************/ | ||
2963 | /****************************************************************************/ | ||
2964 | |||
2965 | /** | ||
2966 | * Implementation of YAHOO.widget.DataSource using a native Javascript array as | ||
2967 | * its live data source. | ||
2968 | * | ||
2969 | * @class DS_JSArray | ||
2970 | * @constructor | ||
2971 | * @extends YAHOO.widget.DataSource | ||
2972 | * @param aData {String[]} In-memory Javascript array of simple string data. | ||
2973 | * @param oConfigs {Object} (optional) Object literal of config params. | ||
2974 | */ | ||
2975 | YAHOO.widget.DS_JSArray = function(aData, oConfigs) { | ||
2976 | // Set any config params passed in to override defaults | ||
2977 | if(typeof oConfigs == "object") { | ||
2978 | for(var sConfig in oConfigs) { | ||
2979 | this[sConfig] = oConfigs[sConfig]; | ||
2980 | } | ||
2981 | } | ||
2982 | |||
2983 | // Initialization sequence | ||
2984 | if(!aData || (aData.constructor != Array)) { | ||
2985 | return; | ||
2986 | } | ||
2987 | else { | ||
2988 | this.data = aData; | ||
2989 | this._init(); | ||
2990 | } | ||
2991 | }; | ||
2992 | |||
2993 | YAHOO.widget.DS_JSArray.prototype = new YAHOO.widget.DataSource(); | ||
2994 | |||
2995 | ///////////////////////////////////////////////////////////////////////////// | ||
2996 | // | ||
2997 | // Public member variables | ||
2998 | // | ||
2999 | ///////////////////////////////////////////////////////////////////////////// | ||
3000 | |||
3001 | /** | ||
3002 | * In-memory Javascript array of strings. | ||
3003 | * | ||
3004 | * @property data | ||
3005 | * @type Array | ||
3006 | */ | ||
3007 | YAHOO.widget.DS_JSArray.prototype.data = null; | ||
3008 | |||
3009 | ///////////////////////////////////////////////////////////////////////////// | ||
3010 | // | ||
3011 | // Public methods | ||
3012 | // | ||
3013 | ///////////////////////////////////////////////////////////////////////////// | ||
3014 | |||
3015 | /** | ||
3016 | * Queries the live data source defined by data for results. Results are passed | ||
3017 | * back to a callback function. | ||
3018 | * | ||
3019 | * @method doQuery | ||
3020 | * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results. | ||
3021 | * @param sQuery {String} Query string. | ||
3022 | * @param oParent {Object} The object instance that has requested data. | ||
3023 | */ | ||
3024 | YAHOO.widget.DS_JSArray.prototype.doQuery = function(oCallbackFn, sQuery, oParent) { | ||
3025 | var aData = this.data; // the array | ||
3026 | var aResults = []; // container for results | ||
3027 | var bMatchFound = false; | ||
3028 | var bMatchContains = this.queryMatchContains; | ||
3029 | if(sQuery) { | ||
3030 | if(!this.queryMatchCase) { | ||
3031 | sQuery = sQuery.toLowerCase(); | ||
3032 | } | ||
3033 | |||
3034 | // Loop through each element of the array... | ||
3035 | // which can be a string or an array of strings | ||
3036 | for(var i = aData.length-1; i >= 0; i--) { | ||
3037 | var aDataset = []; | ||
3038 | |||
3039 | if(aData[i]) { | ||
3040 | if(aData[i].constructor == String) { | ||
3041 | aDataset[0] = aData[i]; | ||
3042 | } | ||
3043 | else if(aData[i].constructor == Array) { | ||
3044 | aDataset = aData[i]; | ||
3045 | } | ||
3046 | } | ||
3047 | |||
3048 | if(aDataset[0] && (aDataset[0].constructor == String)) { | ||
3049 | var sKeyIndex = (this.queryMatchCase) ? | ||
3050 | encodeURIComponent(aDataset[0]).indexOf(sQuery): | ||
3051 | encodeURIComponent(aDataset[0]).toLowerCase().indexOf(sQuery); | ||
3052 | |||
3053 | // A STARTSWITH match is when the query is found at the beginning of the key string... | ||
3054 | if((!bMatchContains && (sKeyIndex === 0)) || | ||
3055 | // A CONTAINS match is when the query is found anywhere within the key string... | ||
3056 | (bMatchContains && (sKeyIndex > -1))) { | ||
3057 | // Stash a match into aResults[]. | ||
3058 | aResults.unshift(aDataset); | ||
3059 | } | ||
3060 | } | ||
3061 | } | ||
3062 | } | ||
3063 | |||
3064 | this.getResultsEvent.fire(this, oParent, sQuery, aResults); | ||
3065 | oCallbackFn(sQuery, aResults, oParent); | ||
3066 | }; | ||