]> git.wh0rd.org Git - 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 });