Automatically Passing Certian values in BackBone Model Ajax Functions - javascript

I am using Backbone js to create an app like service. Every application has a user_id and an application_id that has been set. Each time that backbone calls a fetch(), save(), or any other RESTful/ajax function, I want the user_id and the application_id to automatically be passed with the models data.
I know I am going to have to extend the backbone model, and then make sure all my models extend from this model, but what do I modify and how do I call the parent model?
Psuedo Example (not quite sure)
MyModel = Backbone.Model.extend({
save : function(data) {
data.application_id = 3;
data.user_id = 5;
parent.save(data);
}
});
Scores = MyModel.extend({
default : {
score : 0
}
});
//This should automatically grab the application id and user id
scores = new Scores();
scores.set('score', 5);
score.save()
How do I correctly accomplish this? And by accomplish I mean an single point in the code that will work for save(), fetch() and destroy() ?

How about modifying your Backbone sync? If you're sure you want to pass the user_id and application_id with EVERY model on save() fetch() and destroy() then you could do something like this I believe...
/* alias away the sync method */
Backbone._sync = Backbone.sync;
/* new Backbone.sync method */
Backbone.sync = function(method, model, options) {
// For example purpose, this will only run on POST, PUT, and DELETE requests
// If you want you can also set it for method == 'read' for your fetch()
if (method == 'create' || method == 'update' || method == 'delete') {
model.set('user_id', userID);
model.set('application_id', appID);
}
/* proxy the call to the old sync method */
return Backbone._sync(method, model, options);
};
I do something like this for my CSRF token check to prevent cross site registration forgeries. Except instead of manipulating the model, I make sure all my POST, PUT, and DELETE requests have a special X-CSRF header with my unique token.

If your values (appid and userid) are pre-determined, you can do this in the initialize method of your model
MyModel = Backbone.Model.extend({
this.set('application_id', appID);
this.set('user_id', userID);
});
These values are now part of your model and therefore part of every CRUD operation.

Related

How to re fetch backbone collection after collection create?

