backbone collection to view to template - javascript

I'm connecting to a 3rd-party API that returns an object which contains an array.
I'm trying to get that into a backbone collection, and then pipe that out to a view.
I tried a number of things, the most recent being something simple like this:
var MyCollection = Backbone.Collection.extend({
url: '/api/data',
parse: function (resp) {
return JSON.parse(resp);
},
});
var myCollection = new MyCollection();
myCollection.fetch();
return Backbone.View.extend({
template: _.template(tmpl),
render: function() {
this.$el.html(this.template({
coll: myCollection.toJSON()
}));
return this;
}
This just give me [Object Object] in my template.
If I write it out to the console, I just see:
YourCollection
[Object]
yourdata.metadata: "www.xyz.edu/"
value: Array[3]
0: Object
Id: "000"
Name: "Name0"
IsValid: True
1: Object
ID: "111"
Name: "name1"
IsValid: True
3: Object
ID: "222"
Name: "name2"
IsValid: True
It would be nice if I could get each array element into it's own model, but I'm not sure how to do that.
Thanks!

Seems like you need to filter actual collection inside your parse method:
function (resp) {
return JSON.parse(resp).value;
}

Related

Backbone: synchronizing Models and LocalStorage

I've extended a model A and a collection of As as follows:
define(['underscore', 'backbone', 'backbone.localStorage'], function(_, Backbone) {
var A = Backbone.Model.extend({
initialize: function() {
}
});
var A_Collection = Backbone.Collection.extend({
model: A,
localStorage: new Backbone.LocalStorage("as")
});
return {
Model: A,
Collection: A_Collection
};
});
Collections are stored in localStorage and all works fine in my application. Then I clear and replace the localStorage directly by code (using clear and setItem functions) and try to instantiate a new collection, but the changes are not detected:
var aux = new A.Collection();
aux.fetch();
// aux is empty
Otherwise if a try:
var aux = new A.Collection();
aux.localStorage = new Backbone.LocalStorage("as");
aux.fetch();
// aux contains new data
The latter is not valid for me because I'd have to modify all the creation of collections in my project.
What am I missing?
Instances of Backbone.LocalStorage are not designed to listen for LocalStorage changes that occur outside their own code. That's why you get the behavior you are getting. However, there is a workaround.
When you define a collection like this:
var A_Collection = Backbone.Collection.extend({
model: A,
localStorage: new Backbone.LocalStorage("as")
});
the localStorage value is shared by all the instances of A_Collection. You can automatically create a new instance of Backbone.LocalStorage, like this:
var A_Collection = Backbone.Collection.extend({
model: A,
initialize: function() {
A_Collection.__super__.initialize.apply(this, arguments);
A_Collection.prototype.localStorage = new Backbone.LocalStorage("as");
},
});
We have to set it on the prototype so that it is shared by all instance of A_Collection, which is the same behavior as your original code. With this in place, whenever you create a new instance of A_Collection, you will get a new instance of Backbone.LocalStorage, which will get information anew from LocalStorage.
Here is a plunker illustrating. Here is the relevant code, for reference:
var A = Backbone.Model.extend({
initialize: function() {}
});
var A_Collection = Backbone.Collection.extend({
model: A,
initialize: function() {
A_Collection.__super__.initialize.apply(this, arguments);
A_Collection.prototype.localStorage = new Backbone.LocalStorage("as");
},
});
// Setup a collection.
var collection = new A_Collection();
collection.fetch();
// Clean it out from previous runs... Note that we have to use destroy to destroy all items.
// Reset won't save to LocalStorage.
while (collection.length > 0) {
var model = collection.at(0);
model.destroy();
collection.remove(model);
}
// and set some elements.
collection.create({
name: "1"
});
collection.create({
name: "2"
});
console.log("collection length:", collection.length);
// Mess with it outside the Backbone code.
localStorage.clear();
// Manually create data that looks like what Backbone expects.
localStorage.setItem("as-1", JSON.stringify({
name: "foo",
id: "1"
}));
localStorage.setItem("as-2", JSON.stringify({
name: "bar",
id: "2"
}));
localStorage.setItem("as-3", JSON.stringify({
name: "baz",
id: "3"
}));
localStorage.setItem("as", "1,2,3");
// Create a new collection that loads from LocalStorage
var collection2 = new A_Collection();
collection2.fetch();
console.log("collection 2 length:", collection2.length);
console.log("first item", collection2.at(0).toJSON());
console.log("third item", collection2.at(2).toJSON());
console.log("instance is shared?", collection.localStorage === collection2.localStorage);
The code above generates this on the console:
collection length: 2
collection 2 length: 3
first item Object {name: "foo", id: "1"}
third item Object {name: "baz", id: "3"}
instance is shared? true

Mongoose, array in object

Update:
This problem seems to be related to mongoose. Please see update at the bottom.
In the parent I use the following code to pass choosenItem to a grandChild:
childContextTypes: {
foo: React.PropTypes.object.isRequired,
},
getChildContext: function() {
return { foo: this.state.choosenDriver }
},
choosenDriver is a mongoose Model that looks like this:
var Schema = mongoose.Schema;
var driverSchema = new mongoose.Schema({
driver: String,
age: String,
cars: [{ type: Schema.Types.ObjectId, ref: 'Car' }]
});
module.exports = mongoose.model('Driver', driverSchema);
This works fine and in the GrandChild I can do the following:
contextTypes: {
foo: React.PropTypes.object.isRequired,
},
//some other stuff...
render: function(){
return (
<h1>{this.context.foo.name}</h1>
)
});
And this displays the name of the choosenDriver.
I would also like to loop through the cars and thought it could be done like this:
render: function(){
var cars = new Array(this.context.foo.cars);
var driversCars = cars.map(function(car,i){
return <li key={i}> {car} </li>;
});
return (
<div>
<h1>{this.context.foo.name}</h1>
<ul>{driversCars}</ul>
</div>
)
}
});
This returns all the drivers cars in a string displayed on one single line.
I took a look in react developers tool and saw that cars is not defined as an Array.
Is this maybe because choosenDriver is a state and arrays are not supported in this manner?
The cars obviously exists but not as an Array.
Any ideas on how to access cars as an Array in this example?
Thank you!
Update:
When looking at this.context.foo.cars in react-Tools i see this:
cars:
->_proto_ :{..}
0: "4242594395935934",
1: "3543435095340509"
etc...
cars should be an Array so I would expect to see:
cars: Array[4]
Allthough in the DB everythingg looks as it should:
"cars":["5607b0747eb3eefc225aed61","5607b07a7eb3eefc225aed62","5606bf4b0e76916c1d1668b4","5607b07a7eb3eefc225aed62"]}]
Last Update:
Here they are next to eachother i react Tools:
(I have a list of drivers and this shows that the Driver-object Changes when it gets selected)
State:
choosenDriver: {..}
_v:0
_id: "235345353453"
name: "Roger"
cars:
->_proto_ :{..}
0: "4242594395935934",
_id: undefined
Same driver but not choosen:
drivers: array[3]
->0: {}
->1: {}
_v: 0
_id: "4242594395935934"
cars: Array[1]
So something happens to the Array when it gets selected. Weird...
If you mean cars is not the Array you expect (and not this.context.foo.cars)...
The Array constructor builds an array with each argument to the constructor as an element in the returned array.
So, new Array([1,2,3]) gives you: [[1,2,3]].
You want Array.from([1,2,3]) which will give you [1,2,3] which appears to be what you want.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
But with your code, why can't you just skip creating the intermediate array and just call this.context.foo.cars.map(...) directly?

