Adding a PATCH method to Backbone collections - backbone.js

I have a web application that is using a Django/Tastypie backend with a Backbone/Marionette frontend. I'd like to use Tastypie's bulk operations to create multiple objects via my API using a PATCH request to a list endpoint.
My understanding is that Backbone doesn't support this. What is the best way to add this to Backbone? I assume I'll need to add a save method to Backbone's collection object and extend the Backbone sync method.

From http://backbonejs.org/
If instead, you'd only like the changed attributes to be sent to the server, call
model.save(attrs, {patch: true}). You'll get an HTTP PATCH request to the server
with just the passed-in attributes.
Fiddle Sending patch request on backbone collection sync:
$(function() {
Backbone.$ = $;
var User = Backbone.Model.extend({
urlRoot: "/testUrl",
isNew : function () { return false; },
defaults: {
name: 'John Doe',
age: 25
}
});
var user1 = new User();
var user2 = new User();
var user3 = new User();
var user4 = new User();
var UserCollection = Backbone.Collection.extend({
model: User,
url: "/testUrl"
});
var userCollection = new UserCollection([ user1, user2, user3]);
// update
user1.set('name','Jane Doe');
user4.set('name','Another User');
// delete
userCollection.remove(user2);
// add
userCollection.add(user4);
userCollection.sync('patch', userCollection , { error: function () {
console.log(userCollection); } });
});

Related

Backbone PUT is sending data www-form-urlencoded instead of application/json

I'm pretty new to backbone and how it works and inherited a bunch of code but I can't solve this at all:
I have a user model:
var User = Backbone.Model.extend({
idAttribute: 'username',
defaults: {
username: "",
email: "",
roles : [],
password: ""
}
});
var Users = Backbone.Collection.extend({
model: User,
initialize: function(args, options) {
if (options && options.dialog) {
this.dialog = options.dialog;
}
},
parse: function(response) {
if (this.dialog) {
this.dialog.populate(response);
}
return response;
},
url: function() {
var segment = AdminUrl + "/users";
return segment;
}
});
Then elsewhere in my view I'm doing:
user.save({username: $newtarget.val()},null);
or
user.save();
The PUT is fired to the correct url but every time its triggered it sends the data
Content-Type application/x-www-form-urlencoded; charset=UTF-8
but my Jersey endpoint accepts application/json
Everywhere I read people are struggling to put urlencoded data but my problem is the otherway around!
Parameters are being send as url params:
username=admin&email=&password=admin&roles%5B%5D=ROLE_USER&roles%5B%5D=ROLE_ADMIN&id=1
===EDIT===
If I force the content type and data:
user.save({}, {data: JSON.stringify(user.attributes),contentType: "application/json"});
The put works fine, which is bizarre.
Backbone.emulateJSON = false;
is true for some reason
From the docs
Turn on emulateJSON to support legacy servers that can’t deal with
direct application/json requests … will encode the body as
application/x-www-form-urlencoded instead and will send the model in a
form param named model.
http://backbonejs.org/docs/backbone.html

Backbone.js: Dynamic model/collection url based on user input

