The Backbone.js model updates itself - and its views - locally on the page before
passing the data to the server and checking the server's response, so if the
server says "sorry charlie that's no good" the page has already shown the data
as having changed and thus doesn't correctly represent the server-side state of the object. What's the correct/elegant way to handle this on the Backbone.js side when the server returns an error?
If you are directly editing a model and trying to sync it, and also using it elsewhere in your app simultaneously, then that can lead to a world of problems.
In many cases it is better to clone the model for editing, and then when it syncs successfully, apply the clone's attributes back on to the original model.
You can get a cloned model by simply calling:
var clonedModel = originalModel.clone();
And then you can apply the attributes back in a success handler like
originalModel.set(clonedModel.attributes)
How about passing success, error callback functions to the statement where you update the server side state. May be something like this....
this.model.save(
{}, {
success: function() {
/* update the view now */
},
error: function() {
/* handle the error code here */
}
});
Taking it a level higher, you might override the Backbone.sync to globally handle the server-side error codes.
Use
model.save({wait: true});
Source: http://backbonejs.org/#Model-save
Related
I have an object that I'm sending over to a web server to store in the db. Between when I send it and when the operation completes I need to modify it slightly so the server can handle it.
In effect I have the following code:
$scope.o = {...};
$scope.send = function()
{
$scope.sanitize($scope.o);
SomeService.Save($scope.o).then( function() {
$scope.unsanitize($scope.o);
});
}
Sanitize just gets the object ready to send the server and unsanitize puts it back into a form that the view can handle more easily.
The issue I'm having is that between the call to sanitize and unsanitize, there's a few milliseconds of delay (or seconds, depending on how slow my network is at the time). During that time the view is updated and shows the changes that sanitize made. I don't want those changes showing up to the user while the server is doing its thing, though.
Is there a way of temporarily preventing angular from updating changes to a specific object?
I've created a jsfiddle that illustrates the problem here: http://jsfiddle.net/29ze4exp/5/
Sure! Just don't modify the object that is bound to the view. Instead, use a copy.
var copy = angular.copy($scope.o);
$scope.sanitize(copy);
SomeService.Save(copy).then(function() {
I'm new to Firebase and AngularJS (and AngularFire), but am managing to work most things out... however this one's stumping me.
Situation:
I've got a server and a separate frontend. The frontend has NO WRITE PERMISSIONS for Firebase - it can only read it's own data (using a token provided by the server). The server has an API which the frontend utilises to make updates.
For the frontend to request an update to a particular item in a list, it needs to provide that item's Firebase ID to the server (along with whatever other information the API needs). The server will first verify that ID and then use it to update the correct Firebase data.
I've got AngularFire doing a three-way data binding for these actions, which is awesome!
Problem:
Lets say my Firebase structure is as follows:
actions: {
-JFeuuEIFDh: { // Firebase IDs for arrays
label: "One",
.
.
.
},
-JfuDu2JC81: {
"label": "Two",
.
.
.
}
I have the following HTML:
<div ng-controller"SuperController">
<ul>
<!-- key is the Firebase ID for the action, which is
required for my server to know which object to update -->
<li ng-repeat="(key, action) in actions">
<a ng-click="doAction(key, action)">action.label</a>
<!-- **Loading IS NOT and SHOULD NOT be stored in Firebase,**
it's simply a local state which AngularJS should manage -->
<p ng-hide="!action.loading">Loading...</p>
</li>
</ul>
</div>
doAction looks something like this:
$scope.doAction = function(key, item) {
$scope.actions[key].loading = true;
var onComplete = function () {
$scope.actions[key].loading = false;
}
// Calls to the server, etc...
.
.
.
}
I'm using $scope.actions[key].loading to provide a local state so the "Loading..." paragraph will appear when the user initiates doAction, and disappear when the doAction logic completes.
However, because AngularFire has set up a three-way data binding, it tries to save that change to the database, which fails because the client does not have permission to write!
I don't want it to save loading to the database! It's there simply to update that paragraph's ng-hide - it shouldn't be persistent and there's no way this would justify providing write permission to the client.
So what am I supposed to do? I can't think of any way to update the scope without firing off the three-way binding... Am I thinking of this the wrong way?
EDIT
Nested deep in the AngularJS documentation, under $FirebaseObject.$bindTo, was this:
use a variable prefixed with _, which will not be saved to the server, but will trigger $watch().
However when I used $scope.actions[key]._loading instead, the same problem still occurred. There was no apparent difference.
I couldn't find a clean solution to the problem.
use a variable prefixed with _, which will not be saved to the server, but will trigger $watch().
This wasn't actually implemented in code. I was considering submitting an implementation myself but it wasn't as simple as I hoped. Even after toJSON stopped returning _'d variables, it still tried to save the (unchanged) JSON to Firebase, so you'd have to fix it earlier somehow... I didn't go there.
To solve the problem I used AngularFire's $asArray instead of $asObject. The array is READ ONLY. Firebase won't try to sync any changes unless you call special functions. In my case, this works, however in other cases it might not be sufficient.
I had to change a bit my templating to work with an array instead of an object since a numerical key was now being provided instead of the actual key being used in Firebase. I converted the numerical key to the proper one with: actions.$keyAt(parseInt(key)).
It was a mess.. but it'll get me through for now.
I am having same issue. but this seems to fix it.
https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject-bindtoscope-varname
If $destroy() is emitted by scope (this happens when a controller is > destroyed), then this object is automatically unbound from scope. It can > also be manually unbound using the unbind() method, which is passed into ? > the promise callback.
//Setup synced array as usual
$scope.syncedArray = $firebaseArray(ref);
//call unbind on the variable
$scope.syncedArray.unbind();
//reorder, filter and maniuplate arrays as usual and when done call .bind() on it.
I use Rails 4 + backbone in my application.
Everything is good. New model is created in backbone and saved by calling:
newItem.save(null, {success: this.sendSuccess, error: this.sendError});
However, implementing a new feature I need to change one of the model attributes. What I see that a PUT action is fired just before sendSuccess is called, which I want to avoid.
Moreover, the url is very strange. Save action calls this url:
Remote Address:127.0.0.1:3000
Request URL:http://www.lvh.me:3000/api/user/1/tickets
Request Method:POST
and then, after server return the json with the modified attribute, backbone calls this url:
Remote Address:127.0.0.1:3000
Request URL:http://www.lvh.me:3000/api/user/1/tickets
Request Method:PUT
without the ticket id!
Is there any way to prevent backbone fire an update when server return the model with different attributes?
The problem was that I had a listener in my model exactly on the column that server changed:
initialize: function() {
this.on("change:status", this.statusChanged, this);
},
Now I had to figure out why the update url does not contain the model id.
I figured out that when I first created the model, from some reasons I couldn't assign it to the collection, so in order to save it I assign the url manually:
var newTicket = new MyApp.Ticket( ticketData );
newTicket.url = this.collection.url();
Now, the bug is that url is a function, and I simply overrided it!
I changed the code to:
newTicket.urlRoot = this.collection.url();
and now it works.
Backbone will always perform PUT if your model has an id attribute setted. Which makes sense when using RESTfull.
Be sure that you're really SAVING(new model withoud an ID) a data to server instead of UPDATING(model with an ID) to server.
I want to call save on a Backbone model and have it write data to the server, but not update the client. How do I do this?
To clarify: when Backbone saves a model, it sends all the data to the server, and then retrieves the data from the server and updates it on the client. This second step is what I want not to happen.
To clarify further: The model ('parent' model) has an attribute which is itself a model ('child' model); when it's saved to the server, this child model is converted to JSON. When the parent model updates after the save, the attribute that previously contained a reference to the child model is replaced with the parsed JSON object of the child model that was saved. This is what I need not to happen.
When the data is initially pulled from the server, the parent model "reconstitutes" that object into an appropriate child model, but this is an expensive process and there is no reason to re-do it every time save fires on the parent model, since the child model will never change.
It sounds like you do not want to parse your model when you receive the response from the server on a model.save
You can try something such as:
model.save(attributes,{
success: function() { ... },
parse : false // This will be passed to your parse function as an option
});
You would have to set-up your parse function in your corresponding model as follows:
parse: function(resp,options) {
// don't update model with the server response if {parse:false} is passed to save
if (options && !options.parse) return;
/ ... rest of your parse code ... /
Backbone currently defaults options.parse to true. Here is a short-discussion on the topic.
As discussed in that thread, perhaps you want to consider why you do not want want to update the server response to the client. There may be a cleaner way to achieve the results you desire.
Depending on how/what your server setup is, all you really have to do is issue a regular AJAX request. This is exactly what backbone does in the background so you'll just bypass the client side logic.
You could do this with native JavaScript, but I'm fairly sure you have some other library in use that can make things much easier.
For the completeness of this answer, I'll give an example with jQuery:
$.ajax({
type: "POST",
url: "http://same.as.your.model",
data: { "the" : "model" },
dataType: "JSON",
success: function(){
// once the request has returned
}
});
The $.ajax function also has some additional functionality, and you can read about it in the jQuery docs.
On client you mean Views? If you want to save your model but not render your views which happens since save will trigger a change event, you should call save with option silent:true, or set a custom option like dontchange:true when calling save and check it in when handling change. I prefer the custom option, because silent has side effects (at least in my version of backbone 1.0.0)
a little code:
when you save:
model.save({},{dontchange: true});
you install your event listeners in the view:
this.listenTo(model, 'change', function(model, options){
if (options.dontchange)
return;
this.render();
});
I ran into same problem. model.save(attrs,{patch:true, parse:false}) really did not invoke parse method but model was still merged with server response.
It is not elegant, but this worked for me:
model.clone().save(attrs,{patch:true})
I believe it's best to avoid this situation by clean REST api design.
When a save, or create is tossed towards the server, the server responds with a new randomly created object. The object can be one of many different Classes, and Backbone responds to these differentiating objects and loads a relative view.
I can only seem to figure this logic out on bootstrap, as no view has been loaded yet, so I can based on what information I am randomly receiving from the server, bootstrap and navigate to that specific route.
However, I am stuck on trying to figure out how to do this when I save an object, and receive my return data.
Here's my code broken down.
The information is saved.
#model.save(#model.toJSON(),
I have a listenener waiting for this save :
constructor: (options) ->
super(options)
#model.bind 'change:verb', _.chooser, options
_.maestra_chooser is a mixin I have in a utility belt :
_.mixin
_chooser : (item) =>
console.log item
Something to note here. The variable item is unfortunately, the same #model that was just saved. No new data there.
What I'm hoping for item to be is the new variable data from the server, so that I can take that data, see what kind of data it is, and then route to the relevant view.
This is where I believe I'm also making an architecturally unsound idea. But for reasons I don't understand enough to explain.
Does anyone know where I can access the return data from the server and appropriately navigate my app to that respective route?
Additional Information
This is how I bootstrap it appropriately :
window.router = new Project.Routers.QuestionsRouter(
{
words: #{ #words.to_json.html_safe }
});
Backbone.history.start();
router.navigate("#{#words.kind_of?(Array) ? "bar" : "foo"}", {trigger: true, replace: true})
The change event is only ever going to give you the model and the value that changed...
You can pass a success callback to your save:
#model.save(#model.toJSON(), success: (model, resp) ->
# do whatever with resp
)
where resp will contain the raw response from the server and model will contain the server side state of your model.
You can also bind to your model's sync event as mentioned in the comments:
#model.bind 'sync', _.masetra_chooser, options
the sync callback is called with arguments: model, resp and options where options is the set of options passed to save.
https://github.com/documentcloud/backbone/blob/9a12b7640f07839134e979b66df658b70e6e4fe9/backbone.js#L383
Not really sure why you are expecting to get data back from a save that'll change your page though. Seems a bit odd.
What type of data are you expecting to receive after a save that wouldn't be in your model?