Can someone tell me how to re fetch a Backbone collection after calling collection's create function when I create a new model?
When I call fetch on my collection after creating new model, sometimes I'm getting that model and sometimes not.
My problem is when I create a new model in my collection, I'm not getting the id back of my model and then I can't update it immediately, I need to refresh the page and then I got the id of the created model.
I tried with listenTo but I can't use it because I need to send more collections to one function.
And that my view for my bootstrap modal, on save I'm creating my model it persists to database and I'm getting all attributes in my console when I create it except models id.
Backbone view:
app.types.EditView = Backbone.View.extend({
tagName: "div",
$container: $('#containerEdit'),
template: _.template($('#itemEdit-template').html()),
events:
{
"click .save": "save",
},
initialize: function(options)
{
this.options = options;
this.$container.html(this.render());
this.start();
this.end();
},
render: function()
{
this.$el.html(this.template());
return this.$el;
},
save: function()
{
console.log("save");
$('#openModal').modal('hide');
var dan = this.model.dan_u_tjednu_usera.datum;
var mjesec = this.model.dan_u_tjednu_usera.mjesecBrojevi;
var godina = this.model.dan_u_tjednu_usera.godina;
var start = $("#start").val();
var end = $("#end").val();
var user_id = this.model.user.id;
this.model.shifts.create({day: dan, month: mjesec, year: godina, time_from: start, time_to: end, user_id: user_id});
this.options.model.el.html($("<td href='#openModal' width='25%' align='center' class='list-group test' scope='row'>" + start + " - " + end + " " + "Admin" + "</td>"));
this.model.shifts.fetch({sync: true});
console.log("test", this.model.shifts);
}
Here you can see that in my response im not getting the id attribute, on create.
And here you can see when i click on my cell i log my collection and i have not the id attribute of the created model here. And im not getting the id attribute it too when i log this.model
This is because the request sent to the server when you call Collection.create is asynchronous, the Javascript code will continue to execute before the server receives and responds to the request.
If you want to have the Model updated with the ID coming back from the server, you can specify {wait: true} in the Collection.create call. This will mean that the Collection will not have the Model added straight away, but instead only when the server responds (successfully).
In this case you should not run the fetch immediately afterwards, as it will also need to wait for the create operation to complete. You should setup any following actions to trigger when the create operation has completed. Here is an example:
var model = collection.create({field: 'abc'}, {wait: true});
model.once('sync', function() { window.alert(model.id); });
Backbone's create
Convenience to create a new instance of a model within a collection.
Equivalent to instantiating a model with a hash of attributes, saving
the model to the server, and adding the model to the set after being
successfully created.
There's no need to fetch a collection after a create, the model id and any other field are automatically merged within its attributes hash.
Fetch after model creation
While mikeapr4 is not wrong, his example could be improved.
The { wait: true } is unnecessary if the only problem comes from the fetch, not from the model already being inside the collection.
Also, once should be avoided as it's the "old" way, and instead listenToOnce should be used. See Difference between ListenTo and on.
If you really want to fetch once a model is created, using events is overkill here, and instead, using the success callback is best:
save: function() {
// ..snip...
this.model.shifts.create({ /* ...snip... */ }, {
context: this,
success: this.onModelCreated
});
},
onModelCreated: function() {
// the model is now created and its attributes are up-to-date
this.model.shifts.fetch();
}
Other notes on your code
There are no sync option in Backbone. Only a "sync" event and a sync function.
Avoid using the global jQuery selector (like $('.class-name')) and instead, whenever the element is within the view's element, use this.$('.class-name').
Also, cache the jQuery element to avoid the costly search of the find method.
Like $("#start") could be cache and reused. Only reset the cached elements when re-rendering.
The Backbone .render function should return this by convention.
Then, your rendering call should look like:
this.$container.html(this.render().el); // el is enough

consuming RESTful api with backbone

I have a backbone application and a RESTful api. I used the sample created by Coenraets to understand the architecture of a backbone app, but I decided to setup my own structure and just use the data for testing.
I want to know the best way to return data from the RESTful api. I currently have my app folder structure setup with model, collection, view and service folders. I have a node server running with express that handles the backend and is working fine.
What I want to know is what is the best practice for accessing the restful data api? Should I do that in my service class or in my view class? How do I make this work dynamically using the returned data from my restful api: http://localhost:3000/employees
It seems like there are many ways to do this and for now I just want something that works, but eventually I do want to know what is the best way to do it. Ultimately I want to have a CRUD setup. But I'm not sure where that should be setup. Similar to what is detailed here: http://www.codeproject.com/Articles/797899/BackBone-Tutorial-Part-CRUD-Operations-on-Backbone
My files are as follows:
employeecolletion.js
var Backbone = require('backbone');
var Employee = require('../models/employeemodel.js');
module.exports = Backbone.Collection.extend({
model: Employee,
url:"http://localhost:3000/employees"
});
employeemodel.js
var Backbone = require('backbone');
var EmployeeCollection = require('../collections/employeecollection.js');
module.exports = Backbone.Model.extend({
urlRoot:"http://localhost:3000/employees"
// initialize:function () {
// this.reports = new EmployeeCollection();
// //this.reports.url = this.urlRoot + "/" + 1 + "/reports";
// }
});
employee.js (employee view that binds to my template)
var fs = require('fs');
var base = require('./base.js');
var EmployeeList = require('../collections/employeecollection.js');
var employeeService = require('../services/employeeService.js');
var template = fs.readFileSync('app/templates/employee.mu', { encoding: 'utf8' });
module.exports = base.extend({
el: '.view',
template:template,
collection: employeeService.collection,
initialize: function () {
this.viewModel = {
employee_list: this.collection.toJSON()
//employee_list: this.collection.fetch() --HERE I EXPERIMENTED WITH FETCHING THE DATA
};
this.render();
}
});
employeeservice.js (file in service folder that would ideally return the collection which I would just bind to my template in they employees view file)
var EmployeeCollection = require('../collections/employeecollection.js');
//if wanting to pass in data manually
var employee_list = [
{
id:1,
firstName:"James",
lastName:"King",
fullName:"James King",
managerId:0,
managerName:"",
title:"President and CEO",
department:"Corporate",
cellPhone:"617-000-0001",
officePhone:"781-000-0001",
email:"jking#fakemail.com",
city:"Boston, MA",
pic:"james_king.jpg",
twitterId:"#fakejking",
blog:"http://coenraets.org"
}
];
//HERE I WAS EXPERIMENTING WITH A DIFFERENT SYNTAX TO DO THE FILTERING BY ID
//IN MY SERVICE AND SIMPLY RETURNING THE FINAL DATA I WANT TO MY VIEW CLASS
// var employees = new EmployeeCollection({id: id});
// employees.fetch({
// success: function (data) {
// console.log(data);
// }
// });
module.exports = {
collection: new EmployeeCollection(employee_list)
};
Backbone is meant for RESTful services.
I'll try to explain the basics using some easy to understand terms.
So backbone is based on models and views.
The model is responsible to the data.
That means, that the model is the one who fetches the data from the server and stores it.
In an interactive application, the model should have a url or urlRoot properties which indicate what is the url of the specific resource this model refers to.
For example, if we had a Person resource, and assuming we are consuming a standard RESTfult service, I would expect something similiar to this:
var Person = Backbone.Model.extend({
url : 'http://localhost:3000/api/Person'
});
That actually lets us create new instances of this model and manipulate it.
This url will be used by the model for all CRUD operations related to it.
For example, if we now create a new instance:
var person = new Person();
We now have the following basic CRUD operations:
fetch: this method is executing an async AJAX GET request behind the scenes, and injects the data into the model.
Now, after we fetched the data, we can use it by simply calling get:
person.get('name'); * assuming there's a name property.
save this method is exectuing an async AJAX POST or PUT request behind the scene.
If the model's idAttribute is undefined, it will executed POST, otherwise PUT. The idAttribute is a model property which indicates what is the model's unique id.
A sample usage:
person.set({name : 'Mor'});
person.save();
The abvoe will execute a post request with the name: 'Mor' in the request body.
If for example I fetched the model, and already have an idAttribute assigned, calling the same save method will use the PUT request.
destroy this method will execute a DELETE request behind the scene.
Sample usage: person.destroy();.
Obviously I have just shown you the basic usages, there's a lot more options out there.
A collection is simply a list of models so there's not much to explain, you can read more here: http://backbonejs.org/#Collection
A view is all you see. It is the visual part of the application.
What Backbone lets us do, is to bind views to models and collections.
By that, we can create some dynamic content and visuals.
A basic view would like something like that:
var PersonView = Backbone.View.extend({
el: '.person',
initialize: function(){
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html("hello :"+this.model.get("name"));
}
});
As you can see, I used listenTo. It is an event listener that calls render each time the model changes.
When I refer to this.model I refer to a model I will pass to the view when I initiate it:
var view = new View({ model : person});
By that, and since I used listenTo, my view is now binded with the person model.
This is basically it.
Obviously, there's a lot more to learn and understand, but this pretty much covers the basics.
Please refer to http://backbonejs.org/ and read some more information.

Backbone sync issue firing POST instead of PUT

I have written the following code in my model:
urlroot: '/url/sms',
setAuthId: function(value) {
var _this = this ;
if (this.get("smsauth") != value) {
this.set("smsauth",value);
this.save();
//Ideally, I want to achieve this AJAX call request with backbone.
// $.ajax({
// url: "/url/sms",
// data: value,
// type: 'PUT',
// processData: false,
// success: function(result) {
// _this.set("authId", value);
// },
// error : function(){
// console.log('Error setting authid');
// }
// });
}
},
Ideally, we should be firing a "PUT" request everytime. But the backbone is firing a POST request because "ID" is not present.
I'm quite new to backbone, I was wondering if there is anyway to sync with server without having to pass an ID? How can I solve this problem?
I basically want to fire a PUT request NOT post request for the URL. (Since my backend only supports PUT request).
The only real way to force Backbone.Model.save() to do a PUT is the way #dbf explained, you have to set your idAttribute. To properly set idAttribute your model should have an attribute that is unique. (This is not a hard requirement, since model.isNew() just checks that your model has a property named id or whatever string you supply to your model idAttribute property. It doesn't check for uniqueness).
I sense that in your case, you may not have a unique attribute in your models, so setting idAttribute may be a challenge. For that reason, I suggest you don't specify an idAttribute in your model definition. Rather, we just handle it dynamically.Just refactor your code like this:
setAuthId: function(value) {
var _this = this ;
if (this.get("smsauth") != value) {
// any model attribute is fine, we just need to return a prop
this.prototype.idAttribute = "smsauth"
this.save("smsauth",value) // Save will do a set before the server request
// model.save returns a promise. Here we'll reset the idAttribute
.then(/* success handler */ function (response) {
_this.set("authId",value);
_this.prototype.idAttribute = 'id' // You can set this to "authId" if that
// uniquely identifies this model
},/* error handler */ function (response) {
_this.prototype.idAttribute = 'id' // reset idAttribute to default
});
}
}
It's not really clear to me what you are saving. The fact you are handling with POST suggest its a new entry. Quoting from the docs this behaviour is correct. PUT is update, POST is create (under CRUD operations).
If the model isNew, the save will be a "create" (HTTP POST), if the
model already exists on the server, the save will be an "update" (HTTP
PUT).
What you might try is what #Sami suggest by overwriting the save with an update request (do realise that all solutions here are incorrect/workarounds).
If you really need PUT and cannot alter your backend for some mysterious reason to accept POST, you could change the idAttribute within the model.
var smsModel = Backbone.Model.extend({
idAttribute: "smsauth" // for example
});
Do realise that your backend, very likely, has a design flaw where you are changing and creating workarounds to work with it, which you should consider to avoid.
I have used this snippet.
this.save({}, {
type: 'PUT'
});
I found all your answers really fascinating, I am going to try everyone of them.
Thanks for your suggestions, this is why I like SO. Something new to learn.
You can override save method.
Something like
var model = Backbone.Model.extend({
save: function(options){
return Backbone.sync("update", this, options);
}
});

How do I save just a subset of a Backbone Model's attributes to the server without triggering a PATCH instead of a POST

The API I am hitting explodes if I send any "extra" properties back to the server. Now, I'm sure I've broken some MVC rules or something by having properties on my client side Backbone model that don't exist server side, but, what I need to do is initiate a CREATE request but only pass through some of the attributes from my model I am initiating the CREATE request from.
I can easily do this in backbone:
Model.save({key: val}, {patch: true});
And then modify the Backbone sync methodMap defaults patch routes to POST instead of PATCH and that nets me exactly what I'm looking for except that later on I want to actually be able to PATCH (instead of POST) when appropriate. I just need it to POST as if it were PATCHing for create actions only (not for update actions!).
So, in short, I need to take something like this:
new Backbone.Model({'foo': 'bar', 'baz': 'beh'});
And tell it to sync itself to the server, but ONLY send 'foo' across and not send 'baz', but it has to send it as a POST (it can't be a PATCH).
Any ideas?
Below is a save override that can be placed in a base Backbone model or any individual model as appropriate. It may not cover every use case but it seems to fit this one.
JS Fiddle: http://jsfiddle.net/hEN88/1/
This override allows you to set a 'serverAttrs' array on any model to filter which properties are sent to the server.
save: function (attrs, options) {
attrs = attrs || this.toJSON();
options = options || {};
// If model defines serverAttrs, replace attrs with trimmed version
if (this.serverAttrs) attrs = _.pick(attrs, this.serverAttrs);
// Move attrs to options
options.attrs = attrs;
// Call super with attrs moved to options
Backbone.Model.prototype.save.call(this, attrs, options);
}
This question sounds like it may be a dupe of Exclude model properties when syncing (Backbone.js) and Backbone.js/express.js parameters for model.save()
The answer in both of those cases came down to overriding the save() function.
You can override toJSON method
// set viewOnlyAttrs for attributes no need to save
viewOnlyAttrs: ['url'],
toJSON: function(options) {
var obj = this;
// if is calling from model's save method
if(_.has(options || {}, 'emulateHTTP') && obj.viewOnlyAttrs)
return _.omit(obj.attributes, obj.viewOnlyAttrs);
return Backbone.Model.prototype.toJSON.call(this, options);
}

Backbone: Adding a model to a collection from a collection view?

I have some code where I want a NoteCollectionView to add a new Note to the NoteCollection. This is triggered by a function newNote in the NoteCollectionView:
newNote: function(data) {
var note = new Note(data);
this.collection.add(note);
},
I'm still very new to backbone, and I want to make sure this syncs with the server. The concerns I have are:
1) Will simply adding this note to the collection trigger a save() from the server, and update the model with the ID that the server gives it? Or,
2) If the server does not update my model and give me an actual ID, how do I save the model with note.save() and get back an ID from the server?
To address your first question, no, .add will not trigger any kind of call to the server; it will only add a model to a collection.
However, you do have a couple options. One would be to create the new note model, save it to the database, and then add it to the collection:
newNote: function(data) {
var note = new Note(data);
note.save();
this.collection.add(note);
}
The second option is to simply use Backbone's collection.create method. Give it a hash of attributes and it will
Create the model
Save it to the database
Add it to the collection
All in one fell swoop, like so:
newNote: function(data) {
return this.collection.create(data);
}
collection.create also returns the newly created model, illustrated by my return statement above.

Categories