Backbone: Creating models from a Collection of Models that contain a Collection of Models - backbone.js

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).

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.

How to insert association/child data into Ext.data.list as separate entries

I'm using ST2 and using MVC. I'm very new to ExtJS and Sencha, so am not au fair the best practices for many things - and on this issue I've hit a dead end despite research.
I'll use a toy example to illustrate my issue below, but essentially I have a relationship as follows (which all works correctly from an association perspective).
Business X -- Location A
|
-- Location N
The Problem
I want to then populate the data into (for instance) an Ext.dataview.List, but to process it such that each location (i.e. child location) has its own separate entry in the table; not just a simple itemTpl formatting a single entry. However, at present I can't find any way to do that. Is it possible to hook into a List and format the data as I want, or should I be creating a new store? Ideally I want to make best use of the associations.
As a rough example, each entry would look like this, with some parent data and some child data:
---------------------------
|Smith Co - 1 Smith Street|
---------------------------
|Smith Co - 24 High Street|
---------------------------
|Tea[...] - 12 Tea Leaf |
---------------------------
|Tea[...] - 3 Bis Kit |
---------------------------
Example Code
Raw data
[
{
"id":1,
"name":"Smith Co",
"locations":[
{
"address":"1 Smith Street"
},
{
"address":"24 High Street"
}
]
},
{
"id":2,
"name":"Tea So Good",
"locations":[
{
"address":"12 Tea Leaf"
},
{
"address":"3 Bis Kit"
}
]
}
]
Location Model
Ext.define('Example.model.Location', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'address', type: 'string' }
],
proxy: { ... }, // Rest proxy that loads data as shown above.
BelongsTo: 'Example.model.Company'
}
});
Company Model
Ext.define('Example.model.Company', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' }
],
proxy: { ... }, // Rest proxy that loads data as shown above.
hasMany: { model: 'Example.model.Location', name: 'locations' }
}
});
Store
Ext.define('Example.store.Companies', {
extend: 'Ext.data.Store',
require: 'Example.model.Company',
config: {
model: 'Example.model.Company'
}
});
Controller
// (works correctly, relationships are traversable)
// Companies store is looked up and loaded in #launch()
View
Ext.define('Example.view.CompaniesList', {
extend: 'Ext.List',
xtype: 'companieslist',
config: {
layout: 'fit',
store: 'Businesses',
itemTpl: [
// Tpl is only to format inside each element
]
}
});
Solution Edit (15th Sept 2013):
Solution
I used the solution #rixo suggested (and I had been hoping to avoid in the original question).
I created a separate store for the list, and loaded the data I need into it by using a load listener on the Companies store. This seems to be the most graceful solution available, although it means you may need to add extra logic in various places to ensure it remains satisfactorily synchronised.
By pushing the location objects themselves into the new store the associations remain intact (i.e. you can still do location.getCompany()).
Yes, create another store for locations.
You may have tried a template like this:
itemTpl: [
'{name}',
'<tpl for="locations">',
', {address}',
'</tpl>'
]
But that will indeed only display the information, it won't let you interact with each location as an individual list item.
You could get it working by hacking the view's doRefresh method, but that's just going against the lib's intention and other developer' expectations.
Maybe the problem is that you can get the data only in this format, that is with locations as children of companies, and you can't get the server to send you a flat list of companies. In that case, I think the most meaningful approach would be to customize a reader to flatten locations from companies, and feed a standalone location store. The extractData method seems a very promising start for that (see how the JSON reader uses it to implements its root property).

Backbone: fetch information to model by demand

I want to make a Model-View behaviour in backbone.js, so that not all information will be loaded at the begining. For example I have a player infromation in the Player Model:
var Player = Backbone.Model.extend({
initialize: function() {
},
defaults: {
name: "",
surname: "",
someOtherInfo: ...
}
});
I want to show the players list in a table, where only player name and surname will be shown, however, if user clicks a player, more detailed information will be shown, by fetching other attributes (someOtherInfo).
Is there a way to do it when calling fetch?
Your API methods returning the list/collection can return different data than the API method for fetching a specific model. Just populate the collection with only the data needed for the list and when an item is selected fetch that specific model to fill in the blanks.

pulling in a collection with 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();

Backbone: Using a form to save model, as well as model relationship into the database

I have been struggling with a form in one of my Backbone views. This form is supposed to save the information for a project model (e.g. project name, project description, project members). While the project-specific information is saved without any issues into the corresponding database table, I did not manage to save the project-user relationships in a joint database table (projects_users, contains the corresponding ids for the two entities). The users that can be added to the project in the form are already present in the database, so nothing needs to be added into the users database table.
Could anyone put me on the right track here? I tried learning about relations in Backbone. These are two of the links that I have already looked into, but could not translate their content into a solution:
Backbone-relational
Model relationships in Rails and Backbone
Thank you,
Alexandra
EDIT
It was suggested that some code from my side would be useful. Since I do not have a good understanding of what I need to do, my code is pretty much a mess right now ... but let me try.
My form view:
App.Views.Projects.Common.Form = Backbone.View.extend({
...
submitted: function(formElement) {
var newData = this.serializeFormData(formElement);
this.model = new App.Models.Project({
name : newData.name,
description : newData.description
// Somehow put the users array associated with the project here ...
});
this.saveFormData(newData);
return false;
},
serializeFormData: function(formElement) {
var fields = formElement.serializeArray();
var serializedData = {};
$.each(fields, function(index, field) {
serializedData[field.name] = field.value;
});
return serializedData;
},
saveFormData: function(newData) {
var project = this.model;
// placeholder for the users that would be associated with the project
// parsing of the data from the form is required to get a corresponding array of user models
var users = App.users;
project.attributes.users = users;
// this line should save the project to the database table and the project-users relationships
// in the projects_users table; it needs the success and error functions
project.save({}, {});
},
...
})
For the project and user model files, I was thinking along these lines:
App.Models.Project = Backbone.RelationalModel.extend({
urlRoot: '/projects',
// Default attributes for the project.
defaults: {
description: "",
users: []
},
relations: [{
type : Backbone.HasMany,
key : 'users',
relatedModel : 'App.Models.User'
}]
});
App.Models.User = Backbone.RelationalModel.extend({
getId: function() {
return this.get('id');
},
getName: function() {
return this.get('name');
},
getEmail: function() {
return this.get('email');
}
});
Although the same information can be found as one of the comments to my question, I was asked to mark this as the answer, to make it easy for other people on StackOverflow. The solution that worked for me can be found here - see my own answer.

Resources