Using promisified data access in bogart

I'm trying to set up a simple blog with bogartjs and couchdb.
I use the viewEngine like this:
var viewEngine = bogart.viewEngine('mustache', path.join(bogart.maindir(), 'views'));
To get the list of all posts from the database I use bogart.promisify to do the actual call and return a promise, like this:
router.get('/postsp', function(req) {
var articles = nano.db.use('articles');
var readlist = bogart.promisify(articles.list);
readlist().then(function(data) {
console.log(data);
return viewEngine.respond('posts.html', data)
});
console.log('render');
});
But this method does not return the template posts.html with the data. This is in the console:
render
{ total_rows: 2,
offset: 0,
rows:
[ { id: '1ec1ba2efd99b08a296022a471000adc',
key: '1ec1ba2efd99b08a296022a471000adc',
value: [Object] },
{ id: '20ce1f108a8bdf2f19f04f42b0001a04',
key: '20ce1f108a8bdf2f19f04f42b0001a04',
value: [Object] } ] }
How do I return/render the template with the outcome of the promise?
The context is required to your object. This is for the same reason:
var log = console.log;
log(15); // exception in browsers.
Pass the context:
var readlist = bogart.promisify(articles.list, articles);

Backbone.Model save -- returned model's child is array not Backbone.Collection.

