Parse Backbone.js Handlebars [Object object] - backbone.js

Rather new to handlbars.js but my #each is functional until I try to access a nested attribute. Specifically author.name
<!-- Handlebars.js Blog Template -->
<script id="posts-tpl" type="text/x-handlebars-template">
{{#each post}}
<div class="blog-post">
<h2 class="blog-post-title">{{title}}</h2>
<p class="blog-post-meta">{{author.name}}</p>
<div>{{{body}}}</div>
</div>
{{/each}}
</script>
Chrome Javascript console shows author as having attribute 'name' with the correct data.
The Backbone.js view appears to be working also:
var PostsView = Parse.View.extend({
template: Handlebars.compile($('#posts-tpl').html()),
render: function(){
var collection = { post: this.collection.toJSON() };
this.$el.html(this.template(collection));
}
I have a full example at http://picze.co/blog

If you take a look at what you're passing into the template, you'll see the issue.
In your render function, try inspecting what this.collection.toJSON(); actually is.
Basically, it looks something like this:
[
{
author: {
__type: "pointer",
className: "_User",
objectId: "JCAjG1AIN0"
},
title: "WTFBBQ",
body: "<p><h3>test</h3> egg on face. This cannot be good. </p>"
}
]
Looks like the author is missing the name attribute
EDIT
Seems like the name attribute exists in the model but it's nested as another model so when you call .toJSON() you won't get it.
One way would be to include the author model as well:
var collection = { post: this.collection.toJSON() };
for (var i=0; i<collection.post.length; i++) {
if (collection.post[i].author) {
collection.post[i].author = this.collection.at(i).get('author').toJSON();
}
}
I'm not familiar with parse.js so this may not be the best solution, but it should work

Related

Read existing data from DOM into model, ng-model for static data?

I'm currently playing around with rewriting the functionality of an existing page using Angular. The gist of it is that I have a plain HTML page with a list of stuff, like this:
<ul>
<li class="item">
<h1>Foo</h1>
<ul class="categories">
<li class="category">Bar</li>
</ul>
</li>
...
</ul>
This is augmented by some Javascript which parses this data once and adds a dynamic category filter menu to the page. I.e. it extracts all li.category elements and displays a menu with them, and clicking on one of these categories filters the item list to display only items with the chosen category.
I've replicated the basics of that in Angular with a lot less code than I had before. However, I'm still doing a lot of jQuery traversing of the .item elements to build that initial list of categories:
myApp.controller('MyController', function ($scope) {
$scope.categories = [];
angular.element('.item').each(function () {
angular.element(this).find('.categories .category').each(function () {
var category = this.textContent;
for (var i = 0, length = $scope.categories.length; i < length; i++) {
if ($scope.categories[i].name == category) {
$scope.categories[i].count++;
$scope.categories[i].items.push(this);
return;
}
}
$scope.categories.push({ name : category, count : 1, items : [this] });
});
});
});
This does not seem to be in the spirit of Angular, and I'd like to replace it with something like:
<ul>
<li class="item" ng-item>
<h1>Foo</h1>
<ul class="categories">
<li class="category" ng-category>Bar</li>
</ul>
</li>
...
</ul>
A directive should then be able to parse all ng-item/ng-category elements and add them to the model/scope once. Something like ng-model, but for static data.
I have virtually no experience with Angular, how can I accomplish this; or shouldn't I want to do something entirely different in the first place?
For creating your own ng-item and ng-category directives, I suggest that you can go through Creating Custom Directives part in Angular Offical Develop Guide:
http://docs.angularjs.org/guide/directive
It will tell you how to begin creating your directive from add directive to module like this:
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
Edit:
This two is also useful tutorial:
http://www.ng-newsletter.com/posts/directives.html
http://www.befundoo.com/university/tutorials/angularjs-directives-tutorial/
Edit2:
To answer your comment:
Do you have a concrete sample of how I'd write a directive that reads data from its element and modifies the controller's scope?
I thought that it has clear explanation in Angular Official Guide:
.directive('myCustomer', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-iso.html'
};
});
In this example:
restrict: 'E' : directive name match element name
So directive look like this:
<my-customer></my-customer>
scope: { customerInfo: '=info'}
<my-customer info="myInfo"></my-customer>
this will bind myInfo to scope just like this expression:
$scope.customerInfo = myInfo;
this is a concrete sample of how to read data from its element and modify the controller's scope.

Backbone Underscore Template

So I'm checking out the changes related to the latest backbone/underscore version. Prior I have a project running with BB 0.5.2 and underscore 1.1.7. I'm noticing something strange with regards to defining a template property within a view in the new version, which gives me reservations in going forward with upgrading.
In my current version I would define a view as such:
var MyView = Backbone.View.extend({
template: _.template($('#exampleTemplate').html()),
initialize: function() {...},
render: function() { $(this.el).html(this.template(someObjectParam)); },
});
However, if I attempt to work in the same manner, using a simplified todo clone attempt as an example, I setup an html with an inline script template as such:
<script>
$(document).ready(function() {
app.init();
});
</script>
<script type="text/template" id="itemViewTemplate">
<div class="item">
<input type="checkbox" name="isComplete" value="<%= item.value %>"/>
<span class="description"><%= item.description %></span>
</div>
</script>
In my included JS file I have:
var ItemView = Backbone.View.extend({
el: 'body',
// Below causes error in underscore template, as the jquery object .html() call
// returns null. Commenting will allow script to work as expected.
templateProp: _.template($('#itemViewTemplate').html()),
initialize: function() {
// Same call to retrieve template html, returns expected template content.
console.log($('#itemViewTemplate').html());
// Defining view template property inside here and calling it,
// properly renders.
this.template = _.template($('#itemViewTemplate').html());
this.$el.html(this.template({item: {value: 1, description: 'Something'}}));
},
});
var app = {
init: function() {
console.log('Fire up the app');
var itemView = new ItemView();
}
}
So I'm left confused as to why defining the template property directly causes the call to retrieve the template html to return a null value, thus breaking the attempt to define an underscore template object (mouthful). However, if the definition is done within the initialize function, the call to retrieve the template html properly finds the template so its contents can be passed to the underscore template. Anyone see what I'm potentially missing?
Thanks in advance!
If this:
var ItemView = Backbone.View.extend({
//...
templateProp: _.template($('#itemViewTemplate').html()),
//...
});
is failing because $('#itemViewTemplate').html() is null, then you have a simple timing problem: you're trying to read the content of #itemViewTemplate before it exists. Your old version should suffer from exactly the same problem.
Either make sure everything is loaded in the right order (i.e. your views after your template <script>s) or compile the template in your view's initialize. You can check for the templateProp in your view's prototype and only compile it on first use if you want:
initialize: function() {
if(!this.constructor.prototype.template)
this.constructor.prototype.template = _.template($('#itemViewTemplate').html());
//...
}
Demo: http://jsfiddle.net/ambiguous/HmP8U/

Backbone.js - relationship between a View and Model?

I have a few different problems going on, I hope though this example is easy to follow. The code uses an HTML template with elements hidden by default (using CSS). The Backbone View uses data in a Model to display appropriate values OR hide the UI element if no value is present in the Mode.
Given a template where everything is hidden by default (using CSS), for example:
<script type="text/template" id="Person-Template">
<span class="fname" title="FirstName"></span>
<span class="lname" title="LastName"></span>
<span class="age" title="Age"></span>
</script>
To hide each UI element the CSS is:
span.fname,
span.lname,
span.age {
display:none;
}
My Backbone.js Model would therefore be:
PersonModel = Backbone.Model.extend({
defaults: {
fname: undefined,
lname: undefined,
age: undefined
}
});
The View (simplified) would be:
PersonView = Backbone.View.extend({
tagName: 'div',
initialize: function() {
this.model.on("fname", this.updateFName, this);
this.model.on("lname", this.updateLName, this);
this.model.on("age", this.updateAge, this);
},
updateFName: function() {
// Pseudo code
Get 'new' value from Model
Obtain reference to DOM element span.fname
Update span.fname
if (model value is empty) {
Hide UI element.
}
},
updateLName: function() {
// Same as above
},
updateAge: function() {
// Same as above
},
render: function() {
// Get model values to display
var values = {
FirstName : this.model.get('fname'),
LastName : this.model.get('lname'),
Age: this.model.get('age'),
};
// Load HTML template
var template = $('#Person-Template').html();
// Populate template with values
var t = _.template(template, values);
// Show / hide UI elements
this.updateFname();
this.updateLName();
this.updateAge();
}
}
Finally, the question: It seems hacky calling each updateXYZ() method from render() just to determine whether the UI element should be set to hidden or visible. I have a lot of attributes in my model and the code just seems a little absurd really.
I have been told on SO that the View should not be responsible for determining what should or should be displays. My questions is, well then what is responsible? The user may perform some (valid) aciton which clears the First Name, in which case I don't want my View displaying 'First name:' followed by no value.
First of all, you don't need to build your values by hand, just use toJSON:
var values = this.model.toJSON();
Then, you have to add your filled in template to your view's el:
this.$el.html(_.template(template, values));
and your template should probably include something to display in your template:
<script type="text/template" id="Person-Template">
<span class="fname" title="FirstName">F: <%= fname %></span>
<span class="lname" title="LastName">L: <%= lname %></span>
<span class="age" title="Age">A: <%= age %></span>
</script>
You don't separate functions for each of the three parts, you could just loop through them in your render:
_(values).each(function(v, k) {
var m = _(v).isUndefined() ? 'hide' : 'show';
this.$('.' + k)[m]();
}, this);
Now back to your events. There is no such thing as an "fname" event unless you've added a custom one. But there's no need for that, the model will trigger "change" and "change:fname" events when the fname is changed; you only need to care about "change" though:
initialize: function() {
_.bindAll(this, 'render');
this.model.on("change", this.render);
},
I've also bound render to your view instance using _.bindAll so that you don't have to worry about the third argument to this.model.on.
Now you have something that works: http://jsfiddle.net/ambiguous/46puP/
You can also push the "should this be displayed" logic into the template:
<script type="text/template" id="Person-Template">
<% if(fname) { %><span class="fname" title="FirstName">F: <%= fname %></span><% } %>
<% if(lname) { %><span class="lname" title="LastName">L: <%= lname %></span><% } %>
<% if(age) { %><span class="age" title="Age">A: <%= age %></span><% } %>
</script>
and simplify your render:
render: function() {
var values = this.model.toJSON();
var template = $('#Person-Template').html();
this.$el.html(_.template(template, values));
return this;
}
Demo: http://jsfiddle.net/ambiguous/W9cnJ/
This approach would probably be the most common and there's nothing wrong with it. I think you're misunderstanding what the previous answer was trying to tell you. The template chooses what pieces of information to display through <%= ... %> already so there's no good reason that it shouldn't see if fname, for example, is set before trying to display it. Depending on the nature of your data, you might want to use if(!_(fname).isUndefined()) and such in your template but a simple truthiness check is probably fine; the age might be an issue in some cases though so you might want to be a bit stricter with that.

backbone.js view id tag using information from model

I am using backbone.js,
I have a model that is formatted like this:
{
username:"name",
id:"1",
picture:"image.jpg"
}
I want to have a view like this:
<div id="1">
<span class="name">name<span>
<img src="image.jpg" />
</div>
from this template:
<script type="text/template" id="users-template">
<span class="name"><%= username %></span>
<img src="<%= image %>" />
</script>
but I get stuck when it comes to putting the users id into the views id attribute.
UserView = Backbone.View.extend({
id: this.model.id, //this is what I have tried but it doesnt work
initialize: function(){
this.template = _.template($('#users-template').html());
},
render: function(){
...
}
})
does anyone know how put the current models id into the id attribute?
I use that
id : function () {
return this.model.get("id");
}
U can get the model's data only using ".get()"
Properties like tagName, id, className, el, and events may also be defined as a function, if you want to wait to define them until runtime. #from Backbonejs.org
First of all, your model has picture but your template uses image, they should be the same or you'll get a "missing variable" error from your template.
The id attribute on a view is supposed to be the DOM id of an existing element; Backbone won't add it to the el, Backbone uses it to find the el. Besides, this won't be a view when id: this.model.id is executed (this will probably be window or some other useless thing) and the model property isn't set until the view is instantiated anyway.
The view's el is:
created from the view's tagName, className, id and attributes properties, if specified. If not, el is an empty div.
So drop the id from your view and use the default <div> for the el. Then, in your render method, you can set the id attribute on this.el; also, using a numeric id attribute causes trouble with some browsers so you should usually prefix it the id with something non-numeric:
render: function() {
this.el.id = 'v' + this.model.get('id');
this.$el.append(this.template(this.model.toJSON()));
return this;
}
Demo: http://jsfiddle.net/ambiguous/ZBE5z/
Open your console when running the demo and you'll be able to see the HTML that ends up in your view's this.el.
This happens because the model isn't set when the JavaScript initially gets executed. Try this:
initialize: function(){
this.id = this.model.id;
this.template = _.template($('#users-template').html());
},

Rendering problem

From the backbone documentation:
All views have a DOM element at all times (the el property), whether they've already been inserted into the page or not.
I have following very simple javascript file:
CBBItem = Backbone.Model.extend(
{
});
CBBTrackItem = Backbone.View.extend(
{
template: _.template("<span><%= title %></span>"),
initialize: function()
{
_.bindAll(this, "render");
},
render: function()
{
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
And a html page like this:
<script type="text/javascript">
$(function()
{
var itm1 = new CBBItem({ title: 'track 1'});
var itmUI1 = new CBBTrackItem({ model: itm1, id: "kzl" });
itmUI1.render();
});
</script>
<body>
<div id="kzl"></div>
</body>
My view item doesn't want to render although there is a created div on the page. I can trick the situation in many ways. For example doing something like this
var itm1 = new CBBItem({ title: 'track 1'});
var itmUI1 = new CBBTrackItem({ model: itm1, id: "big_kzl" });
$(itmUI1.render().el).appendTo("#kzl");
But, why is the main case not working?
Here's one possibility: you aren't setting the el for the view, so it doesn't know what to do with your template. Could you modify your view-calling code to look like this?
var itmUI1 = new CBBTrackItem({
model: itm1,
id: "big_kz1",
el: "#kz1"
});
itmUT1.render();
Alternatively, you could set the el value within the initialize of the view if the value never varies. The advantage to doing so is that callers of the view don't have to know this information and thus the view is more self-contained.
If the document already has the element you want to use as el for a particular view, you have to manually set that dom element as the el attribute when the view is initialized. Backbone provides you no shortcut for doing that.
I've experienced problems when passing values like ID and events in during construction as opposed to defining them during extension. You may want to check and see if that's the difference you're looking for.

Resources