]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / form / _AutoCompleterMixin.js.uncompressed.js
1 define("dijit/form/_AutoCompleterMixin", [
2 "dojo/_base/connect", // keys keys.SHIFT
3 "dojo/data/util/filter", // patternToRegExp
4 "dojo/_base/declare", // declare
5 "dojo/_base/Deferred", // Deferred.when
6 "dojo/dom-attr", // domAttr.get
7 "dojo/_base/event", // event.stop
8 "dojo/keys",
9 "dojo/_base/lang", // lang.clone lang.hitch
10 "dojo/query", // query
11 "dojo/regexp", // regexp.escapeString
12 "dojo/_base/sniff", // has("ie")
13 "dojo/string", // string.substitute
14 "dojo/_base/window", // win.doc.selection.createRange
15 "./DataList",
16 "../registry", // registry.byId
17 "./_TextBoxMixin" // defines _TextBoxMixin.selectInputText
18 ], function(connect, filter, declare, Deferred, domAttr, event, keys, lang, query, regexp, has, string, win,
19 DataList, registry, _TextBoxMixin){
20
21 // module:
22 // dijit/form/_AutoCompleterMixin
23 // summary:
24 // A mixin that implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
25
26
27 return declare("dijit.form._AutoCompleterMixin", null, {
28 // summary:
29 // A mixin that implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
30 // description:
31 // All widgets that mix in dijit.form._AutoCompleterMixin must extend `dijit.form._FormValueWidget`.
32 // tags:
33 // protected
34
35 // item: Object
36 // This is the item returned by the dojo.data.store implementation that
37 // provides the data for this ComboBox, it's the currently selected item.
38 item: null,
39
40 // pageSize: Integer
41 // Argument to data provider.
42 // Specifies number of search results per page (before hitting "next" button)
43 pageSize: Infinity,
44
45 // store: [const] dojo.store.api.Store
46 // Reference to data provider object used by this ComboBox
47 store: null,
48
49 // fetchProperties: Object
50 // Mixin to the store's fetch.
51 // For example, to set the sort order of the ComboBox menu, pass:
52 // | { sort: [{attribute:"name",descending: true}] }
53 // To override the default queryOptions so that deep=false, do:
54 // | { queryOptions: {ignoreCase: true, deep: false} }
55 fetchProperties:{},
56
57 // query: Object
58 // A query that can be passed to 'store' to initially filter the items,
59 // before doing further filtering based on `searchAttr` and the key.
60 // Any reference to the `searchAttr` is ignored.
61 query: {},
62
63 // autoComplete: Boolean
64 // If user types in a partial string, and then tab out of the `<input>` box,
65 // automatically copy the first entry displayed in the drop down list to
66 // the `<input>` field
67 autoComplete: true,
68
69 // highlightMatch: String
70 // One of: "first", "all" or "none".
71 //
72 // If the ComboBox/FilteringSelect opens with the search results and the searched
73 // string can be found, it will be highlighted. If set to "all"
74 // then will probably want to change `queryExpr` parameter to '*${0}*'
75 //
76 // Highlighting is only performed when `labelType` is "text", so as to not
77 // interfere with any HTML markup an HTML label might contain.
78 highlightMatch: "first",
79
80 // searchDelay: Integer
81 // Delay in milliseconds between when user types something and we start
82 // searching based on that value
83 searchDelay: 100,
84
85 // searchAttr: String
86 // Search for items in the data store where this attribute (in the item)
87 // matches what the user typed
88 searchAttr: "name",
89
90 // labelAttr: String?
91 // The entries in the drop down list come from this attribute in the
92 // dojo.data items.
93 // If not specified, the searchAttr attribute is used instead.
94 labelAttr: "",
95
96 // labelType: String
97 // Specifies how to interpret the labelAttr in the data store items.
98 // Can be "html" or "text".
99 labelType: "text",
100
101 // queryExpr: String
102 // This specifies what query ComboBox/FilteringSelect sends to the data store,
103 // based on what the user has typed. Changing this expression will modify
104 // whether the drop down shows only exact matches, a "starting with" match,
105 // etc. Use it in conjunction with highlightMatch.
106 // dojo.data query expression pattern.
107 // `${0}` will be substituted for the user text.
108 // `*` is used for wildcards.
109 // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
110 queryExpr: "${0}*",
111
112 // ignoreCase: Boolean
113 // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
114 ignoreCase: true,
115
116 // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
117 maxHeight: -1,
118
119 // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
120 _stopClickEvents: false,
121
122 _getCaretPos: function(/*DomNode*/ element){
123 // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
124 var pos = 0;
125 if(typeof(element.selectionStart) == "number"){
126 // FIXME: this is totally borked on Moz < 1.3. Any recourse?
127 pos = element.selectionStart;
128 }else if(has("ie")){
129 // in the case of a mouse click in a popup being handled,
130 // then the win.doc.selection is not the textarea, but the popup
131 // var r = win.doc.selection.createRange();
132 // hack to get IE 6 to play nice. What a POS browser.
133 var tr = win.doc.selection.createRange().duplicate();
134 var ntr = element.createTextRange();
135 tr.move("character",0);
136 ntr.move("character",0);
137 try{
138 // If control doesn't have focus, you get an exception.
139 // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
140 // There appears to be no workaround for this - googled for quite a while.
141 ntr.setEndPoint("EndToEnd", tr);
142 pos = String(ntr.text).replace(/\r/g,"").length;
143 }catch(e){
144 // If focus has shifted, 0 is fine for caret pos.
145 }
146 }
147 return pos;
148 },
149
150 _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
151 location = parseInt(location);
152 _TextBoxMixin.selectInputText(element, location, location);
153 },
154
155 _setDisabledAttr: function(/*Boolean*/ value){
156 // Additional code to set disabled state of ComboBox node.
157 // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
158 this.inherited(arguments);
159 this.domNode.setAttribute("aria-disabled", value);
160 },
161
162 _abortQuery: function(){
163 // stop in-progress query
164 if(this.searchTimer){
165 clearTimeout(this.searchTimer);
166 this.searchTimer = null;
167 }
168 if(this._fetchHandle){
169 if(this._fetchHandle.cancel){
170 this._cancelingQuery = true;
171 this._fetchHandle.cancel();
172 this._cancelingQuery = false;
173 }
174 this._fetchHandle = null;
175 }
176 },
177
178 _onInput: function(/*Event*/ evt){
179 // summary:
180 // Handles paste events
181 this.inherited(arguments);
182 if(evt.charOrCode == 229){ // IME or cut/paste event
183 this._onKey(evt);
184 }
185 },
186
187 _onKey: function(/*Event*/ evt){
188 // summary:
189 // Handles keyboard events
190
191 if(this.disabled || this.readOnly){ return; }
192 var key = evt.charOrCode;
193
194 // except for cutting/pasting case - ctrl + x/v
195 if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == keys.SHIFT){
196 return; // throw out weird key combinations and spurious events
197 }
198
199 var doSearch = false;
200 var pw = this.dropDown;
201 var highlighted = null;
202 this._prev_key_backspace = false;
203 this._abortQuery();
204
205 // _HasDropDown will do some of the work:
206 // 1. when drop down is not yet shown:
207 // - if user presses the down arrow key, call loadDropDown()
208 // 2. when drop down is already displayed:
209 // - on ESC key, call closeDropDown()
210 // - otherwise, call dropDown.handleKey() to process the keystroke
211 this.inherited(arguments);
212
213 if(this._opened){
214 highlighted = pw.getHighlightedOption();
215 }
216 switch(key){
217 case keys.PAGE_DOWN:
218 case keys.DOWN_ARROW:
219 case keys.PAGE_UP:
220 case keys.UP_ARROW:
221 // Keystroke caused ComboBox_menu to move to a different item.
222 // Copy new item to <input> box.
223 if(this._opened){
224 this._announceOption(highlighted);
225 }
226 event.stop(evt);
227 break;
228
229 case keys.ENTER:
230 // prevent submitting form if user presses enter. Also
231 // prevent accepting the value if either Next or Previous
232 // are selected
233 if(highlighted){
234 // only stop event on prev/next
235 if(highlighted == pw.nextButton){
236 this._nextSearch(1);
237 event.stop(evt);
238 break;
239 }else if(highlighted == pw.previousButton){
240 this._nextSearch(-1);
241 event.stop(evt);
242 break;
243 }
244 }else{
245 // Update 'value' (ex: KY) according to currently displayed text
246 this._setBlurValue(); // set value if needed
247 this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting
248 }
249 // default case:
250 // if enter pressed while drop down is open, or for FilteringSelect,
251 // if we are in the middle of a query to convert a directly typed in value to an item,
252 // prevent submit
253 if(this._opened || this._fetchHandle){
254 event.stop(evt);
255 }
256 // fall through
257
258 case keys.TAB:
259 var newvalue = this.get('displayedValue');
260 // if the user had More Choices selected fall into the
261 // _onBlur handler
262 if(pw && (
263 newvalue == pw._messages["previousMessage"] ||
264 newvalue == pw._messages["nextMessage"])
265 ){
266 break;
267 }
268 if(highlighted){
269 this._selectOption(highlighted);
270 }
271 // fall through
272
273 case keys.ESCAPE:
274 if(this._opened){
275 this._lastQuery = null; // in case results come back later
276 this.closeDropDown();
277 }
278 break;
279
280 case ' ':
281 if(highlighted){
282 // user is effectively clicking a choice in the drop down menu
283 event.stop(evt);
284 this._selectOption(highlighted);
285 this.closeDropDown();
286 }else{
287 // user typed a space into the input box, treat as normal character
288 doSearch = true;
289 }
290 break;
291
292 case keys.DELETE:
293 case keys.BACKSPACE:
294 this._prev_key_backspace = true;
295 doSearch = true;
296 break;
297
298 default:
299 // Non char keys (F1-F12 etc..) shouldn't open list.
300 // Ascii characters and IME input (Chinese, Japanese etc.) should.
301 //IME input produces keycode == 229.
302 doSearch = typeof key == 'string' || key == 229;
303 }
304 if(doSearch){
305 // need to wait a tad before start search so that the event
306 // bubbles through DOM and we have value visible
307 this.item = undefined; // undefined means item needs to be set
308 this.searchTimer = setTimeout(lang.hitch(this, "_startSearchFromInput"),1);
309 }
310 },
311
312 _autoCompleteText: function(/*String*/ text){
313 // summary:
314 // Fill in the textbox with the first item from the drop down
315 // list, and highlight the characters that were
316 // auto-completed. For example, if user typed "CA" and the
317 // drop down list appeared, the textbox would be changed to
318 // "California" and "ifornia" would be highlighted.
319
320 var fn = this.focusNode;
321
322 // IE7: clear selection so next highlight works all the time
323 _TextBoxMixin.selectInputText(fn, fn.value.length);
324 // does text autoComplete the value in the textbox?
325 var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr';
326 if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){
327 var cpos = this.autoComplete ? this._getCaretPos(fn) : fn.value.length;
328 // only try to extend if we added the last character at the end of the input
329 if((cpos+1) > fn.value.length){
330 // only add to input node as we would overwrite Capitalisation of chars
331 // actually, that is ok
332 fn.value = text;//.substr(cpos);
333 // visually highlight the autocompleted characters
334 _TextBoxMixin.selectInputText(fn, cpos);
335 }
336 }else{
337 // text does not autoComplete; replace the whole value and highlight
338 fn.value = text;
339 _TextBoxMixin.selectInputText(fn);
340 }
341 },
342
343 _openResultList: function(/*Object*/ results, /*Object*/ query, /*Object*/ options){
344 // summary:
345 // Callback when a search completes.
346 // description:
347 // 1. generates drop-down list and calls _showResultList() to display it
348 // 2. if this result list is from user pressing "more choices"/"previous choices"
349 // then tell screen reader to announce new option
350 this._fetchHandle = null;
351 if( this.disabled ||
352 this.readOnly ||
353 (query[this.searchAttr] !== this._lastQuery) // TODO: better way to avoid getting unwanted notify
354 ){
355 return;
356 }
357 var wasSelected = this.dropDown.getHighlightedOption();
358 this.dropDown.clearResultList();
359 if(!results.length && options.start == 0){ // if no results and not just the previous choices button
360 this.closeDropDown();
361 return;
362 }
363
364 // Fill in the textbox with the first item from the drop down list,
365 // and highlight the characters that were auto-completed. For
366 // example, if user typed "CA" and the drop down list appeared, the
367 // textbox would be changed to "California" and "ifornia" would be
368 // highlighted.
369
370 var nodes = this.dropDown.createOptions(
371 results,
372 options,
373 lang.hitch(this, "_getMenuLabelFromItem")
374 );
375
376 // show our list (only if we have content, else nothing)
377 this._showResultList();
378
379 // #4091:
380 // tell the screen reader that the paging callback finished by
381 // shouting the next choice
382 if(options.direction){
383 if(1 == options.direction){
384 this.dropDown.highlightFirstOption();
385 }else if(-1 == options.direction){
386 this.dropDown.highlightLastOption();
387 }
388 if(wasSelected){
389 this._announceOption(this.dropDown.getHighlightedOption());
390 }
391 }else if(this.autoComplete && !this._prev_key_backspace
392 // when the user clicks the arrow button to show the full list,
393 // startSearch looks for "*".
394 // it does not make sense to autocomplete
395 // if they are just previewing the options available.
396 && !/^[*]+$/.test(query[this.searchAttr].toString())){
397 this._announceOption(nodes[1]); // 1st real item
398 }
399 },
400
401 _showResultList: function(){
402 // summary:
403 // Display the drop down if not already displayed, or if it is displayed, then
404 // reposition it if necessary (reposition may be necessary if drop down's height changed).
405 this.closeDropDown(true);
406 this.openDropDown();
407 this.domNode.setAttribute("aria-expanded", "true");
408 },
409
410 loadDropDown: function(/*Function*/ /*===== callback =====*/){
411 // Overrides _HasDropDown.loadDropDown().
412 // This is called when user has pressed button icon or pressed the down arrow key
413 // to open the drop down.
414
415 this._startSearchAll();
416 },
417
418 isLoaded: function(){
419 // signal to _HasDropDown that it needs to call loadDropDown() to load the
420 // drop down asynchronously before displaying it
421 return false;
422 },
423
424 closeDropDown: function(){
425 // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open).
426 // This method is the callback when the user types ESC or clicking
427 // the button icon while the drop down is open. It's also called by other code.
428 this._abortQuery();
429 if(this._opened){
430 this.inherited(arguments);
431 this.domNode.setAttribute("aria-expanded", "false");
432 this.focusNode.removeAttribute("aria-activedescendant");
433 }
434 },
435
436 _setBlurValue: function(){
437 // if the user clicks away from the textbox OR tabs away, set the
438 // value to the textbox value
439 // #4617:
440 // if value is now more choices or previous choices, revert
441 // the value
442 var newvalue = this.get('displayedValue');
443 var pw = this.dropDown;
444 if(pw && (
445 newvalue == pw._messages["previousMessage"] ||
446 newvalue == pw._messages["nextMessage"]
447 )
448 ){
449 this._setValueAttr(this._lastValueReported, true);
450 }else if(typeof this.item == "undefined"){
451 // Update 'value' (ex: KY) according to currently displayed text
452 this.item = null;
453 this.set('displayedValue', newvalue);
454 }else{
455 if(this.value != this._lastValueReported){
456 this._handleOnChange(this.value, true);
457 }
458 this._refreshState();
459 }
460 },
461
462 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
463 // summary:
464 // Set the displayed valued in the input box, and the hidden value
465 // that gets submitted, based on a dojo.data store item.
466 // description:
467 // Users shouldn't call this function; they should be calling
468 // set('item', value)
469 // tags:
470 // private
471 var value = '';
472 if(item){
473 if(!displayedValue){
474 displayedValue = this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API)
475 this.store.getValue(item, this.searchAttr) : item[this.searchAttr];
476 }
477 value = this._getValueField() != this.searchAttr ? this.store.getIdentity(item) : displayedValue;
478 }
479 this.set('value', value, priorityChange, displayedValue, item);
480 },
481
482 _announceOption: function(/*Node*/ node){
483 // summary:
484 // a11y code that puts the highlighted option in the textbox.
485 // This way screen readers will know what is happening in the
486 // menu.
487
488 if(!node){
489 return;
490 }
491 // pull the text value from the item attached to the DOM node
492 var newValue;
493 if(node == this.dropDown.nextButton ||
494 node == this.dropDown.previousButton){
495 newValue = node.innerHTML;
496 this.item = undefined;
497 this.value = '';
498 }else{
499 newValue = (this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API)
500 this.store.getValue(node.item, this.searchAttr) : node.item[this.searchAttr]).toString();
501 this.set('item', node.item, false, newValue);
502 }
503 // get the text that the user manually entered (cut off autocompleted text)
504 this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length);
505 // set up ARIA activedescendant
506 this.focusNode.setAttribute("aria-activedescendant", domAttr.get(node, "id"));
507 // autocomplete the rest of the option to announce change
508 this._autoCompleteText(newValue);
509 },
510
511 _selectOption: function(/*DomNode*/ target){
512 // summary:
513 // Menu callback function, called when an item in the menu is selected.
514 this.closeDropDown();
515 if(target){
516 this._announceOption(target);
517 }
518 this._setCaretPos(this.focusNode, this.focusNode.value.length);
519 this._handleOnChange(this.value, true);
520 },
521
522 _startSearchAll: function(){
523 this._startSearch('');
524 },
525
526 _startSearchFromInput: function(){
527 this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
528 },
529
530 _getQueryString: function(/*String*/ text){
531 return string.substitute(this.queryExpr, [text]);
532 },
533
534 _startSearch: function(/*String*/ key){
535 // summary:
536 // Starts a search for elements matching key (key=="" means to return all items),
537 // and calls _openResultList() when the search completes, to display the results.
538 if(!this.dropDown){
539 var popupId = this.id + "_popup",
540 dropDownConstructor = lang.isString(this.dropDownClass) ?
541 lang.getObject(this.dropDownClass, false) : this.dropDownClass;
542 this.dropDown = new dropDownConstructor({
543 onChange: lang.hitch(this, this._selectOption),
544 id: popupId,
545 dir: this.dir,
546 textDir: this.textDir
547 });
548 this.focusNode.removeAttribute("aria-activedescendant");
549 this.textbox.setAttribute("aria-owns",popupId); // associate popup with textbox
550 }
551 this._lastInput = key; // Store exactly what was entered by the user.
552
553 // Setup parameters to be passed to store.query().
554 // Create a new query to prevent accidentally querying for a hidden
555 // value from FilteringSelect's keyField
556 var query = lang.clone(this.query); // #5970
557 var options = {
558 start: 0,
559 count: this.pageSize,
560 queryOptions: { // remove for 2.0
561 ignoreCase: this.ignoreCase,
562 deep: true
563 }
564 };
565 lang.mixin(options, this.fetchProperties);
566
567 // Generate query
568 var qs = this._getQueryString(key), q;
569 if(this.store._oldAPI){
570 // remove this branch for 2.0
571 q = qs;
572 }else{
573 // Query on searchAttr is a regex for benefit of dojo.store.Memory,
574 // but with a toString() method to help dojo.store.JsonRest.
575 // Search string like "Co*" converted to regex like /^Co.*$/i.
576 q = filter.patternToRegExp(qs, this.ignoreCase);
577 q.toString = function(){ return qs; };
578 }
579 this._lastQuery = query[this.searchAttr] = q;
580
581 // Function to run the query, wait for the results, and then call _openResultList()
582 var _this = this,
583 startQuery = function(){
584 var resPromise = _this._fetchHandle = _this.store.query(query, options);
585 Deferred.when(resPromise, function(res){
586 _this._fetchHandle = null;
587 res.total = resPromise.total;
588 _this._openResultList(res, query, options);
589 }, function(err){
590 _this._fetchHandle = null;
591 if(!_this._cancelingQuery){ // don't treat canceled query as an error
592 console.error(_this.declaredClass + ' ' + err.toString());
593 _this.closeDropDown();
594 }
595 });
596 };
597
598 // #5970: set _lastQuery, *then* start the timeout
599 // otherwise, if the user types and the last query returns before the timeout,
600 // _lastQuery won't be set and their input gets rewritten
601
602 this.searchTimer = setTimeout(lang.hitch(this, function(query, _this){
603 this.searchTimer = null;
604
605 startQuery();
606
607 // Setup method to handle clicking next/previous buttons to page through results
608 this._nextSearch = this.dropDown.onPage = function(direction){
609 options.start += options.count * direction;
610 // tell callback the direction of the paging so the screen
611 // reader knows which menu option to shout
612 options.direction = direction;
613 startQuery();
614 _this.focus();
615 };
616 }, query, this), this.searchDelay);
617 },
618
619 _getValueField: function(){
620 // summary:
621 // Helper for postMixInProperties() to set this.value based on data inlined into the markup.
622 // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value.
623 return this.searchAttr;
624 },
625
626 //////////// INITIALIZATION METHODS ///////////////////////////////////////
627
628 constructor: function(){
629 this.query={};
630 this.fetchProperties={};
631 },
632
633 postMixInProperties: function(){
634 if(!this.store){
635 var srcNodeRef = this.srcNodeRef;
636 var list = this.list;
637 if(list){
638 this.store = registry.byId(list);
639 }else{
640 // if user didn't specify store, then assume there are option tags
641 this.store = new DataList({}, srcNodeRef);
642 }
643
644 // if there is no value set and there is an option list, set
645 // the value to the first value to be consistent with native Select
646 // Firefox and Safari set value
647 // IE6 and Opera set selectedIndex, which is automatically set
648 // by the selected attribute of an option tag
649 // IE6 does not set value, Opera sets value = selectedIndex
650 if(!("value" in this.params)){
651 var item = (this.item = this.store.fetchSelectedItem());
652 if(item){
653 var valueField = this._getValueField();
654 // remove getValue() for 2.0 (old dojo.data API)
655 this.value = this.store._oldAPI ? this.store.getValue(item, valueField) : item[valueField];
656 }
657 }
658 }
659
660 this.inherited(arguments);
661 },
662
663 postCreate: function(){
664 // summary:
665 // Subclasses must call this method from their postCreate() methods
666 // tags:
667 // protected
668
669 // find any associated label element and add to ComboBox node.
670 var label=query('label[for="'+this.id+'"]');
671 if(label.length){
672 label[0].id = (this.id+"_label");
673 this.domNode.setAttribute("aria-labelledby", label[0].id);
674
675 }
676 this.inherited(arguments);
677 },
678
679 _getMenuLabelFromItem: function(/*Item*/ item){
680 var label = this.labelFunc(item, this.store),
681 labelType = this.labelType;
682 // If labelType is not "text" we don't want to screw any markup ot whatever.
683 if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){
684 label = this.doHighlight(label, this._escapeHtml(this._lastInput));
685 labelType = "html";
686 }
687 return {html: labelType == "html", label: label};
688 },
689
690 doHighlight: function(/*String*/ label, /*String*/ find){
691 // summary:
692 // Highlights the string entered by the user in the menu. By default this
693 // highlights the first occurrence found. Override this method
694 // to implement your custom highlighting.
695 // tags:
696 // protected
697
698 var
699 // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true
700 modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""),
701 i = this.queryExpr.indexOf("${0}");
702 find = regexp.escapeString(find); // escape regexp special chars
703 return this._escapeHtml(label).replace(
704 // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}"
705 new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers),
706 '<span class="dijitComboBoxHighlightMatch">$1</span>'
707 ); // returns String, (almost) valid HTML (entities encoded)
708 },
709
710 _escapeHtml: function(/*String*/ str){
711 // TODO Should become dojo.html.entities(), when exists use instead
712 // summary:
713 // Adds escape sequences for special characters in XML: &<>"'
714 str = String(str).replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
715 .replace(/>/gm, "&gt;").replace(/"/gm, "&quot;"); //balance"
716 return str; // string
717 },
718
719 reset: function(){
720 // Overrides the _FormWidget.reset().
721 // Additionally reset the .item (to clean up).
722 this.item = null;
723 this.inherited(arguments);
724 },
725
726 labelFunc: function(/*item*/ item, /*dojo.store.api.Store*/ store){
727 // summary:
728 // Computes the label to display based on the dojo.data store item.
729 // returns:
730 // The label that the ComboBox should display
731 // tags:
732 // private
733
734 // Use toString() because XMLStore returns an XMLItem whereas this
735 // method is expected to return a String (#9354).
736 // Remove getValue() for 2.0 (old dojo.data API)
737 return (store._oldAPI ? store.getValue(item, this.labelAttr || this.searchAttr) :
738 item[this.labelAttr || this.searchAttr]).toString(); // String
739 },
740
741 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item){
742 // summary:
743 // Hook so set('value', value) works.
744 // description:
745 // Sets the value of the select.
746 this._set("item", item||null); // value not looked up in store
747 if(!value){ value = ''; } // null translates to blank
748 this.inherited(arguments);
749 },
750 _setTextDirAttr: function(/*String*/ textDir){
751 // summary:
752 // Setter for textDir, needed for the dropDown's textDir update.
753 // description:
754 // Users shouldn't call this function; they should be calling
755 // set('textDir', value)
756 // tags:
757 // private
758 this.inherited(arguments);
759 // update the drop down also (_ComboBoxMenuMixin)
760 if(this.dropDown){
761 this.dropDown._set("textDir", textDir);
762 }
763 }
764 });
765 });