How do I specify various URLs in a backbone app? - backbone.js

I need one of my backbone models to hit a variety of URLs depending on the type of action being performed. How do I determine the action within the URL function so that I can specify the appropriate URL? For example:
DELETE: /myapipath/itemtype/id/
POST: /myapipath/special-path/
GET: /myapipath/special-path/?code=ABC
I know how to tell the difference between a POST and everything else: this.isNew()
But how do I tell the difference between a DELETE and a GET in a custom model.url function?
Please don't suggest that I change the server-side api. That isn't up to me.
Thanks!

Conceptually the url of a Backbone model is the primary GET url of the resource. To use a different url for some of the actions, override the model's sync function. Fortunately, Backbone makes it easy to override:
window.MyModel = Backbone.Model.extend({
// ... other stuff ...
url: '/myapipath/special-path/?code=ABC',
methodUrl: {
'create': '/myapipath/special-path/',
'delete': '/myapipath/itemtype/id/'
},
sync: function(method, model, options) {
if (model.methodUrl && model.methodUrl[method.toLowerCase()]) {
options = options || {};
options.url = model.methodUrl[method.toLowerCase()];
}
Backbone.sync(method, model, options);
}
}
Edit: I took another look at the Backbone source and noticed that it merges the whole options argument to build the params, not options.params, and updated my example accordingly.

Related

Use of the url parameter in Backbone.Model.extend

I'm new to backbone and i'm wondering what is the url parameter used for as
app.FormModel = Backbone.Model.extend({
defaults: {
success: false,
saveData: ''
}
});
will give me a
Uncaught Error: A "url" property or function must be specified
what is url that I need to provide to extend method
According to the documentation it says
Returns the relative URL where the model's resource would be located on the server. If your models are located somewhere else, override this method with the correct logic.
What does it mean by resources.
Change
app.FormModel = Backbone.Model.extend({
to
app.FormModel = new Backbone.Model.extend({
If you'd like to send/retrieve data to server, backbone needs to know where the server is. The URL is exactly this.
If you don't set this property and try to sync with server, backbone will throw this exception.
http://backbonejs.org/#Model-url

ExtJs Model Proxy vs. Store Proxy

OK, I'm stuck on what should be a basic task in ExtJs. I'm writing a simple login script that sends a user name and password combination to a RESTful web service and receives a GUID if the credentials are correct.
My question is, do I use a Model Proxy or a Store Proxy?
To my understanding, Models represent a single record, whereas Stores are for handling sets of data containing more than one record. If this is correct then it would seem that a Model proxy is the way to go.
Following Sencha's documentation at http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.data.Model the code would look something like this:
Ext.define('AuthenticationModel', {
extend: 'Ext.data.Model',
fields: ['username', 'password'],
proxy: {
type: 'rest',
url : '/authentication'
}
});
//get a reference to the authentication model class
var AuthenticationModel = Ext.ModelManager.getModel('AuthenticationModel');
So far everything is OK, until the next step:
//Use the configured RestProxy to make a GET request
AuthenticationModel.load('???', {
success: function(session) {
console.log('Login successful');
}
});
The load() method for the Model class is a static call expecting a single unique identifier. Logins typically depend upon two factors, username and password.
So it appears Store proxies are the only way to validate someone's username and password credential combination in ExtJS. Can someone verify and explain? Any help to understand this would be greatly appreciated.
You just need to know the following:
The store will use it's own proxy if you configured one for this
instance and if not he takes the proxy from the model.
So you can easily go with two proxy configurations to enable the multi-CRUD operations on the store and the single-CRUD operations on the Models. Note the the static load method of the Model expects the model id because it is supposed to load a model by just one Id (yes, composite keys are not supported). You will also have to fetch the model instance in the callback (As you did).
Back to your Username/password problem
You may apply your session Model with a custom 'loadSession' method
loadSession: function(username,password, config) {
config = Ext.apply({}, config);
config = Ext.applyIf(config, {
action: 'read',
username: username,
password: password
});
var operation = new Ext.data.Operation(config),
scope = config.scope || this,
callback;
callback = function(operation) {
var record = null,
success = operation.wasSuccessful();
if (success) {
record = operation.getRecords()[0];
// If the server didn't set the id, do it here
if (!record.hasId()) {
record.setId(username); // take care to apply the write ID here!!!
}
Ext.callback(config.success, scope, [record, operation]);
} else {
Ext.callback(config.failure, scope, [record, operation]);
}
Ext.callback(config.callback, scope, [record, operation, success]);
};
this.getProxy().read(operation, callback, this);
}
Now call this instead of load.
I found it in the documentation of sencha App Architecture Part 2
Use proxies for models:
It is generally good practice to do this as it allows you to load and
save instances of this model without needing a store. Also, when
multiple stores use this same model, you don’t have to redefine your
proxy on each one of them.
Use proxies for stores:
In Ext JS 4, multiple stores can use the same data model, even if the
stores will load their data from different sources. In our example,
the Station model will be used by the SearchResults and the Stations
store, both loading the data from a different location. One returns
search results, the other returns the user’s favorite stations. To
achieve this, one of our stores will need to override the proxy
defined on the model.

How to fetch and populate backbone model for Google Places JS API?

I'm implementing a system that require access to Google Places JS API. I've been using rails for most of the project, but now I want to inject a bit of AJAX in one of my views. Basically it is a view that displays places near your location. For this, I'm using the JS API of Google places. A quick workflow would be:
1- The user inputs a text query and hits enter.
2- There is an AJAX call to request data from Google Places API.
3- The successful result is presented to the user.
The problem is primarily in step 2. I want to use backbone for this but when I create a backbone model, it requests to the 'rootURL'. This wouldn't be a problem if the requests to Places was done from the server but it is not.
A place call is done like this:
service = new google.maps.places.PlacesService(map);
service.nearbySearch(request, callback);
Passing a callback function:
function callback(results, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
var place = results[i];
createMarker(results[i]);
}
}
}
Is it possible to override the 'fetch' method in backbone model and populate the model with the successful Places result? Is this a bad idea?
It is possible to override the fetch method of your backbone model.
var mapModel = Backbone.Model.extend({
fetch: function (options) {
// do your call to google places here
},
callBackFunctionForGoogleMaps: function (results, status) {
// call back function here would set model properties
}
});
return mapModel;
This way you override fetch and remove the defaults behavior of Backbone to make an ajax call.
Just as an FYI if you want to override Backbone models fetch but still have the default behavior of model.fetch you can do the following. Note the return calling Backbone.Model.fetch.
var mapModel = Backbone.Model.extend({
fetch: function (options) {
// do any pre-fetch actions here
return Backbone.Model.fetch.call(options);
}
});
return mapModel;
It is probably not a bad idea to override the fetch method here because you are still fetching data for your model, just not through ajax calls on your end. It would be smart though to leave comments noting that you are overriding fetch in this manner for a reason.

Backbone.js DELETE request not firing

I'm trying to get the backbone.js DELETE request to fire, but don't see any requests being made in my console.
I have collection model like so:
var Model = Backbone.Model.extend(
{
urlRoot: '/test',
defaults:{}
});
var TableList = Backbone.Collection.extend(
{
url: '/test',
model: Model
});
In my view I'm running this:
this.model.destroy();
Everything seems to be running fine, I can see output coming from the remove function that calls the destroy so I know it's getting there plus it also successfully runs an unrender method that I have. Can't see any requests being made to the sever though?
If I am not mistaken, you have to have an id property on your model to ensure that it hits the correct url. IE if your model was...
var Model = Backbone.Model.extend({
url: '/some/url'
});
var model = new Model({
id: 1
});
model.destroy(); // I THINK it will now try and DELETE to /some/url/1
Without an id it doesn't know how to build the url correctly, typically you'd fetch the model, or create a new one and save it, then you'd have a Url...
See if that helps!
I found the issue to my problem, thought not a solution yet. I'm not sure this is a bug with backbone or not, but I'm using ajaxSetup and ajaxPrefilter. I tried commenting it out and it worked. I narrowed it down to the ajaxSetup method and the specifically the use of the data parameter to preset some values.
Have you tried using success and error callbacks?
this.model.destroy({
success : _.bind(function(model, response) {
...some code
}, this),
error : _.bind(function(model, response) {
...some code
}, this);
});
Might be instructive if you're not seeing a DELETE request.

How to not make backbone.js use the save response as model attributes

So when I save a model on the backend, My api send back a response telling everything went fine and giving you some other pointers in json format
My problem is that backbone think I want to use that response as attributes of my model and automatically dump them in the model attributes..
I just saved it on the front-end and do not want to save the attributs again.
That is the what Backbone.Model.parse is for. By default, it is just a pass-through, so you don't need to call "super".
Let's say you only care about two properties that come back (id and foo) and you don't care about anything else:
var myModel = Backbone.Model.extend({
parse : function(resp, xhr) {
return {
id: resp.id,
foo: resp.foo
};
}
});
Note that I included "id" in my example. It is really important that creates (POST) return an id property. Without it, the Backbone model won't know how to update/delete in the future. Even if it has a different name for id (like "objectId"), you should still set the id in this function.
Indeed that's the default behaviour, and if you want to change it, you have to overwrite some Backbone functions.
Looking at how save is implemented, you have two options - either overwrite save for your model, or overwrite parse to make it aware of the data you are sending.
http://documentcloud.github.com/backbone/docs/backbone.html#section-41
Or, you could give up sending the 'pointers' in the response, because an empty response means that the model does not change after save.
I have the exact issue you are encountering. Backbone is a pretty young framework with the additional fact that javascript is really dynamic. So the saying that there are a thousand ways to solve a problem applies really well here.
I think a more suitable way to go about this is to employ something called Mixins. Here's what I did:
define([
'underscore',
'backbone',
'jquery'
], function (_, Backbone, $) {
return {
parse: function(response, xhr){
var data = response;
if(response.response && response.response.status != 0){
return {};
}
if(response.response && response.response.data)
{
data = _.first(response.response.data);
if(typeof data == 'undefined'){
data={};
}
}
if(_.isFunction(this.postParse)){
return this.postParse.call(this, data);
}
return data;
}
}
});
As you can see, I've overridden the Backbone.Model.parse prototype method and came up with my own that takes in a response. And parse the response according to my server's spec. In you case, you would implement whatever it takes to understand your server's response.
With the ground work out of the way, specifying a model is very easy:
var group = Backbone.Model.extend(
_.extend({}, ModelMixin, {
initialize:function () {
}
})
);
return group;
Voila! All the parse methods and checks that you need to write is encapsulated in that one ModelMixin "superclass".

Resources