I have a Model that looks like:
var Playlist = Backbone.Model.extend({
defaults: function() {
return {
id: null,
items: new PlaylistItems()
};
}
});
where PlaylistItems is a Backbone.Collection.
After I create a Playlist object, I call save.
playlist.save({}, {
success: function(model, response, options) {
console.log("model:", model, response, options);
},
error: function (error) {
console.error(error);
}
});
In here, my model is a Backbone.Model object. However, its child, items, is of type Array and not Backbone.Collection.
This was unexpected behavior. Am I missing something? Or, do I need to manually pass my array into a new Backbone.Collection and initialize this myself?
It kind of depends on what your server is expecting and what it responds with. Backbone does not know that the attribute items is a Backbone Collection and what to do with it. Something like this might work, depending on your server.
var Playlist = Backbone.Model.extend({
defaults: function() {
return {
id: null,
items: new PlaylistItems()
};
},
toJSON: function(){
// return the json your server is expecting.
var json = Backbone.Model.prototype.toJSON.call(this);
json.items = this.get('items').toJSON();
return json;
},
parse: function(data){
// data comes from your server response
// so here you need to call something like:
this.get('items').reset(data.items);
// then remove items from data:
delete data.items;
return data;
}
});

Backbone model .toJSON() doesn't render all attributes to JSON

I need to render a model's attributes to JSON so I can pass them into a template.
Here is what a render() function for a view looks like:
render: function() {
console.log(this.model);
console.log(this.model.toJSON());
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
Here is the attributes output after doing console.log(this.model):
created_at: "2012-04-19"
id: "29"
name: "item"
resource_uri: "/api/v1/item/29/"
updated_at: "2012-04-21"
user: "/api/v1/user/9/"
Here is the model's JSON output after doing console.log(this.model.toJSON()):
id: "29"
__proto__: Object
What happened?
Edit:
Here is the instantiation:
var goal = new Goal({id: id});
goal.fetch();
var goalFullView = new GoalFullView({
model: goal,
});
Here are the contents of the new view:
console.log(this.model.attributes);
console.log(this.model.toJSON());
Here is what the console says:
Object
created_at: "2012-04-23"
id: "32"
name: "test"
resource_uri: "/api/v1/goal/32/"
updated_at: "2012-04-23"
user: "/api/v1/user/9/"
__proto__: Object
Object
id: "32"
name: "test"
__proto__: Object
If the toJSON is supposed to make a clone of the attributes, why doesn't it copy the correct name or why doesn't it copy the created_at, updated_at fields?
Edit 2:
Here is the model:
var Goal = Backbone.Model.extend({
// Default attributes for Goal
defaults: {
name: "empty goal",
},
// Check that the user entered a goal
validate: function(attrs) {
if (!attrs.name) {
return "name is blank";
}
},
// Do HTTP requests on this endpoint
url: function() {
if (this.isNew()) {
return API_URL + "goal/" + this.get("id") + FORMAT_JSON;
}
return API_URL + "goal/" + FORMAT_JSON;
//API_URL + "goal" + FORMAT_JSON,
},
});
Edit 3:
I figured out that I need to use the success callback from fetch to render a view that uses the model:
goal.fetch({success: function(model) {
var goalFullView = new GoalFullView({
model: goal,
});
}});
The toJSON() method just returns a shallow clone of the model's attributes property.
From the annotated Backbone.js source:
toJSON: function(options) {
return _.clone(this.attributes);
}
Without seeing more of your code, it looks like you directly set properties on the model object, rather than using the set function to set model attributes.
I.e. don't do this:
model.name = "item";
do this:
model.set("name", "item");
EDIT:
For your specific issue, it's possible that you called toJSON before the model had finished loading from the server.
E.g. This won't always work as expected:
var model = new Goal({id: 123});
model.fetch();
console.log(model.toJSON());
But this will:
var model = new Goal({id: 123});
model.fetch({
success: function() {
console.log(model.toJSON());
}
});

Categories