Field dependencies
A form will typically have a number of fields that are dependent on conditions with another field. Consider a selection that is required if another field is used. Or maybe a field is disabled if a certain combo value is chosen. No matter, these dependencies have to be managed somewhere. In traditional client development this is likely to be handled by a controller (i.e. MVC) but there are times the dependencies are not complex and a full blown controller is just not necessary.
Take the example I posted in my previous blog entry shown again below.
fields: [
{ name: "major", title: "Major", editorType: "ComboBoxItem", type: "select",
width: "*",
optionDataSource: "majorValuesDS",
allowEmptyValue: true,
valueField: "major",
displayField: "description",
completeOnTab: true,
changed: function(form, item, value) {
var field = form.getField('minor');
field.setValue(null);
field.setDisabled(!value);
},
pickListWidth: 450,
pickListFields: [
{ name: "major", width: 50 },
{ name: "description" }
]
},
{ name: "minor", title: "Minor", editorType: "ComboBoxItem", type: "select",
width: "*",
optionDataSource: "minorValuesDS",
autoFetchData: false,
allowEmptyValue: true,
disabled: true,
valueField: "minor",
displayField: "description",
completeOnTab: true,
pickListWidth: 450,
pickListFields: [
{ name: "minor", width: 50 },
{ name: "description" }
],
getPickListFilterCriteria: function() {
return {
major: this.form.getValue("major"),
minor: this.form.getValue("minor")
}
}
}
]
Notice that when the major is changed the minor is automatically cleared and is enabled only if the major actually has a value. All of this is handled in the changed handler of the major. Finally, note also that the minor puts the pick list criteria together using the value from major. So the dependency is dealt with by both fields. I would rather see all of this in one place: the minor.
So, how can we accomplish this? We can use an observer. The minor field just needs to register an observer on the parent field (major) changed event. The observer can then be written as:
{ name: "minor", title: "Minor", editorType: "ComboBoxItem", type: "select",
...
majorChanged: function(item, majorItem) {
// Major changed - clear our selection, if any
item.setValue(null);
// Disable this field if major is empty
var major = majorItem.getValue();
item.setDisabled(!major);
},
...
That looks nice. Now when majorChanged is called the minor field can update itself as needed. But how is this method called? We need to register an observer on the major field changed event handler. Something like:
{ name: "minor", title: "Minor", editorType: "ComboBoxItem", type: "select",
...
init: function() {
this.Super("init", arguments);
// Register observer on major
var field = this.getField('major');
this.observe(field, "changed", "observer.itemIdChanged(observer, observed)");
},
...
That’s easy enough! But, wait! It doesn’t work. Apparently it’s too early at init() time to register an observer on another field. So we need a later event to make this observation. Once the fields have been defined on the form all fields are initialized with a call to setValue(null) by the form. So we can use this event to register our observer.
Final working code ends up as:
{ name: "minor", title: "Minor", editorType: "ComboBoxItem", type: "select",
...
setValue: function(newValue) {
this.Super("setValue", arguments);
// Register observer on major
if (!this._observing) {
var field = this.getField('major');
this.observe(field, "changed", "observer.majorChanged(observer, observed)");
this._observing = true;
}
},
majorChanged: function(item, majorItem) {
// Major changed - clear our selection, if any
item.setValue(null);
// Disable this field if major is empty
var major = majorItem.getValue();
item.setDisabled(!major);
},
...
Now, after removing the major’s changed event handler only the minor field knows about its dependency on the contents of major.
I don’t particularly like using setValue in this way but I haven’t found a better solution. If you have a suggestion, please let me know. Otherwise, happy SmartClienting.