1 | define([ |
---|
2 | "dojo/aspect", |
---|
3 | "dojo/_base/declare", // declare |
---|
4 | "dojo/dom-attr", // domAttr.get |
---|
5 | "dojo/keys", |
---|
6 | "dojo/_base/lang", // lang.clone lang.hitch |
---|
7 | "dojo/query", // query |
---|
8 | "dojo/regexp", // regexp.escapeString |
---|
9 | "dojo/sniff", // has("ie") |
---|
10 | "./DataList", |
---|
11 | "./_TextBoxMixin", // defines _TextBoxMixin.selectInputText |
---|
12 | "./_SearchMixin" |
---|
13 | ], function(aspect, declare, domAttr, keys, lang, query, regexp, has, DataList, _TextBoxMixin, SearchMixin){ |
---|
14 | |
---|
15 | // module: |
---|
16 | // dijit/form/_AutoCompleterMixin |
---|
17 | |
---|
18 | var AutoCompleterMixin = declare("dijit.form._AutoCompleterMixin", SearchMixin, { |
---|
19 | // summary: |
---|
20 | // A mixin that implements the base functionality for `dijit/form/ComboBox`/`dijit/form/FilteringSelect` |
---|
21 | // description: |
---|
22 | // All widgets that mix in dijit/form/_AutoCompleterMixin must extend `dijit/form/_FormValueWidget`. |
---|
23 | // tags: |
---|
24 | // protected |
---|
25 | |
---|
26 | // item: Object |
---|
27 | // This is the item returned by the dojo/store/api/Store implementation that |
---|
28 | // provides the data for this ComboBox, it's the currently selected item. |
---|
29 | item: null, |
---|
30 | |
---|
31 | // autoComplete: Boolean |
---|
32 | // If user types in a partial string, and then tab out of the `<input>` box, |
---|
33 | // automatically copy the first entry displayed in the drop down list to |
---|
34 | // the `<input>` field |
---|
35 | autoComplete: true, |
---|
36 | |
---|
37 | // highlightMatch: String |
---|
38 | // One of: "first", "all" or "none". |
---|
39 | // |
---|
40 | // If the ComboBox/FilteringSelect opens with the search results and the searched |
---|
41 | // string can be found, it will be highlighted. If set to "all" |
---|
42 | // then will probably want to change `queryExpr` parameter to '*${0}*' |
---|
43 | // |
---|
44 | // Highlighting is only performed when `labelType` is "text", so as to not |
---|
45 | // interfere with any HTML markup an HTML label might contain. |
---|
46 | highlightMatch: "first", |
---|
47 | |
---|
48 | // labelAttr: String? |
---|
49 | // The entries in the drop down list come from this attribute in the |
---|
50 | // dojo.data items. |
---|
51 | // If not specified, the searchAttr attribute is used instead. |
---|
52 | labelAttr: "", |
---|
53 | |
---|
54 | // labelType: String |
---|
55 | // Specifies how to interpret the labelAttr in the data store items. |
---|
56 | // Can be "html" or "text". |
---|
57 | labelType: "text", |
---|
58 | |
---|
59 | // Flags to _HasDropDown to limit height of drop down to make it fit in viewport |
---|
60 | maxHeight: -1, |
---|
61 | |
---|
62 | // For backwards compatibility let onClick events propagate, even clicks on the down arrow button |
---|
63 | _stopClickEvents: false, |
---|
64 | |
---|
65 | _getCaretPos: function(/*DomNode*/ element){ |
---|
66 | // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 |
---|
67 | var pos = 0; |
---|
68 | if(typeof(element.selectionStart) == "number"){ |
---|
69 | // FIXME: this is totally borked on Moz < 1.3. Any recourse? |
---|
70 | pos = element.selectionStart; |
---|
71 | }else if(has("ie")){ |
---|
72 | // in the case of a mouse click in a popup being handled, |
---|
73 | // then the document.selection is not the textarea, but the popup |
---|
74 | // var r = document.selection.createRange(); |
---|
75 | // hack to get IE 6 to play nice. What a POS browser. |
---|
76 | var tr = element.ownerDocument.selection.createRange().duplicate(); |
---|
77 | var ntr = element.createTextRange(); |
---|
78 | tr.move("character", 0); |
---|
79 | ntr.move("character", 0); |
---|
80 | try{ |
---|
81 | // If control doesn't have focus, you get an exception. |
---|
82 | // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). |
---|
83 | // There appears to be no workaround for this - googled for quite a while. |
---|
84 | ntr.setEndPoint("EndToEnd", tr); |
---|
85 | pos = String(ntr.text).replace(/\r/g, "").length; |
---|
86 | }catch(e){ |
---|
87 | // If focus has shifted, 0 is fine for caret pos. |
---|
88 | } |
---|
89 | } |
---|
90 | return pos; |
---|
91 | }, |
---|
92 | |
---|
93 | _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ |
---|
94 | location = parseInt(location); |
---|
95 | _TextBoxMixin.selectInputText(element, location, location); |
---|
96 | }, |
---|
97 | |
---|
98 | _setDisabledAttr: function(/*Boolean*/ value){ |
---|
99 | // Additional code to set disabled state of ComboBox node. |
---|
100 | // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). |
---|
101 | this.inherited(arguments); |
---|
102 | this.domNode.setAttribute("aria-disabled", value ? "true" : "false"); |
---|
103 | }, |
---|
104 | |
---|
105 | _onKey: function(/*Event*/ evt){ |
---|
106 | // summary: |
---|
107 | // Handles keyboard events |
---|
108 | |
---|
109 | if(evt.charCode >= 32){ |
---|
110 | return; |
---|
111 | } // alphanumeric reserved for searching |
---|
112 | |
---|
113 | var key = evt.charCode || evt.keyCode; |
---|
114 | |
---|
115 | // except for cutting/pasting case - ctrl + x/v |
---|
116 | if(key == keys.ALT || key == keys.CTRL || key == keys.META || key == keys.SHIFT){ |
---|
117 | return; // throw out spurious events |
---|
118 | } |
---|
119 | |
---|
120 | var pw = this.dropDown; |
---|
121 | var highlighted = null; |
---|
122 | this._abortQuery(); |
---|
123 | |
---|
124 | // _HasDropDown will do some of the work: |
---|
125 | // |
---|
126 | // 1. when drop down is not yet shown: |
---|
127 | // - if user presses the down arrow key, call loadDropDown() |
---|
128 | // 2. when drop down is already displayed: |
---|
129 | // - on ESC key, call closeDropDown() |
---|
130 | // - otherwise, call dropDown.handleKey() to process the keystroke |
---|
131 | this.inherited(arguments); |
---|
132 | |
---|
133 | if(evt.altKey || evt.ctrlKey || evt.metaKey){ |
---|
134 | return; |
---|
135 | } // don't process keys with modifiers - but we want shift+TAB |
---|
136 | |
---|
137 | if(this._opened){ |
---|
138 | highlighted = pw.getHighlightedOption(); |
---|
139 | } |
---|
140 | switch(key){ |
---|
141 | case keys.PAGE_DOWN: |
---|
142 | case keys.DOWN_ARROW: |
---|
143 | case keys.PAGE_UP: |
---|
144 | case keys.UP_ARROW: |
---|
145 | // Keystroke caused ComboBox_menu to move to a different item. |
---|
146 | // Copy new item to <input> box. |
---|
147 | if(this._opened){ |
---|
148 | this._announceOption(highlighted); |
---|
149 | } |
---|
150 | evt.stopPropagation(); |
---|
151 | evt.preventDefault(); |
---|
152 | break; |
---|
153 | |
---|
154 | case keys.ENTER: |
---|
155 | // prevent submitting form if user presses enter. Also |
---|
156 | // prevent accepting the value if either Next or Previous |
---|
157 | // are selected |
---|
158 | if(highlighted){ |
---|
159 | // only stop event on prev/next |
---|
160 | if(highlighted == pw.nextButton){ |
---|
161 | this._nextSearch(1); |
---|
162 | // prevent submit |
---|
163 | evt.stopPropagation(); |
---|
164 | evt.preventDefault(); |
---|
165 | break; |
---|
166 | }else if(highlighted == pw.previousButton){ |
---|
167 | this._nextSearch(-1); |
---|
168 | // prevent submit |
---|
169 | evt.stopPropagation(); |
---|
170 | evt.preventDefault(); |
---|
171 | break; |
---|
172 | } |
---|
173 | // prevent submit if ENTER was to choose an item |
---|
174 | evt.stopPropagation(); |
---|
175 | evt.preventDefault(); |
---|
176 | }else{ |
---|
177 | // Update 'value' (ex: KY) according to currently displayed text |
---|
178 | this._setBlurValue(); // set value if needed |
---|
179 | this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting |
---|
180 | } |
---|
181 | // fall through |
---|
182 | |
---|
183 | case keys.TAB: |
---|
184 | var newvalue = this.get('displayedValue'); |
---|
185 | // if the user had More Choices selected fall into the |
---|
186 | // _onBlur handler |
---|
187 | if(pw && (newvalue == pw._messages["previousMessage"] || newvalue == pw._messages["nextMessage"])){ |
---|
188 | break; |
---|
189 | } |
---|
190 | if(highlighted){ |
---|
191 | this._selectOption(highlighted); |
---|
192 | } |
---|
193 | // fall through |
---|
194 | |
---|
195 | case keys.ESCAPE: |
---|
196 | if(this._opened){ |
---|
197 | this._lastQuery = null; // in case results come back later |
---|
198 | this.closeDropDown(); |
---|
199 | } |
---|
200 | break; |
---|
201 | } |
---|
202 | }, |
---|
203 | |
---|
204 | _autoCompleteText: function(/*String*/ text){ |
---|
205 | // summary: |
---|
206 | // Fill in the textbox with the first item from the drop down |
---|
207 | // list, and highlight the characters that were |
---|
208 | // auto-completed. For example, if user typed "CA" and the |
---|
209 | // drop down list appeared, the textbox would be changed to |
---|
210 | // "California" and "ifornia" would be highlighted. |
---|
211 | |
---|
212 | var fn = this.focusNode; |
---|
213 | |
---|
214 | // IE7: clear selection so next highlight works all the time |
---|
215 | _TextBoxMixin.selectInputText(fn, fn.value.length); |
---|
216 | // does text autoComplete the value in the textbox? |
---|
217 | var caseFilter = this.ignoreCase ? 'toLowerCase' : 'substr'; |
---|
218 | if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ |
---|
219 | var cpos = this.autoComplete ? this._getCaretPos(fn) : fn.value.length; |
---|
220 | // only try to extend if we added the last character at the end of the input |
---|
221 | if((cpos + 1) > fn.value.length){ |
---|
222 | // only add to input node as we would overwrite Capitalisation of chars |
---|
223 | // actually, that is ok |
---|
224 | fn.value = text;//.substr(cpos); |
---|
225 | // visually highlight the autocompleted characters |
---|
226 | _TextBoxMixin.selectInputText(fn, cpos); |
---|
227 | } |
---|
228 | }else{ |
---|
229 | // text does not autoComplete; replace the whole value and highlight |
---|
230 | fn.value = text; |
---|
231 | _TextBoxMixin.selectInputText(fn); |
---|
232 | } |
---|
233 | }, |
---|
234 | |
---|
235 | _openResultList: function(/*Object*/ results, /*Object*/ query, /*Object*/ options){ |
---|
236 | // summary: |
---|
237 | // Callback when a search completes. |
---|
238 | // description: |
---|
239 | // 1. generates drop-down list and calls _showResultList() to display it |
---|
240 | // 2. if this result list is from user pressing "more choices"/"previous choices" |
---|
241 | // then tell screen reader to announce new option |
---|
242 | var wasSelected = this.dropDown.getHighlightedOption(); |
---|
243 | this.dropDown.clearResultList(); |
---|
244 | if(!results.length && options.start == 0){ // if no results and not just the previous choices button |
---|
245 | this.closeDropDown(); |
---|
246 | return; |
---|
247 | } |
---|
248 | this._nextSearch = this.dropDown.onPage = lang.hitch(this, function(direction){ |
---|
249 | results.nextPage(direction !== -1); |
---|
250 | this.focus(); |
---|
251 | }); |
---|
252 | |
---|
253 | // Fill in the textbox with the first item from the drop down list, |
---|
254 | // and highlight the characters that were auto-completed. For |
---|
255 | // example, if user typed "CA" and the drop down list appeared, the |
---|
256 | // textbox would be changed to "California" and "ifornia" would be |
---|
257 | // highlighted. |
---|
258 | |
---|
259 | this.dropDown.createOptions( |
---|
260 | results, |
---|
261 | options, |
---|
262 | lang.hitch(this, "_getMenuLabelFromItem") |
---|
263 | ); |
---|
264 | |
---|
265 | // show our list (only if we have content, else nothing) |
---|
266 | this._showResultList(); |
---|
267 | |
---|
268 | // #4091: |
---|
269 | // tell the screen reader that the paging callback finished by |
---|
270 | // shouting the next choice |
---|
271 | if("direction" in options){ |
---|
272 | if(options.direction){ |
---|
273 | this.dropDown.highlightFirstOption(); |
---|
274 | }else if(!options.direction){ |
---|
275 | this.dropDown.highlightLastOption(); |
---|
276 | } |
---|
277 | if(wasSelected){ |
---|
278 | this._announceOption(this.dropDown.getHighlightedOption()); |
---|
279 | } |
---|
280 | }else if(this.autoComplete && !this._prev_key_backspace |
---|
281 | // when the user clicks the arrow button to show the full list, |
---|
282 | // startSearch looks for "*". |
---|
283 | // it does not make sense to autocomplete |
---|
284 | // if they are just previewing the options available. |
---|
285 | && !/^[*]+$/.test(query[this.searchAttr].toString())){ |
---|
286 | this._announceOption(this.dropDown.containerNode.firstChild.nextSibling); // 1st real item |
---|
287 | } |
---|
288 | }, |
---|
289 | |
---|
290 | _showResultList: function(){ |
---|
291 | // summary: |
---|
292 | // Display the drop down if not already displayed, or if it is displayed, then |
---|
293 | // reposition it if necessary (reposition may be necessary if drop down's height changed). |
---|
294 | this.closeDropDown(true); |
---|
295 | this.openDropDown(); |
---|
296 | this.domNode.setAttribute("aria-expanded", "true"); |
---|
297 | }, |
---|
298 | |
---|
299 | loadDropDown: function(/*Function*/ /*===== callback =====*/){ |
---|
300 | // Overrides _HasDropDown.loadDropDown(). |
---|
301 | // This is called when user has pressed button icon or pressed the down arrow key |
---|
302 | // to open the drop down. |
---|
303 | this._startSearchAll(); |
---|
304 | }, |
---|
305 | |
---|
306 | isLoaded: function(){ |
---|
307 | // signal to _HasDropDown that it needs to call loadDropDown() to load the |
---|
308 | // drop down asynchronously before displaying it |
---|
309 | return false; |
---|
310 | }, |
---|
311 | |
---|
312 | closeDropDown: function(){ |
---|
313 | // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open). |
---|
314 | // This method is the callback when the user types ESC or clicking |
---|
315 | // the button icon while the drop down is open. It's also called by other code. |
---|
316 | this._abortQuery(); |
---|
317 | if(this._opened){ |
---|
318 | this.inherited(arguments); |
---|
319 | this.domNode.setAttribute("aria-expanded", "false"); |
---|
320 | } |
---|
321 | }, |
---|
322 | |
---|
323 | _setBlurValue: function(){ |
---|
324 | // if the user clicks away from the textbox OR tabs away, set the |
---|
325 | // value to the textbox value |
---|
326 | // #4617: |
---|
327 | // if value is now more choices or previous choices, revert |
---|
328 | // the value |
---|
329 | var newvalue = this.get('displayedValue'); |
---|
330 | var pw = this.dropDown; |
---|
331 | if(pw && (newvalue == pw._messages["previousMessage"] || newvalue == pw._messages["nextMessage"])){ |
---|
332 | this._setValueAttr(this._lastValueReported, true); |
---|
333 | }else if(typeof this.item == "undefined"){ |
---|
334 | // Update 'value' (ex: KY) according to currently displayed text |
---|
335 | this.item = null; |
---|
336 | this.set('displayedValue', newvalue); |
---|
337 | }else{ |
---|
338 | if(this.value != this._lastValueReported){ |
---|
339 | this._handleOnChange(this.value, true); |
---|
340 | } |
---|
341 | this._refreshState(); |
---|
342 | } |
---|
343 | // Remove aria-activedescendant since it may not be removed if they select with arrows then blur with mouse |
---|
344 | this.focusNode.removeAttribute("aria-activedescendant"); |
---|
345 | }, |
---|
346 | |
---|
347 | _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ |
---|
348 | // summary: |
---|
349 | // Set the displayed valued in the input box, and the hidden value |
---|
350 | // that gets submitted, based on a dojo.data store item. |
---|
351 | // description: |
---|
352 | // Users shouldn't call this function; they should be calling |
---|
353 | // set('item', value) |
---|
354 | // tags: |
---|
355 | // private |
---|
356 | var value = ''; |
---|
357 | if(item){ |
---|
358 | if(!displayedValue){ |
---|
359 | displayedValue = this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API) |
---|
360 | this.store.getValue(item, this.searchAttr) : item[this.searchAttr]; |
---|
361 | } |
---|
362 | value = this._getValueField() != this.searchAttr ? this.store.getIdentity(item) : displayedValue; |
---|
363 | } |
---|
364 | this.set('value', value, priorityChange, displayedValue, item); |
---|
365 | }, |
---|
366 | |
---|
367 | _announceOption: function(/*Node*/ node){ |
---|
368 | // summary: |
---|
369 | // a11y code that puts the highlighted option in the textbox. |
---|
370 | // This way screen readers will know what is happening in the |
---|
371 | // menu. |
---|
372 | |
---|
373 | if(!node){ |
---|
374 | return; |
---|
375 | } |
---|
376 | // pull the text value from the item attached to the DOM node |
---|
377 | var newValue; |
---|
378 | if(node == this.dropDown.nextButton || |
---|
379 | node == this.dropDown.previousButton){ |
---|
380 | newValue = node.innerHTML; |
---|
381 | this.item = undefined; |
---|
382 | this.value = ''; |
---|
383 | }else{ |
---|
384 | var item = this.dropDown.items[node.getAttribute("item")]; |
---|
385 | newValue = (this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API) |
---|
386 | this.store.getValue(item, this.searchAttr) : item[this.searchAttr]).toString(); |
---|
387 | this.set('item', item, false, newValue); |
---|
388 | } |
---|
389 | // get the text that the user manually entered (cut off autocompleted text) |
---|
390 | this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); |
---|
391 | // set up ARIA activedescendant |
---|
392 | this.focusNode.setAttribute("aria-activedescendant", domAttr.get(node, "id")); |
---|
393 | // autocomplete the rest of the option to announce change |
---|
394 | this._autoCompleteText(newValue); |
---|
395 | }, |
---|
396 | |
---|
397 | _selectOption: function(/*DomNode*/ target){ |
---|
398 | // summary: |
---|
399 | // Menu callback function, called when an item in the menu is selected. |
---|
400 | this.closeDropDown(); |
---|
401 | if(target){ |
---|
402 | this._announceOption(target); |
---|
403 | } |
---|
404 | this._setCaretPos(this.focusNode, this.focusNode.value.length); |
---|
405 | this._handleOnChange(this.value, true); |
---|
406 | // Remove aria-activedescendant since the drop down is no loner visible |
---|
407 | // after closeDropDown() but _announceOption() adds it back in |
---|
408 | this.focusNode.removeAttribute("aria-activedescendant"); |
---|
409 | }, |
---|
410 | |
---|
411 | _startSearchAll: function(){ |
---|
412 | this._startSearch(''); |
---|
413 | }, |
---|
414 | |
---|
415 | _startSearchFromInput: function(){ |
---|
416 | this.item = undefined; // undefined means item needs to be set |
---|
417 | this.inherited(arguments); |
---|
418 | }, |
---|
419 | |
---|
420 | _startSearch: function(/*String*/ key){ |
---|
421 | // summary: |
---|
422 | // Starts a search for elements matching key (key=="" means to return all items), |
---|
423 | // and calls _openResultList() when the search completes, to display the results. |
---|
424 | if(!this.dropDown){ |
---|
425 | var popupId = this.id + "_popup", |
---|
426 | dropDownConstructor = lang.isString(this.dropDownClass) ? |
---|
427 | lang.getObject(this.dropDownClass, false) : this.dropDownClass; |
---|
428 | this.dropDown = new dropDownConstructor({ |
---|
429 | onChange: lang.hitch(this, this._selectOption), |
---|
430 | id: popupId, |
---|
431 | dir: this.dir, |
---|
432 | textDir: this.textDir |
---|
433 | }); |
---|
434 | } |
---|
435 | this._lastInput = key; // Store exactly what was entered by the user. |
---|
436 | this.inherited(arguments); |
---|
437 | }, |
---|
438 | |
---|
439 | _getValueField: function(){ |
---|
440 | // summary: |
---|
441 | // Helper for postMixInProperties() to set this.value based on data inlined into the markup. |
---|
442 | // Returns the attribute name in the item (in dijit/form/_ComboBoxDataStore) to use as the value. |
---|
443 | return this.searchAttr; |
---|
444 | }, |
---|
445 | |
---|
446 | //////////// INITIALIZATION METHODS /////////////////////////////////////// |
---|
447 | |
---|
448 | postMixInProperties: function(){ |
---|
449 | this.inherited(arguments); |
---|
450 | if(!this.store){ |
---|
451 | var srcNodeRef = this.srcNodeRef; |
---|
452 | // if user didn't specify store, then assume there are option tags |
---|
453 | this.store = new DataList({}, srcNodeRef); |
---|
454 | |
---|
455 | // if there is no value set and there is an option list, set |
---|
456 | // the value to the first value to be consistent with native Select |
---|
457 | // Firefox and Safari set value |
---|
458 | // IE6 and Opera set selectedIndex, which is automatically set |
---|
459 | // by the selected attribute of an option tag |
---|
460 | // IE6 does not set value, Opera sets value = selectedIndex |
---|
461 | if(!("value" in this.params)){ |
---|
462 | var item = (this.item = this.store.fetchSelectedItem()); |
---|
463 | if(item){ |
---|
464 | var valueField = this._getValueField(); |
---|
465 | // remove getValue() for 2.0 (old dojo.data API) |
---|
466 | this.value = this.store._oldAPI ? this.store.getValue(item, valueField) : item[valueField]; |
---|
467 | } |
---|
468 | } |
---|
469 | } |
---|
470 | }, |
---|
471 | |
---|
472 | postCreate: function(){ |
---|
473 | // summary: |
---|
474 | // Subclasses must call this method from their postCreate() methods |
---|
475 | // tags: |
---|
476 | // protected |
---|
477 | |
---|
478 | // find any associated label element and add to ComboBox node. |
---|
479 | var label = query('label[for="' + this.id + '"]'); |
---|
480 | if(label.length){ |
---|
481 | if(!label[0].id){ |
---|
482 | label[0].id = this.id + "_label"; |
---|
483 | } |
---|
484 | this.domNode.setAttribute("aria-labelledby", label[0].id); |
---|
485 | |
---|
486 | } |
---|
487 | this.inherited(arguments); |
---|
488 | aspect.after(this, "onSearch", lang.hitch(this, "_openResultList"), true); |
---|
489 | }, |
---|
490 | |
---|
491 | _getMenuLabelFromItem: function(/*Item*/ item){ |
---|
492 | var label = this.labelFunc(item, this.store), |
---|
493 | labelType = this.labelType; |
---|
494 | // If labelType is not "text" we don't want to screw any markup ot whatever. |
---|
495 | if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ |
---|
496 | label = this.doHighlight(label, this._lastInput); |
---|
497 | labelType = "html"; |
---|
498 | } |
---|
499 | return {html: labelType == "html", label: label}; |
---|
500 | }, |
---|
501 | |
---|
502 | doHighlight: function(/*String*/ label, /*String*/ find){ |
---|
503 | // summary: |
---|
504 | // Highlights the string entered by the user in the menu. By default this |
---|
505 | // highlights the first occurrence found. Override this method |
---|
506 | // to implement your custom highlighting. |
---|
507 | // tags: |
---|
508 | // protected |
---|
509 | |
---|
510 | var |
---|
511 | // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true |
---|
512 | modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""), |
---|
513 | i = this.queryExpr.indexOf("${0}"); |
---|
514 | find = regexp.escapeString(find); // escape regexp special chars |
---|
515 | //If < appears in label, and user presses t, we don't want to highlight the t in the escaped "<" |
---|
516 | //first find out every occurences of "find", wrap each occurence in a pair of "\uFFFF" characters (which |
---|
517 | //should not appear in any string). then html escape the whole string, and replace '\uFFFF" with the |
---|
518 | //HTML highlight markup. |
---|
519 | return this._escapeHtml(label.replace( |
---|
520 | new RegExp((i == 0 ? "^" : "") + "(" + find + ")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers), |
---|
521 | '\uFFFF$1\uFFFF')).replace( |
---|
522 | /\uFFFF([^\uFFFF]+)\uFFFF/g, '<span class="dijitComboBoxHighlightMatch">$1</span>' |
---|
523 | ); // returns String, (almost) valid HTML (entities encoded) |
---|
524 | }, |
---|
525 | |
---|
526 | _escapeHtml: function(/*String*/ str){ |
---|
527 | // TODO Should become dojo.html.entities(), when exists use instead |
---|
528 | // summary: |
---|
529 | // Adds escape sequences for special characters in XML: `&<>"'` |
---|
530 | str = String(str).replace(/&/gm, "&").replace(/</gm, "<") |
---|
531 | .replace(/>/gm, ">").replace(/"/gm, """); //balance" |
---|
532 | return str; // string |
---|
533 | }, |
---|
534 | |
---|
535 | reset: function(){ |
---|
536 | // Overrides the _FormWidget.reset(). |
---|
537 | // Additionally reset the .item (to clean up). |
---|
538 | this.item = null; |
---|
539 | this.inherited(arguments); |
---|
540 | }, |
---|
541 | |
---|
542 | labelFunc: function(item, store){ |
---|
543 | // summary: |
---|
544 | // Computes the label to display based on the dojo.data store item. |
---|
545 | // item: Object |
---|
546 | // The item from the store |
---|
547 | // store: dojo/store/api/Store |
---|
548 | // The store. |
---|
549 | // returns: |
---|
550 | // The label that the ComboBox should display |
---|
551 | // tags: |
---|
552 | // private |
---|
553 | |
---|
554 | // Use toString() because XMLStore returns an XMLItem whereas this |
---|
555 | // method is expected to return a String (#9354). |
---|
556 | // Remove getValue() for 2.0 (old dojo.data API) |
---|
557 | return (store._oldAPI ? store.getValue(item, this.labelAttr || this.searchAttr) : |
---|
558 | item[this.labelAttr || this.searchAttr]).toString(); // String |
---|
559 | }, |
---|
560 | |
---|
561 | _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item){ |
---|
562 | // summary: |
---|
563 | // Hook so set('value', value) works. |
---|
564 | // description: |
---|
565 | // Sets the value of the select. |
---|
566 | this._set("item", item || null); // value not looked up in store |
---|
567 | if(value == null /* or undefined */){ |
---|
568 | value = ''; |
---|
569 | } // null translates to blank |
---|
570 | this.inherited(arguments); |
---|
571 | } |
---|
572 | }); |
---|
573 | |
---|
574 | if(has("dojo-bidi")){ |
---|
575 | AutoCompleterMixin.extend({ |
---|
576 | _setTextDirAttr: function(/*String*/ textDir){ |
---|
577 | // summary: |
---|
578 | // Setter for textDir, needed for the dropDown's textDir update. |
---|
579 | // description: |
---|
580 | // Users shouldn't call this function; they should be calling |
---|
581 | // set('textDir', value) |
---|
582 | // tags: |
---|
583 | // private |
---|
584 | this.inherited(arguments); |
---|
585 | // update the drop down also (_ComboBoxMenuMixin) |
---|
586 | if(this.dropDown){ |
---|
587 | this.dropDown._set("textDir", textDir); |
---|
588 | } |
---|
589 | } |
---|
590 | }); |
---|
591 | } |
---|
592 | |
---|
593 | return AutoCompleterMixin; |
---|
594 | }); |
---|