backbone.js complex model - javascript

I am building a web app using backbone.js as an MVC framework. I am struggling in designing my models. I have one parent/main object but it is quite complex/nested in nature i.e over 50 attributes and some nesting going on. What i am struggling with is:
I have something like:
{section1:{
key1:"value1",
key1:"value2"},
section2:{
key1:"value1",
key1:"value2"}
}
Should i flatten the object out like:
{section1_key1:"value1",
section1_key2:"value2",
section2_key1:"value1",
section2_key2:"value2"
}
or should I:
use the backbone-nested plug-in and pass in the large nested JSON object as is?
or create separate models for each section and nest somehow within the parent
model.
or simply create models for the child objects and not worry about the nesting and add some type of reference
Suggestions appreciated.

I'm struggling on this right now as well.
I'm leaning towards option 2.
I have each of the nested sections built out into separate models, and creating an interface similar to SQLObject in Python (or really any ORM)
So given a simple blog post idea:
var Tag = Backbone.Model.extend({
defaults: {
name: ''
},
validate: function(atts){
if(!atts.name || atts.name.length < 2){
return "You must provide a name that's longer than 2 characters";
}
}
});
var TagList = Backbone.Collection.extend({
model: Tag
})
var Entry = Backbone.Model.extend({
defaults: {
author: "Todd",
pub_date: new Date(),
content: "",
title: "",
tags: new TagList()
},
add_tag: function(tag){
var tag_collection = this.get('tags');
tag_collection.add({name: tag});
this.set({tags: tag_collection});
},
remove_tag: function(tag){
tag.destroy();
},
tags: function(){
return this.get('tags').models;
}
});
An alternate idea would to let initialize handle the de-nesting and creation and adding of items to the collection that is then stored as a property on that model.

Related

Efficient Marionette sorted CollectionView for multiple attributes

I am trying to find a way to efficiently display a sorted Marionette.CollectionView, sorted by multiple attributes without modifying the underlying Backbone collection. For this example, I am using 'name' and 'online' attributes, and want my CollectionView to be displayed in 2 parts:
online, alphabetically
offline, alphabetically
I have a single Collection of Backbone.Models and want to use this across my web application. So having a sort function on my Collection doesn't feel right to me.
My example code is as follows:
var MyCollection = Backbone.Collection.extend({
model:Backbone.Model
});
var myCollection = new MyCollection();
myCollection.set([
{ name : 'Freddy', online : true },
{ name : 'Zorro', online : false },
{ name : 'Charlie', online : false },
{ name : 'Alice', online : true }
]);
var MyView = ...
/*
omitted for brevity, though my template is like
<li>{{name}} {{#if online}}(Online){{/if}}</li>
*/
var MyCollectionView = Marionette.Collection.extend({
childView:MyView,
viewComparator: function (item1, item2) {
var item1Online = item1.get('online');
var item2Online = item2.get('online');
if (item1Online != item2Online)
return item1Online ? -1 : 1;
var item1Name = item1.get('name');
var item2Name = item2.get('name');
return item1Name.localeCompare(item2Name);
}
});
var myCollectionView = new MyCollectionView({collection:myCollection});
appView.getRegion('example').show(myCollectionView);
I would like this to be displayed as:
Alice (Online)
Freddy (Online)
Charlie
Zorro
This is fine when all of the data is added to the collection at once, or add/remove events but if one of the attributes is updated on a model that is already in the collection, the view does not update.
If Charlie's 'online' property changed to true - e.g by performing.
charlieModel.set('online', true)
I would like the CollectionView to have rendered automatically as:
Alice (Online)
Charlie (Online)
Freddy (Online)
Zorro
Any suggestions? Many thanks in advance.
From the backbone documentation
Collections with a comparator will not automatically re-sort if you later change model attributes, so you may wish to call sort after changing model attributes that would affect the order.
You can put somewhere convenient in your code a listener on a change in the model attributes your are targeting that will trigger a re-sort of your collection.
// for example in your collection
initialize: function() {
this.on('change:name', function() { this.sort() }, this);
}
The Marionette team advised having a separate Backbone collection behind the scenes, rather than using the viewComparator on the CollectionView.
Using reorderOnSort on the CollectionView made a huge difference (at least 10x speed up) in terms of render speed.
This option is useful when you have performance issues when you resort your CollectionView.
Without this option, your CollectionView will be completely re-rendered, which can be
costly if you have a large number of elements or if your ChildViews are complex. If this option
is activated, when you sort your Collection, there will be no re-rendering, only the DOM nodes
will be reordered.
My final example code:
var MyView = Backbone.Marionette.ItemView.extend({
modelEvents:{
'change:name change:online': 'render'
},
template:template
});
var MyCollection = Backbone.Collection.extend({
initialize : function(){
this.on('change:name change:online', this.sort, this);
},
comparator : function(item1, item2){
var item1online = item1.get('online');
var item2online = item2.get('online');
if (item1online != item2online)
return item1online ? -1 : 1;
return item1.get('name').localeCompare(item2.get('name'));
}
});
var myCollection = new MyCollection();
var MyCollectionView = Marionette.CollectionView.extend({
childView : MyView,
reorderOnSort : true
});
var myCollectionView = new MyCollectionView({
collection : myCollection
});
appView.region('example').show(myCollectionView);

