Structuring ember.js code - javascript

I'm trying to figure out the "right" way to structure a part of my Ember app
A basic overview:
Let's say I have a Course model defined similarly:
var Course = DS.Model.extend({
videos: hasMany('video', { async: true }),
categories: hasMany('category', { async: true })
});
Each video has a category associated with it.
In my CourseView or CourseController, how am I supposed to get a list of the videos for each category?
First, I tried using a computed property, but any call like this.get('model').get('videos') returns a Promise – not what I want.
Next, I saw some posts about using that and then setting properties on the controller after the Promise resolves, so the View would update when the properties changed, but this involves a lot of almost-callback hell and I'm hoping there's a better way to approach the problem.
Is there any way I can "load" the videos/categories before the view gets rendered? Ideally I'd be able to do something like:
{{#each category in categories}}
// render category-related stuff and only videos in this category
{{/each}}
Would it be better to have all of the videos on the Category? Is there any way I can define/add properties to a model without having them be sent to the server? I guess this can be seen as a general question about API design as well – should videos be under categories? Essentially there'll be a list of Courses, each with its own set of categories. Every video must fall under one category. After typing it out this way it makes sense for videos to be under categories if all of them must be under some category...
Edit: Here's an implementation I just went with for the sake of time:
My Category model now looks something like this:
var Category = DS.Model.extend({
name: attr('string'),
description: attr('string'),
videos: hasMany('video', { async: true })
});
In the Course template:
...
{{#each category in model.categories}}
{{render "course/category" category}}
{{/each}}
...
In the course/category template:
<h2 class="sub-title">{{model.name}}</h2>
{{render "course/video-gallery" model.videos}}
where course/video-gallery is a something I built for a collection of Videos.
This works fine, but now there are a bunch of Video models everywhere; on the Course (all of them) and on each Category (all of them that belong to that Category).

I don't know your specific use-case, but if I was designing something similar, I would have my video class have a hasMany('categories') relationship, expressing the real world notion that a video can be "tagged" with multiple categories, then the models would look something like this:
var Course = DS.Model.extend({
videos: hasMany('video', { async: true }),
categories: hasMany('category', { async: true })
});
var Video = DS.Model.extend({
categories: hasMany('category'),
//rest of the attributes
});
var Category = DS.Model.extend({
name: DS.attr(),
description: DS.attr(),
//other attributes
});
now, you need a list of videos for each category, to break it down into a simpler problem, lets imagine you can get all the videos for one category. That's easy enough, all we need to do is get the list of videos and select those that have this category's id in their categories list. So the resulting code in ember, if made a computed property on the controller would look like this:
videosByCategory: function() {
var cats = this.get('categories'), //grab courses' categories.
videos = this.get('videos'); //grab the courses' videos
return videos.filter(function(video) {
var videoCategories = video.get('categories');
return Ember.EnumerableUtils.intersection(videoCategories,cats).length > 0;
});
}.property('videos'),
now, in the template, if you access this computed property like this:
{{#each video in videosByCategory}}
//whatever goes here
{{/each}}
Hope this helps. I would sideload the videos with the course, when downloading the course.

Related

Backbone fetching data from model vs collection

I created a simple backbone project, where it fetches all books details and show it in UI. I am fetching all the books details from the model. not at all using collection something like this
var BookModel= Backbone.Model.extend({
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
This is working fine. But why do I have to use collections ? If I would have used collection it would be something like as follows
var BookModel= Backbone.Model.extend({
defaults:{
id:'',
name: '',
author: ''
}
});
var BookCollection= Backbone.Collection.extend({
model: BookModel,
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
The same effect. I don't understand why to use Collections in my case. please help me to understand this concept with my example why do I have to use collections here. I Googled a lot and could find the best answer.
Thanks
Imagine that you have two 2 routes:
/books
/books/:id
Now for getting a specific book you can send a request to /book/:id route, where :id is the id of the book.
GET /books/1
< { id: 1, title: 'On the Genealogy of Morality', ... }
Now what happens if you want to get all the books? You send a request to /books route.
GET /books
< [{ id: 1, title: '...', ... }, { id: 2, title: '...', ... }, ...]
Backbone follows the same principle. Model for a single book. Collection for many books. When you use a collection, Backbone creates one Model for each book. Using a Model for more than one item is wrong.
You said "Backbone creates one Model for each book.". at what step it creates?
It creates the models on the sync event, i.e. when the request for getting all the items is complete.
...how does it helps me. In my case I always fetch all books, not single book.
Backbone Collections always use Backbone Models. If you don't set the model property of the Collection explicitly, Backbone uses a normal Model, i.e. the model property of a Collection should always refer to a Model.
// https://github.com/jashkenas/backbone/blob/master/backbone.js#L782
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
Consider a Model as an object and a Collection as an array of objects.
More than anything, dividing your data into logical units (models) and grouping them into categories (collections) make it easy for you to reason about, manipulate and change your data. Once you build something that is even just a tiny bit more complex than what you have built, this becomes a priority. The point isn't that you are getting some magic functionality that you couldn't otherwise get. It's all javascript after all. The point is that models and collections provide data-structuring that is helpful when building dynamic applications. In fact that's the whole point of backbone and MV* in general, to provide helpful tools and abstractions. Collections are a solution to a whole host of problems that you will run into later down the road, when you add even the tiniest bit of extra complexity to your app.
So, you ask why you have to use collections and I guess the answer that you already knew is, you don't have to use collections. In fact, it sounds like you don't need to use an MV* library at all.

Backbone.js View, ParentView, Model, Collection and Template Best Practices

I am looking for some description of best practices for views and models/collections in Backbone. I know how to add models to collections, render views using templates and use views in parent views, however I'm looking for more context and perhaps some example links.
I've updated this question to be more specific.
Let's say you have a more grid layout with all kinds of variation, that gets pulled from the same collection. What would you do here to create this page? A simple child view repeated in a parent view won't work because the variation of the grid items is too great. Do you:
create tons of tiny views and collections and render all of these different views using the relevant collections into that one page?
create a complex template file that has a loop in it, that as you go through the loop, the loop outputs different markup?
Do people put multiple views inside a parent view, all from the same model?
Similarly, do people mix different models into the same parent view? For example movies and tv shows - these different models, can get they added to the same collection that renders that list?
Thanks!
You've asked good question. To answer it lets take a look to this from other angle:
On my exp i used to check first is there any logic on parent view, like sorting, validation, search and so on. Second - Collection with models or just model with array as property : is the model is independent and may exist without collection , for example you have navigation item, and there are no sense to make separate model for each item and navigation as collection as you will never use item itself. Another case you have user list. You may use user model in a lot of places and its better to make a separate model for user and collection to combine it.
Your case with UL may be resolved with single model and items properties with array of li, simple grid may have same approach as i don't see some special logic on wrap from your description.
But i should point out - i had close task to build mansory grid with collection parsed from server, items were models as it had different data structure, different templates and different events listener.
Taking decision i considered folowing:
item as independent tile, may be used as in grid and also independent.
item is model + template + view. different Models types helped to support different data structure, different Views types helped to support different events listeners and user interaction, different templates - diff looks.
collection as a tool to fetch initial data + load extra items + arrange mansonry view + create models according to fetched result.
UPDATE
Lets consider this pseudo code as masnonry implementation:
Models may looks like these:
var MansonryModel = Backbone.Model.extend({
/* Common methods and properties */
}),
MansonryVideoModel = MansonryModel.extend({
defaults: {
type: 'video',
videoUrl: '#',
thumbnail: 'no-cover.jpg'
}
}),
MansonryImageModel = MansonryModel.extend({
defaults: {
type: 'image',
pictureUrl: '#',
alt: 'no-pictire'
}
});
var MansonryCollection = Backbone.Collection.extend({
model: MansonryModel
});
Views could be like this:
var MansonryTileView = Marionette.ItemView.extend({
/* place to keep some common methods and properties */
}),
MansonryVideoTile = MansonryTileView.extend({
template: '#video-tile',
events: {
'click .play': 'playVideo'
},
playVideo: function(){}
}),
MansonryImageTile = MansonryTileView.extend({
template: '#image-tile',
events: {
'click .enlarge': 'enlargePicture'
},
enlargePicture: function(){}
});
var MansonryListView = Marionette.CollectionView.extend({
childView : MansonryItem
})
Hope this help

Ember.js: Passing arguments to render helper

Say I have two objects in my application: 1) Topic, 2) Categories.
Topic has a route /topic/:topic_id
Categories doesn't have a URL route. It contains the list of all available categories that topics can be in.
On the page for Topic, I would like to show a list of all categories, while highlighting the category that the topic is a part of (the 'active' category)
Original Solution
I had originally solved this with a component as follows (a lot of code removed for brevity):
Component Template:
(Goes trough each category and lists them. If the current one is active, it says so.)
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{#if this.active}} Active -> {{/if}}
{{this.name}}
<br />
{{/each}}
</script>
Component Object:
(Goes through the models via the CategoriesController, marks one category as the current active category for that topic via a "current" parameter passed in from the {{category-list}} component helper)
App.CategoryListComponent = Ember.Component.extend({
tagName: '',
didInsertElement: function () {
var categories = App.CategoriesController;
var current = this.get('current').get('id');
categories.get('content').forEach(function (c) {
if (c.id === current) {
c.active = true;
}
});
this.set('categories', categories);
}.observes('current')
});
Displaying the Category List:
In the Topic view ('category' is the property of Topic that says what category the topic is in):
{{category-list current=category}}
This all works, but I have a feeling that it is not the correct way to go about solving the problem. Components strike me as something that should be very reusable, and this really only is just an encapsulated part of one view.
Second Attempt
Now I am trying to use the category list as a view. So now instead of just a Component, I have a CategoriesRoute (not hooked up in the router though), a CategoriesController, and a CagetoriesView.
When the Topic resource loads up, it sets up the categories controller with the loaded model data:
App.TopicRoute = Ember.Route.extend({
model: function (params) {
return Em.RSVP.hash({
topic: this.store.find('topic', params.topic_id),
categories: this.store.find('category')
});
},
setupController: function (controller, context) {
this._super(controller, context.topic);
this.controllerFor('categories').set('model', context.categories);
}
});
The CategoriesController is just a standard array controller:
App.CategoriesController = Ember.ArrayController.extend({});
The CategoriesView is also very simple:
App.CategoriesView = Ember.View.extend({
templateName: 'categories',
tagName: ''
});
And finally the categories template:
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{this.name}}
<br />
{{/each}}
</script>
I am rendering the list with the {{render}} helper in the Topic template:
{{render "categories" }}
The Problem:
The above seems to work fine in terms of showing the list of categories. However, I can't figure out how to tell the view which category is active (the category in which the topic is in).
I looked around on the internet, and it looks like the {{render}} helper used to allow for an optional options hash (which is how I solved the problem with a Component in my first attempt), but it seems to have been removed in some recent version of Ember.
I found a StackOverflow entry that mentioned making my own {{render}} helper, but it looks like that is to dynamically change models, or something of that nature. I'd like to keep the models backing for the categories view, I just need to be able to tell it which category is active via the Topic.category property.
Or, was my first attempt the better solution? I am very new to Ember, so I'm not sure what would be the best strategy here. My instinct tells me I should use my second attempt rather than the first, but I am not positive.
Any help is very appreciated.
You are right in saying that components must be re-usable everywhere and should not be tied to any particular controller. I would use a view for this. The way I would do is, I would have a
App.CategoriesController = Em.ArrayController.extend({
itemController: 'category',
//some other stuff here
});
for categories and then have an itemController called
App.CategoryController = Em.ObjectController.extend({
setActiveState: function() {
if (this.get('parentController.current.id') === this.get('content.id')) {
this.set('active', true);
}
}
});
and in the template, you could say
{{#each category in categories}}
{{category.name}}
{{/each}}

Optimal URL structure for Backbone.js and Backbone implementation

I'm developing a RESTful API for a Quiz app, which is going to be built with Backbone.js and Marionette. I'm quite new to backbone and was wondering what de best URL structure would be. I have the following resources:
Answer,
Question which contains Answers,
Question Group which contains Questions,
Quiz which contains Question Groups.
Two possible URL structures come to mind:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /quizzes/:id/questiongroups/:id
GET /quizzes/:id/questiongroups/:id/questions
GET /quizzes/:id/questiongroups/:id/questions/:id
GET /quizzes/:id/questiongroups/:id/questions/:id/answers
or:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /questiongroups/:id
GET /questiongroups/:id/questions
...
Now, I have been trying to use both of these options. With the first one, I can't figure out how to define the collections as a property of the parent models in Backbone so that I can use fetch() on them. The problem with the second option is a bit different: as I understand it, Backbone derives the url for a model from its collection, but the collection is a child of another resource, whereas the url for getting a single resource uses another collection, namely the global set of resources.
I'm pretty sure I'd have to override url() in both cases. I tried some things but didn't come up with anything useable at all. Also, I'd rather not override every single url()-model in the app, changing the API structure to suit the preferences of Backbone seems like a better option to me.
Any pointers as to what seems the right way to do it with Backbone would be great!
Thanks
If questiongroups can only appear in a single quiz, then the first option (the hierarchical one) is an obvious choice. To comply with RESTful conventions, you might want to consider using singular nouns instead: /quiz/:id/questiongroups/:id/question/:id/answer/:id
To solve your fetching problem, I would recommend using nested backbone models as per this answer: https://stackoverflow.com/a/9904874/1941552. I've also added a cheeky little parentModel attribute.
For example, your QuizModel could look something like this:
var Quiz = Backbone.Model.extend({
urlRoot: '/quiz/', // backbone appends the id automatically :)
defaults: {
title: 'My Quiz'
description: 'A quiz containing some question groups.'
},
model: {
questionGroups: QuestionGroups,
},
parse: function(response){
for(var key in this.model){
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {
parse:true,
parentModel:this
});
}
return response;
}
});
Then, your QuestionGroups model could have the following url() function:
var QuestionGroups = Backbone.Model.extend({
// store metadata and each individual question group
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
Alternatively, if you don't need to store any metadata, you could use a Backbone.Collection:
var QuestionGroups = Backbone.Collection.extend({
model: QuestionGroup,
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
I'm afraid I haven't tested any of this, but I hope it can be useful anyway!

Limit the results of {{#each a in b}}

I have a Model called Course that has an array variable called users. Is there a way to just limit the results of, let say, IDs from users rather than the {{#each}} rendering every single id?
Such that
<script type="text/x-handlebars" data-template-name="course">
{{#each user in users 5}}
{{user.name}}
{{/each}}
</script>
Will show the first 5 IDs rather than every single id stored in users?
Here is the Ember-data model that I am using
App.Course = DS.Model.extend({
//Attributes
name: DS.attr('string'),
description: DS.attr('string'),
//Relations
users: DS.hasMany('App.User'),
});
I tried multiple times at creating a registerHelper, but where it always seems to go wrong is when an 'a in b' is present.
Any help or ideas would be appreciated. Thank you very much!
Rather than modifying the #each helper you could take a different approach to show only a limited amount of items when looping over the array with the #each helper.
See this for a possible implementation:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
Ember.Object.create({id:1, name:'Red'}),
Ember.Object.create({id:2, name:'Green'}),
Ember.Object.create({id:3, name:'Blue'})
];
}
});
App.IndexController = Ember.ArrayController.extend({
limitedContent: function() {
return this.get('content').splice(0, 2);
}.property('content')
});
The .property('content') defines a binding to the controller's content and takes care that the #each helper rerenders when the content changes.
And then in your template you loop over the limitedContent rather then the content:
<script type="text/x-handlebars" data-template-name="course">
{{#each color in limitedContent}}
{{color.name}}
{{/each}}
</script>
And here a working jsbin that show the concept mentioned.
Hope it helps
I was using the method in the accepted method, but ran into an issue with it.
The problem lay in that using splice in ember modifies the underlying data:
filtered: function() {
return this.get('content').splice(0,2);
}.property('content'),
splice removes the elements from the underlying content.
A better method is to use Ember's built-in slice function, changing the above code into
filtered: function() {
return this.get('content').slice(0,2);
}.property('content'),
And that's it, now it doesn't modify the underlying array, because slice returns a new array and leaves the underlying data untouched.
JSBin showing that splice modifies underlying data:
http://emberjs.jsbin.com/ridixawimugo/1/
JSBin with the fixed solution:
http://emberjs.jsbin.com/voceye/1/

Categories