pulling in a collection with backbone.js - backbone.js

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();

Related

Best way to layout application?

Edit: Fixed JSFiddle Link
So i've been playing with Backbone and Marionette since a couple of weeks. I did some courses on Udemy, read the docs for both Backbone and Marionette. I can grasp most of the logic but somehow my mind can't wrap itself around the best way to approach a SPA I am trying to create.
API
So I have a rest api that returns some data:
http://localhost:3000/index
returns the following:
[
{
"id": 1,
"magazineTitle": "Title",
"magazineEditie": "Date",
"indexTitle": "Index Title",
"indexSubtitle": "Index Subtitle",
"mediaType": "image", //can also be "video"
"mainMedia": "https://source.unsplash.com/B0iF3I4bLBQ"
}
]
What I want
I want to be able to use this response and populate it over 2 seperate views.
one view will use the data to create a navigation bar
the other view will use it to create a hero header
What I can't seem to understand
Somehow I can't wrap my head around how this would be set up without making this 'illogical'
I feel like loading 2 views with the same model inside my Marionette.Application doesn't make any sense? Or the fact that I fetch my Collections and/or Models there...
I need some help clearing up some Layout issues and best practices I guess.
My code
Besides the fact that I get the data from a localhost url and I have my app setup with webpack this is more or less the code that I am using:
JSFiddle Demo
I have figured out what I needed to do. Based on the documentation (which was kind of confusing me) I figured out a way to render two views with it's needed data.
I was using a CollectionView to read a single data point (1 model) I somehow couldn't figure out a way to immediately get a single Model.
So far the model I had to do:
index.model.js
const IndexModel = Backbone.Model.extend({
urlRoot: "http://localhost:3000/index",
default: {
id: 1,
magazineTitle: "Mag Title",
magazineEditie: "Date",
indexTitle: "Title",
indexSubtitle: "Subtitle",
mediaType: "image",
mainMedia: "http://placehold.it/1900x800/",
},
});
The urlRoot argument here is what I need to do the exact call.
Then I was still confused how to structure my app but I ultimately used Regions and Marionette.View to setup the application.
App.js
export default Marionette.Application.extend({
region: "#content",
onBeforeStart() {
const router = new Router();
},
onStart() {
this.showView(new AppView());
},
});
app.view.js
const AppView = Marionette.View.extend({
tagName: "main",
id: "app",
template: template,
regions: {
navigationRegion: "#main-navigation",
appRegion: "#main-region",
pagesRegion: "#pages-region",
},
initialize() {
this.headerData = new IndexModel({ id: 1 });
this.pagesData = new PagesCollection();
},
onRender() {
this.showChildView("appRegion", new HeroView({ model: this.headerData, }));
this.showChildView("pagesRegion", new PagesView({ collection: this.pagesData, }));
},
});
I had to create a wrapping AppView that utilises regions to specify where child views should render.

Backbone pass specific models in the collection to view?

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.

Wrap items in backbone collection?

