ext.data.record conditional validation - extjs

I am working w/ Ext 3.3
How can I do conditional value checking, on the Ext.data package level, like;
if the person is married, require spouse name, otherwise let it be blank.
The solution I am looking for should be on the data package. (lets say that I dont have much control about how data is edited, but I am responsible for validating it.)
Ext.Data.Field lets me say allowBlank, true or false. I wonder if I can pass a function there which accepts the record, and returns true or false based on the other fields on the record.
I am looking for an alternative solutions for this, on the Ext.data package, (on the Store or Record level).

It depends on when you are doing the validation.
If you are validating when store.load(); is invoked, then I suggest the following:
myStore.on('beforeload', function(store, loadOptions) {
var isValid = true;
var modifiedRecs = store.getModifiedRecords();
Ext.each(modifiedRecs, (function(record, index, modifiedArray) {
// do validation here
// if validation failed, use the following two lines of code:
// isValid = false;
// return false; // this exits modifiedRecs.each
}, this);
return isValid; // If falsey, this return statement cancels loading.
// Note: the 'loadexception' event will be now be fired
// by myStore if isValid is falsey.
});
If you are validating whenever data changes in the store, then use the following:
myStore.on('beforesave', function(store, data) {
// simply do validation against `data`.
// data will contain an array of records for each type of action that
// was being saved, e.g., data['update'] === [updatedRec1, ...].
// if validation failed, just `return false` to cancel saving.
});
Here's what it means to be falsey.

Related

ExtJS findExact() and custom validator bug

I'm using a custom validator on my combobox's:
function(v) {
console.log(v === 'some value I know for SURE is in the store'); // (1)
var index = this.getStore().findExact(this.displayField, v);
return (index!==-1) ? true : 'Invalid selection';
}
Basically admits the same set as forceSelection but allows the user to type arbitrary text to attempt to auto-complete.
However; I'm having really odd results with findExact(). For example, if the combobox's value is currently valid, and a user does a space + backspace, the validator will fail, even though the output of (1) is true.
Any ideas what is causing the problem? The end-experience is currently very buggy-feeling..
When you type additional space, store is filtered. After you press backspace, and validator is fired, store is still empty.
If you have local store, then you could validate combo with some delay after each change. Example:
listeners: {
change: function() {
this.validate();
},
delay: 100
}
That should be enough.
On the other hand if you have remote store, try something like this:
validator: function(v) {
var store = this.getStore(),
index = store.findExact(this.displayField, v);
if (index === -1 && store.isLoading()) {
store.on('load', function() {
this.validate();
}, this, { single: true, delay: 100 });
}
return (index !== -1) ? true : 'Invalid selection';
}
I had a similar issue and found this entry. My problem was, that I reused the same store instance across multiple ComboBoxes. After giving each ComboBox an own store with cloned data, everything was fine.
See also:
https://www.sencha.com/forum/showthread.php?305182-ComboBox-forceSelection-clears-input&p=1127218&viewfull=1#post1127218
I just spent a few days on this issue and found a really great solution (by accident, really). You can - as the accepted answer suggests - utilize the provided validator function; however, from my understanding, there is a much simpler solution than the accepted answer provides: evaluating whether or not the user-provided input equates to a value in the store (which is the underlying question in the original post).
The advantage of thinking about the input in this way is that it enables us to handle the use case of an invalid value entered by the user (validated; no value) and - after the field loses focus - Ext JS sets the field back to its previous value (which remembers its store value).
This is an entirely different route than your thinking, but it should work, especially as .validate() runs regardless of whether you provide an implementation of the validator procedure:
validator : function(someParam) {
if(this.value === null) {
return "error message"; //falsy
} else {
return true;
}
}
If you enable forceSelection, the above works, very well, and gets rid of the buggy feeling. This allows you to rely on the .validate to do its magic elsewhere (notice I don't even call it; read the doc. to figure out when its called in relationship to validator) and not have to worry about what the user correctly explains in the accepted answer.
We were having trouble with forceSelection clearing user text before they were finished typing. We seemed to get what we needed by setting forceSelection false and just checking that they selected something.
validator: function(v) {
if (this.getSelection() === null) {
return 'invalid text';
}else{
return true;
}
}

I am getting me.store.loading is undefined when I trigger fields using on load event on second load of the window

enter code hereI have a window in which I have a form with trigger fields. So by trigerring I mean , if I select a value from first combo, its trigerring the succeding combos with their's first values. This is working fine when I open the window for the first time. But if close it and open it for the second time, it will generate error as me.store.loading is undefined.
I am using on load event of combo to fire the next combo with its first value. Please see code below which I have put in the render event of a field in that window.
Thanks,
sj
me.control({
'addinp #renderCmp':{
render:me.registerTriggerCalls
}
})
registerTriggerCalls : function() {
var stcombo = Ext.getCmp('StField');
stcombo.store.on('load', function(store, record, opts)
{debugger;
if (store.totalCount <= 0)
{ return; }
stcombo.setValue(store.getAt(0).data.stThru);
stcombo.fireEvent('select', stcombo);
});
var adcombo = Ext.getCmp('AdField');
adcombo.store.on('load', function(store, record, opts)
{
if (store.totalCount <= 0)
{ return; }
adcombo.setValue(store.getAt(0).data.adDate);
adcombo.fireEvent('select', adcombo);
});
}
When does the store get created/destroyed? Are you creating a new store with each combo box or are you reusing a global store each time?
In the comments above, I give you a way to troubleshoot, but if you're reusing the same store object over and over, you either need to use a managed listener (preferred) or unregister your handlers when your combo boxes are destroyed.
var stcombo = Ext.getCmp('StField');
stcombo.mon(store, 'load', function(store, record, opts)
{
if (store.totalCount <= 0)
{ return; }
stcombo.setValue(store.getAt(0).data.stThru);
stcombo.fireEvent('select', stcombo);
});
var adcombo = Ext.getCmp('AdField');
adcombo.mon(store, 'load', function(store, record, opts)
{
if (store.totalCount <= 0)
{ return; }
adcombo.setValue(store.getAt(0).data.adDate);
adcombo.fireEvent('select', adcombo);
});
Assuming that's the case, what's happening is that the life span of your store and your combo box is disconnected. The listeners are tied to the store's life span which doesn't have visibility to the combo box's life span. Hence, the old listeners don't get removed until the store is destroyed which is obviously bad -- for a number of reasons -- but causing your exception because the closure references a destroyed combo box.
A managed listener solves this by essentially tying the listener's life span to the combo box instead of the store.

Model validation before store sync

Listening on beforeSync event of store. Options parameter has hash of all records to be synchronized, broken down into create, update and destroy.
I need to validate them against their model validation rules.
Is it possible?
I tried this but it always returns true:
Ext.Array.forEach(options.create, function (item) {
console.log(item.isValid());
});
Thanks
I've just realized that invalid records are not being inserted in hash of records to be synchronized (options parameter).
Instead I can iterate through store's items:
Ext.Array.forEach(st.data.items, function (item) {
console.log(item.isValid())
} );

How can I persist custom attributes over a collection fetch

I have a an "Asset" backbone model that has a custom attribute called "selected". Its custom in the sense that it is not part of the object on the server side. I use to represent which of the list of assets the user has currently selected.
var Asset = Backbone.Model.extend({
defaults: {
selected: false
},
idAttribute: "AssetId"
});
This model is part of a backbone collection that I fetch periodically to get any changes from the server.
The problem I have is that every time I fetch the collection, the collection is doing a reset (I can tell by the listening for the reset event) and hence the value of the selected attribute is wiped out by the data coming in from the ajax request.
The backbone.js documentation seems to suggest that there is a intelligent merge that will solve this problem. I believe I'm doing this in my fetch methods
allAssets.fetch({ update: true ,cache: false});
And I have also set the "idAttribute" field in the model so that the ids of the object coming in can be compared with the objects in the collection.
The way I have solved this is by writing my own Parse method in my collection object
parse: function (response) {
// ensure that the value of the "selected" for any of the models
// is persisted into the model in the new collection
this.each(function(ass) {
if (ass.get("selected")) {
var newSelectedAsset = _.find(response, function(num) { return num.AssetId == ass.get("AssetId"); });
newSelectedAsset.selected = true;
}
});
return response;
}
Is there a better way to do this?
Collection.update (introduced in Backbone 0.9.9) does indeed try to merge existing models, but does so by merging all set attributes in the new model into the old model. If you check Backbone source code, you'll see
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
existing.set(model.attributes, options);
needsSort = sort;
}
models.splice(i, 1);
continue;
}
All attributes, including defaults, are set, that's why your selected attribute is reset to false. Removing the default value for selected will work as intended: compare http://jsfiddle.net/nikoshr/s5ZXN/ to http://jsfiddle.net/nikoshr/s5ZXN/3/
That said, I wouldn't rely on a model property to store my app state, I would rather move it to a controller somewhere else.

