You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
8.5 KiB
JavaScript
249 lines
8.5 KiB
JavaScript
dojo.provide("dijit.form.FilteringSelect");
|
|
|
|
dojo.require("dijit.form.ComboBox");
|
|
|
|
dojo.declare(
|
|
"dijit.form.FilteringSelect",
|
|
[dijit.form.MappedTextBox, dijit.form.ComboBoxMixin],
|
|
{
|
|
// summary:
|
|
// An enhanced version of the HTML SELECT tag, populated dynamically
|
|
//
|
|
// description:
|
|
// An enhanced version of the HTML SELECT tag, populated dynamically. It works
|
|
// very nicely with very large data sets because it can load and page data as needed.
|
|
// It also resembles ComboBox, but does not allow values outside of the provided ones.
|
|
// If OPTION tags are used as the data provider via markup, then the
|
|
// OPTION tag's child text node is used as the displayed value when selected
|
|
// while the OPTION tag's value attribute is used as the widget value on form submit.
|
|
// To set the default value when using OPTION tags, specify the selected
|
|
// attribute on 1 of the child OPTION tags.
|
|
//
|
|
// Similar features:
|
|
// - There is a drop down list of possible values.
|
|
// - You can only enter a value from the drop down list. (You can't
|
|
// enter an arbitrary value.)
|
|
// - The value submitted with the form is the hidden value (ex: CA),
|
|
// not the displayed value a.k.a. label (ex: California)
|
|
//
|
|
// Enhancements over plain HTML version:
|
|
// - If you type in some text then it will filter down the list of
|
|
// possible values in the drop down list.
|
|
// - List can be specified either as a static list or via a javascript
|
|
// function (that can get the list from a server)
|
|
|
|
_isvalid: true,
|
|
|
|
// required: Boolean
|
|
// True if user is required to enter a value into this field.
|
|
required: true,
|
|
|
|
_lastDisplayedValue: "",
|
|
|
|
isValid: function(){
|
|
// Overrides ValidationTextBox.isValid()
|
|
return this._isvalid || (!this.required && this.attr('displayedValue') == ""); // #5974
|
|
},
|
|
|
|
_callbackSetLabel: function( /*Array*/ result,
|
|
/*Object*/ dataObject,
|
|
/*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Callback function that dynamically sets the label of the
|
|
// ComboBox
|
|
|
|
// setValue does a synchronous lookup,
|
|
// so it calls _callbackSetLabel directly,
|
|
// and so does not pass dataObject
|
|
// still need to test against _lastQuery in case it came too late
|
|
if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery)||(!dataObject && result.length && this.store.getIdentity(result[0])!= this._lastQuery)){
|
|
return;
|
|
}
|
|
if(!result.length){
|
|
//#3268: do nothing on bad input
|
|
//this._setValue("", "");
|
|
//#3285: change CSS to indicate error
|
|
this.valueNode.value = "";
|
|
dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange===undefined && !this._focused));
|
|
this._isvalid = false;
|
|
this.validate(this._focused);
|
|
this.item = null;
|
|
}else{
|
|
this._setValueFromItem(result[0], priorityChange);
|
|
}
|
|
},
|
|
|
|
_openResultList: function(/*Object*/ results, /*Object*/ dataObject){
|
|
// Overrides ComboBox._openResultList()
|
|
|
|
// #3285: tap into search callback to see if user's query resembles a match
|
|
if(dataObject.query[this.searchAttr] != this._lastQuery){
|
|
return;
|
|
}
|
|
this._isvalid = results.length != 0; // FIXME: should this be greater-than?
|
|
this.validate(true);
|
|
dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
|
|
},
|
|
|
|
_getValueAttr: function(){
|
|
// summary:
|
|
// Hook for attr('value') to work.
|
|
|
|
// don't get the textbox value but rather the previously set hidden value
|
|
// TODO: seems suspicious that we need this; how is FilteringSelect different
|
|
// than another MappedTextBox widget?
|
|
return this.valueNode.value;
|
|
},
|
|
|
|
_getValueField: function(){
|
|
// Overrides ComboBox._getValueField()
|
|
return "value";
|
|
},
|
|
|
|
_setValue: function( /*String*/ value,
|
|
/*String*/ displayedValue,
|
|
/*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Internal function for setting the displayed value and hidden value.
|
|
// Differs from _setValueAttr() in that _setValueAttr() only takes a single
|
|
// value argument, and has to look up the displayed value from that.
|
|
// tags:
|
|
// private
|
|
this.valueNode.value = value;
|
|
dijit.form.FilteringSelect.superclass._setValueAttr.call(this, value, priorityChange, displayedValue);
|
|
this._lastDisplayedValue = displayedValue;
|
|
},
|
|
|
|
_setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Hook so attr('value', value) works.
|
|
// description:
|
|
// Sets the value of the select.
|
|
// Also sets the label to the corresponding value by reverse lookup.
|
|
if(!this._onChangeActive){ priorityChange = null; }
|
|
this._lastQuery = value;
|
|
|
|
if(value === null || value === ''){
|
|
this._setDisplayedValueAttr('', priorityChange);
|
|
return;
|
|
}
|
|
|
|
//#3347: fetchItemByIdentity if no keyAttr specified
|
|
var self = this;
|
|
var handleFetchByIdentity = function(item, priorityChange){
|
|
if(item){
|
|
if(self.store.isItemLoaded(item)){
|
|
self._callbackSetLabel([item], undefined, priorityChange);
|
|
}else{
|
|
self.store.loadItem({
|
|
item: item,
|
|
onItem: function(result, dataObject){
|
|
self._callbackSetLabel(result, dataObject, priorityChange);
|
|
}
|
|
});
|
|
}
|
|
}else{
|
|
self._isvalid = false;
|
|
// prevent errors from Tooltip not being created yet
|
|
self.validate(false);
|
|
}
|
|
};
|
|
this.store.fetchItemByIdentity({
|
|
identity: value,
|
|
onItem: function(item){
|
|
handleFetchByIdentity(item, priorityChange);
|
|
}
|
|
});
|
|
},
|
|
|
|
_setValueFromItem: function(/*item*/ item, /*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Set the displayed valued in the input box, and the hidden value
|
|
// that gets submitted, based on a dojo.data store item.
|
|
// description:
|
|
// Users shouldn't call this function; they should be calling
|
|
// attr('displayedValue', value) or attr('value', ...) instead
|
|
// tags:
|
|
// private
|
|
this._isvalid = true;
|
|
this.item = item; // Fix #6381
|
|
this._setValue( this.store.getIdentity(item),
|
|
this.labelFunc(item, this.store),
|
|
priorityChange);
|
|
},
|
|
|
|
labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
|
|
// summary:
|
|
// Computes the label to display based on the dojo.data store item.
|
|
// returns:
|
|
// The label that the ComboBox should display
|
|
// tags:
|
|
// private
|
|
return store.getValue(item, this.searchAttr);
|
|
},
|
|
|
|
_doSelect: function(/*Event*/ tgt){
|
|
// summary:
|
|
// Overrides ComboBox._doSelect(), the method called when an item in the menu is selected.
|
|
// description:
|
|
// FilteringSelect overrides this to set both the visible and
|
|
// hidden value from the information stored in the menu.
|
|
this._setValueFromItem(tgt.item, true);
|
|
},
|
|
|
|
_setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){
|
|
// summary:
|
|
// Hook so attr('displayedValue', label) works.
|
|
// description:
|
|
// Sets textbox to display label. Also performs reverse lookup
|
|
// to set the hidden value.
|
|
|
|
// When this is called during initialization it'll ping the datastore
|
|
// for reverse lookup, and when that completes (after an XHR request)
|
|
// will call setValueAttr()... but that shouldn't trigger an onChange()
|
|
// event, even when it happens after creation has finished
|
|
if(!this._created){
|
|
priorityChange = false;
|
|
}
|
|
|
|
if(this.store){
|
|
var query = dojo.clone(this.query); // #6196: populate query with user-specifics
|
|
// escape meta characters of dojo.data.util.filter.patternToRegExp().
|
|
this._lastQuery = query[this.searchAttr] = label.replace(/([\\\*\?])/g, "\\$1");
|
|
// if the label is not valid, the callback will never set it,
|
|
// so the last valid value will get the warning textbox set the
|
|
// textbox value now so that the impending warning will make
|
|
// sense to the user
|
|
this.textbox.value = label;
|
|
this._lastDisplayedValue = label;
|
|
var _this = this;
|
|
var fetch = {
|
|
query: query,
|
|
queryOptions: {
|
|
ignoreCase: this.ignoreCase,
|
|
deep: true
|
|
},
|
|
onComplete: function(result, dataObject){
|
|
dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange);
|
|
},
|
|
onError: function(errText){
|
|
console.error('dijit.form.FilteringSelect: ' + errText);
|
|
dojo.hitch(_this, "_setValue")("", label, false);
|
|
}
|
|
};
|
|
dojo.mixin(fetch, this.fetchProperties);
|
|
this.store.fetch(fetch);
|
|
}
|
|
},
|
|
|
|
postMixInProperties: function(){
|
|
this.inherited(arguments);
|
|
this._isvalid = !this.required;
|
|
},
|
|
|
|
undo: function(){
|
|
this.attr('displayedValue', this._lastDisplayedValue);
|
|
}
|
|
}
|
|
);
|