What is backbone's convention/best practice for retrieving data from a RESTful web service, based on data entered in a form?
From what I understand I can register a click observer in the view, so when the form submit button is clicked the view will retrieve the data from the form. But I'm unclear on whether I execute the AJAX call from the view, model, or router.
Essentially, I'm unclear as to which of the following is convention:
Have the view get the parameters, execute the AJAX call and then pass the returned JSON to the model
Have the view get the parameters and pass them to the model and have it execute the AJAX call and retrieve the data from the web service
Have the view get the parameters and pass them to a router and have it execute the AJAX call and populate the appropriate model and view accordingly?
NOTE: I need to submit search criteria to a RESTful Web Service, which expects the data to be POSTed; correct me if I'm wrong, but it doesn't seem like I can use: fetch or save. Should I create a function within the model that uses $.ajax({...}) to post the params and receive the JSON data?
The backbone.js models are already setup to do RESTful requests. Method 2 is what you want to do.
The only thing you have to setup in the model is a URL:
MyModel = Backbone.Model.extend({
url: 'http://path/to/my/RESTful/service'
});
MyView = Backbone.View.extend({
events: {
'submit #myform': 'saveToModel'
},
initialize: function() {
// ...
},
saveToModel: function() {
// this triggers a RESTFul POST (or PUT) request to the URL specified in the model
this.model.save({
'foo': 'Foo!',
'bar': 'Bar!'
});
}
});
Bind the submit event to the form id or the div id, not the button id.
Related
I have an AngularJS model like this:
$scope.model = {name: "Joe", isMale: true};
In the view I bind the model.isMale to a checkbox.
Now I ask a backend for an update ($http.get()). In the onSuccess callback I simply assign the response value to $scope.model. If the backend does not send me the whole model, e.g. does not send the isMale flag, the $scope.model does not have the flag isMale any more. When I later on change the name for example in the UI and send these data to the backend, the flag isMale is not sent to the backend anymore, since the model does not contain it. Is there a way to check if the view elements' model-bindings do exist? Is there a better solution than just overwriting the model with the value I get from the backend?
Thanks
You can extend your model and overwrite your default model value with the one from api response. That way your default will be overridden only if that is present in api response:
angular.extend($scope.model, response)
for shallow copy, or
angular.merge($scope.model, response)
for deep(recursive) copy
This should work in your scenario
And do refer for the intricate details of difference between angular.copy(), 'angular.extend()' and angular.merge() here: http://davidcai.github.io/blog/posts/copy-vs-extend-vs-merge/
I have been struggling to find a consistent and good example of a put operation using AngularJS $resource. An example of when I want to update, but can't seem to is located here: AngularJS PUT on voting application to REST Service
At the core, I need to understand the best practice/normal way to conduct a put operation both for form submissions or in the voting application mentioned in my post above. Does anyone have a good example that demonstrates a put?
If you're creating a new entity in your data store you want to use POST/save. If you're updating the data associated with an already existing entity in your data store you want to use PUT/update. Patch is usually reserved for when you just want to update a subset of the entity data.
Look at the RFC
Several applications extending the Hypertext Transfer Protocol (HTTP)
require a feature to do partial resource modification. The existing
HTTP PUT method only allows a complete replacement of a document. This
proposal adds a new HTTP method, PATCH, to modify an existing HTTP
resource.
You would supply an id with both PUT and PATCH operations. You would not supply one with a POST operation.
When we load our angular forms it is done one of two ways usually. If the form is loaded when we are creating a new entity then we won't have an id. We will know this in the controller and will call resource.save. If we supply the controller loading the form with an id that's used to pull data from an endpoint to populate the form, we now have the id we can use to do a resource.update or resource.patch operations depending on how much of the entity we are updating.
Here's an example save function that handles both update and save operations. Here we check to see if an id was supplied via the route before we make our resource call.
angular.module('appModule').controller('ExampleCtrl',
['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.saveForm = function () {
//Do input validation before you make a resource call
if ($routeParams.id) {
//call resource update since we have an id
}
else {
//call resource save since we don't have an id
}
};
}]);
Here's the example from the angularjs documentation:
How to create a custom PUT request:
var app = angular.module('app', ['ngResource', 'ngRoute']);
// Some APIs expect a PUT request in the format URL/object/ID
// Here we are creating an 'update' method
app.factory('Notes', ['$resource', function($resource) {
return $resource('/notes/:id', null,
{
'update': { method:'PUT' }
});
}]);
// In our controller we get the ID from the URL using ngRoute and $routeParams
// We pass in $routeParams and our Notes factory along with $scope
app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
function($scope, $routeParams, Notes) {
// First get a note object from the factory
var note = Notes.get({ id:$routeParams.id });
$id = note.id;
// Now call update passing in the ID first then the object you are updating
Notes.update({ id:$id }, note);
// This will PUT /notes/ID with the note object in the request payload
}]);
I'm starting to implement backbone.js on a new app, however, the API I'm using it is not kind of restful, so I want to know if I can still use some similar methods, for sample:
Let's say I have a trivial scenario where I need among other options to delete users, however each option has its own controller, so I trigger them like this:
fetch : FetchController.php
Update : DeleteController.php?data={'id':'x'}
So my view would look like this:
var UserEditView = Backbone.View.extend({
events: {
'submit .delete-form': 'deleteUser'
},
//.... other methods
deleteUser: function(event) {
//user is a instance of my User Collection
this.user.destroy({
//here is where I point my question
});
}
});
Let's say all my controllers are called via Post Method, so I don't have the change to send a Delete Method to destroy... my question is, can I create a own destroy method? or at least configure it to call an custom controller? so I can do the same to create and edit records?
Any help would be really appreciated.
Thanks in advance.
You should definitely use $.ajax options override it with attributes url, method and data
model.fetch({
url: 'FetchController.php'
});
model.destroy({
url: 'DeleteController.php',
method: 'POST',
data: "id=x"
});
You may also override fetch and destroy method inside your model to do not use this every time in views.
Or even override Backbone.sync method to support your backend services. Here is a link to related question.
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.