Backbone Collection Fetch and the form of the Request - javascript

I have a backbone collection
var Stuff = Backbone.Collection.extend({
url: "stuff/"
model: StuffModel
});
I also have an array of ids:
var ids = [ 1, 2, 3, 4 ];
As per the docs, I call fetch on Stuff like so:
this.collection.fetch( { $.param({ ids : exercise_ids.join( "," )})});
This sends a request to the server of the form:
/stuff/?ids=1,2,3,4
This works, but I'm not happy with the form of the request. Is there a way I can send the request with the following form (ie not use the querystring)
/stuff/1,2,3,4
Thanks (in advance) for your help.

Assuming the backend sees [param] as ids when you do /stuff/[param] then there is no difference in functionality. These request are made behind the scenes and don't affect the browser's address bar so there isn't really any concern here. If you want to format your url you can define url as a function in your Backbone Collection
var Stuff = Backbone.Collection.extend({
initialize: function(models, options) {
this.ids = options.ids
//bind functions to 'this' so that you can access ids
_.bind(this, 'setIds', 'url');
},
setIds: function(ids) {
this.ids = ids;
//return 'this' to allow chaining
return this;
},
url: function() {
return 'stuff/' + this.ids.join(',')
}
});
myCollection.setIds([1,2,3,4]).fetch()

Related

Restful call from angular is not returning results