Organize a backbone.js collection?

How does one create a collection (in backbone.js) that is organized by an id. For example I would think it's quite common that collections need to be organized by date, category, keyword, type the list goes on. In my case I am trying to find an elegant way to organize players to team.
I don't think I need a plugin for this, my goal is just to organize the players to be grouped inside a div while using one clean json file so I can have a nice organized list. Again ideally it would be nice to use one json file for this, and structure the HTML similar to how the json itself is structured in terms of nesting, in my example league being the parent of everything, then team being the parent of the players.
I have gone through many backbone tutorials multiple times, and understand the flow and syntax pretty comfortably, but all tutorials work with one collection outputting a simple list in no specific order.
Basically I want to be able to create a clean json array like below. Then turn it into nice organized HTML.
{
"league":
[{
"team":"Lakers",
"players": [
{
"name": "Kobe"
},
{
"name": "Steve"
}
]
},
{
"team":"Heat",
"players": [
{
"name": "LeBron"
},
{
"name": "Mario"
}
]
}]
}
So this json structure is valid but it's not one I have used it's nested a little bit more, so accessing the models requires a little different technique, I am hoping to learn if this kind of array is ideal? Also if so how would I group say Kobe, Steve inside a div with perhaps the class name Lakers whilst obviously separating it from LeBron and Mario keeping those two inside a div with again perhaps a class of Heat.
Another example I could use would be like a actors to movies collection, again is the json format ideal (seems like it to me) here I obviously group actors to their respected movie? I would greatly appreciate some tips on building a clean view or views with a template or templates for outputting this nice and tidy.
{
"movies":
[{
"title":"Prisoners",
"actors": [
{
"name": "Hugh Jackman"
},
{
"name": "Jake Gyllenhaal"
}
]
},
{
"title":"Elysium",
"actors": [
{
"name": "Matt Damon"
},
{
"name": "Jodie Foster"
}
]
}]
}
Again just working with this json data and backbone.js how do I create a maintainable view for this array?
Final Note: I was able to successfully group players to teams using two json files, and assigning a player and id that matched the team id, then looped the team collection with the player collection inside using where to organize it (I am trying to rethink this better). To me this is not taking advantage of backbone, it can get confusing and just seems wrong to me. So again I hope to improve my knowledge here and get better. I would immensely appreciate clear concise information, I really struggle to wrap my head around this topic :)
THANKS!!
Keep your JSON in a Backbone friendly structure, this will mean your models are easily organised once they are placed into the collection.
JSON example
[
{ league : 1, team : 'lakers', player : 'kobe' }
{ league : 1, team : 'lakers', player : 'steve' }
// And so on
]
Consider that most backbone collections are built via a RESTful JSON api this would be easy to fetch directly into a collection and then sorted. The comparator() function on the collection is run each time a model is added to the collection, or when you ask for it to run.
Backbone collection example
var Players = Backbone.Collection.extend({
initialize : function() {
// Grab the JSON from the API
// this.fetching is now a deferred object
this.fetching = this.fetch();
}
// Comparator example one, as a string
comparator : 'team',
// Comparator example two, as a function
comparator : function(model) {
return model.get('team');
}
});
The comparator as a function approach is obviously better suited to more complex sort algorithms, otherwise the comparator as a string approach would be better. Bear in mind that the function approach, though a string can be returned (such as above), -1 or 1 would be better return values to indicate it's sort position.
Comparator example with model comparison
comparator : function(modelA, modelB) {
var teamA = modelA.get('team'),
playerA = modelA.get('player'),
teamB = modelB.get('team'),
playerB = modelB.get('player');
if (teamA < teamB) { // string comparison
return 1;
} else if (playerA < playerB} {
return 1;
} else {
return -1;
}
}
Now whenever a model is added to the collection it is sorted into it's correct location, if using the last example, by team and then by player name.
Simple view example using the collection
var ViewExample = Backbone.View.extend({
el : "#example-container",
render : function() {
var self = this;
// Use the deffered object to make sure models are
// all available in the collection before we render
this.collection.fetching.done(function() {
self.collection.each(function(model) {
self.$el.append('<p>' + model.get('player') + '</p>');
});
});
return this;
}
});
// Create the view and pass in the collection
// that will immediately fetch it's models
var view = new ViewExample({
collection : new Players()
});
Code here is untested
Start by building a working model of your data. Your JSON suggests a hierarchy : a collection of teams that each have a collection of players. Here's a possible implementation :
var Player = Backbone.Model.extend();
var Players = Backbone.Collection.extend({
model: Player
});
var Team = Backbone.Model.extend({
constructor: function(data, opts) {
// I like my subcollections as attributes of the model
// and not on the settable properties
this.players = new Players();
Backbone.Model.call(this, data, _.extend(opts, {parse: true}));
},
parse: function(data) {
// Players are handled in a subcollection
if (_.isArray(data.players))
this.players.reset(data.players);
// They are removed from the model properties
return _.omit(data, 'players');
}
});
var Teams = Backbone.Collection.extend({
model: Team,
parse: function(resp) {
return resp.league;
}
});
Now you can create your root collection. Note that you could also fetch it instead of instantiating it with the data.
// teams list
var teams = new Teams(data, {parse: true});
// for example, to get all players in all teams
var allplayers = _.flatten(teams.map(function(team) {
return team.players.models;
}));
console.log(_.invoke(allplayers, 'get', 'name'));
And a demo : http://jsfiddle.net/8VpFs/
Once you have your structure in place, you can worry about rendering it. Let's imagine you have this (Underscore) template
<ul>
<% _(teams).each(function(team) { %>
<li><strong><%= team.team %></strong>
<ul>
<% _(team.players).each(function(player) { %>
<li><%= player.name %></li>
<% }); %>
</ul>
</li>
<% }); %>
</ul>
You can alter your Team model to output a serialized representation of you model:
var Team = Backbone.Model.extend({
// ... as before
toJSON: function() {
var json = Backbone.Model.prototype.toJSON.call(this);
json.players = this.players.toJSON();
return json;
}
});
and render your template
var tpl = _.template($('#tpl').html());
var html = tpl({
teams: teams.toJSON()
})
$('body').append(html);
This usually would go into a view.
http://jsfiddle.net/8VpFs/1/
With a fetch
var teams = new Teams();
teams.fetch().then(function() {
var tpl = _.template($('#tpl').html());
var html = tpl({
teams: teams.toJSON()
});
$('body').append(html);
});
http://jsfiddle.net/8VpFs/2/

