I am trying to figure out how to test a Backbone.Collection using sample data but every time I call fetch on the Collection it does not seem to behave the way I expect it to. For example, using jasmine and coffeescript,
describe "task model", ->
testData = [
{ id: 1, type: "personal", complete: false }
{ id: 2, type: "business", complete: true }
]
collection = null
item = null
testTasks = Alloy.createCollection "task"
addTask = (t) ->
#newTask = new model t
Ti.API.info t
testTasks.add new Alloy.createModel "task", t
# testTasks.length is 2 which is correct
Ti.API.info "testTasks.length after add is #{testTasks.length}"
# add test data to a collection to use for tests/dev
addTask t for t in testData
beforeEach ->
collection = Alloy.createCollection "task", testTasks
item = Alloy.createModel "task"
collection.fetch view
# fails: Expected 0 to be 2
it "has sample data for development", ->
collection.fetch view
expect(collection.length).toEqual 2
I am using a framework called Titanium Alloy which is using BackboneJS 0.9.2
You need to wait with your assertion until the collection is fetched.
$.when( collection.fetch view).then (data, textStatus, jqXHR )->
expect(collection.length).toEqual 2
Or
collection.fetch view,
checkFunction = ->
expect(collection.length).toEqual 2
success: =>
checkFunction()
error: =>
checkFunction()
Related
I'm new to Backbone and on fetching a collection, I can see the server return all 15 collections. The fetch success returns all 15 models in the response object but the collection object has only the last of the 15 models.
var BracketModel = Backbone.Model.extend({
defaults: {
id: '',
name: '',
title: ''
},
urlRoot: 'http://test.com/bracket/rest.php',
.....
}),
var BracketsCollection = Backbone.Collection.extend({
url: 'http://test.com/bracket/rest.php?op=list',
model: BracketModel,
}),
bracketCollection.fetch({
success: function (collection, response) {
// Collection.models only has one model, response has 15
var bracketsView = new BracketsView({collection: collection});
},
Try
var bracketsView = new BracketsView({collection: response});
Or
var bracketsView = new BracketsView({collection: collection.toJSON()});
I haven't tested it now, but if I remember well, both are equivalent.
The first parameter returns the collection object, which gives you access to different collection attributes. The second parameter returns 'an array containing the attributes hash of each model in the collection', which is likely the thing you are looking for.
The pattern that I usually go with for passing a collection to a view goes like this:
var bracketCollection = new BracketsCollection();
var view = new brackatsView({collection: bracketCollection});
brackCollection.fetch();
Then inside of your view's initialization method do this:
this.listenTo(this.collection, 'sync', this.render);
What this all is doing is creating your collection and your view, and then when you create the view you are telling it about the collection. Calling fetch on the collection is an asynchronous event that will fire a 'sync' even when it is done. The view will listen for this sync event, and when it happens will call the render function.
var theseMessages = window.App.data.messages.where({ chat_id: 2 })
this.messagesView = new MessagesView({ collection: theseMessages });
I am not sure the proper way to pass in to the view constructor the collection that has a specific property.
Example my collection may look like
[
{
"chat_id": "1",
"facebook_name": "Tim Parker",
"facebook_email": "tim#aol.com",
"facebook_id": "1663565265",
"gravatar": null,
"message": "Whats up",
"_id": "533b6a4c7cc6647012f441ad",
"__v": 0,
"sent": "2014-04-02T01:39:24.747Z"
},
{
"chat_id": "2",
"facebook_name": "nick matthews",
"facebook_email": "john#aol.com",
"facebook_id": "1663565265",
"gravatar": null,
"message": "Hey admin",
"_id": "434636",
"__v": 0,
"sent": "2014-04-02T01:48:45.585Z"
}
]
So when I want to render this collection in my app and only pass the models that have chat_id : 2 I would think something like this is sufficient?
var theseMessages = window.App.data.messages.where({ chat_id: 2 })
this.messagesView = new MessagesView({ collection: theseMessages });
But it gives me an error any ideas?
Edit: Should have been more specific from the get go, I am using a marionette collection view. So I need to pass a collection, but there has to be a way to limit the collection to items with specific attributes, maybe I need to change my logic?
var Marionette = require('backbone.marionette');
var MessageView = Marionette.ItemView.extend({
className: 'message row',
template: require('../../templates/message.hbs')
});
module.exports = CollectionView = Marionette.CollectionView.extend({
className: 'collection',
initialize: function() {
this.listenTo(this.collection, 'change', this.render);
},
itemView: MessageView
});
I think you meant to do this:
this.messagesView = new MessagesView({ model: theseMessages });
In your line where you filter the Collection:
var theseMessages = window.App.data.messages.where({ chat_id: 2 })
the variable theseMessages just holds an array of items from the Collection, not another Collection itself -- that's probably why you're getting an error in MessagesView.
I would do the filtering dynamically in either the Collection or in MessagesView itself when rendering, rather than trying to pass an already-filtered Collection to MessagesView's constructor. Most of the time, I'd probably do it in MessageView, since I tend to think of filtering as a view-specific thing. But filtering in the Collection would be fine if that Collection isn't going to be used in another view at the same time.
Either way, here's some example code. To filter in the MessageView:
// within MessageView
serializeData: function() {
var data = Marionette.CollectionView.prototype.serializeData.call(this);
return _.where(data, { chat_id: 2 });
}
Or to filter in your Collection instead:
// within your Collection
toJSON: function() {
return this.chain()
.where({ chat_id: 2 })
.map(function(model){ return model.toJSON(); }
.value();
}
I haven't tested that code, but something like that should work.
Incidentally, to clarify the collection: vs model: subject that was brought up in other comments here: you are correct in using the collection: option in the view's constructor. Backbone & Marionette want a collection: for a Collection, and a model: for a single Model.
i'm starting using parse.com to develop a web app but i'm stuck on a simple problem.
I defined a model (or object in Parse SDK) as:
Book.Model = Parse.Object.extend("book", {
// Default attributes for the book.
defaults: {
title: "placeholder...",
},
// Ensure that each book created has `title`.
initialize: function() {
if (!this.get("title")) {
this.set({"title": this.defaults.title});
}
},
});
and a collection:
Book.List = Parse.Collection.extend({
// Reference to this collection's model.
model: Book.Model,
initialize: function() {
},
});
Then, if i try something like
var books = new Book.List();
books.fetch({
success: function(collection) {
console.warn(collection);
},
error: function(collection, error) {
// The collection could not be retrieved.
}
});
Everything goes fine. Log:
child {length: 5, models: Array[5], _byId: Object, _byCid: Object, model: function…}
_byCid: Object
_byId: Object
length: 5
models: Array[5]
__proto__: EmptyConstructor
BUT if i try to use event callback instead of success method i get an empty array. Code:
books.on('reset', this.log());
books.fetch();
log: function() {
console.log(books);
}
and log:
child {length: 0, models: Array[0], _byId: Object, _byCid: Object, model: function…}
_byCid: Object
_byId: Object
length: 5
models: Array[5]
__proto__: EmptyConstructor
which is quite strange (because i think that each solution wait for the collection to be populated from the server). Does anybody know why is this happening?
I'm actually using Backbone Boilerplate and Parse.com js SDK.
The Collection#fetch behavior has changed, it used to reset the collection by default but as of 1.0.0 it merges the new models using set:
When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, [...]
and set doesn't trigger "reset" events, it triggers other events:
All of the appropriate "add", "remove", and "change" events are fired as this happens.
If you want your fetch to reset the collection then you have to say so:
books.fetch({ reset: true });
I have a basic Backbone application which obtain an array of JSON objects from a remote service and displays them: all good so far. However, each JSON object has an array of tags and I want to display the tags in a separate area of the webpage.
My question is: what is the most Backbone-friendly way of doing this? I could parse the existing data again in a second view, which is cleaner but takes up more computation (processing the entire array twice).
An alternative is gathering up the tag information in the primary view as it is working through the array and then passing it along to the subsidiary view, but then I'm linking the views together.
Finally, I'd like to filter based on those tags (so the tags will become toggle buttons and turning those buttons on/off will filter the information in the primary view); does this make any difference to how this should be laid out?
Bonus points for code snippets.
Hm. I'm not sure if this is the Backbone-friendly way, but I'll put the logic to retrieve a list of tags (I think that's what you meant by "parse") in the collection.
Both the main view and the subview will "listen" to the same collection, and the subview will just call collection.getTags() to get a list of tags it needs.
// Model that represents the list data
var ListDataModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
tags: []
};
}
});
// Collection of list data
var ListDataCollection = Backbone.Collection.extend({
model: ListDataModel,
initialize: function() {
var me = this;
// Expires tag collection on reset/change
this.on('reset', this.expireTagCache, this);
this.on('change', this.expireTagCache, this);
},
/**
* Expires tag cache
* #private
*/
expireTagCache: function() {
this._cachedTags = null;
},
/**
* Retrieves an array of tags in collection
*
* #return {Array}
*/
getTags: function() {
if (this._cachedTags === null) {
this._cachedTags = _.union.apply(this, this.pluck('tags'));
}
return this._cachedTags;
},
sync: function(method, model, options) {
if (method === 'read') {
var me = this;
// Make an XHR request to get data for this demo
Backbone.ajax({
url: '/echo/json/',
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
json: JSON.stringify([
{ id: 1, name: 'one', tags: [ 'number', 'first', 'odd' ] },
{ id: 2, name: 'two', tags: [ 'number', 'even' ] },
{ id: 3, name: 'a', tags: [ 'alphabet', 'first' ] }
]),
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
var listColl = new ListDataCollection();
listColl.fetch({
success: function() {
console.log(listColl.getTags());
}
});
I guess two reasons for handling this in the collection:
It keeps the View code cleaner (This is given that we are not doing very complex logic in the tag extraction - It's just a simple _.pluck() and _.union().
It has 0 business logic involved - It can arguably belong to the data layer.
To address the performance issue:
It does go through the collection twice - However, if the amont of data you are consuming is too much for the client to process even in this case, you may want to consider asking the Backend to provide an API endpoint for this. (Even 500 pieces of data with a total of 1000 tags shouldn't bee too much for a somewhat modern browser to handle nowadays.)
Hmm. Does this help?
JSFiddle to go with this with the collection and the model: http://jsfiddle.net/dashk/G8LaB/ (And, a log statement to demonstrate the result of .getTags()).
I am trying to pull in a collection from the url attribute and am having some problems. It seems fetch() returns successfully, but then I cannot access the models in my collection with get(). I am using bbb and requireJS to develop my modules
var rooms = new Rooms.Collection(); // calls the rooms module
rooms.fetch({success: function(){
console.log(rooms.get(1)); // should output the first model
});
Here is my collection code in the rooms module:
Rooms.Collection = Backbone.Collection.extend({
model: Rooms.Model,
url: 'http://localhost:8888/projects/meeting-room/app/data/rooms.json'
});
If I output rooms, everything turns out fine. But when I try for a specific model, that is when I get an error.
[{
"id": 12345,
"name": "Ford",
"occupied": false
},
{
"id": 23458,
"name": "Chevy",
"occupied": false
},
{
"id": 83565,
"name": "Honda",
"occupied": false
}]
The collection.get method looks up a model by id. If you want to find a model by position, use collection.at instead.
Also notice that array indices in javascript are 0-based, so the first model can be found with:
var model = collection.at(0);
For convenience, Backbone collections also implement some of underscore's array and collection methods, including first. That means you can also find the first model with:
var model = collection.first();