I'm total new to Backbone so please pointing me the right way to do it. Here is what I have:
A Backbone login View with 3 inputs: serverIP, username, password. I am doing all the validation and send jquery ajax() request to my backend host based on the serverIP that the user has to enter earlier.
My backend is js PHP using Slim restful framework, check user, password like usual basic stuff.
On the callback of successful ajax() call, I want to set the urlRoot for latter use of all models and collections as I'm using Slim for all database interactions and this PHP file located on the server.
I tried to set it on the global app variable like this in app.js:
var app = {
api_url: '',
views: {},
models: {},
routers: {},
utils: {},
adapters: {}
};
In the callback of login view I set:
app.api_url = "http://"+serverIP;
And try to use app.api_url in backbone model url but it's apparently undefined.
May be this is not the correct approach I'm trying and I messed up with the variable scope? Then how can I set model.url from the view? Please, any suggestions are much appreciated.
Thanks,
Hungnd
EDIT: Ok, I will try to elaborate my problem again:
Here is the login function in my LoginView.js, basically it take user inputs and send to my model to interact with the server, if success navigate to home view:
var user = new app.models.Login();
var userDetails = {
serverIP: $('#serverIP').val(),
username: $('#username').val(),
password: $('#password').val()
};
user.save(userDetails, {
success: function(data) {
/* update the view now */
if(data.error) { // If there is an error, show the error messages
}
else { // If not, send them back to the home page
app.router = new app.routers.AppRouter();
app.router.navigate('home',true);
}
},
error: function() {
/* handle the error code here */
}
Here is my LoginModel.js, get the serverIP from user input on login form and send to the server to process
app.models.Login = Backbone.Model.extend({
urlRoot: function(){
var serverIP = this.get('serverIP');
return "http://"+serverIP+"/api/login";
},
defaults: {
'serverIP': '',
'username': '',
'password': '',
}
});
Now, after successful login, navigate to HomeView.js, on initialize it calls to EmployeeCollection, so far so good
initialize: function () {
//Search result
this.searchResults = new app.models.EmployeeCollection();
this.searchResults.fetch({data: {name: ''}});
this.searchresultsView = new app.views.EmployeeListView({model: this.searchResults});
}
Here is my EmployeeModel.js where I have the problem, I dont know how to access the serverIP variable.
app.models.Employee = Backbone.Model.extend({
urlRoot:"api/employees",
//urlRoot: app.api_url+"/api/employees",
initialize:function () {
this.reports = new app.models.EmployeeCollection();
this.reports.url = app.api_url+'/api/employees/' + this.id + '/reports';
}
});
app.models.EmployeeCollection = Backbone.Collection.extend({
model: app.models.Employee,
//url: "api/employees",
url: function() {
//How to get serverIP?
},
});
All models in backbone already have an url property which will be used to fetch data. In your case you could define it as a function to generate url dynamically.
Here is an example :
//we are inside the definition of the loginModel
data: {
serverIP : null,
username : null,
password : null
},
url: function() {
var url = "rootUrl",
data = this.get("data");
return function() {
return url + '?' + $.param(data);
};
}
url is then defined as a closure, and object being references in javascript, the url generated will use the current values in the data object.

Setting Default Options for Backbone Collections

I have a Backbone Collection like so:
var ThreadCollection = Backbone.Collection.extend({
url: '/api/rest/thread/getList'
});
var myCollection = new ThreadCollection();
And then I'm fetching it from the server using the data object to append the query parameters (so in this case it comes out '/api/rest/thread/getList?userId=487343')
myCollection.fetch({
data: {
userId: 487343
}
})
There are other parameters that I may want to use instead of userId (groupId, orgId, etc) but I'd ideally define the data parameters upon initialization and from then on be able to run fetch() without specifying. Something like this:
var myCollection = new ThreadCollection({
data: {
userId: 487343
}
});
myCollection.fetch()
but it doesn't work. Does anyone know if there's a way to do this? Thanks!
One way is to define a custom fetch method on your collection which calls the super fetch method with some overridable defaults:
var ThreadCollection = Backbone.Collection.extend({
url: '/api/rest/thread/getList',
fetch: function(options) {
return Backbone.Collection.prototype.fetch.call(this, _.extend({
data: {
userId: 48743
}
}, options));
}
});
var myCollection = new ThreadCollection();
myCollection.fetch();

How to specify url and header in backbone to use crud method on my model?

i need to make request on server that needs of particulary api key and i need to use the crud method tu update my model and as soon as...
For example i have this code in ajax to get element from server:
function getapi() {
$.ajax({
url: 'https://api.parse.com/1/classes/autolavaggi/QSfl*****',
type: 'GET',
dataType: 'json',
success: function(obj) {
alert("nome autolavaggio "+obj.nome);
},
error: function() {
alert('Errore');
},
beforeSend: setHeader
});
}
//GET GET GET GET GET GET GET GET Header Header Header Header
function setHeader(xhr) {
xhr.setRequestHeader('X-Parse-Application-Id', 'aqLJlmE2rRXBOy***************');
xhr.setRequestHeader('X-Parse-REST-API-Key', 'gvT2Isd5vAvjgq*****************');
}
How can i do to assign this particular ajax call to crud method save,fetch or another??
Each of the crud methods accept an options hash that will get forwarded to the ajax call. In the case of a collection fetch:
var Model = Backbone.Model.extend({});
var Collection = Backbone.Collection.extend({
model: Model,
url: 'https://api.parse.com/1/classes/autolavaggi/QSfl*****'
});
var setHeader = function (xhr) {
xhr.setRequestHeader('X-Parse-Application-Id', 'aqLJlmE2rRXBOy***************');
xhr.setRequestHeader('X-Parse-REST-API-Key', 'gvT2Isd5vAvjgq*****************');
}
var collection = new Collection();
collection.fetch({ beforeSend: setHeader });
Alternatively, override sync:
var sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
options.beforeSend = function (xhr) {
xhr.setRequestHeader('X-Parse-Application-Id', 'aqLJlmE2rRXBOy***************');
xhr.setRequestHeader('X-Parse-REST-API-Key', 'gvT2Isd5vAvjgq*****************');
};
// Update other options here.
sync(method, model, options);
};

How to structure backbone.js so it returns template + data and ( login-based reroute in mind )

I am building a small app that uses backbone.js on the client side, node.js/socket.io on the server side, and the connection goes trough websockets only.
Now how would I make my setup if I want to get the template and the data at once.
I do a fetch() in the router, which gets the data and the template. With this I construct my view -> collection -> model.
I do a fetch() in the view itself. (Maybe in the initialize)
===
I want to extend this problem with the following.
Let's say a user browses to http://mysite.com/products and the user is not logged in yet, he is not allowed to view the page. He has to be rerouted to /login.
The problem is I can only verify this on the server, so the server has to send back the correct data whether the user is logged in or not, and backbone.js has to deal with this.
Summarized:
I make a fetch to the server which will send back data + template html.
Backbone.js has to render the template with the data,
or reroute (Backbone.history.navigate('login', {trigger: true})) when the server sends back a flag.
You could use parse method in your Backbone collection for example :
Collections.Products = Backbone.Collection.extend({
url : '/products',
parse : function (response) {
//
// you should return JSON from your server and the object must be smth like
// { template : "<p>template for products</p>", data : productsInJSON }
//
if ( response.template && response.data ) {
this.trigger('template', response.template);
return response.data;
} else {
return response;
}
}
});
Views.Page = Backbone.View.extend({
initialize : function () {
_.bind(this, 'render');
var self = this;
this.collection = new Collections.Products();
this.collection.on('template', function(template) {
self.render(template);
});
},
render: function(template) {
$("div#page").html(template);
}
});
$(function() {
window.app = {};
window.app.view = new Views.Page();
// here you are sending {template:true} to '/products' in your server
window.app.view.collection.fetch( { data : { template : true } } );
});
If you are using socket.io, you should create a new Backbone.sync method for your requests.
It is a little bit out of the Backbone philosophy and the integrated GET, PUT, POST, DELETE methods, so there will be a lot of coding.
You could send a template for unlogged in users with no data for the collection.

Resources