Better way to build JSON array and retrieve its elements

Here's how I'm initializing and building an array:
var newCountyInfo = new Object();
newCountyInfo.name = newCountyName;
newCountyInfo.state = newCountyState;
newCountyInfo.zips = newCountyZips;
newCountyInfo.branchID = newCountyBranchID;
So I have my four elements in the array. I'm then passing newCountyInfo to another function to pull out the elements for display in some HTML elements.
The only way I know how to get to the individual elements in the function that uses them is this:
JSON.parse(JSON.stringify(newCountyValidation)).name
JSON.parse(JSON.stringify(newCountyValidation)).state
... etc...
There's got to be a better/shorter/more elegant way of doing this!
What is it?
Why are you serializing at all? I don't understand what JSON has to do with this, unless you're using web workers, ajax, or something else which demands serialization. Start with object literal syntax:
var newCountyInfo = {
name: newCountyName,
state: newCountyState,
zips: newCountyZips,
branchID: newCountyBranchID
};
And just pass the whole object to the other function:
someOtherFunction(newCountyInfo);
Which can access the fields using plain old property accesses:
function someOtherFunction(foo) {
console.log(foo.name); // whatever was in newCountyname
}
No JSON whatsoever.
Something like this should work just fine:
var newCountyInfo = {
name: newCountyName,
state: newCountyState,
zips: newCountyZips,
branchID: newCountyBranchID
}
function test(newCountyValidation)
{
alert(newCountyValidation.name);
}
test(newCountyInfo);

