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