Backbone.js Model different url for create and update? - rest

lets say I have a Backbone Model and I create an instance of a model like this:
var User = Backbone.Model.extend({ ... });
var John = new User({ name : 'John', age : 33 });
I wonder if it is possible when I use John.save() to target /user/create when I use John.save() on second time (update/PUT) to target /user/update when I use John.fetch() to target /user/get and when I use John.remove() to target /user/remove
I know that I could define John.url each time before I trigger any method but I'm wondering if it could be happen automatically some how without overriding any Backbone method.
I know that I could use one url like /user/handle and handle the request based on request method (GET/POST/PUT/DELETE) but I'm just wondering if there is a way to have different url per action in Backbone.
Thanks!

Methods .fetch(), .save() and .destroy() on Backbone.Model are checking if the model has .sync() defined and if yes it will get called otherwise Backbone.sync() will get called (see the last lines of the linked source code).
So one of the solutions is to implement .sync() method.
Example:
var User = Backbone.Model.extend({
// ...
methodToURL: {
'read': '/user/get',
'create': '/user/create',
'update': '/user/update',
'delete': '/user/remove'
},
sync: function(method, model, options) {
options = options || {};
options.url = model.methodToURL[method.toLowerCase()];
return Backbone.sync.apply(this, arguments);
}
}

To abstract dzejkej's solution one level further, you might wrap the Backbone.sync function to query the model for method-specific URLs.
function setDefaultUrlOptionByMethod(syncFunc)
return function sync (method, model, options) {
options = options || {};
if (!options.url)
options.url = _.result(model, method + 'Url'); // Let Backbone.sync handle model.url fallback value
return syncFunc.call(this, method, model, options);
}
}
Then you could define the model with:
var User = Backbone.Model.extend({
sync: setDefaultUrlOptionByMethod(Backbone.sync),
readUrl: '/user/get',
createUrl: '/user/create',
updateUrl: '/user/update',
deleteUrl: '/user/delete'
});

Are you dealing with a REST implementation that isn't to spec or needs some kind of workaround?
Instead, consider using the emulateHTTP option found here:
http://documentcloud.github.com/backbone/#Sync
Otherwise, you'll probably just need to override the default Backbone.sync method and you'll be good to go if you want to get real crazy with that... but I don't suggest that. It'd be best to just use a true RESTful interface.

No you can't do this by default with backbone. What you could to is to add to the model that will change the model url on every event the model trigger. But then you have always the problem that bckbone will use POST add the first time the model was saved and PUT for every call afterward. So you need to override the save() method or Backbone.sync as well.
After all it seems not a good idea to do this cause it break the REST pattern Backbone is build on.

