]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dijit/form/FilteringSelect", [ |
2 | "dojo/data/util/filter", // filter.patternToRegExp | |
3 | "dojo/_base/declare", // declare | |
4 | "dojo/_base/lang", // lang.mixin | |
5 | "dojo/when", | |
6 | "./MappedTextBox", | |
7 | "./ComboBoxMixin" | |
8 | ], function(filter, declare, lang, when, MappedTextBox, ComboBoxMixin){ | |
9 | ||
10 | // module: | |
11 | // dijit/form/FilteringSelect | |
12 | ||
13 | return declare("dijit.form.FilteringSelect", [MappedTextBox, ComboBoxMixin], { | |
14 | // summary: | |
15 | // An enhanced version of the HTML SELECT tag, populated dynamically | |
16 | // | |
17 | // description: | |
18 | // An enhanced version of the HTML SELECT tag, populated dynamically. It works | |
19 | // very nicely with very large data sets because it can load and page data as needed. | |
20 | // It also resembles ComboBox, but does not allow values outside of the provided ones. | |
21 | // If OPTION tags are used as the data provider via markup, then the | |
22 | // OPTION tag's child text node is used as the displayed value when selected | |
23 | // while the OPTION tag's value attribute is used as the widget value on form submit. | |
24 | // To set the default value when using OPTION tags, specify the selected | |
25 | // attribute on 1 of the child OPTION tags. | |
26 | // | |
27 | // Similar features: | |
28 | // | |
29 | // - There is a drop down list of possible values. | |
30 | // - You can only enter a value from the drop down list. (You can't | |
31 | // enter an arbitrary value.) | |
32 | // - The value submitted with the form is the hidden value (ex: CA), | |
33 | // not the displayed value a.k.a. label (ex: California) | |
34 | // | |
35 | // Enhancements over plain HTML version: | |
36 | // | |
37 | // - If you type in some text then it will filter down the list of | |
38 | // possible values in the drop down list. | |
39 | // - List can be specified either as a static list or via a javascript | |
40 | // function (that can get the list from a server) | |
41 | ||
42 | // required: Boolean | |
43 | // True (default) if user is required to enter a value into this field. | |
44 | required: true, | |
45 | ||
46 | _lastDisplayedValue: "", | |
47 | ||
48 | _isValidSubset: function(){ | |
49 | return this._opened; | |
50 | }, | |
51 | ||
52 | isValid: function(){ | |
53 | // Overrides ValidationTextBox.isValid() | |
54 | return !!this.item || (!this.required && this.get('displayedValue') == ""); // #5974 | |
55 | }, | |
56 | ||
57 | _refreshState: function(){ | |
58 | if(!this.searchTimer){ // state will be refreshed after results are returned | |
59 | this.inherited(arguments); | |
60 | } | |
61 | }, | |
62 | ||
63 | _callbackSetLabel: function( | |
64 | /*Array*/ result, | |
65 | /*Object*/ query, | |
66 | /*Object*/ options, | |
67 | /*Boolean?*/ priorityChange){ | |
68 | // summary: | |
69 | // Callback from dojo.store after lookup of user entered value finishes | |
70 | ||
71 | // setValue does a synchronous lookup, | |
72 | // so it calls _callbackSetLabel directly, | |
73 | // and so does not pass dataObject | |
74 | // still need to test against _lastQuery in case it came too late | |
75 | if((query && query[this.searchAttr] !== this._lastQuery) || (!query && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){ | |
76 | return; | |
77 | } | |
78 | if(!result.length){ | |
79 | //#3268: don't modify display value on bad input | |
80 | //#3285: change CSS to indicate error | |
81 | this.set("value", '', priorityChange || (priorityChange === undefined && !this.focused), this.textbox.value, null); | |
82 | }else{ | |
83 | this.set('item', result[0], priorityChange); | |
84 | } | |
85 | }, | |
86 | ||
87 | _openResultList: function(/*Object*/ results, /*Object*/ query, /*Object*/ options){ | |
88 | // Callback when a data store query completes. | |
89 | // Overrides ComboBox._openResultList() | |
90 | ||
91 | // #3285: tap into search callback to see if user's query resembles a match | |
92 | if(query[this.searchAttr] !== this._lastQuery){ | |
93 | return; | |
94 | } | |
95 | this.inherited(arguments); | |
96 | ||
97 | if(this.item === undefined){ // item == undefined for keyboard search | |
98 | // If the search returned no items that means that the user typed | |
99 | // in something invalid (and they can't make it valid by typing more characters), | |
100 | // so flag the FilteringSelect as being in an invalid state | |
101 | this.validate(true); | |
102 | } | |
103 | }, | |
104 | ||
105 | _getValueAttr: function(){ | |
106 | // summary: | |
107 | // Hook for get('value') to work. | |
108 | ||
109 | // don't get the textbox value but rather the previously set hidden value. | |
110 | // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur | |
111 | return this.valueNode.value; | |
112 | }, | |
113 | ||
114 | _getValueField: function(){ | |
115 | // Overrides ComboBox._getValueField() | |
116 | return "value"; | |
117 | }, | |
118 | ||
119 | _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item){ | |
120 | // summary: | |
121 | // Hook so set('value', value) works. | |
122 | // description: | |
123 | // Sets the value of the select. | |
124 | // Also sets the label to the corresponding value by reverse lookup. | |
125 | if(!this._onChangeActive){ priorityChange = null; } | |
126 | ||
127 | if(item === undefined){ | |
128 | if(value === null || value === ''){ | |
129 | value = ''; | |
130 | if(!lang.isString(displayedValue)){ | |
131 | this._setDisplayedValueAttr(displayedValue||'', priorityChange); | |
132 | return; | |
133 | } | |
134 | } | |
135 | ||
136 | var self = this; | |
137 | this._lastQuery = value; | |
138 | when(this.store.get(value), function(item){ | |
139 | self._callbackSetLabel(item? [item] : [], undefined, undefined, priorityChange); | |
140 | }); | |
141 | }else{ | |
142 | this.valueNode.value = value; | |
143 | this.inherited(arguments); | |
144 | } | |
145 | }, | |
146 | ||
147 | _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ | |
148 | // summary: | |
149 | // Set the displayed valued in the input box, and the hidden value | |
150 | // that gets submitted, based on a dojo.data store item. | |
151 | // description: | |
152 | // Users shouldn't call this function; they should be calling | |
153 | // set('item', value) | |
154 | // tags: | |
155 | // private | |
156 | this.inherited(arguments); | |
157 | this._lastDisplayedValue = this.textbox.value; | |
158 | }, | |
159 | ||
160 | _getDisplayQueryString: function(/*String*/ text){ | |
161 | return text.replace(/([\\\*\?])/g, "\\$1"); | |
162 | }, | |
163 | ||
164 | _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){ | |
165 | // summary: | |
166 | // Hook so set('displayedValue', label) works. | |
167 | // description: | |
168 | // Sets textbox to display label. Also performs reverse lookup | |
169 | // to set the hidden value. label should corresponding to item.searchAttr. | |
170 | ||
171 | if(label == null){ label = ''; } | |
172 | ||
173 | // This is called at initialization along with every custom setter. | |
174 | // Usually (or always?) the call can be ignored. If it needs to be | |
175 | // processed then at least make sure that the XHR request doesn't trigger an onChange() | |
176 | // event, even if it returns after creation has finished | |
177 | if(!this._created){ | |
178 | if(!("displayedValue" in this.params)){ | |
179 | return; | |
180 | } | |
181 | priorityChange = false; | |
182 | } | |
183 | ||
184 | // Do a reverse lookup to map the specified displayedValue to the hidden value. | |
185 | // Note that if there's a custom labelFunc() this code | |
186 | if(this.store){ | |
187 | this.closeDropDown(); | |
188 | var query = lang.clone(this.query); // #6196: populate query with user-specifics | |
189 | ||
190 | // Generate query | |
191 | var qs = this._getDisplayQueryString(label), q; | |
192 | if(this.store._oldAPI){ | |
193 | // remove this branch for 2.0 | |
194 | q = qs; | |
195 | }else{ | |
196 | // Query on searchAttr is a regex for benefit of dojo/store/Memory, | |
197 | // but with a toString() method to help dojo/store/JsonRest. | |
198 | // Search string like "Co*" converted to regex like /^Co.*$/i. | |
199 | q = filter.patternToRegExp(qs, this.ignoreCase); | |
200 | q.toString = function(){ return qs; }; | |
201 | } | |
202 | this._lastQuery = query[this.searchAttr] = q; | |
203 | ||
204 | // If the label is not valid, the callback will never set it, | |
205 | // so the last valid value will get the warning textbox. Set the | |
206 | // textbox value now so that the impending warning will make | |
207 | // sense to the user | |
208 | this.textbox.value = label; | |
209 | this._lastDisplayedValue = label; | |
210 | this._set("displayedValue", label); // for watch("displayedValue") notification | |
211 | var _this = this; | |
212 | var options = { | |
213 | ignoreCase: this.ignoreCase, | |
214 | deep: true | |
215 | }; | |
216 | lang.mixin(options, this.fetchProperties); | |
217 | this._fetchHandle = this.store.query(query, options); | |
218 | when(this._fetchHandle, function(result){ | |
219 | _this._fetchHandle = null; | |
220 | _this._callbackSetLabel(result || [], query, options, priorityChange); | |
221 | }, function(err){ | |
222 | _this._fetchHandle = null; | |
223 | if(!_this._cancelingQuery){ // don't treat canceled query as an error | |
224 | console.error('dijit.form.FilteringSelect: ' + err.toString()); | |
225 | } | |
226 | }); | |
227 | } | |
228 | }, | |
229 | ||
230 | undo: function(){ | |
231 | this.set('displayedValue', this._lastDisplayedValue); | |
232 | } | |
233 | }); | |
234 | }); |