I keep running into some confusing solutions and unclear ways to wrap items that match into a div using backbone.
I am just building a simple example for myself, and would like to nest all models in a collection that have the same attribute team, using a comparator works well in organizing the list, but for the life of me I can't find a clear solution to wrapping each so that I have more control over the list of players inside the team.
There has to be a clear easy solution for a beginner like me. I really just want to keep things as clean and simple as possible. My desired html result looks like below.
<div class="pacers">
<li>Paul</li>
<li>Roy</li>
</div>
<div class="bulls">
<li>Kirk</li>
<li>Taj</li>
</div>
Based on a backbone friendly json array like below.
[
{
"name": "Paul",
"team": "pacers"
},
{
"name": "Kirk",
"team": "bulls"
},
{
"firstname": "George",
"team": "pacers"
},
{
"name": "Taj",
"team": "bulls"
}
]
So using a comparator is awesome I just write this comparator : 'team' and it handles the list order for me, cool, but I dont have much control I would like to wrap the list in a more hierarchical system.
Another approach:
If you are using underscore's templates this could be one way of doing it. You can use underscore's groupBy function to group the list based on teams.
var teams = [
{
"name": "Paul",
"team": "pacers"
},
{
"name": "Kirk",
"team": "bulls"
},
{
"firstname": "George",
"team": "pacers"
},
{
"name": "Taj",
"team": "bulls"
}
];
var groupedList = _.groupBy(list, function(l){
return l.team;
});
console.log(JSON.stringify(groupedList));
This is how it would be grouped.
{
"pacers": [
{
"name": "Paul",
"team": "pacers"
},
{
"firstname": "George",
"team": "pacers"
}
],
"bulls": [
{
"name": "Kirk",
"team": "bulls"
},
{
"name": "Taj",
"team": "bulls"
}
]
}
You can then use for each loop and in template and generate HTML in following way. The groupedList is passed as teams to below template.
<%
_.each(teams, function(team, teamName){
%>
<div class="<%=teamName%>">
<%
_.each(team, function(player){
%>
<li><%=player.name%></li>
<%
});
%>
</div>
<%
});
%>
This would generate the HTML the way you expected.
NOTE:
The code snippets are given considering underscore templating, you might have to make changes based on what you use. Hope it helps.
Correct me if I am wrong the problem being described relates more to controlling the contents of each item in relation to it's model as well as how to simply render them in groups.
1) Niranjan has covered grouping out the data into separate lists but remember that this list returned is not a Backbone construct.
2) As per the manual the '_.groupBy' method should be available to you via the collection i.e.:
myCollection.groupBy(etc);
3) I would personally consider mapping the results of the groupBy back into models and pass each and every model into a separate view and render them from within the main list view.
var CollectionView = Backbone.View.extend({
initialize : function () {
// Note: I am pretending that you have a real collection.
this.collection.fetch().then(
this.addAll(true);
);
}
addOne : function (model) {
// call .render individual template items here for each model.
var view = new ItemView(model);
this.$el.append(view.render();
},
addAll : function (groupOpts) {
var col = this.collection;
if(groupOpts === true) {
// Do grouping (or do it in the model). Maybe put back into new collection?
}
_.each(col, function(model) {
this.addOne(model);
}, this);
},
render : function () {
// Render your template here.
}
});
var ItemView = Backbone.View.extend({
render : function () {
}
});
Not a complete example but that's the general pattern I would follow when attempting the same thing. Having an individual view/model for each item, in my opinion, gives you more control.
This could be handled in a pretty crazy view template (depends on your template language)... or you could use a simpler template/view and just make some more crazy collection queries (first using a pluck to get the team, de-dupping that array, then running some where's for each of the teams... but you can see how this gets crazy)
I'd vote for the view and view template should handle this... what are you using? Jade? Mustache?
Something like this - logical psuedo code here since I don't know your template language:
var team;
forEach player in players
if(!team) {
set team = player.team
print open holder and then the first row
} (team !== player.team {
set team = player.team
print close of previous holder, then open holder and then the first row of new team
} else {
print just the player row
}
Even so, you can see how this is a bit dirty in and of itself... but what you are describing is a view/presentation concern, and you can do it here with no new additional loops and maps and wheres (like you'd have to do if you did it in the data layer before calling the views)

Backbone: Creating models from a Collection of Models that contain a Collection of Models

I have an API that is producing GeoJSON data of a number of Venues and Events that are occurring at each Venue.
See an example output:
{
"crs":null,
"type":"FeatureCollection",
"features":[
{
"geometry":{
"type":"Point",
"coordinates":[
-122.330056,
47.603828
]
},
"type":"Feature",
"id":39,
"properties":{
"city_slug":"seattle",
"neighborhood_name":"Downtown",
"events__all":[
{
"category__category":"Gallery",
"eventid":16200847,
"description":"A Wider View, curated by Onyx Fine Arts Collective, features 60 works by 23 artists of African descent.",
"title":"A Wider View",
"cost":"Free",
"category__slug":"gallery",
"slug":"a-wider-view"
}
],
"venue_name":"City Hall Lobby Gallery",
"venue_address":"600 4th Avenue, Seattle, WA 98104, USA",
"city_name":"Seattle",
"neighborhood_slug":"downtown",
"venue_slug":"city-hall-lobby-gallery"
}
},
{
"geometry":{
"type":"Point",
"coordinates":[
-122.348512,
47.6217233
]
},
"type":"Feature",
"id":42,
"properties":{
"city_slug":"seattle",
"neighborhood_name":"Downtown",
"events__all":[
{
"category__category":"Museums",
"eventid":15455000,
"description":"The Art of Video Games tackles a 40-year history, with a focus on video game as art form. Nerdy heartstrings will be tugged in this nostalgia-inducing retrospective, including everything from the Atari VCS to Playstation 3.",
"title":"The Art of Video Games",
"cost":"$20",
"category__slug":"museums",
"slug":"the-art-of-video-games"
},
{
"category__category":"Museums",
"eventid":15213972,
"description":"There's just something about the black leather jacket. It's a garment that invariably comes with context, that cannot help but be an icon. Worn to Be Wild: The Black Leather Jacket explores the evolution of the leather jacket from \"protective gear to revolutionary garb.\"",
"title":"Worn to Be Wild: The Black Leather Jacket",
"cost":"$20",
"category__slug":"museums",
"slug":"worn-to-be-wild-the-black-leather-jacket"
}
],
"venue_name":"Experience Music Project | Science Fiction Museum.",
"venue_address":"325 5th Avenue North, Seattle, WA 98109, USA",
"city_name":"Seattle",
"neighborhood_slug":"downtown",
"venue_slug":"experience-music-project-science-fiction-museum"
}
}
],
"bbox":[
-122.348512,
47.6035448,
-122.3233742,
47.6217233
]
}
I want to map this into a Collection called VenueEvents. VenueEvents contains models called JsonVenues, and each of these Venues then have contain a collection called EventSet, containing a number of Event models (side topic: is naming a model 'Event' a recipe for disaster?).
My models are outlined as such:
var Event = Backbone.Model.extend({
parse: function(response){
return {
id: response.eventid,
slug: response.slug,
title: repsonse.title,
description: response.description,
category: response.category__category,
cost: response.cost
}
}
});
var EventSet = Backbone.Collection.extend({
model: Event,
}
});
var JsonVenue = Backbone.Model.extend({
initialize: function(attributes) {
console.log(attributes)
},
parse: function(response){
// var eventSet = new EventSet(response.properties.events__all);
return {
name: response.properties.venue_name,
address: response.properties.venue_address,
neighborhood: response.properties.neighborhood_name,
//eventSet: eventSet
}
}
});
// Is this actually a model?
var VenueEvents = Backbone.Collection.extend({
model: JsonVenue,
url: '/json/',
parse: function(response){
return response.features;
}
});
The VenueEvents and JsonVenue objects get created as expected, with the exception that the response.properties.events__all object doesn't seem to make it's way to the JsonVenue model (which is where I'd expect to use it to create the EventSet collection). I've put a console.log(attributes) in the initialize parameter of the JsonVenue object and it shows that while all the other values within features.properties of a JsonVenue make its way to the model, the events__all does not.
Is there any reason why this would be happening? Is this method of loading nested JSON data into models even possible? In most examples, people are only including the id of the nested object in their JSON output, and then (I assume) building a model out of that object in another JSON string and relating them based on the ID. This seems like it would require more traffic between the server and client. I also see people side-loading data, is this the recommended method of relating models in a single API call?
Thanks!
Well.. Ive just tried your code, using:
new VenueEvents(json, {parse: true});
to create your collection. And... it works just fine it seems...
Still, Backbone-relational might have the behavior you want to simplify your code (this is just an assumption, I've never tested it myself, nor have had a real look at it).

How properly initialize a model to use the `get` function?

I have a model, and when I do model.attributes.model I see the attributes of the model. One attribute is name, so model.attributes.model.name returns the right name. However, when I do model.get('name') I get the default that I had set in the model.
How do I set all the attributes of the model so that it works with get?
JSON used to build the model
[{
"model":{
"name":"My name",
"description":
"Description goes here!",
"vote_score":null
},
"context":{}
}]
Either modify your server-side code to return an array of model attributes, as you said in the comments, or modify your model definition to override the parse method :
var MyModel=Backbone.Model.extend({
parse: function(data) {
return data.model;
}
});

Resources