Backbone with complex REST interface - backbone.js

The rest interface I'm currently working has something like this,
Returns a collection
POST /base/Holiday/getHolidaySetup/
FORM_DATA: client_id:1
Even worse..
POST /base/Holiday/setAddHoliday/
FORM_DATA: client_id:1
... other form data..
Clearly in a newer scheme, it'd look something like
/base/Holiday/client/
POST to create a new holiday.
/base/Holiday/client/
GET To retrieve holidays
But, that's just not what we're working with..
So how do I set Backbone up for such a scheme.

You can completely change how your models and collections handle their persistence by overriding their respective sync methods.
For example, to fetch a collection
var M = Backbone.Model.extend({
idAttribute: "client_id"
});
var C = Backbone.Collection.extend({
model: M,
url: "/base/Holiday/getHolidaySetup/",
sync: function(method, model, options) {
var params = {
type: 'POST',
dataType: 'json',
data: JSON.stringify({client_id: 1}),
url: _.result(this, 'url'),
processData: false
};
var xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
}
});
http://jsfiddle.net/7L9v5rkz/5/
or to save a model
var M = Backbone.Model.extend({
idAttribute: "client_id",
url: '/base/Holiday/setAddHoliday/',
sync: function(method, model, options) {
options = options || {};
var params = {
type: 'POST',
dataType: 'json',
processData: false
};
if ((method === 'create') || (method === 'update')) {
params.url = _.result(this, 'url');
params.data = JSON.stringify(this.toJSON(options));
} else {
// handle read and delete operations
}
var xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
}
});
http://jsfiddle.net/7L9v5rkz/2/

Related

Edit data before sending with ngResource

Hey I want to change the data before sending it with ngResource (build FormData object). I do everything as in the examples that I found, however I can't make them work. Here is my code:
My controller where I set the data and try to send them:
var vm = this;
vm.application = new Application();
vm.application.title = 'Test title';
Application.save({}, vm.application, function(){
});
My service:
function application(ApiBaseUrl, $resource) {
var actions = {
'save': {
metod: 'POST',
url: ApiBaseUrl + "/applications",
headers: { 'Content-Type': false },
transformRequest: function (data) {
console.log(data); //Returns 'undefined'
return data;
}
}
};
return $resource(ApiBaseUrl + "applications/:id", {}, actions);
}
In the function transformRequest data object is always marked as 'undefined'. Am I doing something wrong? Is there a better way to edit the data before sending it?
The problem was I had
metod: 'POST'
when I should have used:
method: 'POST'

Kendo-UI datastore leveraging Angular $resource

I am attempting to implement a abstraction over a RESTful back-end for my persistence layer and have ran into something I find a little confusing. I am using the angular framework and more specifically the ngResource module to get access to the $resource service. I can execute my queries and work against the back-end without issue. My problem comes when integrating back into my kendo-ui datasource, the datasource never recognizes when the query has returned. From my understanding the $resource will immediately return a empty collection (array) for possible assignment and then populate that array with the results of the query when it finishes. Kendo-ui's DataSource should watch this variable and upon update reflect this back to anyone leveraging the datasource. I have successfully implemented this using a slightly different model (passing a object literal that I update myself as required) and the DataSource has no problem recognizing the updates. Any insight would be helpful!
app.provider('remotePersistence', function () {
this.$get = function ($resource) {
var definitions = {
widgets: $resource('http://127.0.0.1:3000\:3000/widget.json',{},{
archive: { method: 'POST', params: { archive: true }},
active: { method: 'POST', params: { active: true }}
})
};
var datastore = {}
var namespaces = ['widgets'];
namespaces.forEach(function (namespace) {
datastore[namespace] = {
endpoint: definitions[namespace],
cache: definitions[namespace].query()
};
});
return datastore;
};
});
app.controller(
"WidgetsSearchController",
function ($scope, remotePersistence){
$scope.widgets = undefined;
$scope.visibleWidgets = new kendo.data.DataSource({
// data: remotePersistence.widgets.cache,
transport: {
read: function (options) {
options.success(remotePersistence.widgets.cache);
}
}
});
});
//This works but is not desirable style
//$scope.widgets = remotePersistence.widgets.query(function(){ $scope.visibleWidgets.data($scope.widgets) });
The data source needs to be notified that data has been received. Perhaps the ngResource module will trigger some callback or event when it finishes loading data. Then you can use the data() method of the Kendo DataSource to populate it with data items. All Kendo UI widgets bound to that data source will receive a notification when you use the data method.
For anyone following behind here is my current implementation that works nicely. I am still a bit unhappy with the manipulation I must do for the sort to be passed but it works along with paging.
app.controller(
"WidgetSearchController",
function ($scope, remotePersistence){
$scope.visibleWidgets = new kendo.data.DataSource({
widget: {
read: function (options) {
if(options.data.sort){
options.data.order = _.map(options.data.sort, function (sortItem) {
return sortItem.field + " " + sortItem.dir
}).join(", ");
}
remotePersistence.widgets.endpoint.query(options.data, function(response){
console.log(response);
options.success(response);
});
}
},
schema: {
data: "widgets",
total: "total"
},
pageSize: 20,
serverSorting: true,
serverPaging: true
// serverFiltering: true
});
});
app.provider(
'remotePersistence',
function () {
this.$get = function ($resource) {
var definitions = {
widgets: $resource('http://127.0.0.1:3000\:3000/widgets/:id',{ id: '#id' },{
archive: { method: 'PUT', params: { archive: true }},
update: { method: 'PUT' },
active: { method: 'PUT', params: { active: true }},
query: { method: 'GET', isArray: false},
})
};
var datastore = {}
var namespaces = ['widgets'];
namespaces.forEach(function (namespace) {
datastore[namespace] = {
endpoint: definitions[namespace],
cache: definitions[namespace].query()
};
});
return datastore;
};
});