I got inspired by this solution, where you just create your own ajax call for the methods that are not for fetching the model. Here is a trimmed down version of it:
var Backbone = require("backbone");
var $ = require("jquery");
var _ = require("underscore");
function _request(url, method, data, callback) {
$.ajax({
url: url,
contentType: "application/json",
dataType: "json",
type: method,
data: JSON.stringify( data ),
success: function (response) {
if ( !response.error ) {
if ( callback && _.isFunction(callback.success) ) {
callback.success(response);
}
} else {
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
},
error: function(mod, response){
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
});
}
var User = Backbone.Model.extend({
initialize: function() {
_.bindAll(this, "login", "logout", "signup");
},
login: function (data, callback) {
_request("api/auth/login", "POST", data, callback);
},
logout: function (callback) {
if (this.isLoggedIn()) {
_request("api/auth/logout", "GET", null, callback);
}
},
signup: function (data, callback) {
_request(url, "POST", data, callback);
},
url: "api/auth/user"
});
module.exports = User;
And then you can use it like this:
var user = new User();
// user signup
user.signup(data, {
success: function (response) {
// signup success
}
});
// user login
user.login(data, {
success: function (response) {
// login success
}
});
// user logout
user.login({
success: function (response) {
// logout success
}
});
// fetch user details
user.fetch({
success: function () {
// logged in, go to home
window.location.hash = "";
},
error: function () {
// logged out, go to signin
window.location.hash = "signin";
}
});

Related

Unable to loadDatabase with lokiJs

I have trying to create a database with a collection added to this database and the changes saved to a IndexedDB.
Below is my code
Two controllers SaveController and LoadController.
myApp.controller('SaveController', ['$scope', 'Loki', function ($scope, Loki) {
// SAVE : will save App/Key/Val as 'finance'/'test'/{serializedDb}
// if appContect ('finance' in this example) is omitted, 'loki' will be used
var idbAdapter = new LokiIndexedAdapter('finance');
var db = new loki('ProjectDb', { adapter: idbAdapter });
var coll = db.addCollection('SampleCollection');
coll.insert({ SampleId: 'Sample text.....' });
db.saveDatabase(); // could pass callback if needed for async complete
}]);
and then in my LoadController i use the
myApp.controller('LoadController', ['$scope', 'Loki', function ($scope, Loki) {
var idbAdapter = new LokiIndexedAdapter('finance');
var db = new loki('ProjectDb', { adapter: idbAdapter, autoload: true });
db.loadDatabase({}, function (result) {
console.log(result);
});
alert(db.getCollection("SampleCollection"));
}]);
I get a null when i alert "alert(db.getCollection("SampleCollection"));" . It never enters the call back of the "loadDatabase" method.
Is there something that i am missing ?
IndexedDB in Browser
Here the page html
Edit for default localstorage implementation
I use the default implementation for loki js and i try to load the offline db is shows result as null every time even though the db exist
var offlineDb = new loki('DbOfflineNew');
offlineDb.loadDatabase({},function (result) {
console.log(result);
if (result == null) {
alert('loading for first time..');
}
else {
alert('existing load..');
}
});
Every time the alert "loading for first time.. " is fired.. Any thing i am missing here..?
Basically all your logic needs to be in the loadDatabase callback. If you try to console.log the collection before it's loaded it will be null. Many people fall in this trap.
In other words:
myApp.controller('LoadController', ['$scope', 'Loki', function ($scope, Loki) {
var idbAdapter = new LokiIndexedAdapter('finance');
var db = new loki('ProjectDb', { adapter: idbAdapter, autoload: true });
db.loadDatabase({}, function (result) {
console.log(result);
// put your log call here.
alert(db.getCollection("SampleCollection"));
});
}]);
Hope this helps.

DotNetNuke Service API Authorization throwing 401 Unauthorized code

I am having a bit of difficulty figuring out why I am getting 401 Unauthorized status from service framework. At the moment I have it setup to allow everyone to do as they please but that because when I try to enable authorisation I get 401 error code.
//[SupportedModules("Boards")]
//[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[AllowAnonymous]
public class BoardsServiceController : DnnApiController
{ ... }
The strange thing is I have another module which is more than happy to work away with DnnModuleAuthorize
[SupportedModules("Assignments")]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
public class AsgnsServiceController : DnnApiController
{ ... }
In both cases I have checked to make sure the user has permissions to view the page on which the module lives.
I have cross referenced both projects and everything seems to be spot on. Yet one is working away just fine and the other one returns 401.
Any suggestions?
Update
For Assignments module I am mostly using jQuery style of ajax request just because I haven't got around to revising the module. So a typical GET request would look something like this:
$.ajax({
type: "GET",
url: sf.getServiceRoot( "Assignments" ) + "AsgnsService/GetAssignments",
data: data,
beforeSend: sf.setModuleHeaders
}).done( function ( items ) {
//removed for brevity
}).fail( function ( xhr, result, status ) {
//removed for brevity
});
As for Boards module the code structure is slightly different due knockout implementation. There is a dedicated ServiceCaller but it all boils down to the same ajax call to the server except that instead of having full blown ajax call defined as above it looks much neater.
var that = this;
that.serviceCaller = new dnn.boards.ServiceCaller($, this.moduleId, 'BoardsService');
var success = function (model) {
if (typeof model !== "undefined" && model != null) {
viewModel = new boardViewModel(model.colLists);
ko.bindingHandlers.sortable.beforeMove = viewModel.verifyAssignments;
ko.bindingHandlers.sortable.afterMove = viewModel.updateLastAction;
// normally, we apply moduleScope as a second parameter
ko.applyBindings(viewModel, settings.moduleScope);
}
//console.log('success', model);
};
var failure = function (response, status) {
console.log('request failure: ' + status);
};
var params = {
BoardId: this.boardId
};
that.serviceCaller.get('GetBoardLists', params, success, failure);
And the ServiceCaller ajax function itself looks like this:
function (httpMethod, method, params, success, failure, synchronous) {
var options = {
url: that.getRoot() + method,
beforeSend: that.services.setModuleHeaders,
type: httpMethod,
async: synchronous == false,
success: function (d) {
if (typeof (success) != 'undefined') {
success(d || {});
}
},
error: function (xhr, textStatus, errorThrown) {
if (typeof (failure) != 'undefined') {
var message = undefined;
if (xhr.getResponseHeader('Content-Type').indexOf('application/json') == 0) {
try {
message = $.parseJSON(xhr.responseText).Message;
} catch (e) {
}
}
failure(xhr, message || errorThrown);
}
}
};
if (httpMethod == 'GET') {
options.data = params;
} else {
options.contentType = 'application/json; charset=utf-8';
options.data = ko.toJSON(params);
options.dataType = 'json';
}
$.ajax(options);
};
This would be the two GET requests from two different modules where one is happy and the other throws a status 401 when I enable the same annotations.
Does this provide any clues?
Update
Now in saying all of the above if one takes a look at the original Boards module code base one will notice [DnnAuthorize] annotation attached to every function.
During module revision I removed all instances of [DnnAuthorize] annotation and replaced it with two of my own on the service class itself.
When I add [DnnAuthorize] as annotation on service class itself things work as expected. So why [SupportedModules("Boards")] and [DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)] combination doesn't !?
I am not sure but working with the WebAPI you have to register the Service Framework anti forgery stuff
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
This is part of asking the API to work with a specific module.

In sails.js: fetch model's data from remote server

my sails.js app is embedded in php project where php generate some date for sails model. Can't find the way to fetch this date in beforeCreate callback or some custom method. I don't want use db to sync 2 models (model from sails & model from php). And i need to send some date to remote php app.
sails.js v.0.9.x
here is my controller:
module.exports = {
index: function (req, res) {},
create: function (req, res) {
if ( !req.param('login') )
throw new Error('Login is undefined');
if ( !req.param('message') )
throw new Error('Initial message is undefined');
var user, thread;
User.create({
id: 1,
name: req.param('login'),
ip: req.ip
}).done( function (err, model) {
user = model;
if (err) {
return res.redirect('/500', err);
}
user.fetch(); // my custom method
});
return res.view({ thread: thread });
}
};
and model:
module.exports = {
attributes: {
id: 'integer',
name: 'string',
ip: 'string',
fetch: function (url) {
var app = sails.express.app;
// suggest this but unlucky :)
app.get('/path/to/other/loc', function (req, res, next) {
console.log(req, res)
return next()
});
}
}
};
UPD My solution
Model:
beforeCreate: function (values, next) {
var options = 'http://localhost:1337/test',
get = require('http').get;
get(options, function (res) {
res.on('data', function (data) {
_.extend( values, JSON.parse( data.toString() ) );
next();
});
}).on('error', function () {
throw new Error('Unable to fetch remote data');
next();
});
}
Yikes--app.get is nowhere near what you need. That's binding a route handler inside of your app and has nothing to do with requesting remote data. Don't do this.
The easiest way to fetch remote data in Node is using the Request library. First install it in your project directory using npm install request. Then at the top of your model file:
var request = require('request');
and in your fetch method:
fetch: function(url, callback) {
request.get(url, function(error, response, body) {
return callback(error, body);
});
}
Note the added callback parameter for fetch; this is needed because the request is an asynchronous operation. That is, the call to fetch() will return immediately, but the request will take some time, and when it's done it will send the result back via the callback function. Seeing as fetch is at this point just a wrapper around request.get, I'm not sure why it's necessary to have it as a model method at all, but if the URL was based on something within the model instance then it would make sense.

Backbone Send Post data encoded as query string

I'm creating a backbone app that's connecting to a RESTful backend. When I call save() on a model, it sends the post data as stringified JSON:
{"firstName":"first","lastName":"last","Email":"email#gmail.com"}
but my server expects it to be formatted like a querystring:
firstName=first&lastName=last&Email=email#gmail.com
is there a way to have backbone send it differently?
Backbone doesn't provide anything like this out of the box.
But is easy to override and customize it to your needs.
Have a look to the source code:
http://documentcloud.github.com/backbone/docs/backbone.html
and check out that calling save, it will trigger a sync call in the background.
So what you need is to override Backbone.sync function with your own.
I would modify the part of:
if (!options.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
with
if (!options.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = $.param(model); // <-- CHANGED
}
Notice I'm using jQuery param
If you want to use a custom function, check this question:
Query-string encoding of a Javascript Object
[Update.]
No need to modify directly. Better override it with your own function 'Backbone.sync'
Check the "TODO" example of the Backbone repository. It has a localStorage.js file that overrides Backbone.sync function https://github.com/documentcloud/backbone/tree/master/examples
I ran into this problem at work and the Backbone.emulateJSON didn't work for me either. With some help I was able to come up with this workaround. We overrode the Backbone.ajax function and changed the contentType to "application/x-www-form-urlencoded" and used $.param to properly serialize the data.
Backbone.ajax = function() {
if(arguments[0].data && arguments[0].contentType == "application/json"){
arguments[0].data = $.param(JSON.parse(arguments[0].data));
arguments[0].contentType = "application/x-www-form-urlencoded";
}
return Backbone.$.ajax.apply(Backbone.$, arguments);
}
maybe this can help you,try:
http://backbonejs.org/#Sync-emulateJSON
I have done this by overriding model's sync function:
var MyModel = Backbone.Model.extend({
"sync": function(method, model, options) {
if (method == "update" || method == "create") {
options = options ? _.clone(options) : {};
options['data'] = $.param(this['attributes']);
}
var arguments = [method, model, options];
return Backbone.sync.apply(this, arguments);
}
});
I find solutions, see :
I use
Backbone.emulateJSON = true;
I write the "update" case:
options.url = "/user/"+Math.random(1, 1000);
options.type = "POST";
//.1/2 WORK
//options.data = (model instanceof Backbone.Model)?model.toJSON():{};
options.data = model.toJSON();
break;
Backbone.sync uses the jQuery.ajax function, so we can modify the jqXHR or data that is sended to the server (via beforeSend).
var oldSync = Backbone.Model.prototype.sync;
var SomeModel = Backbone.Model.extend({
url: 'test.json',
defaults: {
id: 1,
foo: 'test'
},
sync: function (method, model, options) {
// options are passed to the jQuery.ajax
_.extend(options, {
emulateHTTP: true,
emulateJSON: false,
beforeSend: function(xhr, settings) {
// settings.data is a body of our request.
if (_.isString(settings.data)) {
// settings.data is a JSON-string like '{"id":1, "foo":"test"}'
settings.data = Backbone.$.parseJSON(settings.data);
}
settings.data = Backbone.$.param(settings.data);
// settings.data is 'id=1&foo=test'
}
});
oldSync.apply(this, arguments);
}
});
var model = new SomeModel();
model.save();
Actually we can create a mixin! :)

How to get Meteor.Call to return value for template?

I've tried to understand this post regarding this concept, however, I'm failing to get it. I have the following simple setup:
/server/test.js
Meteor.methods({
abc: function() {
var result = {};
result.foo = "Hello ";
result.bar = "World!";
return result;
}
});
/client/myapp.js
var q = Meteor.call('abc');
console.log(q);
This structure returns to the console undefined.
If I change the myapp.js file to:
Meteor.call('abc', function(err, data) {
!err ? console.log(data) : console.log(err);
}
I receive the Object in my console.
Ideally this is what I'd like to be able to do, but it doesn't work, stating in the console: Cannot read property 'greeting' of undefined
/client/myapp.js
var q = Meteor.call('abc');
Template.hello.greeting = function() {
return q.foo;
}
Any help in passing the data from the server object into the template would be greatly appreciated. I'm still learning JavaScript & Meteor.
Thanks!
From the Meteor.call documentation:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
So, you'll want to do it like this:
Meteor.call('abc', function(err, data) {
if (err)
console.log(err);
Session.set('q', data);
});
Template.hello.greeting = function() {
return Session.get('q').foo;
};
This will reactively update the template once the data is available.
This happens because Npm.require has Async behavior. That's the reason that you have to write a callback for Meteor.call.
But there is a solution, just use install(mrt add npm) and you'll get a function named Meteor.sync(//...) with this you can do both games: sync and async in your Meteor.call().
Reference: http://www.sitepoint.com/create-a-meteor-app-using-npm-module/
You can get the return value of a Meteor method for use in a template by using a reactive variable. Check out the working demonstration on Meteorpad
I went for a ghetto solution. But, it works for me, which is what matters, to me. Below is my code, which, in concept, I think, solves OP's problem.
In the client's main.js:
Meteor.setInterval(function() {
confirmLogin();
}, 5000);
This runs the confirmLogin() function every five seconds.
The confirmLogin function (in the client's main.js):
function confirmLogin() {
Meteor.call('loggedIn', function (error, result) {
Session.set("loggedIn", result);
});
}
The loggedIn method (in the server's main.js):
loggedIn: function () {
var toReturn = false;
var userDetails = Meteor.user();
if (typeof userDetails["services"] !== "undefined") {
if (typeof userDetails["services"]["facebook"] != "undefined") {
toReturn = true;
}
}
return toReturn;
},
The relevant helper:
loggedIn: function () {
return Session.get("loggedIn");
}