It's a known feature of backbone.js that when you set data that hasn't changed it won't fire the change event, nor will it go through validations. I however need the change event to fire as I'm storing a JSON response from an AJAX call which stores results of backend validation. If the user keeps submitting the form while leaving the same field empty, the backend validation will return the same JSON result and when I save it to the model it won't trigger the change event.
A few things I've tried within the AJAX success callback where I set the data into the model:
Attempted Solution #1
t.model.unset('fieldErrors',{silent: true});
t.model.set({fieldErrors: JSONResponse});
Attempted Solution #2
t.model.set({fieldErrors: null},{silent: true});
t.model.set({fieldErrors: JSONResponse});
Neither of these results in the change event firing a second time when the call is made and the user has the same JSONResponse.
Manually trigger the change event:
t.model.trigger('change', t.model);
or
t.model.trigger('change:fieldErrors', t.model, newFieldErrorsValue);
this.model.set({fieldErrors: JSONResponse}, {silent:true});
this.model.trigger('change:fieldErrors');
see this conversation:
Can I force an update to a model's attribute to register as a change even if it isn't?
Related
I have this scenario where a field is invalid due to another selection on the form. When that selection changes I want to revalidate. I tried calling $setViewValue on the field when the selection changes, but that doesn't refire the validation. Any ideas?
I have a hack working, but I would prefer a clean solution.
I ran into the same issue and found a workaround/feature that appears to be undocumented. If you need to trigger ngModelController to revalidate, you can either do:
ngModelCtrl.$setViewValue(value, 'your event name', true);
or if you don't need to update your model value
ngModelCtrl.$commitViewValue(true);
The true in both cases above is a flag for revalidation. Without this flag, the issue I was running into was that if the model value does not change, then angular simply skips the validation. I am using this way to manually mark a custom control as $dirty
Source:
https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L1843
Write a directive, say validation, and place it on your field in the definition of this directive in the:
link:function(scope,element,args){
element.bind('onfocus',function(){
// Your logic
})
// Similarly, bind other relevant events like key presses, etc.
}
Put a ng-change on your select and broadcast an event in it:
$rootScope.$broadcast("selectChangedEvent");
Then, in a directive you have placed on your field, simply put in the link function:
$rootScope.$on("selectChangedEvent", () => ngModelCtrl.$validate());
$validate runs each of the registered validators of your field.
My backbone.js model has an array property. I bound the change event to save().
After sync() (triggered by save(), my app server returns an identical JSON, but backbone thinks the array has been changed (due to a different reference to the array I guess?), and trigger changes again. Then an infinite loop occurs.
save() -> sync() -> triggered `change` -> save()...
What shall I do?
Idea: I can bind the change event to a function that checks if the changed attributes are of type object/array, and do a deep comparison and call save only if the array/object really changed. If true then save()?
Thanks!
Try the Edge version of Backbone (master branch) this behavior changed after 0.9.9 - see https://github.com/documentcloud/backbone/pull/2004
Backbone has a special option on many methods to prevent just this sort of issue: silent:true. If you pass that option to your save method, the resulting sync won't trigger a change event.
So, if you want to set your change event handler to save silently, something like:
changeHandler: function() {
this.save({silent:true});
}
should do the trick.
I have a collection of models I fetch from a REST API every 10 seconds. (collection.fetch() every 10 seconds with a timer).
The user can also edit the model in a dialog box and click Save going back to the table of models.
How do I prevent cases where the user saves a model in a dialog and the auto fetch exactly comes back with a stale model so the model stays with the stale data until the next auto fetch.
Two suggestions:
Use collection.fetch({ update: true }) - that way models will only be add/remove/change'd rather than recreated on each fetch.
When the model is edited via the your dialog box, only save() the specific attributes that the user changed, like model.save(changedData, { patch: true }); -- using this patch behavior will make sure you're only sending the attributes that were just changed. Then your server can respond with the other recently-changed attributes, and all should be fine.
I've got a backbone.js model which contains a calendar. The user can go back and forth in the calendar and I can get the events for the selected calendar day.
In my Model, I have
initialize: function(){
this.on('change:date',this.get_cal());
},
get_cal: function(){
alert('get calendar');
this.fetch(...
}
and in my view I have
cal_date: function(move){
Myapp.cal.attributes.date.setDate(Myapp.cal.attributes.date.getDate()+move);
}
when the date changes, I expected backbone to trigger the change event, and get the calendar events for the new date. Unfortunately, that isn't happening.
I've also tried putting the printed date into the model as
Myapp.cal.set({print_date: formatted_date});
thinking that maybe backbone is missing the update because I'm not calling 'set', or because it sees a date object and thinks that it already had a date object and therefore didn't change.
I've also tried to trigger the change with Myapp.cal.trigger('change'), in the view but that didn't work either. Nor did removing the calendar events by calling Myapp.cal.cal_events.refresh() where cal_events is the collection holding the days events.
Do you see what's wrong here?
I think the issue is with the parentheses after get_call on this line:
this.on('change:date',this.get_cal());
You should remove them because they call get_call right in initialize instead of making them an event handler.
I am converting my backbone app so that it starts communicating with the server, previously I had just been populating the collection with test data using .add()
I have tied some events to the collections add event. So every time an item is added to the collection I can render the view and update some statistics.
it appears that as soon as i add the .fetch() call to get data from the server the add events stop.
for example
var PayableCommitmentCollection = Backbone.Collection.extend({
model:PayableCommitment,
url:"/cc/account/contributions/",
initialize: function() {
this.bind("add",this.setInitialAmount,this);
}
}
this.SetInitialAmount() is never called after fetch creates the models in the collection.
I also have 2 views that are watching for items to be added to this collection that are now not updating.
My obvious work around is to write my own AJAX call so that I can add the items the same way I have been during development so far, however I'm sure backbone has the smarts to help me out here.
Can anyone suggest a way i can bind to the completion of fetch, or to make it stimulate the add event.
The fetch method accepts a hash of options. One of these options can be the "add" option, which calls add on the collection instead of reset.
collection.fetch({ add: true });
From the fine manual:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, resetting the collection when they arrive.
And a reset:
reset collection.reset(models, [options])
Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you'd rather just update the collection in bulk. Use reset to replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end.
So a fetch will trigger a single "reset" event rather than a bunch of "add" events. You need a collection-wide version of setInitialAmount that you can bind to "reset".
In Backbone 1.0, Collection#fetch has this to say:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, setting them on the collection when they arrive.
[...]
The behavior of fetch can be customized by using the available set options. For example, to fetch a collection, getting an "add" event for every new model, and a "change" event for every changed existing model, without removing anything: collection.fetch({remove: false})
So, if you're using 1.0+ then all you need to do is call your fetch with the remove: false option.