Backbone Marionette and ICanHaz (Mustache) templates configuration - backbone.js

I'm migrating a Backbone basic app to Marionette and I would like to use ICanHaz.js as a template system (based on Mustache).
I'm using AMD and Require.js and the only way to make ICanHaz.js working with it and Backbone was to use jvashishtha's version.
I first implemented the app in pure Backbone style.
In particular I used to load each template as raw strings with the Require.js' text plugin and then add the template to the ich object. This create a method in ich object that has the same name of the loaded template:
define([
'jquery',
'underscore',
'backbone',
'iCanHaz',
'models/Job',
'text!templates/Job.html'
], function( $, _, Backbone, ich, Job, jobTemplate) {
var JobView = Backbone.View.extend({
render: function () {
/* since the render function will be called iterativetly
at the second cycle the method ich.jobRowTpl has been already defined
so ich will throw an error because I'm trying to overwrite it.
That is the meaning of the following if (SEE: https://github.com/HenrikJoreteg/ICanHaz.js/issues/44#issuecomment-4036580)
*/
if (_.isUndefined(ich.jobRowTpl)){
ich.addTemplate('jobRowTpl', jobTemplate);
};
this.el = ich.jobRowTpl(this.model.toJSON());
return this;
}
});
return JobView;
});
So far so good. The problem comes with Marionette.
The previous code works fine in the Backbone version, but there is no need to define a render function in Marionette's views.
Marionette assumes the use of UnderscoreJS templates by default. Someone else has already asked how to use Mustache with Marionette.
From that answer I've tried to implement my solution. In app.js:
app.addInitializer(function(){
//For using Marionette with ICH
var templateName=''; //not sure if this would help, anyway it makes no difference
Backbone.Marionette.Renderer.render = function(template, data){
if (_.isUndefined(ich.template)){
//this defines a new template in ich called templateName
ich.addTemplate(templateName, template);
};
return ich.templateName(data);
}
But the console throws me:
Uncaught TypeError: Object #<Object> has no method 'templateName'
So the template has not been defined. Anyway, any hint if I'm in the right direction?

I think it's just a problem with your JS inside your renderer function.
This line:
return ich.templateName(data);
Will look for a template literally called "templateName"
What you want, since templateName is a variable is something like:
return ich[templateName](data);
Then it will interprete the value of the templateName variable instead.

Related

Reference to HTML elements in view, a convention?

I'm currently in the progress of learning Backbone.js and I'm using the book Developping Backbone Applications.
I have a questions about the reference to HTML elements and how they are stored. For example:
initialize: function() {
this.$input = this.$('#new-todo');
Here the HTML element with ID to-do is stored in the this.$input, why do we use the $ in front of input, is this merely a convention? If I change this.$input to this.input my code works fine. I find this confusing because the book states:
The view.$el property is equivalent to $(view.el) and view.$(selector) is equivalent to $(view.el).find(selector).
I would think that $(view.el) does something completely different than (view.el).
How is this.$input saved in Backbone.js? If I console.log it, it produces:
Object[input#new-todo property value = "" attribute value = "null"]
Could someone give me some insight? :)
Using $ infront of a variable name is just a naming convention. It helps developer in distinguishing variable holding jQuery objects from others.
view.$el is a helper variable provided by Backbone, so that we can use it directly, instead of explicitly forming the jQuery object. Hence view.$el is equivalent to $(view.el).
view.$el is assigned in setElement method:
setElement: function(element, delegate) {
// Some code
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
// Some code
}
Backbone.$ is reference to $ global variable exported by jQuery.
view.$(selector) is a method defined in View. It's definition does exactly same as $(view.el).find(selector)
$: function(selector) {
return this.$el.find(selector);
}

Backbone, RequireJS - Collection JSON

I want to load JSON data from file and load into a Collection.
Collection:
define(['backbone', 'model'], function(Backbone, Model) {
return Backbone.Collection.extend({
model: Model,
url: 'data/data.json'
});
});
Edit:
The problem now seem to be the data is collected after the render function is executed the first time. So if I comment out the render function and make the template update from the success function, it works, but this is of course not the proper way of doing it. Any better ideas?
this.template(this.coll.toJSON())
will probably solve your problem. Never use the collection itself when forwarding data to template.
If you're using handlebars or mustache, you should event use :
this.template({col : this.coll.toJSON()})
it's usually good practice to not use an array as root element for context.

Updating some parts of a Backbone Marionette ItemView

I'm a new to Backbone.js and also to Backbone.Marionette and I'm having some troubles trying to follow the best patterns for my application.
I'm creating a mobile web application encapsulated by PhoneGap. I have a common layout (Facebook like): 2 sidebars, a main header and a content zone where all the views are loaded.
My initialization code for a Backbone.Marionette application is:
var App = new Backbone.Marionette.Application();
App.addRegions({
leftRegion: '#left-drawer',
rightRegion: '#right-drawer',
headerRegion: '#header',
contentRegion: '#content',
mainRegion: '#main'
});
This are my regions where the content will be attached. Later, just after initialize the router I have the next:
App.headerRegion.show(App.Views.Header);
App.leftRegion.show(App.Views.LeftSidebar);
App.rightRegion.show(App.Views.RightSidebar);
This will load my content to this new regions. The problem occurs in the HeaderView. The template for my Header view:
<button id="open-left"><img src="assets/img/icons/menu-icon.png"></button>
<h1 class="title logo"><img src="assets/img/logo.png" height="28px" width="167px"></h1>
<button id="open-right"><img src="assets/img/icons/chat-icon.png"></button>
The ItemView it's very simple too:
var HeaderView = Backbone.Marionette.ItemView.extend({
template: _.template(template),
});
In some parts of the app I don't want to use the <img> in the header. Some pages of the app will need to update this to a text, for example something like <h1>Settings</h1>. My question is, what's the best way to do it? Or better, what is the way to do it? Right now I don't know where to start and the only idea I've in mind is to create
a new ItemView with another template.
Thanks in advance!
Marionette provides a useful function for this case, getTemplate, and you use this function isntead of the template : template declarations, like this.
SampleView = Backbone.Marionette.ItemView.extend({
getTemplate: function(){
if (this.model.get("foo"))
return "#sample-template";
else
return "#a-different-template";
},
});
So in the parts of yur application where you need a diferent template wihtout image you just change modify a value on the view or your model that can be used in this function to made the change on the template at the time you call render.

Best practice for shared objects in Backbone/Require Application

I've been developing Backbone applications for a little while now, and am just starting to learn to use Backbone with Require.js.
In my backbone app that I am refactoring, I defined a namespace like this: App.model.repo. This model is used over and over again in different views. I do the same thing with a few collections, for example, App.collection.files. These models and collections are bootstrapped in with the initial index file request.
I did find this example, which looks like a great way to get that bootstrapped data in. However, I am struggling with the best way to reuse/share these models and collection between views.
I can think of three possible solutions. Which is best and why? Or is there another solution I am missing entirely?
Solution 1
Define these common modules and collections in the index (when they are bootstrapped in), and then pass them along to each Backbone view as an option (of initialize).
define(['jquery', 'underscore', 'backbone', 'handlebars', 'text!templates/NavBar.html'],
function($, _, Backbone, Handlebars, template){
return Backbone.View.extend({
template: Handlebars.compile(template),
initialize: function(options){
this.repoModel = options.repoModel; // common model passed in
}
});
}
);
These seems clean as far as separation, but could get funky quick, with tons of things being passed all over the place.
Solution 2
Define a globals module, and add commonly used models and collections to it.
// models/Repo.js
define(['backbone'],
function(Backbone){
return Backbone.Model.extend({
idAttribute: 'repo_id'
});
}
);
// globals.js (within index.php, for bootstrapping data)
define(['underscore', 'models/Repo'],
function(_, RepoModel){
var globals = {};
globals.repoModel = new Repo(<?php echo json_encode($repo); ?>);
return globals
}
);
define(['jquery', 'underscore', 'backbone', 'handlebars', 'text!templates/NavBar.html', 'globals'],
function($, _, Backbone, Handlebars, template, globals){
var repoModel = globals.repoModel; // repoModel from globals
return Backbone.View.extend({
template: Handlebars.compile(template),
initialize: function(options){
}
});
}
);
Does this solution defeat the whole point of AMD?
Solution 3
Make some models and collections return an instance, instead of a constructor (effectively making them Singletons).
// models/repo.js
define(['backbone'],
function(Backbone){
// return instance
return new Backbone.Model.extend({
idAttribute: 'repo_id'
});
}
);
// Included in index.php for bootstrapping data
require(['jquery', 'backbone', 'models/repo', 'routers/Application'],
function($, Backbone, repoModel, ApplicationRouter){
repoModel.set(<?php echo json_encode($repo); ?>);
new ApplicationRouter({el: $('.site-container')});
Backbone.history.start();
}
);
define(['jquery', 'underscore', 'backbone', 'handlebars', 'text!templates/NavBar.html', 'models/repo'],
function($, _, Backbone, Handlebars, template, repoModel){
// repoModel has values set by index.php
return Backbone.View.extend({
template: Handlebars.compile(template),
initialize: function(options){
}
});
}
);
This I worry could get real confusing about what is a constructor and what is an instance.
End
If you read this far, you are awesome! Thanks for taking the time.
In my case, I prefer option 3. Although, to prevent confusion, I put every singleton instance in their own folder named instances. Also, I tend to separate the model/collection from the instance module.
Then, I just call them in:
define([
"instance/photos"
], function( photos ) { /* do stuff */ });
I prefer this option as every module is forced to define its dependencies (which is not the case via namespace for example). The solution 2 could do the job, but if I'm using AMD, I want my module as small as possible - plus keeping them small make it easier to unit test.
And lastly, about unit test, I can just re-define the instance inside my unit test to use mock data. So, definitely, option 3.
You can see an example of this pattern on an Open source app I'm working on ATM: https://github.com/iplanwebsites/newtab-bookmarks/tree/master/app
I would take a look at this example repo https://github.com/tbranyen/github-viewer
It is a working example of backbone boiler plate (https://github.com/tbranyen/backbone-boilerplate)
Backbone Boiler plate does a lot of unnecessary fluff, but what is really useful about it, is that it gives some clear directions on common patterns for developing complex javascript apps.
I'll try and come back later today to answer you question more specifically (if someone doesn't beat me to it :)
I prefer Solution 1. It is generally good to avoid using singletons, and using globals is also something to avoid, especially since you are using RequireJS.
Here are some advantages I can think of for Solution 1:
It makes the view code more readable. Someone looking at the module for the first time can immediately see from looking at the initialize function which models it uses. If you use globals, something might be accessed 500 lines down in the file.
It makes it easier to write unit tests for the view code. Since you could possibly pass in fake models in your tests.

Backbone structure

I'm new to backbone, but have watched several tutorial screencasts on it, both with and without requirejs.
My question involves the setup structure (both file structure if using require, and/or variable/object structure).
Most of the tutorials I have watched, seem to prefer a App.Models, App.Collections, and App.Views approach, and each item inside has the name of the module: ie,
App.Models.todo = Backbone.Model.extend({...});
App.Collections.todos = Backbone.Collection.extend({...});
App.Views.todo = Backbone.View.extend({...});
After a little research, trying to find someone that uses the same style as I would like to use, I finally found: File structure for a web app using requirejs and backbone. They seem to prefer more of a App.[Module Name] method: ie,
App.Todo.Model = Backbone.Model.extend({...});
App.Todo.Collection = Backbone.Collection.extend({...});
App.Todo.Views = Backbone.View.extend({...});
I personally prefer the App.[Module Name] structure over having my modules split up, but would like to know the benefits, if any, of having the different structures.
Which structure do you use, and how has it helped you over a different structure you may have seen or used in the past?
I like the approach described in this blog:
http://weblog.bocoup.com/organizing-your-backbone-js-application-with-modules/
If you are using requireJS you don't need/want to attach the models/views to a global namespace object attached to the window (no App.Views, App.Models). One of the nice things about using requireJS or a different AMD module loader is that you can avoid globals.
You can define a model like this:
define(['underscore', 'backbone'],
function(_, Backbone) {
var MyModel = Backbone.Model.extend({});
return MyModel;
});
Then you define a view:
define(['underscore', 'backbone', 'tpl!templates/someTemplate.html'],
function(_, Backbone, template) {
var MyView = Backbone.View.extend({});
return MyView;
});
Now you have a model and a view with no globals. Then if some other module needs to create one of these (maybe your App module), you add it to the define() array and you have it.

Resources