Accessing child stories from parent record

I am building a rallygrid to display parent level stories. For each row, I want to iterate all the children of that story and pull some information from each child story. e.g.
Ext.Array.each(data, function(record) {
//Perform custom actions with the data here
//Calculations, etc.
recName=record.get('Name');
if (recName.search(/\[Parent\]/i) != -1) {
// Grab Child Iterations
if (record.get('Children').length) {
var childlist = record.get('Children');
for (var child in childlist) {
// I want to get each child's Iteration !!!
}
} else {
childIter = "none";
}
records.push({
FormattedID: record.get('FormattedID'),
ScheduleState: record.get('ScheduleState'),
Name: recName,
NumChildren: record.get('Children').length,
PRDNumber: record.get('PRDNumber')
});
}
});
But, the record.get('Children') retuns objects that look like:
_rallyAPIMajor "1"
_rallyAPIMinor "34"
_ref "https://rally1.rallydev.com/slm/webservice/1.34/hierarchicalrequirement/7272142216.js"
_refObjectName "[Comp] User Story"
_type "HierarchicalRequirement"
I'm assuming there's some Ext call that will take the _ref URI, download it and parse out the JSON into a nice object I can start doing childrecord.get('field') on, but for the life of me, I can't find the right function to call.
You can use the load method of the record's model to retrieve a specific item as mentioned in this question/answer:
Rally App2.0 - Retrieve a specific story
In your case you can get the model from the existing record:
var model = record.self;
model.load(child.get('ObjectID'), {
//options
});
However in your case if you're just looking for some info on each child story's iteration you can probably just include it in the fetch of your initial WsapiDataStore used to load the parent:
fetch: ['Children', 'Iteration', 'StartDate', 'EndDate']

Printing Ember.js model on Handlebars

I have the following ember.js code:
app.js:
Game = Ember.Application.create({
ready: function() {
Game.gameController.getChallenge();
Game.gameController.participants.pushObject(Game.participants("player1","player1.jpg"));
}
});
Game.challenge = Ember.Object.extend({
challengetype: null,
description: null,
id: null
});
Game.participants = Ember.Object.extend({
name: null,
image: null
});
Game.gameController = Ember.ArrayController.create({
content: [],
current: "",
participants: [],
getChallenge: function() {
var self = this;
var url = "http://api:9393/challenge";
$.getJSON(url, function(data) {
self.insertAt(0,Game.challenge.create(data.challenge));
self.set('current', data.challenge.description);
});
},
});
Game.NewChallengeView = Ember.View.extend({
click: function(evt) {
Game.gameController.getChallenge();
}
});
and the template:
<div id="participants">
{{#each Game.gameController.participants}}
{{name}}
{{/each}}
</div>
current challenge:
<div class="tooltips-gray">
{{Game.gameController.current}}
</div>
I have 2 questions:
I'm using current as a string because I don't know how to use get the first element of the array. My idea is print the first element of the array with a different css class.
The second is, how print the participants array. If I put 4 players on participants array, each makes 4 iterations. But I don't know how to print the fields such as name or image.
Thanks.
I've converted your code to a working fiddle here: http://jsfiddle.net/KbN47/8/
The most natural way to bind to the first element of the array is using firstObject. As of Ember 0.9.5, firstObject and lastObject are not observable, due to performance impact. The Ember team hopes to fix this in the future.
If you don't plan to make many changes to the array in your app, the naive override of firstObject I provided in the fiddle should work for you.
Regarding your second question, I extracted the participants to separate controller for clarity and fixed a few issues with your code.

Categories