Here am trying to make a RESTful call to an external API.
am trying to achieve 2 things in one call. So, I have one function with 2 nested functions within.
The first one calls the search API to search for a product.
The second one calls recommended API to retrieve recommendations based on the results from the first one.
My AngularJS Code is as follow;
var walmartAssn= angular.module('myApp', ['ngResource']);
walmartAssn.controller('walmartAssnController', function($scope,$resource) {
//define the API urls
var urlSearchProductApi= 'http://api.walmartlabs.com/v1/search';
var urlRecProductApi='http://api.walmartlabs.com/v1/nbp';
//define API key
var keyApi='exampleKey123';
$scope.searchProductMethod= function(){
//pass the value from the user input text box
$scope.searchItem = $scope.item ;
$scope.productId;
//get the data from the Walmart product search API
searchRequest = $resource(urlSearchProductApi, { callback:
"JSON_CALLBACK" }, { get: { method: "JSONP" }});
//pass the input text as a parameter through a GET request
$scope.searchedProducts = searchRequest.get({ apiKey: keyApi,
query: $scope.searchItem });
console.log($scope.searchedProducts.$promise);
$scope.searchedProducts.$promise.then(function(eventDetail){
//fetch the ID of the first item
$scope.productId = eventDetail.items[0].itemId;
});
recommendRequest = $resource(urlRecProductApi, { callback:
"JSON_CALLBACK" }, { get: { method: "JSONP" , isArray:true}});
console.log(recommendRequest);
$scope.recommendedProducts = recommendRequest.get({ apiKey:
keyApi, itemId: 42608121 });
console.log($scope.recommendedProducts)
$scope.recommendedProducts.$promise.then(function(){
$scope.recommendedProductsList = eventDetail;
console.log("Print recommended list");
console.log(eventDetail);
console.log($scope.recommendedProductsList);
console.log('End');
});
} });
In the above app, the first function returns result while the second function does not.
In chrome console am getting the following, not the fist function returns an array of JSONs while the second one was blocked.
While on the Network tab in the chrome console, i see the the call was successful, as in shown below;
Moreover, I have tried the URL with hard coded values in the browser and worked successfully.
Any help is appreciated, thanks in advance.
Assuming that the 2nd call does not depend on the first, I see that you are not defining eventDetail as an argument to the second method.
So, instead of:
$scope.recommendedProducts.$promise.then(function(){
It would be:
$scope.recommendedProducts.$promise.then(function(eventDetail){
If you actually mean to use the eventDetail from the first method (the one used with $scope.searchedProducts.$promise), then the whole second request code needs to be called from the first then handler, passing the data needed.
Something like:
var walmartAssn= angular.module('myApp', ['ngResource']);
walmartAssn.controller('walmartAssnController', function($scope,$resource) {
//define the API urls
var urlSearchProductApi= 'http://api.walmartlabs.com/v1/search';
var urlRecProductApi='http://api.walmartlabs.com/v1/nbp';
//define API key
var keyApi='exampleKey123';
$scope.recommend = function(itemId) {
var recommendRequest = $resource(urlRecProductApi, { callback:
"JSON_CALLBACK" }, { get: { method: "JSONP" , isArray:true}});
console.log(recommendRequest);
$scope.recommendedProducts = recommendRequest.get({ apiKey:
keyApi, itemId: itemId });
console.log($scope.recommendedProducts);
$scope.recommendedProducts.$promise.then(function(eventDetail){
$scope.recommendedProductsList = eventDetail.items; // or just `eventDetail`?
console.log("Print recommended list");
console.log(eventDetail);
console.log($scope.recommendedProductsList);
console.log('End');
});
};
$scope.searchProductMethod= function(){
//pass the value from the user input text box
$scope.searchItem = $scope.item ;
$scope.productId;
//get the data from the Walmart product search API
var searchRequest = $resource(urlSearchProductApi, { callback:
"JSON_CALLBACK" }, { get: { method: "JSONP" }});
//pass the input text as a parameter through a GET request
$scope.searchedProducts = searchRequest.get({ apiKey: keyApi,
query: $scope.searchItem });
console.log($scope.searchedProducts.$promise);
$scope.searchedProducts.$promise.then(function(eventDetail){
//fetch the ID of the first item
$scope.productId = eventDetail.items[0].itemId;
$scope.recommend($scope.productId);
});
};
});
One more thing:
Why is isArray:true used only in recommendation but not in search?
Update
It might be worth trying a jQuery JSONP call to see if it works. Maybe the recommendation endpoint does not support JSONP. AngularJS returns 404 in this case according to https://stackoverflow.com/a/24893912/146656

Backbone .fetch() clean data rewriting

I'd like to make a clean request with backbone on a fetch call. My API's url is /users/, and I want to pass data :
var users = new UsersCollection({ users_id: "1|2|3" });
users.fetch({
data: "users_id=1|2|3"
});
Users is a collection of UserModel.
But the url becomes /users/?users_id=1|2|3 but I want it to be /users/1|2|3.
I've a .htaccess with the following line RewriteRule ^(.*)/$ /api.php?fct=$1 [L]
How can I do it ?
I think that using query params for specifying which models the REST api should ask for is better than the "1|2|3" syntax, but if the specifications is to generate a /users/1|2|3 url it can be achieved like this:
var UsersCollection = Backbone.Collection.extend({
initialize: function(models, options){
this.requestedModelsId = options.modelsId;
},
url: function(){
return "/users/" + this.requestedModelsId.join("|"); //generates /users/1|2|3 if for requestedModelsId [1,2,3]
}
});
var users = new UsersCollection(undefined, {modelsId : [1,2,3]});
users.fetch();
This way the responsibility is left for the collection to know what to fetch for and how to fetch it.

JavaScript: unable to access object properties

I'm having trouble accessing the properties of a simple object.
This is what I get when I run alert(JSON.stringify(user)):
{"username": "david", "biography": "Hello world."}
But this is what I get when I run alert(user.username):
undefined
I've also tried user["username"] with the same result. This is in the context of a Backbone application using Handlebars. Here is the Backbone part:
var User = Backbone.Model.extend({
urlRoot: 'http://api.example.com/user',
});
var Router = Backbone.Router.extend({
routes: {
":username": "profile"
},
profile: function (username) {
var user = new User({id: username});
user.fetch({
beforeSend: authenticate,
success: function() {
var profile = new Profile({user: user});
profile.render();
}
});
}
});
var Profile = Backbone.View.extend({
render: function() {
var source = $("#profile").html();
var template = Handlebars.compile(source);
user = this.options.user;
var html = template(user);
$("#content section").html(html);
}
});
What might be the cause such an issue?
if you user object is a backbone model, then try this
console.log(user.get('username'));
or
cosole.log(user.attributes.username);
This is working for me
var user = {"username": "david", "biography": "Hello world."};
alert (user.username);
alert (user["username"]);
JSON.stringify will look for a toJSON function and use the return value of that as the JSON it uses.
Backbone probably does some fancy coding to make it easier to use its model objects. Since they don't think you want all the junk they put in, they provided a clean toJSON() result for you.
#Rayweb_on has the rest of the answer.

Having different models for request and response - backbone.js

//A backbone model
var RequestModel = Backbone.Model.extend({});
//A backbone model
var ResponseModel = Backbone.Model.extend({});
RequestModel.save({
success: function (ResponseModel ) {
alert(ResponseModel .toJSON());
}
})
Can i have a separate Model for Request and Response, as both Request and Response does not match. Its a total RPC call and not a CRUD operation.
I've thought about this same problem before, and I feel there isn't a great way to achieve this in Backbone. The best I've come up with is to implement a fromResponse and toRequest method on the model, and override model.parse and model.sync to map the model object to them. Something like:
var Model = Backbone.Model.extend({
fromResponse: function(responseAttrs) {
var modelAttrs = {}; //map response attributes to modelAttrs
return modelAttrs;
},
toRequest: function() {
//map model attributes to response attributes here
var modelAttrs = this.toJSON();
var responseAttrs = {}; //map models attributes to requestAttrs
return responseAttrs;
},
parse: function(response) {
return this.fromResponse(response);
},
sync: function(method, model, options) {
options = options || {};
options.data = this.toRequest();
Backbone.sync(method, model, options);
}
});
If the parse and sync are overridden in some kind of a base class, then you only need to implement the fromResponse and toRequest mappers for each model.
Another option would be to override Backbone.sync altogether, and map each Model type to some kind of ModelRequestMapper and ModelResponseMapper object to (de-)serialize each model. I feel that would be more complicated, but might scale better, if you have lots of models.
/Code sample not tested

How do I fetch a single model in Backbone?

I have a Clock model in Backbone:
var Clock = Backbone.Model.extend({});
I'm trying to get an instance of that that has the latest information from /clocks/123. Some things I've tried:
a "class"-level method
Clock.fetch(123)
// TypeError: Object function (){ ... } has no method 'fetch'
creating an instance and then calling fetch on it:
c = new Clock({id: 123})
c.fetch()
// Error: A 'url' property or function must be specified
a collection
I tried creating an AllClocks collection resource (even though I have no use for such a thing on the page):
var AllClocks = Backbone.Collection.extend({
model: Clock,
url: '/clocks/'
});
var allClocks = new AllClocks();
allClocks.fetch(123);
// returns everything from /clocks/
How do I just get one API-backed Clock?
Try specifying urlRoot in the model:
From the docs:
var Book = Backbone.Model.extend({urlRoot : '/books'});
var solaris = new Book({id: "1083-lem-solaris"});
solaris.fetch();
Your second approach is the approach I have used. Try adding the following to your Clock model:
url : function() {
var base = 'clocks';
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + this.id;
},
This approach assumes that you have implemented controllers with the hashbang in your URL like so, http://www.mydomain.com/#clocks/123 , but it should work even if you haven't yet.
I personally recommend, following the Model#url method documentation
model = new Model(id: 1)
view = new View(model: model)
collection = new Collection([model])
model.fetch()
in your collection remember to add the collection url:
url: "/models"
and in your View's initialize function do:
this.model.bind("change", this.render)
this way backbone will do an ajax request using this url:
"/models/1"
your model will be updated and the view rendered, without modifying Collection#url or Model#urlRoot
note:
sorry this example came out in coffee script, but you can easily translate it to js adding var statements
var Person = Backbone.Model.extend({urlRoot : '/person/details'});
var myName = new Person({id: "12345"});
myName.fetch();
As a result you make a Ajax request on the
URL http://[domainName]/person/details/id
and you have the JSON response back.
Enjoiiii !!!
...and do this if you don't want the trailing slash on the model urlRoot:
url : function() {
return this.urlRoot + this.id;
},
You probably should be accessing the object trough a collection and keeping it in the collection all the time. This is how to do it:
var AllClocks = Backbone.Collection.extend({
model: Clock,
url: '/clocks/'
});
var allClocks = new AllClocks();
my_clock = allClocks.add({id: 123});
my_clock.fetch()
I want to use RESTful url,but I couldn't understand why 'postId' can't be added to base url.
var PostModel = Backbone.Model.extend({
urlRoot: 'getBlogPost',
defaults: {
postTitle: "defaultTitle",
postTime: "1970-01-01",
postContent: "defaultContent",
postAuthor: "anonymous"
}
});
var post = new PostModel({
postId: 1
});
alert(post.url());
Then I know only after I set 'idAttribute' as 'postId' in Model can I get the right url.
like this:
var PostModel = Backbone.Model.extend({
idAttribute: 'postId',
urlRoot: 'getBlogPost',
defaults: {
postTitle: "defaultTitle",
postTime: "1970-01-01",
postContent: "defaultContent",
postAuthor: "anonymous"
}
});

Categories

Resources