Printing Ember.js model on Handlebars - javascript

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.

Related

Nested array into backbone model

I have the following backbone model:
var Info = Backbone.Model.extend({
urlRoot: 'http://localhost:8080/info',
defaults : {
nombre: '',
tipo : '',
telf : 0,
icono : '',
direccion:[{
direccion:'',
latitud:'',
longitud:''
}]
},
idAttribute:"_id"
});
I want to change the value of the "direccion" atribute of the array inside "direccion".
I have used the following code but is not working:
//clone the array
var direccionArray = _.clone(this.collection.get(idInfo).get('direccion'));
direccionArray.direccion = this.$('#dir-info-edit').val();
Here I obtain the array with the values changed and works fine:
console.log(direccionArray);
Now I set the array into my backbone model as follow and is not working (the model don't change) and I get the same model (changing other atributes like "nombre" or "tipo" works fine but not with the array):
this.collection.get(idInfo).set('direccion',direccionArray);
console.log(this.collection.get(idInfo));
Could someone help me please?
As stated direccion attribute of model is an array that contains object at index 0.
When you trying to do:
//clone the array
var direccionArray = _.clone(this.collection.get(idInfo).get('direccion'));
direccionArray.direccion = this.$('#dir-info-edit').val();
This will not update the direccionArray[0], which you want to update, and just will add new attribute to your array object.
What you want to do is:
direccionArray[0].direccion = this.$('#dir-info-edit').val();
Try console.dir(direccionArray) the old array and you will see the difference.
Update:
Please see this jsfiddle with the explanation of the issue. The main reason for your case can be that you are tring to get the value with jquery's val() method on an element that is not an input.

How to dynamically parse a list of keypaths used within a Ractive.js template(s)

Is it possible to parse out a list of keypaths used by a dynamic partial / component?
If I start with a completely empty data object - and dynamically add a partial / component. Is possible to step through the dynamically added partial's html and discover which keypaths are used.
My intent is to then apply this list of keypaths to my data object on the fly. I'm building a drag and drop wysiwyg ui - and want designers to be able to add templates without touching ractive...
Here is some pseudo code which I hope illustrates what I'm trying to achieve.
<script id="foo" type="text/ractive">
<p>{{blah}}</p>
<p>{{blahblah}}</p>
</script>
-
var ractive = new Ractive({
el: '#container',
template: '{{#items}}{{partial}}{{/items}}',
data: {
items : [] // empty to start with
}
});
ractive.on( 'addpartial', function ( event ) {
// partial gets added
// process the partial to find out what keypaths it contains.. put those keypaths into array
var partialDataArray = [{'blah':''},{'blahblah':''}]
this.push('items' , { 'partial' : 'foo', partialDataArray }
});
The other option would be to set each 'partial' as a component - but I'd be repeating myself loads (and I'm trying to be all DRY etc)
Cheers,
Rob
This code borrows heavily from an example on dynamic components given by Martydpx https://github.com/martypdx - although I am unable to find the post where I found it.
I've created a setup function that basically parses everything out for me. It means that I can supply a file with a long list of templates (to be used by components)
<div data-component="first_component">
<h1>{{h1}}</h1>
<p>{{p1}}</p>
</div>
<div data-component="second_component">
<p>{{p1}}</p>
</div>
-- and here is the JS. Edit - please see JavaScript regex - get string from within infinite number of curly braces for correct regex.
var htmlSnippets = [];
var setup = function() {
// load in our ractive templates - each one is wrapped in a div with
// a data attribute of component.
$.get("assets/snippets/snippets2.htm", function(data) {
var snippets = $.parseHTML(data);
// Each over each 'snippet / component template' parsing it out.
$.each(snippets, function(i, el) {
if ($(el).attr('data-component')) {
var componentName = $(el).attr('data-component')
// reg ex to look for curly braces {{ }} - used to get the names of each keypath
var re = /[^{]+(?=}})/g;
// returns an array containing each keypath within the snippet nb: ['foo' , 'bar']
var componentData = $(el).html().match(re);
// this is probably a bit messy... adding a value to each keypath...
// this returns an array of objects.
componentData = $.map( componentData, function( value ) {
return { [value] : 'Lorem ipsum dolor sit amet' };
});
// combine that array of objects - so its a single data object
componentData = componentData.reduce(function(result, currentObject) {
for(var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
result[key] = currentObject[key];
}
}
return result;
}, {});
// and add the component name to this data object
componentData['componentName'] = componentName;
// We need to use the data elsewhere - so hold it here...
htmlSnippets.push(componentData );
// and lastly set our component up using the html snippet as the template
Ractive.components[componentName] = Ractive.extend({
template: $(el).html()
});
}
});
Ractive.components.dynamic = Ractive.extend({
template: '<impl/>',
components: {
impl: function(){
return this.components[this.get('type')]
}
}
});
});
}();
var ractive = new Ractive({
el: '#container',
template: '#template',
data: {
widgets: htmlSnippets,
pageContent: []
}
});

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/

backbone.js complex model

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.

Categories