|
1 <?xml version="1.0"?> |
|
2 <!-- This Source Code Form is subject to the terms of the Mozilla Public |
|
3 - License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> |
|
5 |
|
6 |
|
7 <bindings id="autocompleteBindings" |
|
8 xmlns="http://www.mozilla.org/xbl" |
|
9 xmlns:html="http://www.w3.org/1999/xhtml" |
|
10 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" |
|
11 xmlns:xbl="http://www.mozilla.org/xbl"> |
|
12 |
|
13 <binding id="autocomplete" role="xul:combobox" |
|
14 extends="chrome://global/content/bindings/textbox.xml#textbox"> |
|
15 <resources> |
|
16 <stylesheet src="chrome://global/content/autocomplete.css"/> |
|
17 <stylesheet src="chrome://global/skin/autocomplete.css"/> |
|
18 </resources> |
|
19 |
|
20 <content> |
|
21 <children includes="menupopup"/> |
|
22 |
|
23 <xul:hbox class="autocomplete-textbox-container" flex="1" align="center"> |
|
24 <children includes="image|deck|stack|box"> |
|
25 <xul:image class="autocomplete-icon" allowevents="true"/> |
|
26 </children> |
|
27 |
|
28 <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext"> |
|
29 <children/> |
|
30 <html:input anonid="input" class="autocomplete-textbox textbox-input" |
|
31 allowevents="true" |
|
32 xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/> |
|
33 </xul:hbox> |
|
34 <children includes="hbox"/> |
|
35 </xul:hbox> |
|
36 |
|
37 <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true" |
|
38 xbl:inherits="open,enablehistory" anonid="historydropmarker"/> |
|
39 |
|
40 <xul:popupset> |
|
41 <xul:panel type="autocomplete" anonid="popup" |
|
42 ignorekeys="true" noautofocus="true" level="top" |
|
43 xbl:inherits="for=id,nomatch"/> |
|
44 </xul:popupset> |
|
45 </content> |
|
46 |
|
47 <implementation implements="nsIDOMXULMenuListElement"> |
|
48 |
|
49 <constructor><![CDATA[ |
|
50 // XXX bug 90337 band-aid until we figure out what's going on here |
|
51 if (this.value != this.mInputElt.value) |
|
52 this.mInputElt.value = this.value; |
|
53 delete this.value; |
|
54 |
|
55 // listen for pastes |
|
56 this.mInputElt.controllers.insertControllerAt(0, this.mPasteController); |
|
57 |
|
58 // listen for menubar activation |
|
59 window.top.addEventListener("DOMMenuBarActive", this.mMenuBarListener, true); |
|
60 |
|
61 // set default property values |
|
62 this.ifSetAttribute("timeout", 50); |
|
63 this.ifSetAttribute("pastetimeout", 1000); |
|
64 this.ifSetAttribute("maxrows", 5); |
|
65 this.ifSetAttribute("showpopup", true); |
|
66 this.ifSetAttribute("disableKeyNavigation", true); |
|
67 |
|
68 // initialize the search sessions |
|
69 if (this.hasAttribute("autocompletesearch")) |
|
70 this.initAutoCompleteSearch(); |
|
71 |
|
72 // hack to work around lack of bottom-up constructor calling |
|
73 if ("initialize" in this.popup) |
|
74 this.popup.initialize(); |
|
75 ]]></constructor> |
|
76 |
|
77 <destructor><![CDATA[ |
|
78 this.clearResults(false); |
|
79 window.top.removeEventListener("DOMMenuBarActive", this.mMenuBarListener, true); |
|
80 this.mInputElt.controllers.removeController(this.mPasteController); |
|
81 ]]></destructor> |
|
82 |
|
83 <!-- =================== nsIAutoCompleteInput =================== --> |
|
84 <!-- XXX: This implementation is currently incomplete. --> |
|
85 |
|
86 <!-- reference to the results popup element --> |
|
87 <field name="popup"><![CDATA[ |
|
88 document.getAnonymousElementByAttribute(this, "anonid", "popup"); |
|
89 ]]></field> |
|
90 |
|
91 <property name="popupOpen" |
|
92 onget="return this.mMenuOpen;" |
|
93 onset="if (val) this.openPopup(); else this.closePopup(); return val;"/> |
|
94 |
|
95 <!-- option to turn off autocomplete --> |
|
96 <property name="disableAutoComplete" |
|
97 onset="this.setAttribute('disableautocomplete', val); return val;" |
|
98 onget="return this.getAttribute('disableautocomplete') == 'true';"/> |
|
99 |
|
100 <!-- if the resulting match string is not at the beginning of the typed string, |
|
101 this will optionally autofill like this "bar |>> foobar|" --> |
|
102 <property name="completeDefaultIndex" |
|
103 onset="this.setAttribute('completedefaultindex', val); return val;" |
|
104 onget="return this.getAttribute('completedefaultindex') == 'true';"/> |
|
105 |
|
106 <!-- option for completing to the default result whenever the user hits |
|
107 enter or the textbox loses focus --> |
|
108 <property name="forceComplete" |
|
109 onset="this.setAttribute('forcecomplete', val); return val;" |
|
110 onget="return this.getAttribute('forcecomplete') == 'true';"/> |
|
111 |
|
112 <property name="minResultsForPopup" |
|
113 onset="this.setAttribute('minresultsforpopup', val); return val;" |
|
114 onget="var t = this.getAttribute('minresultsforpopup'); return t ? parseInt(t) : 1;"/> |
|
115 |
|
116 <!-- maximum number of rows to display --> |
|
117 <property name="maxRows" |
|
118 onset="this.setAttribute('maxrows', val); return val;" |
|
119 onget="return parseInt(this.getAttribute('maxrows')) || 0;"/> |
|
120 |
|
121 <!-- toggles a second column in the results list which contains |
|
122 the string in the comment field of each autocomplete result --> |
|
123 <property name="showCommentColumn" |
|
124 onget="return this.getAttribute('showcommentcolumn') == 'true';"> |
|
125 <setter><![CDATA[ |
|
126 this.popup.showCommentColumn = val; |
|
127 this.setAttribute('showcommentcolumn', val); |
|
128 return val; |
|
129 ]]></setter> |
|
130 </property> |
|
131 |
|
132 <!-- number of milliseconds after a keystroke before a search begins --> |
|
133 <property name="timeout" |
|
134 onset="this.setAttribute('timeout', val); return val;" |
|
135 onget="return parseInt(this.getAttribute('timeout')) || 0;"/> |
|
136 |
|
137 <property name="searchParam" |
|
138 onget="return this.getAttribute('autocompletesearchparam') || '';" |
|
139 onset="this.setAttribute('autocompletesearchparam', val); return val;"/> |
|
140 |
|
141 <property name="searchCount" readonly="true" |
|
142 onget="return this.sessionCount;"/> |
|
143 |
|
144 <method name="getSearchAt"> |
|
145 <parameter name="aIndex"/> |
|
146 <body><![CDATA[ |
|
147 var idx = -1; |
|
148 for (var name in this.mSessions) |
|
149 if (++idx == aIndex) |
|
150 return name; |
|
151 |
|
152 return null; |
|
153 ]]></body> |
|
154 </method> |
|
155 |
|
156 <property name="textValue" |
|
157 onget="return this.value;" |
|
158 onset="this.setTextValue(val); return val;"/> |
|
159 |
|
160 <method name="onSearchBegin"> |
|
161 <body><![CDATA[ |
|
162 this._fireEvent("searchbegin"); |
|
163 ]]></body> |
|
164 </method> |
|
165 |
|
166 <method name="onSearchComplete"> |
|
167 <body><![CDATA[ |
|
168 if (this.noMatch) |
|
169 this.setAttribute("nomatch", "true"); |
|
170 else |
|
171 this.removeAttribute("nomatch"); |
|
172 |
|
173 this._fireEvent("searchcomplete"); |
|
174 ]]></body> |
|
175 </method> |
|
176 |
|
177 <method name="onTextReverted"> |
|
178 <body><![CDATA[ |
|
179 return this._fireEvent("textreverted"); |
|
180 ]]></body> |
|
181 </method> |
|
182 |
|
183 <!-- =================== nsIDOMXULMenuListElement =================== --> |
|
184 |
|
185 <property name="editable" readonly="true" |
|
186 onget="return true;" /> |
|
187 |
|
188 <property name="crop" |
|
189 onset="this.setAttribute('crop', val); return val;" |
|
190 onget="return this.getAttribute('crop');"/> |
|
191 |
|
192 <property name="label" readonly="true" |
|
193 onget="return this.mInputElt.value;"/> |
|
194 |
|
195 <property name="open" |
|
196 onget="return this.getAttribute('open') == 'true';"> |
|
197 <setter> |
|
198 <![CDATA[ |
|
199 var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker"); |
|
200 if (val) { |
|
201 this.setAttribute('open', true); |
|
202 historyPopup.showPopup(); |
|
203 } else { |
|
204 this.removeAttribute('open'); |
|
205 historyPopup.hidePopup(); |
|
206 } |
|
207 ]]> |
|
208 </setter> |
|
209 </property> |
|
210 |
|
211 <!-- =================== PUBLIC PROPERTIES =================== --> |
|
212 |
|
213 <property name="value" |
|
214 onget="return this.mInputElt.value;"> |
|
215 <setter><![CDATA[ |
|
216 this.ignoreInputEvent = true; |
|
217 this.mInputElt.value = val; |
|
218 this.ignoreInputEvent = false; |
|
219 var event = document.createEvent('Events'); |
|
220 event.initEvent('ValueChange', true, true); |
|
221 this.mInputElt.dispatchEvent(event); |
|
222 return val; |
|
223 ]]></setter> |
|
224 </property> |
|
225 |
|
226 <property name="focused" |
|
227 onget="return this.getAttribute('focused') == 'true';"/> |
|
228 |
|
229 <method name="initAutoCompleteSearch"> |
|
230 <body><![CDATA[ |
|
231 var list = this.getAttribute("autocompletesearch").split(" "); |
|
232 for (var i = 0; i < list.length; i++) { |
|
233 var name = list[i]; |
|
234 var contractid = "@mozilla.org/autocomplete/search;1?name=" + name; |
|
235 if (contractid in Components.classes) { |
|
236 try { |
|
237 this.mSessions[name] = |
|
238 Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch); |
|
239 this.mLastResults[name] = null; |
|
240 this.mLastRows[name] = 0; |
|
241 ++this.sessionCount; |
|
242 } catch (e) { |
|
243 dump("### ERROR - unable to create search \"" + name + "\".\n"); |
|
244 } |
|
245 } else { |
|
246 dump("search \"" + name + "\" not found - skipping.\n"); |
|
247 } |
|
248 } |
|
249 ]]></body> |
|
250 </method> |
|
251 |
|
252 <!-- the number of sessions currently in use --> |
|
253 <field name="sessionCount">0</field> |
|
254 |
|
255 <!-- number of milliseconds after a paste before a search begins --> |
|
256 <property name="pasteTimeout" |
|
257 onset="this.setAttribute('pastetimeout', val); return val;" |
|
258 onget="var t = parseInt(this.getAttribute('pastetimeout')); return t ? t : 0;"/> |
|
259 |
|
260 <!-- option for filling the textbox with the best match while typing |
|
261 and selecting the difference --> |
|
262 <property name="autoFill" |
|
263 onset="this.setAttribute('autofill', val); return val;" |
|
264 onget="return this.getAttribute('autofill') == 'true';"/> |
|
265 |
|
266 <!-- if this attribute is set, allow different style for |
|
267 non auto-completed lines --> |
|
268 <property name="highlightNonMatches" |
|
269 onset="this.setAttribute('highlightnonmatches', val); return val;" |
|
270 onget="return this.getAttribute('highlightnonmatches') == 'true';"/> |
|
271 |
|
272 <!-- option to show the popup containing the results --> |
|
273 <property name="showPopup" |
|
274 onset="this.setAttribute('showpopup', val); return val;" |
|
275 onget="return this.getAttribute('showpopup') == 'true';"/> |
|
276 |
|
277 <!-- option to allow scrolling through the list via the tab key, rather than |
|
278 tab moving focus out of the textbox --> |
|
279 <property name="tabScrolling" |
|
280 onset="return this.setAttribute('tabscrolling', val); return val;" |
|
281 onget="return this.getAttribute('tabscrolling') == 'true';"/> |
|
282 |
|
283 <!-- option to completely ignore any blur events while |
|
284 searches are still going on. This is useful so that nothing |
|
285 gets autopicked if the window is required to lose focus for |
|
286 some reason (eg in LDAP autocomplete, another window may be |
|
287 brought up so that the user can enter a password to authenticate |
|
288 to an LDAP server). --> |
|
289 <property name="ignoreBlurWhileSearching" |
|
290 onset="this.setAttribute('ignoreblurwhilesearching', val); return val;" |
|
291 onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/> |
|
292 |
|
293 <!-- state which indicates the current action being performed by the user. |
|
294 Possible values are : none, typing, scrolling --> |
|
295 <property name="userAction" |
|
296 onset="this.setAttribute('userAction', val); return val;" |
|
297 onget="return this.getAttribute('userAction');"/> |
|
298 |
|
299 <!-- state which indicates if the last search had no matches --> |
|
300 <field name="noMatch">true</field> |
|
301 |
|
302 <!-- state which indicates a search is currently happening --> |
|
303 <field name="isSearching">false</field> |
|
304 |
|
305 <!-- state which indicates a search timeout is current waiting --> |
|
306 <property name="isWaiting" |
|
307 onget="return this.mAutoCompleteTimer != 0;"/> |
|
308 |
|
309 <!-- =================== PRIVATE PROPERTIES =================== --> |
|
310 |
|
311 <field name="mSessions">({})</field> |
|
312 <field name="mLastResults">({})</field> |
|
313 <field name="mLastRows">({})</field> |
|
314 <field name="mLastKeyCode">null</field> |
|
315 <field name="mAutoCompleteTimer">0</field> |
|
316 <field name="mMenuOpen">false</field> |
|
317 <field name="mFireAfterSearch">false</field> |
|
318 <field name="mFinishAfterSearch">false</field> |
|
319 <field name="mNeedToFinish">false</field> |
|
320 <field name="mNeedToComplete">false</field> |
|
321 <field name="mTransientValue">false</field> |
|
322 <field name="mView">null</field> |
|
323 <field name="currentSearchString">""</field> |
|
324 <field name="ignoreInputEvent">false</field> |
|
325 <field name="oninit">null</field> |
|
326 <field name="mDefaultMatchFilled">false</field> |
|
327 <field name="mFirstReturn">true</field> |
|
328 <field name="mIsPasting">false</field> |
|
329 |
|
330 <field name="mPasteController"><![CDATA[ |
|
331 ({ |
|
332 self: this, |
|
333 kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard, |
|
334 supportsCommand: function(aCommand) { |
|
335 return aCommand == "cmd_paste"; |
|
336 }, |
|
337 isCommandEnabled: function(aCommand) { |
|
338 return aCommand == "cmd_paste" && |
|
339 this.self.editor.isSelectionEditable && |
|
340 this.self.editor.canPaste(this.kGlobalClipboard); |
|
341 }, |
|
342 doCommand: function(aCommand) { |
|
343 if (aCommand == "cmd_paste") { |
|
344 this.self.mIsPasting = true; |
|
345 this.self.editor.paste(this.kGlobalClipboard); |
|
346 this.self.mIsPasting = false; |
|
347 } |
|
348 }, |
|
349 onEvent: function() {} |
|
350 }) |
|
351 ]]></field> |
|
352 |
|
353 <field name="mMenuBarListener"><![CDATA[ |
|
354 ({ |
|
355 self: this, |
|
356 handleEvent: function(aEvent) { |
|
357 try { |
|
358 this.self.finishAutoComplete(false, false, aEvent); |
|
359 this.self.clearTimer(); |
|
360 this.self.closePopup(); |
|
361 } catch (e) { |
|
362 window.top.removeEventListener("DOMMenuBarActive", this, true); |
|
363 } |
|
364 } |
|
365 }) |
|
366 ]]></field> |
|
367 |
|
368 <field name="mAutoCompleteObserver"><![CDATA[ |
|
369 ({ |
|
370 self: this, |
|
371 onSearchResult: function(aSearch, aResult) { |
|
372 for (var name in this.self.mSessions) |
|
373 if (this.self.mSessions[name] == aSearch) |
|
374 this.self.processResults(name, aResult); |
|
375 } |
|
376 }) |
|
377 ]]></field> |
|
378 |
|
379 <field name="mInputElt"><![CDATA[ |
|
380 document.getAnonymousElementByAttribute(this, "anonid", "input"); |
|
381 ]]></field> |
|
382 |
|
383 <field name="mMenuAccessKey"><![CDATA[ |
|
384 Components.classes["@mozilla.org/preferences-service;1"] |
|
385 .getService(Components.interfaces.nsIPrefBranch) |
|
386 .getIntPref("ui.key.menuAccessKey"); |
|
387 ]]></field> |
|
388 |
|
389 <!-- =================== PUBLIC METHODS =================== --> |
|
390 |
|
391 <method name="getErrorAt"> |
|
392 <parameter name="aIndex"/> |
|
393 <body><![CDATA[ |
|
394 var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex); |
|
395 return obj && this.mLastResults[obj.session] && |
|
396 this.mLastResults[obj.session].errorDescription; |
|
397 ]]></body> |
|
398 </method> |
|
399 |
|
400 <!-- get a value from the autocomplete results as a string via an absolute index--> |
|
401 <method name="getResultValueAt"> |
|
402 <parameter name="aIndex"/> |
|
403 <body><![CDATA[ |
|
404 var obj = this.convertIndexToSession(aIndex); |
|
405 return obj ? this.getSessionValueAt(obj.session, obj.index) : null; |
|
406 ]]></body> |
|
407 </method> |
|
408 |
|
409 <!-- get a value from the autocomplete results as a string from a specific session --> |
|
410 <method name="getSessionValueAt"> |
|
411 <parameter name="aSession"/> |
|
412 <parameter name="aIndex"/> |
|
413 <body><![CDATA[ |
|
414 var result = this.mLastResults[aSession]; |
|
415 return result.errorDescription || result.getValueAt(aIndex); |
|
416 ]]></body> |
|
417 </method> |
|
418 |
|
419 <!-- get the total number of results overall --> |
|
420 <method name="getResultCount"> |
|
421 <body><![CDATA[ |
|
422 return this.view.rowCount; |
|
423 ]]></body> |
|
424 </method> |
|
425 |
|
426 <!-- get the first session that has results --> |
|
427 <method name="getDefaultSession"> |
|
428 <body><![CDATA[ |
|
429 for (var name in this.mLastResults) { |
|
430 var results = this.mLastResults[name]; |
|
431 if (results && results.matchCount > 0 && !results.errorDescription) |
|
432 return name; |
|
433 } |
|
434 return null; |
|
435 ]]></body> |
|
436 </method> |
|
437 |
|
438 <!-- empty the cached result data and empty the results popup --> |
|
439 <method name="clearResults"> |
|
440 <parameter name="aInvalidate"/> |
|
441 <body><![CDATA[ |
|
442 this.clearResultData(); |
|
443 this.clearResultElements(aInvalidate); |
|
444 ]]></body> |
|
445 </method> |
|
446 |
|
447 <!-- =================== PRIVATE METHODS =================== --> |
|
448 |
|
449 <!-- ::::::::::::: session searching ::::::::::::: --> |
|
450 |
|
451 <!-- --> |
|
452 <method name="callListener"> |
|
453 <parameter name="me"/> |
|
454 <parameter name="aAction"/> |
|
455 <body><![CDATA[ |
|
456 // bail if the binding was detached or the element removed from |
|
457 // document during the timeout |
|
458 if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode) |
|
459 return; |
|
460 |
|
461 me.clearTimer(); |
|
462 |
|
463 if (me.disableAutoComplete) |
|
464 return; |
|
465 |
|
466 switch (aAction) { |
|
467 case "startLookup": |
|
468 me.startLookup(); |
|
469 break; |
|
470 |
|
471 case "stopLookup": |
|
472 me.stopLookup(); |
|
473 break; |
|
474 } |
|
475 ]]></body> |
|
476 </method> |
|
477 |
|
478 <!-- --> |
|
479 <method name="startLookup"> |
|
480 <body><![CDATA[ |
|
481 var str = this.currentSearchString; |
|
482 if (!str) { |
|
483 this.clearResults(false); |
|
484 this.closePopup(); |
|
485 return; |
|
486 } |
|
487 |
|
488 this.isSearching = true; |
|
489 this.mFirstReturn = true; |
|
490 this.mSessionReturns = this.sessionCount; |
|
491 this.mFailureItems = 0; |
|
492 this.mDefaultMatchFilled = false; // clear out our prefill state. |
|
493 |
|
494 // Notify the input that the search is beginning. |
|
495 this.onSearchBegin(); |
|
496 |
|
497 // tell each session to start searching... |
|
498 for (var name in this.mSessions) |
|
499 try { |
|
500 this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver); |
|
501 } catch (e) { |
|
502 --this.mSessionReturns; |
|
503 this.searchFailed(); |
|
504 } |
|
505 ]]></body> |
|
506 </method> |
|
507 |
|
508 <!-- --> |
|
509 <method name="stopLookup"> |
|
510 <body><![CDATA[ |
|
511 for (var name in this.mSessions) |
|
512 this.mSessions[name].stopSearch(); |
|
513 ]]></body> |
|
514 </method> |
|
515 |
|
516 <!-- --> |
|
517 <method name="processResults"> |
|
518 <parameter name="aSessionName"/> |
|
519 <parameter name="aResults"/> |
|
520 <body><![CDATA[ |
|
521 if (this.disableAutoComplete) |
|
522 return; |
|
523 |
|
524 const ACR = Components.interfaces.nsIAutoCompleteResult; |
|
525 var status = aResults.searchResult; |
|
526 if (status != ACR.RESULT_NOMATCH_ONGOING && |
|
527 status != ACR.RESULT_SUCCESS_ONGOING) |
|
528 --this.mSessionReturns; |
|
529 |
|
530 // check the many criteria for failure |
|
531 if (aResults.errorDescription) |
|
532 ++this.mFailureItems; |
|
533 else if (status == ACR.RESULT_IGNORED || |
|
534 status == ACR.RESULT_FAILURE || |
|
535 status == ACR.RESULT_NOMATCH || |
|
536 status == ACR.RESULT_NOMATCH_ONGOING || |
|
537 aResults.matchCount == 0 || |
|
538 aResults.searchString != this.currentSearchString) |
|
539 { |
|
540 this.mLastResults[aSessionName] = null; |
|
541 if (this.mFirstReturn) |
|
542 this.clearResultElements(false); |
|
543 this.mFirstReturn = false; |
|
544 this.searchFailed(); |
|
545 return; |
|
546 } |
|
547 |
|
548 if (this.mFirstReturn) { |
|
549 if (this.view.mTree) |
|
550 this.view.mTree.beginUpdateBatch(); |
|
551 this.clearResultElements(false); // clear results, but don't repaint yet |
|
552 } |
|
553 |
|
554 // always call openPopup...we may not have opened it |
|
555 // if a previous search session didn't return enough search results. |
|
556 // it's smart and doesn't try to open itself multiple times... |
|
557 // be sure to add our result elements before calling openResultPopuup as we need |
|
558 // to know the total # of results found so far. |
|
559 this.addResultElements(aSessionName, aResults); |
|
560 |
|
561 this.autoFillInput(aSessionName, aResults, false); |
|
562 if (this.mFirstReturn && this.view.mTree) |
|
563 this.view.mTree.endUpdateBatch(); |
|
564 this.openPopup(); |
|
565 this.mFirstReturn = false; |
|
566 |
|
567 // if this is the last session to return... |
|
568 if (this.mSessionReturns == 0) |
|
569 this.postSearchCleanup(); |
|
570 |
|
571 if (this.mFinishAfterSearch) |
|
572 this.finishAutoComplete(false, this.mFireAfterSearch, null); |
|
573 ]]></body> |
|
574 </method> |
|
575 |
|
576 <!-- called each time a search fails, except when failure items need |
|
577 to be displayed. If all searches have failed, clear the list |
|
578 and close the popup --> |
|
579 <method name="searchFailed"> |
|
580 <body><![CDATA[ |
|
581 // if all searches are done and they all failed... |
|
582 if (this.mSessionReturns == 0 && this.getResultCount() == 0) { |
|
583 if (this.minResultsForPopup == 0) { |
|
584 this.clearResults(true); // clear data and repaint empty |
|
585 this.openPopup(); |
|
586 } else { |
|
587 this.closePopup(); |
|
588 } |
|
589 } |
|
590 |
|
591 // if it's the last session to return, time to clean up... |
|
592 if (this.mSessionReturns == 0) |
|
593 this.postSearchCleanup(); |
|
594 ]]></body> |
|
595 </method> |
|
596 |
|
597 <!-- does some stuff after a search is done (success or failure) --> |
|
598 <method name="postSearchCleanup"> |
|
599 <body><![CDATA[ |
|
600 this.isSearching = false; |
|
601 |
|
602 // figure out if there are no matches in all search sessions |
|
603 var failed = true; |
|
604 for (var name in this.mSessions) { |
|
605 if (this.mLastResults[name]) |
|
606 failed = this.mLastResults[name].errorDescription || |
|
607 this.mLastResults[name].matchCount == 0; |
|
608 if (!failed) |
|
609 break; |
|
610 } |
|
611 this.noMatch = failed; |
|
612 |
|
613 // if we have processed all of our searches, and none of them gave us a default index, |
|
614 // then we should try to auto fill the input field with the first match. |
|
615 // note: autoFillInput is smart enough to kick out if we've already prefilled something... |
|
616 if (!this.noMatch) { |
|
617 var defaultSession = this.getDefaultSession(); |
|
618 if (defaultSession) |
|
619 this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true); |
|
620 } |
|
621 |
|
622 // Notify the input that the search is complete. |
|
623 this.onSearchComplete(); |
|
624 ]]></body> |
|
625 </method> |
|
626 |
|
627 <!-- when the focus exits the widget or user hits return, |
|
628 determine what value to leave in the textbox --> |
|
629 <method name="finishAutoComplete"> |
|
630 <parameter name="aForceComplete"/> |
|
631 <parameter name="aFireTextCommand"/> |
|
632 <parameter name="aTriggeringEvent"/> |
|
633 <body><![CDATA[ |
|
634 this.mFinishAfterSearch = false; |
|
635 this.mFireAfterSearch = false; |
|
636 if (this.mNeedToFinish && !this.disableAutoComplete) { |
|
637 // set textbox value to either override value, or default search result |
|
638 var val = this.popup.overrideValue; |
|
639 if (val) { |
|
640 this.setTextValue(val); |
|
641 this.mNeedToFinish = false; |
|
642 } else if (this.mTransientValue || |
|
643 !(this.forceComplete || |
|
644 (aForceComplete && |
|
645 this.mDefaultMatchFilled && |
|
646 this.mNeedToComplete))) { |
|
647 this.mNeedToFinish = false; |
|
648 } else if (this.isWaiting) { |
|
649 // if the user typed, the search results are out of date, so let |
|
650 // the search finish, and tell it to come back here when it's done |
|
651 this.mFinishAfterSearch = true; |
|
652 this.mFireAfterSearch = aFireTextCommand; |
|
653 return; |
|
654 } else { |
|
655 // we want to use the default item index for the first session which gave us a valid |
|
656 // default item index... |
|
657 for (var name in this.mLastResults) { |
|
658 var results = this.mLastResults[name]; |
|
659 if (results && results.matchCount > 0 && |
|
660 !results.errorDescription && results.defaultIndex != -1) |
|
661 { |
|
662 val = results.getValueAt(results.defaultIndex); |
|
663 this.setTextValue(val); |
|
664 this.mDefaultMatchFilled = true; |
|
665 this.mNeedToFinish = false; |
|
666 break; |
|
667 } |
|
668 } |
|
669 |
|
670 if (this.mNeedToFinish) { |
|
671 // if a search is happening at this juncture, bail out of this function |
|
672 // and let the search finish, and tell it to come back here when it's done |
|
673 if (this.isSearching) { |
|
674 this.mFinishAfterSearch = true; |
|
675 this.mFireAfterSearch = aFireTextCommand; |
|
676 return; |
|
677 } |
|
678 |
|
679 this.mNeedToFinish = false; |
|
680 var defaultSession = this.getDefaultSession(); |
|
681 if (defaultSession) |
|
682 { |
|
683 // preselect the first one |
|
684 var first = this.getSessionValueAt(defaultSession, 0); |
|
685 this.setTextValue(first); |
|
686 this.mDefaultMatchFilled = true; |
|
687 } |
|
688 } |
|
689 } |
|
690 |
|
691 this.stopLookup(); |
|
692 |
|
693 this.closePopup(); |
|
694 } |
|
695 |
|
696 this.mNeedToComplete = false; |
|
697 this.clearTimer(); |
|
698 |
|
699 if (aFireTextCommand) |
|
700 this._fireEvent("textentered", this.userAction, aTriggeringEvent); |
|
701 ]]></body> |
|
702 </method> |
|
703 |
|
704 <!-- when the user clicks an entry in the autocomplete popup --> |
|
705 <method name="onResultClick"> |
|
706 <body><![CDATA[ |
|
707 // set textbox value to either override value, or the clicked result |
|
708 var errItem = this.getErrorAt(this.popup.selectedIndex); |
|
709 var val = this.popup.overrideValue; |
|
710 if (val) |
|
711 this.setTextValue(val); |
|
712 else if (this.popup.selectedIndex != -1) { |
|
713 if (errItem) { |
|
714 this.setTextValue(this.currentSearchString); |
|
715 this.mTransientValue = true; |
|
716 } else { |
|
717 this.setTextValue(this.getResultValueAt( |
|
718 this.popup.selectedIndex)); |
|
719 } |
|
720 } |
|
721 |
|
722 this.mNeedToFinish = false; |
|
723 this.mNeedToComplete = false; |
|
724 |
|
725 this.closePopup(); |
|
726 |
|
727 this.currentSearchString = ""; |
|
728 |
|
729 if (errItem) |
|
730 this._fireEvent("errorcommand", errItem); |
|
731 this._fireEvent("textentered", "clicking"); |
|
732 ]]></body> |
|
733 </method> |
|
734 |
|
735 <!-- when the user hits escape, revert the previously typed value in the textbox --> |
|
736 <method name="undoAutoComplete"> |
|
737 <body><![CDATA[ |
|
738 var val = this.currentSearchString; |
|
739 |
|
740 var ok = this.onTextReverted(); |
|
741 if ((ok || ok == undefined) && val) |
|
742 this.setTextValue(val); |
|
743 |
|
744 this.userAction = "typing"; |
|
745 |
|
746 this.currentSearchString = this.value; |
|
747 this.mNeedToComplete = false; |
|
748 ]]></body> |
|
749 </method> |
|
750 |
|
751 <!-- convert an absolute result index into a session name/index pair --> |
|
752 <method name="convertIndexToSession"> |
|
753 <parameter name="aIndex"/> |
|
754 <body><![CDATA[ |
|
755 for (var name in this.mLastRows) { |
|
756 if (aIndex < this.mLastRows[name]) |
|
757 return { session: name, index: aIndex }; |
|
758 aIndex -= this.mLastRows[name]; |
|
759 } |
|
760 return null; |
|
761 ]]></body> |
|
762 </method> |
|
763 |
|
764 <!-- ::::::::::::: user input handling ::::::::::::: --> |
|
765 |
|
766 <!-- --> |
|
767 <method name="processInput"> |
|
768 <body><![CDATA[ |
|
769 // stop current lookup in case it's async. |
|
770 this.stopLookup(); |
|
771 // stop the queued up lookup on a timer |
|
772 this.clearTimer(); |
|
773 |
|
774 if (this.disableAutoComplete) |
|
775 return; |
|
776 |
|
777 this.userAction = "typing"; |
|
778 this.mFinishAfterSearch = false; |
|
779 this.mNeedToFinish = true; |
|
780 this.mTransientValue = false; |
|
781 this.mNeedToComplete = true; |
|
782 var str = this.value; |
|
783 this.currentSearchString = str; |
|
784 this.popup.clearSelection(); |
|
785 |
|
786 var timeout = this.mIsPasting ? this.pasteTimeout : this.timeout; |
|
787 this.mAutoCompleteTimer = setTimeout(this.callListener, timeout, this, "startLookup"); |
|
788 ]]></body> |
|
789 </method> |
|
790 |
|
791 <!-- --> |
|
792 <method name="processKeyPress"> |
|
793 <parameter name="aEvent"/> |
|
794 <body><![CDATA[ |
|
795 this.mLastKeyCode = aEvent.keyCode; |
|
796 |
|
797 var killEvent = false; |
|
798 |
|
799 switch (aEvent.keyCode) { |
|
800 case KeyEvent.DOM_VK_TAB: |
|
801 if (this.tabScrolling) { |
|
802 // don't kill this event if alt-tab or ctrl-tab is hit |
|
803 if (!aEvent.altKey && !aEvent.ctrlKey) { |
|
804 killEvent = this.mMenuOpen; |
|
805 if (killEvent) |
|
806 this.keyNavigation(aEvent); |
|
807 } |
|
808 } |
|
809 break; |
|
810 |
|
811 case KeyEvent.DOM_VK_RETURN: |
|
812 |
|
813 // if this is a failure item, save it for fireErrorCommand |
|
814 var errItem = this.getErrorAt(this.popup.selectedIndex); |
|
815 |
|
816 killEvent = this.mMenuOpen; |
|
817 this.finishAutoComplete(true, true, aEvent); |
|
818 this.closePopup(); |
|
819 if (errItem) { |
|
820 this._fireEvent("errorcommand", errItem); |
|
821 } |
|
822 break; |
|
823 |
|
824 case KeyEvent.DOM_VK_ESCAPE: |
|
825 this.clearTimer(); |
|
826 killEvent = this.mMenuOpen; |
|
827 this.undoAutoComplete(); |
|
828 this.closePopup(); |
|
829 break; |
|
830 |
|
831 case KeyEvent.DOM_VK_LEFT: |
|
832 case KeyEvent.DOM_VK_RIGHT: |
|
833 case KeyEvent.DOM_VK_HOME: |
|
834 case KeyEvent.DOM_VK_END: |
|
835 this.finishAutoComplete(true, false, aEvent); |
|
836 this.clearTimer(); |
|
837 this.closePopup(); |
|
838 break; |
|
839 |
|
840 case KeyEvent.DOM_VK_DOWN: |
|
841 if (!aEvent.altKey) { |
|
842 this.clearTimer(); |
|
843 killEvent = this.keyNavigation(aEvent); |
|
844 break; |
|
845 } |
|
846 // Alt+Down falls through to history popup toggling code |
|
847 |
|
848 case KeyEvent.DOM_VK_F4: |
|
849 if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("enablehistory") == "true") { |
|
850 var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker"); |
|
851 if (historyPopup) |
|
852 historyPopup.showPopup(); |
|
853 else |
|
854 historyPopup.hidePopup(); |
|
855 } |
|
856 break; |
|
857 case KeyEvent.DOM_VK_PAGE_UP: |
|
858 case KeyEvent.DOM_VK_PAGE_DOWN: |
|
859 case KeyEvent.DOM_VK_UP: |
|
860 if (!aEvent.ctrlKey && !aEvent.metaKey) { |
|
861 this.clearTimer(); |
|
862 killEvent = this.keyNavigation(aEvent); |
|
863 } |
|
864 break; |
|
865 |
|
866 case KeyEvent.DOM_VK_BACK_SPACE: |
|
867 if (!aEvent.ctrlKey && !aEvent.altKey && !aEvent.shiftKey && |
|
868 this.selectionStart == this.currentSearchString.length && |
|
869 this.selectionEnd == this.value.length && |
|
870 this.mDefaultMatchFilled) { |
|
871 this.mDefaultMatchFilled = false; |
|
872 this.value = this.currentSearchString; |
|
873 } |
|
874 |
|
875 if (!/Mac/.test(navigator.platform)) |
|
876 break; |
|
877 case KeyEvent.DOM_VK_DELETE: |
|
878 if (/Mac/.test(navigator.platform) && !aEvent.shiftKey) |
|
879 break; |
|
880 |
|
881 if (this.mMenuOpen && this.popup.selectedIndex != -1) { |
|
882 var obj = this.convertIndexToSession(this.popup.selectedIndex); |
|
883 if (obj) { |
|
884 var result = this.mLastResults[obj.session]; |
|
885 if (!result.errorDescription) { |
|
886 var count = result.matchCount; |
|
887 result.removeValueAt(obj.index, true); |
|
888 this.view.updateResults(this.popup.selectedIndex, result.matchCount - count); |
|
889 killEvent = true; |
|
890 } |
|
891 } |
|
892 } |
|
893 break; |
|
894 } |
|
895 |
|
896 if (killEvent) { |
|
897 aEvent.preventDefault(); |
|
898 aEvent.stopPropagation(); |
|
899 } |
|
900 |
|
901 return true; |
|
902 ]]></body> |
|
903 </method> |
|
904 |
|
905 <!-- --> |
|
906 <method name="processStartComposition"> |
|
907 <body><![CDATA[ |
|
908 this.finishAutoComplete(false, false, null); |
|
909 this.clearTimer(); |
|
910 this.closePopup(); |
|
911 ]]></body> |
|
912 </method> |
|
913 |
|
914 <!-- --> |
|
915 <method name="keyNavigation"> |
|
916 <parameter name="aEvent"/> |
|
917 <body><![CDATA[ |
|
918 var k = aEvent.keyCode; |
|
919 if (k == KeyEvent.DOM_VK_TAB || |
|
920 k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN || |
|
921 k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN) |
|
922 { |
|
923 if (!this.mMenuOpen) { |
|
924 // Original xpfe style was to allow the up and down keys to have |
|
925 // their default Mac action if the popup could not be opened. |
|
926 // For compatibility for toolkit we now have to predict which |
|
927 // keys have a default action that we can always allow to fire. |
|
928 if (/Mac/.test(navigator.platform) && |
|
929 ((k == KeyEvent.DOM_VK_UP && |
|
930 (this.selectionStart != 0 || |
|
931 this.selectionEnd != 0)) || |
|
932 (k == KeyEvent.DOM_VK_DOWN && |
|
933 (this.selectionStart != this.value.length || |
|
934 this.selectionEnd != this.value.length)))) |
|
935 return false; |
|
936 if (this.currentSearchString != this.value) { |
|
937 this.processInput(); |
|
938 return true; |
|
939 } |
|
940 if (this.view.rowCount < this.minResultsForPopup) |
|
941 return true; // used to be false, see above |
|
942 |
|
943 this.mNeedToFinish = true; |
|
944 this.openPopup(); |
|
945 return true; |
|
946 } |
|
947 |
|
948 this.userAction = "scrolling"; |
|
949 this.mNeedToComplete = false; |
|
950 |
|
951 var reverse = k == KeyEvent.DOM_VK_TAB && aEvent.shiftKey || |
|
952 k == KeyEvent.DOM_VK_UP || |
|
953 k == KeyEvent.DOM_VK_PAGE_UP; |
|
954 var page = k == KeyEvent.DOM_VK_PAGE_UP || |
|
955 k == KeyEvent.DOM_VK_PAGE_DOWN; |
|
956 var selected = this.popup.selectBy(reverse, page); |
|
957 |
|
958 // determine which value to place in the textbox |
|
959 this.ignoreInputEvent = true; |
|
960 if (selected != -1) { |
|
961 if (this.getErrorAt(selected)) { |
|
962 if (this.currentSearchString) |
|
963 this.setTextValue(this.currentSearchString); |
|
964 } else { |
|
965 this.setTextValue(this.getResultValueAt(selected)); |
|
966 } |
|
967 this.mTransientValue = true; |
|
968 } else { |
|
969 if (this.currentSearchString) |
|
970 this.setTextValue(this.currentSearchString); |
|
971 this.mTransientValue = false; |
|
972 } |
|
973 |
|
974 // move cursor to the end |
|
975 this.mInputElt.setSelectionRange(this.value.length, this.value.length); |
|
976 this.ignoreInputEvent = false; |
|
977 } |
|
978 return true; |
|
979 ]]></body> |
|
980 </method> |
|
981 |
|
982 <!-- while the user is typing, fill the textbox with the "default" value |
|
983 if one can be assumed, and select the end of the text --> |
|
984 <method name="autoFillInput"> |
|
985 <parameter name="aSessionName"/> |
|
986 <parameter name="aResults"/> |
|
987 <parameter name="aUseFirstMatchIfNoDefault"/> |
|
988 <body><![CDATA[ |
|
989 if (this.mInputElt.selectionEnd < this.currentSearchString.length || |
|
990 this.mDefaultMatchFilled) |
|
991 return; |
|
992 |
|
993 if (!this.mFinishAfterSearch && |
|
994 (this.autoFill || this.completeDefaultIndex) && |
|
995 this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE && |
|
996 this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) { |
|
997 var indexToUse = aResults.defaultIndex; |
|
998 if (aUseFirstMatchIfNoDefault && indexToUse == -1) |
|
999 indexToUse = 0; |
|
1000 |
|
1001 if (indexToUse != -1) { |
|
1002 var resultValue = this.getSessionValueAt(aSessionName, indexToUse); |
|
1003 var match = resultValue.toLowerCase(); |
|
1004 var entry = this.currentSearchString.toLowerCase(); |
|
1005 this.ignoreInputEvent = true; |
|
1006 if (match.indexOf(entry) == 0) { |
|
1007 var endPoint = this.value.length; |
|
1008 this.setTextValue(this.value + resultValue.substr(endPoint)); |
|
1009 this.mInputElt.setSelectionRange(endPoint, this.value.length); |
|
1010 } else { |
|
1011 if (this.completeDefaultIndex) { |
|
1012 this.setTextValue(this.value + " >> " + resultValue); |
|
1013 this.mInputElt.setSelectionRange(entry.length, this.value.length); |
|
1014 } else { |
|
1015 var postIndex = resultValue.indexOf(this.value); |
|
1016 if (postIndex >= 0) { |
|
1017 var startPt = this.value.length; |
|
1018 this.setTextValue(this.value + |
|
1019 resultValue.substr(startPt+postIndex)); |
|
1020 this.mInputElt.setSelectionRange(startPt, this.value.length); |
|
1021 } |
|
1022 } |
|
1023 } |
|
1024 this.mNeedToComplete = true; |
|
1025 this.ignoreInputEvent = false; |
|
1026 this.mDefaultMatchFilled = true; |
|
1027 } |
|
1028 } |
|
1029 ]]></body> |
|
1030 </method> |
|
1031 |
|
1032 <!-- ::::::::::::: popup and tree ::::::::::::: --> |
|
1033 |
|
1034 <!-- --> |
|
1035 <method name="openPopup"> |
|
1036 <body><![CDATA[ |
|
1037 if (!this.mMenuOpen && this.focused && |
|
1038 (this.getResultCount() >= this.minResultsForPopup || |
|
1039 this.mFailureItems)) { |
|
1040 var w = this.boxObject.width; |
|
1041 if (w != this.popup.boxObject.width) |
|
1042 this.popup.setAttribute("width", w); |
|
1043 this.popup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft"); |
|
1044 this.mMenuOpen = true; |
|
1045 } |
|
1046 ]]></body> |
|
1047 </method> |
|
1048 |
|
1049 <!-- --> |
|
1050 <method name="closePopup"> |
|
1051 <body><![CDATA[ |
|
1052 if (this.popup && this.mMenuOpen) { |
|
1053 this.popup.hidePopup(); |
|
1054 this.mMenuOpen = false; |
|
1055 } |
|
1056 ]]></body> |
|
1057 </method> |
|
1058 |
|
1059 <!-- --> |
|
1060 <method name="addResultElements"> |
|
1061 <parameter name="aSession"/> |
|
1062 <parameter name="aResults"/> |
|
1063 <body><![CDATA[ |
|
1064 var count = aResults.errorDescription ? 1 : aResults.matchCount; |
|
1065 if (this.focused && this.showPopup) { |
|
1066 var row = 0; |
|
1067 for (var name in this.mSessions) { |
|
1068 row += this.mLastRows[name]; |
|
1069 if (name == aSession) |
|
1070 break; |
|
1071 } |
|
1072 this.view.updateResults(row, count - this.mLastRows[name]); |
|
1073 this.popup.adjustHeight(); |
|
1074 } |
|
1075 this.mLastResults[aSession] = aResults; |
|
1076 this.mLastRows[aSession] = count; |
|
1077 ]]></body> |
|
1078 </method> |
|
1079 |
|
1080 <!-- --> |
|
1081 <method name="clearResultElements"> |
|
1082 <parameter name="aInvalidate"/> |
|
1083 <body><![CDATA[ |
|
1084 for (var name in this.mSessions) |
|
1085 this.mLastRows[name] = 0; |
|
1086 this.view.clearResults(); |
|
1087 if (aInvalidate) |
|
1088 this.popup.adjustHeight(); |
|
1089 |
|
1090 this.noMatch = true; |
|
1091 ]]></body> |
|
1092 </method> |
|
1093 |
|
1094 <!-- --> |
|
1095 <method name="setTextValue"> |
|
1096 <parameter name="aValue"/> |
|
1097 <body><![CDATA[ |
|
1098 this.value = aValue; |
|
1099 |
|
1100 // Completing a result should simulate the user typing the result, |
|
1101 // so fire an input event. |
|
1102 var evt = document.createEvent("UIEvents"); |
|
1103 evt.initUIEvent("input", true, false, window, 0); |
|
1104 var oldIgnoreInput = this.ignoreInputEvent; |
|
1105 this.ignoreInputEvent = true; |
|
1106 this.dispatchEvent(evt); |
|
1107 this.ignoreInputEvent = oldIgnoreInput; |
|
1108 ]]></body> |
|
1109 </method> |
|
1110 |
|
1111 <!-- --> |
|
1112 <method name="clearResultData"> |
|
1113 <body><![CDATA[ |
|
1114 for (var name in this.mSessions) |
|
1115 this.mLastResults[name] = null; |
|
1116 ]]></body> |
|
1117 </method> |
|
1118 |
|
1119 <!-- ::::::::::::: miscellaneous ::::::::::::: --> |
|
1120 |
|
1121 <!-- --> |
|
1122 <method name="ifSetAttribute"> |
|
1123 <parameter name="aAttr"/> |
|
1124 <parameter name="aVal"/> |
|
1125 <body><![CDATA[ |
|
1126 if (!this.hasAttribute(aAttr)) |
|
1127 this.setAttribute(aAttr, aVal); |
|
1128 ]]></body> |
|
1129 </method> |
|
1130 |
|
1131 <!-- --> |
|
1132 <method name="clearTimer"> |
|
1133 <body><![CDATA[ |
|
1134 if (this.mAutoCompleteTimer) { |
|
1135 clearTimeout(this.mAutoCompleteTimer); |
|
1136 this.mAutoCompleteTimer = 0; |
|
1137 } |
|
1138 ]]></body> |
|
1139 </method> |
|
1140 |
|
1141 <!-- ::::::::::::: event dispatching ::::::::::::: --> |
|
1142 |
|
1143 <method name="_fireEvent"> |
|
1144 <parameter name="aEventType"/> |
|
1145 <parameter name="aEventParam"/> |
|
1146 <parameter name="aTriggeringEvent"/> |
|
1147 <body> |
|
1148 <![CDATA[ |
|
1149 var noCancel = true; |
|
1150 // handle any xml attribute event handlers |
|
1151 var handler = this.getAttribute("on"+aEventType); |
|
1152 if (handler) { |
|
1153 var fn = new Function("eventParam", "domEvent", handler); |
|
1154 var returned = fn.apply(this, [aEventParam, aTriggeringEvent]); |
|
1155 if (returned == false) |
|
1156 noCancel = false; |
|
1157 } |
|
1158 |
|
1159 return noCancel; |
|
1160 ]]> |
|
1161 </body> |
|
1162 </method> |
|
1163 |
|
1164 <!-- =================== TREE VIEW =================== --> |
|
1165 |
|
1166 <field name="view"><![CDATA[ |
|
1167 ({ |
|
1168 mTextbox: this, |
|
1169 mTree: null, |
|
1170 mSelection: null, |
|
1171 mRowCount: 0, |
|
1172 |
|
1173 clearResults: function() |
|
1174 { |
|
1175 var oldCount = this.mRowCount; |
|
1176 this.mRowCount = 0; |
|
1177 |
|
1178 if (this.mTree) { |
|
1179 this.mTree.rowCountChanged(0, -oldCount); |
|
1180 this.mTree.scrollToRow(0); |
|
1181 } |
|
1182 }, |
|
1183 |
|
1184 updateResults: function(aRow, aCount) |
|
1185 { |
|
1186 this.mRowCount += aCount; |
|
1187 |
|
1188 if (this.mTree) |
|
1189 this.mTree.rowCountChanged(aRow, aCount); |
|
1190 }, |
|
1191 |
|
1192 ////////////////////////////////////////////////////////// |
|
1193 // nsIAutoCompleteController interface |
|
1194 |
|
1195 // this is the only method required by the treebody mouseup handler |
|
1196 handleEnter: function(aIsPopupSelection) { |
|
1197 this.mTextbox.onResultClick(); |
|
1198 }, |
|
1199 |
|
1200 ////////////////////////////////////////////////////////// |
|
1201 // nsITreeView interface |
|
1202 |
|
1203 get rowCount() { |
|
1204 return this.mRowCount; |
|
1205 }, |
|
1206 |
|
1207 get selection() { |
|
1208 return this.mSelection; |
|
1209 }, |
|
1210 |
|
1211 set selection(aVal) { |
|
1212 return this.mSelection = aVal; |
|
1213 }, |
|
1214 |
|
1215 setTree: function(aTree) |
|
1216 { |
|
1217 this.mTree = aTree; |
|
1218 }, |
|
1219 |
|
1220 getCellText: function(aRow, aCol) |
|
1221 { |
|
1222 for (var name in this.mTextbox.mSessions) { |
|
1223 if (aRow < this.mTextbox.mLastRows[name]) { |
|
1224 var result = this.mTextbox.mLastResults[name]; |
|
1225 switch (aCol.id) { |
|
1226 case "treecolAutoCompleteValue": |
|
1227 return result.errorDescription || result.getLabelAt(aRow); |
|
1228 case "treecolAutoCompleteComment": |
|
1229 if (!result.errorDescription) |
|
1230 return result.getCommentAt(aRow); |
|
1231 default: |
|
1232 return ""; |
|
1233 } |
|
1234 } |
|
1235 aRow -= this.mTextbox.mLastRows[name]; |
|
1236 } |
|
1237 return ""; |
|
1238 }, |
|
1239 |
|
1240 getRowProperties: function(aIndex) |
|
1241 { |
|
1242 return ""; |
|
1243 }, |
|
1244 |
|
1245 getCellProperties: function(aIndex, aCol) |
|
1246 { |
|
1247 // for the value column, append nsIAutoCompleteItem::className |
|
1248 // to the property list so that we can style this column |
|
1249 // using that property |
|
1250 if (aCol.id == "treecolAutoCompleteValue") { |
|
1251 for (var name in this.mTextbox.mSessions) { |
|
1252 if (aIndex < this.mTextbox.mLastRows[name]) { |
|
1253 var result = this.mTextbox.mLastResults[name]; |
|
1254 if (result.errorDescription) |
|
1255 return ""; |
|
1256 return result.getStyleAt(aIndex); |
|
1257 } |
|
1258 aIndex -= this.mTextbox.mLastRows[name]; |
|
1259 } |
|
1260 } |
|
1261 return ""; |
|
1262 }, |
|
1263 |
|
1264 getColumnProperties: function(aCol) |
|
1265 { |
|
1266 return ""; |
|
1267 }, |
|
1268 |
|
1269 getImageSrc: function(aRow, aCol) |
|
1270 { |
|
1271 if (aCol.id == "treecolAutoCompleteValue") { |
|
1272 for (var name in this.mTextbox.mSessions) { |
|
1273 if (aRow < this.mTextbox.mLastRows[name]) { |
|
1274 var result = this.mTextbox.mLastResults[name]; |
|
1275 if (result.errorDescription) |
|
1276 return ""; |
|
1277 return result.getImageAt(aRow); |
|
1278 } |
|
1279 aRow -= this.mTextbox.mLastRows[name]; |
|
1280 } |
|
1281 } |
|
1282 return ""; |
|
1283 }, |
|
1284 |
|
1285 getParentIndex: function(aRowIndex) { }, |
|
1286 hasNextSibling: function(aRowIndex, aAfterIndex) { }, |
|
1287 getLevel: function(aIndex) {}, |
|
1288 getProgressMode: function(aRow, aCol) {}, |
|
1289 getCellValue: function(aRow, aCol) {}, |
|
1290 isContainer: function(aIndex) {}, |
|
1291 isContainerOpen: function(aIndex) {}, |
|
1292 isContainerEmpty: function(aIndex) {}, |
|
1293 isSeparator: function(aIndex) {}, |
|
1294 isSorted: function() {}, |
|
1295 toggleOpenState: function(aIndex) {}, |
|
1296 selectionChanged: function() {}, |
|
1297 cycleHeader: function(aCol) {}, |
|
1298 cycleCell: function(aRow, aCol) {}, |
|
1299 isEditable: function(aRow, aCol) {}, |
|
1300 isSelectable: function(aRow, aCol) {}, |
|
1301 setCellValue: function(aRow, aCol, aValue) {}, |
|
1302 setCellText: function(aRow, aCol, aValue) {}, |
|
1303 performAction: function(aAction) {}, |
|
1304 performActionOnRow: function(aAction, aRow) {}, |
|
1305 performActionOnCell: function(aAction, aRow, aCol) {} |
|
1306 }); |
|
1307 ]]></field> |
|
1308 |
|
1309 </implementation> |
|
1310 |
|
1311 <handlers> |
|
1312 <handler event="input" |
|
1313 action="if (!this.ignoreInputEvent) this.processInput();"/> |
|
1314 |
|
1315 <handler event="keypress" phase="capturing" |
|
1316 action="return this.processKeyPress(event);"/> |
|
1317 |
|
1318 <handler event="compositionstart" phase="capturing" |
|
1319 action="this.processStartComposition();"/> |
|
1320 |
|
1321 <handler event="focus" phase="capturing" |
|
1322 action="this.userAction = 'typing';"/> |
|
1323 |
|
1324 <handler event="blur" phase="capturing" |
|
1325 action="if ( !(this.ignoreBlurWhileSearching && this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/> |
|
1326 |
|
1327 <handler event="mousedown" phase="capturing" |
|
1328 action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/> |
|
1329 </handlers> |
|
1330 </binding> |
|
1331 |
|
1332 <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup"> |
|
1333 <resources> |
|
1334 <stylesheet src="chrome://global/content/autocomplete.css"/> |
|
1335 <stylesheet src="chrome://global/skin/autocomplete.css"/> |
|
1336 </resources> |
|
1337 |
|
1338 <content ignorekeys="true" level="top"> |
|
1339 <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1"> |
|
1340 <xul:treecols anonid="treecols"> |
|
1341 <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/> |
|
1342 <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/> |
|
1343 </xul:treecols> |
|
1344 <xul:treechildren anonid="treebody" class="autocomplete-treebody"/> |
|
1345 </xul:tree> |
|
1346 </content> |
|
1347 |
|
1348 <implementation implements="nsIAutoCompletePopup"> |
|
1349 <constructor><![CDATA[ |
|
1350 if (this.textbox && this.textbox.view) |
|
1351 this.initialize(); |
|
1352 ]]></constructor> |
|
1353 |
|
1354 <destructor><![CDATA[ |
|
1355 if (this.view) |
|
1356 this.tree.view = null; |
|
1357 ]]></destructor> |
|
1358 |
|
1359 <field name="textbox"> |
|
1360 document.getBindingParent(this); |
|
1361 </field> |
|
1362 |
|
1363 <field name="tree"> |
|
1364 document.getAnonymousElementByAttribute(this, "anonid", "tree"); |
|
1365 </field> |
|
1366 |
|
1367 <field name="treecols"> |
|
1368 document.getAnonymousElementByAttribute(this, "anonid", "treecols"); |
|
1369 </field> |
|
1370 |
|
1371 <field name="treebody"> |
|
1372 document.getAnonymousElementByAttribute(this, "anonid", "treebody"); |
|
1373 </field> |
|
1374 |
|
1375 <field name="view"> |
|
1376 null |
|
1377 </field> |
|
1378 |
|
1379 <!-- Setting tree.view doesn't always immediately create a selection, |
|
1380 so we ensure the selection by asking the tree for the view. Note: |
|
1381 this.view.selection is quicker if we know the selection exists. --> |
|
1382 <property name="selection" onget="return this.tree.view.selection;"/> |
|
1383 |
|
1384 <property name="pageCount" |
|
1385 onget="return this.tree.treeBoxObject.getPageLength();"/> |
|
1386 |
|
1387 <field name="maxRows">0</field> |
|
1388 <field name="mLastRows">0</field> |
|
1389 |
|
1390 <method name="initialize"> |
|
1391 <body><![CDATA[ |
|
1392 this.showCommentColumn = this.textbox.showCommentColumn; |
|
1393 this.tree.view = this.textbox.view; |
|
1394 this.view = this.textbox.view; |
|
1395 this.maxRows = this.textbox.maxRows; |
|
1396 ]]></body> |
|
1397 </method> |
|
1398 |
|
1399 <property name="showCommentColumn" |
|
1400 onget="return !this.treecols.lastChild.hidden;" |
|
1401 onset="this.treecols.lastChild.hidden = !val; return val;"/> |
|
1402 |
|
1403 <method name="adjustHeight"> |
|
1404 <body><![CDATA[ |
|
1405 // detect the desired height of the tree |
|
1406 var bx = this.tree.treeBoxObject; |
|
1407 var view = this.view; |
|
1408 var rows = this.maxRows || 6; |
|
1409 if (!view.rowCount || (rows && view.rowCount < rows)) |
|
1410 rows = view.rowCount; |
|
1411 |
|
1412 var height = rows * bx.rowHeight; |
|
1413 |
|
1414 if (height == 0) |
|
1415 this.tree.setAttribute("collapsed", "true"); |
|
1416 else { |
|
1417 if (this.tree.hasAttribute("collapsed")) |
|
1418 this.tree.removeAttribute("collapsed"); |
|
1419 this.tree.setAttribute("height", height); |
|
1420 } |
|
1421 ]]></body> |
|
1422 </method> |
|
1423 |
|
1424 <method name="clearSelection"> |
|
1425 <body> |
|
1426 this.selection.clearSelection(); |
|
1427 </body> |
|
1428 </method> |
|
1429 |
|
1430 <method name="getNextIndex"> |
|
1431 <parameter name="aReverse"/> |
|
1432 <parameter name="aPage"/> |
|
1433 <parameter name="aIndex"/> |
|
1434 <parameter name="aMaxRow"/> |
|
1435 <body><![CDATA[ |
|
1436 if (aMaxRow < 0) |
|
1437 return -1; |
|
1438 |
|
1439 if (aIndex == -1) |
|
1440 return aReverse ? aMaxRow : 0; |
|
1441 if (aIndex == (aReverse ? 0 : aMaxRow)) |
|
1442 return -1; |
|
1443 |
|
1444 var amount = aPage ? this.pageCount - 1 : 1; |
|
1445 aIndex = aReverse ? aIndex - amount : aIndex + amount; |
|
1446 if (aIndex > aMaxRow) |
|
1447 return aMaxRow; |
|
1448 if (aIndex < 0) |
|
1449 return 0; |
|
1450 return aIndex; |
|
1451 ]]></body> |
|
1452 </method> |
|
1453 |
|
1454 <!-- =================== nsIAutoCompletePopup =================== --> |
|
1455 |
|
1456 <field name="input"> |
|
1457 null |
|
1458 </field> |
|
1459 |
|
1460 <!-- This property is meant to be overriden by bindings extending |
|
1461 this one. When the user selects an item from the list by |
|
1462 hitting enter or clicking, this method can set the value |
|
1463 of the textbox to a different value if it wants to. --> |
|
1464 <property name="overrideValue" readonly="true" onget="return null;"/> |
|
1465 |
|
1466 <property name="selectedIndex"> |
|
1467 <getter> |
|
1468 if (!this.view || !this.selection.count) |
|
1469 return -1; |
|
1470 var start = {}, end = {}; |
|
1471 this.view.selection.getRangeAt(0, start, end); |
|
1472 return start.value; |
|
1473 </getter> |
|
1474 <setter> |
|
1475 if (this.view) { |
|
1476 this.selection.select(val); |
|
1477 if (val >= 0) { |
|
1478 this.view.selection.currentIndex = -1; |
|
1479 this.tree.treeBoxObject.ensureRowIsVisible(val); |
|
1480 } |
|
1481 } |
|
1482 return val; |
|
1483 </setter> |
|
1484 </property> |
|
1485 |
|
1486 <property name="popupOpen" onget="return !!this.input;" readonly="true"/> |
|
1487 |
|
1488 <method name="openAutocompletePopup"> |
|
1489 <parameter name="aInput"/> |
|
1490 <parameter name="aElement"/> |
|
1491 <body><![CDATA[ |
|
1492 if (!this.input) { |
|
1493 this.tree.view = aInput.controller; |
|
1494 this.view = this.tree.view; |
|
1495 this.showCommentColumn = aInput.showCommentColumn; |
|
1496 this.maxRows = aInput.maxRows; |
|
1497 this.invalidate(); |
|
1498 |
|
1499 var viewer = aElement |
|
1500 .ownerDocument |
|
1501 .defaultView |
|
1502 .QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
|
1503 .getInterface(Components.interfaces.nsIWebNavigation) |
|
1504 .QueryInterface(Components.interfaces.nsIDocShell) |
|
1505 .contentViewer |
|
1506 .QueryInterface(Components.interfaces.nsIMarkupDocumentViewer); |
|
1507 var rect = aElement.getBoundingClientRect(); |
|
1508 var width = Math.round((rect.right - rect.left) * viewer.fullZoom); |
|
1509 this.setAttribute("width", width > 100 ? width : 100); |
|
1510 // Adjust the direction (which is not inherited) of the autocomplete |
|
1511 // popup list, based on the textbox direction. (Bug 707039) |
|
1512 this.style.direction = aElement.ownerDocument.defaultView |
|
1513 .getComputedStyle(aElement) |
|
1514 .direction; |
|
1515 const nsIPopupBoxObject = Components.interfaces.nsIPopupBoxObject; |
|
1516 this.popupBoxObject.setConsumeRollupEvent(aInput.consumeRollupEvent |
|
1517 ? nsIPopupBoxObject.ROLLUP_CONSUME |
|
1518 : nsIPopupBoxObject.ROLLUP_NO_CONSUME); |
|
1519 this.openPopup(aElement, "after_start", 0, 0, false, false); |
|
1520 if (this.state != "closed") |
|
1521 this.input = aInput; |
|
1522 } |
|
1523 ]]></body> |
|
1524 </method> |
|
1525 |
|
1526 <method name="closePopup"> |
|
1527 <body> |
|
1528 this.hidePopup(); |
|
1529 </body> |
|
1530 </method> |
|
1531 |
|
1532 <method name="invalidate"> |
|
1533 <body> |
|
1534 if (this.view) |
|
1535 this.adjustHeight(); |
|
1536 this.tree.treeBoxObject.invalidate(); |
|
1537 </body> |
|
1538 </method> |
|
1539 |
|
1540 <method name="selectBy"> |
|
1541 <parameter name="aReverse"/> |
|
1542 <parameter name="aPage"/> |
|
1543 <body><![CDATA[ |
|
1544 try { |
|
1545 return this.selectedIndex = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1); |
|
1546 } catch (ex) { |
|
1547 // do nothing - occasionally timer-related js errors happen here |
|
1548 // e.g. "this.selectedIndex has no properties", when you type fast and hit a |
|
1549 // navigation key before this popup has opened |
|
1550 return -1; |
|
1551 } |
|
1552 ]]></body> |
|
1553 </method> |
|
1554 </implementation> |
|
1555 |
|
1556 <handlers> |
|
1557 <handler event="popupshowing"> |
|
1558 if (this.textbox) |
|
1559 this.textbox.mMenuOpen = true; |
|
1560 </handler> |
|
1561 |
|
1562 <handler event="popuphiding"> |
|
1563 if (this.textbox) |
|
1564 this.textbox.mMenuOpen = false; |
|
1565 this.clearSelection(); |
|
1566 this.input = null; |
|
1567 </handler> |
|
1568 </handlers> |
|
1569 </binding> |
|
1570 |
|
1571 <binding id="autocomplete-treebody"> |
|
1572 <implementation> |
|
1573 <field name="popup">document.getBindingParent(this);</field> |
|
1574 |
|
1575 <field name="mLastMoveTime">Date.now()</field> |
|
1576 </implementation> |
|
1577 |
|
1578 <handlers> |
|
1579 <handler event="mouseout" action="this.popup.selectedIndex = -1;"/> |
|
1580 |
|
1581 <handler event="mouseup"><![CDATA[ |
|
1582 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); |
|
1583 if (rc != -1) { |
|
1584 this.popup.selectedIndex = rc; |
|
1585 this.popup.view.handleEnter(true); |
|
1586 } |
|
1587 ]]></handler> |
|
1588 |
|
1589 <handler event="mousemove"><![CDATA[ |
|
1590 if (Date.now() - this.mLastMoveTime > 30) { |
|
1591 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); |
|
1592 if (rc != -1 && rc != this.popup.selectedIndex) |
|
1593 this.popup.selectedIndex = rc; |
|
1594 this.mLastMoveTime = Date.now(); |
|
1595 } |
|
1596 ]]></handler> |
|
1597 </handlers> |
|
1598 </binding> |
|
1599 |
|
1600 <binding id="autocomplete-history-popup" |
|
1601 extends="chrome://global/content/bindings/popup.xml#popup-scrollbars"> |
|
1602 <resources> |
|
1603 <stylesheet src="chrome://global/content/autocomplete.css"/> |
|
1604 <stylesheet src="chrome://global/skin/autocomplete.css"/> |
|
1605 </resources> |
|
1606 |
|
1607 <implementation> |
|
1608 <method name="removeOpenAttribute"> |
|
1609 <parameter name="parentNode"/> |
|
1610 <body><![CDATA[ |
|
1611 parentNode.removeAttribute("open"); |
|
1612 ]]></body> |
|
1613 </method> |
|
1614 </implementation> |
|
1615 |
|
1616 <handlers> |
|
1617 <handler event="popuphiding"><![CDATA[ |
|
1618 setTimeout(this.removeOpenAttribute, 0, this.parentNode); |
|
1619 ]]></handler> |
|
1620 </handlers> |
|
1621 </binding> |
|
1622 |
|
1623 <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker"> |
|
1624 |
|
1625 <implementation> |
|
1626 <method name="showPopup"> |
|
1627 <body><![CDATA[ |
|
1628 var textbox = document.getBindingParent(this); |
|
1629 var kids = textbox.getElementsByClassName("autocomplete-history-popup"); |
|
1630 if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup |
|
1631 var w = textbox.boxObject.width; |
|
1632 if (w != kids[0].boxObject.width) |
|
1633 kids[0].width = w; |
|
1634 kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft"); |
|
1635 textbox.setAttribute("open", "true"); |
|
1636 } |
|
1637 ]]></body> |
|
1638 </method> |
|
1639 </implementation> |
|
1640 |
|
1641 <handlers> |
|
1642 <handler event="mousedown"><![CDATA[ |
|
1643 this.showPopup(); |
|
1644 ]]></handler> |
|
1645 </handlers> |
|
1646 </binding> |
|
1647 |
|
1648 </bindings> |
|
1649 |