Backbone base64 encoding

I don't get how to make Backbone.sync suitable for my case.
That's why I still use this usual Ajax-Request for my project:
$.ajax({
url: request,
status: status,
type: 'GET',
dataType: 'json',
beforeSend: function (req) { req.setRequestHeader("Authorization", auth) },
success: function (data, status) {
//update the model
},
error: function(XMLHttpRequest, textStatus, errorThrown){
//do stuff
}
});
I need to add a base64 encoded authorization to the request header and update a model. The data I get from the server contain more information than my model needs. That's why I can't refer the model directly to an url like this:
var MyApp.myModel = Backbone.Model.extend({
url: '/someResourceUrl'
});
MyApp.myModel.fetch();
I need to do sth. like:
var MyApp.myModel = Backbone.Model.extend({
url: 'anyurl',
sync: myOwnSpecificSync,
});
//Define the sync function:
myOwnSpecificSync = function(method, model, options) {
//add header
//return only specific parameters of the success data
};
//let the model fetch the data from the server
MyApp.myModel.fetch();
But I have no idea how to implement the functions .. or if it's correct at all.
var AuthSync = function(method, model, options) {
options.beforeSend = function () {
console.log('add auth header here');
};
return Backbone.sync(method, model, options);
};
var Model = Backbone.Model.extend({
url : 'http://fiddle.jshell.net/echo/json/',
sync : AuthSync
});
new Model().fetch();

Backbone.js 0.9.10 Collection fetch JSONP

var COLLECTION = Backbone.Collection.extend({
url: "example.com",
sync: function(method, model, options){
params = _.extend({
type: "get",
url: this.url,
async: false,
dataType: "jsonp",
jsonpCallback : "jsonp_callback",
},options);
return $.ajax(params);
},
parse:function(response){
console.log(response);
return response;
}
});
var a = new COLLECTION();
var b = new COLLECTION();
$.when(a.fetch(),b.fetch()).done(function(){ console.log('done'); });
I have two questions:
In sync function, $.ajax(params) response TypeError: a["reset"] is not a function #0.9.10
Use multiple collection.fetch sometimes happen a.fetch() or b.fetch() response parse error
Thanks!!

How to fetch a Backbone.js model by something other than the ID?

Backbone.js's default, RESTful approach to fetching a model by the ID is easy and straight-forward. However, I can't seem to find any examples of fetching a model by a different attribute. How can I fetch a Backbone.js model by a different attribute?
var Widget = Backbone.Model.extend({
urlRoot: '/widgets',
fetchByName: function(){ ... }
});
var foowidget = new Widget({name: 'Foo'});
foowidget.fetchByName();
You can try doing something like this on your base model definition or on demand when calling fetch.
model.fetch({ data: $.param({ someParam: 12345}) });
In your case, along the lines of.
var Widget = Backbone.Model.extend({
initialize: function(options) {
this.name = options.name;
},
urlRoot: '/widgets',
fetchByName: function(){
this.fetch({ data: $.param({ name: this.name }) })
}
});
var foowidget = new Widget({name: 'Foo'});
foowidget.fetchByName();
One approach is to override Backbone.sync() method, either for all classes or for just your class. However, presumably your goal is to override fetch for just a single model. One way to do that is to directly call jQuery.ajax(...), and on success, take the response and set that, e.g.
fetchByName: function() {
var self = this;
$.ajax({
url: self.urlRoot+ "?name="+this.get('name'),
type: 'GET',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
self.set(data);
}
});
}
If the model is part of a collection you can use where() to pull out the models matching some criteria.
See http://backbonejs.org/#Collection-where
I really like the approach suggested by 'user645715'. I have adjusted the code to be more versatile. If you add this to a Backbone Model it will allow you to search the server by one or more attributes, and should work as a direct drop-in replacement for fetch.
fetchByAttributes: function(attributes, callbacks) {
var queryString = [];
for(var a in attributes){
queryString.push( encodeURIComponent(a)+'='+encodeURIComponent(attributes[a]) );
}
queryString = '?'+queryString.join('&');
var self = this;
$.ajax({
url: this.urlRoot+queryString,
type: 'GET',
dataType: "json",
success: function(data) {
self.set(data);
callbacks.success();
},
error: function(data){
callbacks.error();
}
});
}
It can be used like this:
var page = new Page();
page.fetchByAttributes({slug:slug}, {
success: function(){
console.log('fetched something');
},
error: function(){
console.log('nothing found');
}
});
this is simple model.fetch is same as $.ajax in some way
model = Backbone.Model.extend({
urlRoot: "/root/"
});
var Model = new model();
Model.fetch({
beforeSend: function () {
console.log("before");
},
data: {
param1: "param1",
param2: "param2"
},
success: function () {
console.log("success");
},
error: function () {
console.log("failure");
}
});

Resources