Backbone collection, get specific attribute - javascript

I've set an attribute currentPage on my Backbone collection, however I seem to not be able to log it specifically.
onRender: function () {
App.log('this.collection.currentPage onRender')
App.log(this.collection)
App.log(this.collection.accountID)
App.log(this.collection.currentPage)
App.log(this.collection.get('currentPage'))
}
I do the fetch then show, and within onRender, the following console log is produced
where this.collection.currentPage and this.collection.get('currentPage') are undefined even though I can see currentPage as a defined ("2") attribute in the log output of this.collection. this.collection.accountID is working how I expected currentPage to work.
What am I doing wrong?
Edit: I am setting the attribute on the success of .fetch because the data is returned in the response headers. Something like this,
getAccountSegments: function (accountID) {
var accountSegmentsCollection = new Entities.Collections.AccountSegmentCollection({
accountID: accountID
});
accountSegmentsCollection.fetch({
success: function (collection, response, options) {
collection.currentPage = options.xhr.getResponseHeader('X-Current-Page');
}
});
return accountSegmentsCollection;
},
Someone stated below that I am not allowed to set attributes on the collection (except by way of the constructor, but that's before the fetch). So how else could I get this response header data into my view?

Collections in backbone js do not allow you to set attributes.
The best way to store meta information in a backbone collection is through plain javascript object.
For example:
var myCollection = Backbone.Collection.extend({
model: myModel,
metaInfo: {
currentPage: "2"
}
});
Then you can get the current page in your view using this.collection.metaInfo.currentPage

get in this context is a little confusing. In Backbone Models have attributes, but collections do not, this means that get on a collection is different from get on a model.
get on a model retrieves an attribute, while on a collection it will return a model (by id). You could extend your collection to have attribute getters and setters:
var pageCollection = Backbone.Collection.extend({
getAttribute: function(attr) {
return this.attributes?this.attributes[attr]:null;
},
setAttribute: function(attr,val){
this.attributes = this.attributes || {};
this.attributes[attr] = val;
}
});
You could then set your currentPage attribute:
pageCollection.setAttribute('currentPage', 'myPage');
And retrieve it:
App.log(pageCollection.getAttribute('currentPage');
Which should give you the functionality you are after.

Related

interpreting backbone collection data that it received from an api controller

I'm trying to use a simple collection and view to write out data from my backbone collection to my website. I just want to iterate over the collection and display properties like Id, Name, etc. in my template.
My collection gets its data from an api controller(a sample of the data is shown below).
My limited knowledge leads me to guess that the api controller is returning an object and not JSON.
So I'm guessing that is messing up my results. I've written out the collection to my Chrome console and attached a screenshot of what I see below.
So looking at the code below, is there a way that I can format the data returned from the api so that my collection can use it effectively?
Here is the code:
var ResearchCollection = Backbone.Collection.extend({
url: '/api/lab',
getresearch: function() {
this.fetch({
url: this.url
});
}
});
var researchCollection = new ResearchCollection();
return Backbone.View.extend({
className: 'labRender',
template: _.template(tmpl, null, { variable: 'x' }),
render: function () {
researchCollection.getresearch();
console.log('collection: ', researchCollection);
}
Basically, I just want to iterate over my collection and display properties like Id, Name, etc. in my template.
Here is the raw data from the api controller that I am using to populate my collection:
{
"odata.metadata":"http://sol.edu/SOM/Api/v1/$metadata#ApiKeys","value":[
{
"odata.id":"http://sol.edu/SOM/Api/v1/ApiKeys('2f2627ed-3a97-43aa-ac77-92f227888835')","Id":"2f2627ed-3a97-43aa-ac77-92f227888835","Name":"VideoSearch","TimeoutInMinutes":20160,"IsDefault":false,"CreateAuthTicketsForResources":false,"ReportAuthFailureAsError":false,"ExcludePrivatePresentations":true,"Internal":true,"ViewOnlyAccessContext":true
}
]
}
when piped to the browser's console(why is each character a separate attribute?):
I think maybe this is because you mixed up collection and model. In Backbone, Model are fundamental unit, a Model can be used to render a template.However, Collection are ordered sets of 'Models'. So, if you just want to transform a data like you describe above, you may better select a Model instead of 'Collection'. Just try this:
var ResearchModel = Backbone.Model.extend({
initialize: function(attributes) {
this.url = 'api/lab'
}
});
// when you initialize a Model or collection, the first parameter is the attribute you want to initialize
var researchModel = new ResearchModel({});
return Backbone.View.extend({
className: 'labRender',
template: _.template(tmpl, null, { variable: 'x' }),
render: function () {
researchModel.fetch();
console.log('collection: ', researchModel);
}
Otherwise, if you just want to use collection, you had better specify its Model.Backbone use JSON, so you can also specify the model with your key.

Backbonejs - How can I print the results of a fetch?

Hi I'm new to Backbone and I was just playing around with it a little, here is my code:
var Users = Backbone.Collection.extend ({
url : 'http://backbonejs-beginner.herokuapp.com/users'
});
var users = new Users();
users.fetch({
success: function () {
console.log(users);
}
});
The fetch call succeeds and I am returned with an object that looks like:
[
{
"id": "hqel839m1071rbggsxf7",
"firstname": "Thomas",
"lastname": "Davis",
"age": 12
}
]
How can I print different parts of the result?
For example I want to print the "id" parameter of the first item. Can I iterate it like an array?
I tried to do console.log(users[0].id) but it doesn't work.
Thanks.
Not to forget about the arguments passed to success callback of collection.fetch which are (collection, response, options). Check documentation here. You can use the collection argument to select a particular model. Check below code :
var Users = Backbone.Collection.extend ({
url : 'http://backbonejs-beginner.herokuapp.com/users'
});
var users = new Users();
users.fetch({
success: function (collection, response, options) {
//Will log JSON objects of all User objects
console.log(collection.toJSON());
//You can get a Model using 'id'
var user = collection.get("hqesig1ea1br2k6horay");
// Will log User Model for id "hqesig1ea1br2k6horay"
console.log(user);
//You can then fetch Model attributes this way
console.log("ID: ", user.get('id'));
console.log("First name: ", user.get('firstname'));
console.log("Lastname : ", user.get('lastname'));
}
});
A fiddle for your reference.
There are three different ways to access the models in a Backbone.Collection. First, you can use the .get method to find a model based on it's unique ID. This basically looks at all models in the collection and compares their id attribute to the one provided.
var user = collection.get('unique_id'); // return an instance, or null
The second method is to use the .at method to get a model by index. This is useful if your models are sorted. If they are not sorted, they'll be fetched by insertion order (that is, the order in which they were provided to the collection):
var user = collection.at(0); // return the first model in the collection
Lastly, you can access the raw array of models that the Collection wraps. You can access this via the .models attribute, which is just an array. This is not the recommended approach.
var user = collection.models[0];
Once you have a user, you can access any attributes of the user via the .get method on your model:
var age = user.get("age");
user.set("age", 100);
You can view the documentation for the model get method here, and the documentation for Backbone.Collection here.

Can you bind a simple javascript array to your ember.js template?

I'm using ember.js RC1 + ember-data rev 11 (but I also need some plain ajax for configuration like models). I want to loop over a simple objects list and display the records (note -here I create just a basic array)
The content I have bound has the following custom find method defined
App.Foo = DS.Model.extend({
name: DS.attr('string')
}).reopenClass({
records: [],
all: function() {
return this.records;
},
find: function() {
var self = this;
$.getJSON('/api/foo/', function(response) {
response.forEach(function(data) {
//say I want to kill everything in the array here for some strange reason...
self.records = [];
//the template still shows the record ... not an empty list ?
}, this);
});
return this.records;
}
});
My other model uses this directly
App.Related = DS.Model.extend({
listings: function() {
return App.Foo.find();
}.property()
});
Now inside my template
{{#each foo in related.listings}}
{{foo.name}}<br />
{{/each}}
The list loads up with whatever I put in the array by default (say I add a simple object using createRecord like so)
add: function(record) {
this.records.addObject(App.Foo.createRecord(record));
},
and when the template is rendered I see anything listed here... but as I put in the comments above, if I decide to remove records or null out the list that is bound it doesn't seem to reflect this in any way.
Is it possible to bind a simple array as I have and yet remove items from it using something basic such as splice? or even a drastic self.records = []; ?
self.records.splice(i, 1);
Even when I query the client manually after the splice or empty work it returns 0
console.log(App.Foo.all().get('length'));
Initially I see records, but then I see they are gone (yet the html doesn't change)
I understood your question this way, that the following remark is the point your are struggling with:
response.forEach(function(data) {
//say I want to kill everything in the array here for some strange reason...
self.records = [];
//the template still shows the record ... not an empty list ?
}, this);
You are wondering, why your template is showing no empty list? It's because you did not tell Ember when to update the template. You can tell Ember this way:
App.Related = DS.Model.extend({
listings: function() {
return App.Foo.find();
}.property("App.Foo.records.#each")
});
Now Ember knows, whenever something is added or removed from your array, it should update the listings property of your model. And therefore it knows that your view needs rerendering.
One additional remark to the orignal question regarding "simple javascript arrays". When you use Ember, you actually do not instantiate simple js arrays. When you declare:
var a = []; // is the same as -> var a = Ember.A();
Ember does some magic and wraps in an enhanced ember version of an array (Ember.NativeArray), which enables you to use such property dependency declarations mentioned above. This enables Ember to use ArrayObservers on those arrays, although they may feel like a plain JS Array.
You need to use the set method when you modify properties and get when you return them, or else Ember won't be able to do its magic and update the template.
In your case, there is an additional problem, which is that in find(), you return a reference to records before your asynchronous getJSON call replaces it with a new empty array. The calling method will never see the new array of records. You probably want to use clear() instead.
Your model should look something like this:
App.Foo = DS.Model.extend({
name: DS.attr('string')
}).reopenClass({
records: [],
all: function() {
// can't use 'this.get(...)' within a class method
return Ember.get(this, 'records');
},
findAll: function() {
var records = Ember.get(this, 'records');
$.getJSON('/api/foo/', function(response) {
records.clear();
// in this case my json has a 'foos' root
response.foos.forEach(function(json) {
this.add(json);
}, this);
}, this);
// this gets updated asynchronously
return records;
},
add: function(json) {
// in order to access the store within a
// class method, I cached it at App.store
var store = App.get('store');
store.load(App.Foo, json);
var records = Ember.get(this, 'records');
records.addObject(App.Foo.find(json.id));
}
});
Note that the addObject() method respects observers, so the template updates as expected. removeObject() is the corresponding binding-aware method to remove an element.
Here's a working jsfiddle.

Backbone Model: Keep Collection when saving

I have a simple Backbone model that looks like this:
(function () {
App.Company = Backbone.Model.extend({
defaults: {},
urlRoot: "/Contacts/Companies",
initialize: function () {
var contactPersons = this.get("ContactPersons") || [];
this.set("ContactPersons", new App.ContactPersonCollection(contactPersons));
}
});
})();
Whenever I save the model to the server, the ContactPersons collection is reset to an Array.
Is it really necessary for me to manually turn it into a collection, after a model is saved?
UPDATE: This works as intended -- See answer for better approach (IMHO)
(function () {
App.Company = Backbone.Model.extend({
defaults: {},
urlRoot: "/Contacts/Companies",
initialize: function () {
var contactPersons = this.get("ContactPersons") || [];
if (_.isArray(contactPersons)) {
this.set("ContactPersons", new App.ContactPersonCollection(contactPersons));
}
},
parse: function (response) {
if (response.ContactPersons && _.isArray(response.ContactPersons)) {
response.ContactPersons = new App.ContactPersonCollection(response.ContactPersons);
}
return response;
}
});
})();
When you send data back from the server, how are you handling the response? For example if you just send back a [{},{},{}] I don't think Backbone automatically knows to treat that as a collection. Thus, it sets the ContactPersons attribute as what it gets, your vanilla array.
What you can do, is override your set function inside your model which will take the array of objects passed in and write to the collection as proper. See this example:
set: function(attributes, options) {
if (_.has(attributes, 'ContactPersons') && this.get("ContactPersons")) {
this.get('ContactPersons').reset(attributes.ContactPersons);
delete attributes.ContactPersons;
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
So basically as long as your server response is properly namespaced (response.ContactPersons) then after parsing it will pass your response to the set function. The collection data is treated specially as a collection. Here, I'm just reseting the collection that already exists with the new data. All your other model attributes should continue to be passed on to the original set().
UPDATE - Growing doubt about own answer
I haven't been able to get this question/answer out of my mind. It certainly works, but I'm becoming unconvinced that using a modified set() vs. just doing things in parse() is any better. If someone has some comments on the difference between using a modified set() vs. parse() with nested models, I'd really welcome the input.

Get attributes of model in backbone.js

I have this model
var Item = Backbone.Model.extend({
url: 'http://localhost/InterprisePOS/Product/loaditembycategory/Event Materials'
});
var onSuccess = function(){ alert("success"); };
And a collection
var Items = Backbone.Collection.extend({
model: Item
});
And the rest of my code is here:
var item = new Item();
var items = new Items();
item.fetch({ success: onSuccess });
alert(items.get("ItemCode"));
What I want is to simply get the attributes of the model. Now I have this on firebug. Also when I run it on the browser I get the alert success and the next alert is undefined.
This is the output:
{"ErrorMessage":null,"Items":[{"ErrorMessage":null,"CategoryCode":"Event Materials","ClassCode":null,"Components":null,"GroupCode":null,"ImageURL":null,"ItemCode":"ITEM-123","ItemDescription":"Old World Lamppost\u000d\u000a\u000d\u000a","ItemName":"GET123","ItemType":null,"KitItem":null,"Matrix":null,"Prefix":null,"RetailPrice":107.990000,"SalesTaxCode":null,"UPCCode":null,"UnitMeasureCode":"EACH","UnitsInStock":0,"Value":null,"WholesalePrice":95.000000}]}
NOTE
That is just one of the items it returns. I just posted on item so that it won't be that long.
You are calling get on your collection ( see http://documentcloud.github.com/backbone/#Collection-get)
It seems what you really want is to iterate over the collection, and call get on each item
items.each(function(item) {
item.get('ItemCode');
});
If not, please elaborate!
Additionally, if your model url responds with a list of models, you should be defining the url attribute in your Collection instead of your model.
var Items = Backbone.Collection.extend({
model: Item,
url: 'http://localhost/InterprisePOS/Product/loaditembycategory/Event Materials'
});
And your response should be an array, with Items as array elements [<item1>, <item2>, ...], rather than a JSON object with {'Items': [<item1>, <item2>, ...] }. If you don't want to modify the response, you will have to implement the parse function on your collection ( http://documentcloud.github.com/backbone/#Collection-parse ).
Also, as #chiborg mentions, you are calling get right after fetch (which will complete asynchronously), so there is no guarantee that your data will be available.
The proper solution here is to bind a 'refresh' listener on the collection.
items.on('refresh', function() {
// now you have access to the updated collection
}, items);
This is due to the model loading asynchonously - item.get("ItemCode") will only work after the model has been loaded with fetch. Try calling it in your onSuccess function.
Additionally, note that it won't help to initialize Item directly. What you are trying to do is get an element in the collection Items and then call item.get("ItemCode") on that element, like this:
function onSuccess() {
alert('success')
var item = items.get(13); // get item with id 13
alert(item.get("ItemCode"));
}

Categories