extjs 4 form still has record set after form.reset();

I have a grid bound to a form the forms submit action is to update the loaded record if there is one and add a new record if its a blank form. but if I select a record first and then call
myGrid.getSelectionModel().deselectAll();
myform.getForm().reset();
to clear the form so I can add a new record it overwrites the previously selected record with an update.
record = myform.getRecord();
if(record){
record.set(values);
}
shouldn't myform.getRecord(); be null after a reset? how do I clear the record selection?
In short, no, it shouldn't and you don't have legal approaches to clear the record after the first time you load anything via loadRecord.
Although, you could still do myform.getForm()._record = null assignment, I would strongly object against that, as it may break some internal functionality by ExtJS.
Here is an extract from ExtJS API:
getRecord() : Ext.data.Model
Returns the last Ext.data.Model instance
that was loaded via loadRecord
And it does exactly that, returns the last record loaded via loadRecord.
Here are some sources:
getRecord: function() {
return this._record;
},
loadRecord: function(record) {
this._record = record;
return this.setValues(record.data);
},
Actually, those are the only methods of Ext.form.Basic (an instance of which is returned by getForm()) dealing with this._record field.
As for reset
reset: function() {
var me = this;
me.batchLayouts(function() {
me.getFields().each(function(f) {
f.reset();
});
});
return me;
},
As you could see, reset has nothing to do with the record returned by getRecord(), it's just resetting field values.
For anyone interested, you can override the default form panel to add functionality to clear form fields. Add the following to your code:
Ext.override(Ext.form.Panel, {
clearForm:function(){
Ext.each(this.getForm().getFields().items, function(field){
field.setValue('');
});
}
});
You can then clear a form using:
myForm.clearForm()
Where myForm is your form panel.
This is what you looking for:
form.getForm().reset(true);
True to unbind any record set by loadRecord.
See Ext.form.Basic.reset() syntax
I find this question because I have a similar scenario but slightly different:
ExtJS 4.1.1
TreePanel
In ExtJS 4.1 in sync() method you can use an options object in which you can define a callback, success and failure functions. Since I'm sure I'm only synchronozing just one record, I loaded the returned record:
me.getHelpItemsStore().sync({
success: function(batch) {
// We expect single operation so...
var record = batch.operations[0].getRecords()[0];
form.loadRecord(record);
}
});
Little late but hope helps you.

Resources