Backbone templates: Using HBS to dynamically switch templates - backbone.js

I found this example on here of how to use the HBS plug-in to manage templates. It seems like a great solution. #machineghost suggests using RequireJS to include templates like this:
define(['template!path/to/someTemplate'], function(someTemplate) {
var MyNewView = BaseView.extend({template: someTemplate});
$('body').append(new MyNewView().render().el);
}
This is great, except I need to dynamically switch templates. Here is an example of one of my views:
define([
'jquery',
'underscore',
'backbone',
'models/tableModel',
'collections/tablesCollection',
'views/tablesView'
], function($, _, Backbone, tableModel, tablesCollection, tablesView) {
var t = new tablesCollection(null, {url: 'applications-lab'});
return new tablesView({ collection: t, template: 'applications-lab-template', url: 'applications-lab'});
});
As you can seem, I'm passing in the template when the view is rendered. What I'm wondering is can I pass in a variable to the define statement that would tell Backbone which template path to use? I'm a newbie to Backbone and especially RequireJS, and am not sure. Suggestions anyone?

Preliminary notes:
require.js does not allow parameters in a module definition, define accepts a dependency array and a definition function :
define(['dep1', 'dep2', ...], function(dep1, dep2) {
})
I would not define a view, instantiate it and inject its el in the same module but feel free to mix and match to your taste
Let's start with a module defining a simple view with a default template, let's say views/templated.js
define(['backbone', 'hbs!path/to/defaultTemplate'],
function(Backbone, defaultTemplate) {
var MyNewView = Backbone.View.extend({
template: defaultTemplate,
initialize: function(opts) {
opts = opts || {};
// use the template defined in the options or on the prototype
this.template = opts.template || this.template;
}
});
return MyNewView;
});
Now you just have to pull you view definition and an optional template with require:
require(['views/templated', 'hbs!path/to/anotherTemplate'],
function(MyNewView, anotherTemplate) {
// a view with the default template
var v1 = new MyNewView();
// a view with a new template
var v2 = new MyNewView({
template: anotherTemplate
});
});
To create a new class with an overridden default template, you would define a new module (views/override.js)
define(['views/templated', 'hbs!path/to/anotherTemplate'],
function(MyNewView, anotherTemplate) {
var AnotherNewView = MyNewView.extend({
template: anotherTemplate
});
return AnotherNewView;
});
Finally, you can always change the template on a given instance by directly assigning a new value.
var v = new MyNewView();
v.template = tpl;
A Fiddle simulating the views hierarchy : http://jsfiddle.net/nikoshr/URddR/
Coming back to your code, your blocks could look like
require(['models/tableModel', 'collections/tablesCollection', 'views/templated', 'applications-lab-template'],
function(tableModel, tablesCollection, tablesView, tpl) {
var t = new tablesCollection(null, {url: 'applications-lab'});
var v = new tablesView({
collection: t,
template: tpl
url: 'applications-lab'
});
// or, if you prefer and you don't render in initialize
v.template = tpl;
});

Related

Marionette CollectionView with requireJS: undefined is not a function

I will explain my problem with an example. I can make this piece of code work without any problem (using MarionetteJS v1.6.2):
http://codepen.io/jackocnr/pen/tvqHa
But when I try to use it with requireJs and I put it on the initialize method of a Marionette Controller, I'm Getting the following error:
Uncaught TypeError: undefined is not a function backbone.marionette.js:2089
The Error comes when I define the collection view:
var userListView = new UserListView({
collection: userList
});
I Can't figure out what is happening (this is the same code of the link above, but inside the controller initialize method)
define([
'jquery',
'underscore',
'backbone',
'marionette'
], function($,_,Backbone,Marionette){
var Controller = Backbone.Marionette.Controller.extend({
initialize: function(){
var User = Backbone.Model.extend({});
var UserList = Backbone.Collection.extend({
model: User
});
var UserView = Backbone.Marionette.ItemView.extend({
template: _.template($("#user-template").html())
});
var UserListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
itemView: UserView,
initialize: function() {
this.listenTo(this.collection, "add", this.render);
}
});
// instances
var jack = new User({name: "Jack"});
var userList = new UserList(jack);
var userListView = new UserListView({
collection: userList
});
// add to page
$("#user-list").append(userListView.render().el);
$("#add-user").click(function() {
var andy = new User({name: "Andy"});
userList.add(andy);
});
},
});
return Controller;
});
instead of using Backbone.Marionette in main.js shim : { use Marionette
marionette: {
exports: 'Marionette',
deps: ['backbone']
},
Thus while declaring any marionette inheritance juste use Marionette instead of Backbone.Marionette as such
var Controller = Marionette.Controller.extend
var UserView = Marionette.ItemView.extend
var UserListView = Marionette.CollectionView.extend
For some reason the newer version or Marionette.js behave this way. I guest it produce less code.
I have replaced the Marionette 1.6.2 version with the 1.5, and now it works as it does the version without requireJs. So I think it's a release bug or something like that.
Seems to be working fine for me. I made a simple project here.

backbone epoxy.js with underscore text template

Does anyone have pointers for using Backbone.Epoxy ( epoxy.js ) with HTML passed in via template.
aka given a View such as
define([
'jquery',
'underscore',
'backbone',
'epoxy',
'text!views/MyTemplate.html'
], function($, _, Backbone, Epoxy, Template ){
var MyView = Epoxy.View.extend({
template: _.template(Template),
bindings: {
".brand-name": "text:name",
".brand-name": "text:count",
},
// Perhaps render not needed given that epoxy bindings
render : function() {
var data = {
item: this.model,
_: _
};
this.$el.html( this.template(data) );
return this;
}
return MyView;
});
which renders the template but without any bindings.
and does one need a render method anymore..!
No you don't need the render method, just instantiate you view.
var view = new BindingView({model: bindModel});
Here is an example
But when you use the render method and replace the view.el you lost those bindings, in that case try to do :
this.$el.html( this.template(data) );
this.applyBindings();

Issue related to rendering multiple views through a single view

Question Updated
I am implementing a search and result for the same in the same page. i
have two seperate html views and views for search and result.
Here is what is happening..
From my router the below view is loaded. This view will load two other views called search and result. I have included the respective html views in the seperate views. but when i run the proj, it displays nothing.
Am i rendering the view in this file wrong ??
if i am rendering it wrong can somebody please tell me where is the mistake and how to rectify it ??
should i include the respective html templates of search and result also in this file ??
views.driver.search.js :
define(
[ 'jquery',
'underscore',
'backbone',
'app/models/model.driver',
'app/collections/collection.driver',
'app/models/model.driver.searchform',
'app/dashboard/views.driver.searchForm',
'app/models/model.driver.searchresult',
'app/dashboard/views.driver.searchResults',
],
function ($, _, Backbone, DriverModel,SearchResultCollection ,searchFormModel, searchFormView, SearchResultModel,SearchResultView) {
var DriverSearchView = Backbone.View.extend({
el: $('#content'),
initialize: function(){
this.render();
},
render: function () {
// var compiledTemplate = _.template(searchFormTemplate, {});
// this.$el.html(compiledTemplate);
this.searchFormInitModel = new searchFormModel();
var searchformView = new searchFormView({model:this.searchFormInitModel});
this.$("#content").html(searchformView.render());
var driver1 = new DriverModel({id:1,firstName:"driver1",lastName:'last',dob:'12/12/12001',});
var driver2 = new DriverModel({id:2,firstName:"driver2",lastName:'last',dob:'12/12/12001',});
var driver3 = new DriverModel({id:3,firstName:"driver3",lastName:'last',dob:'12/12/12001',});
var driver4 = new DriverModel({id:4,firstName:"driver3",lastName:'last',dob:'12/12/12001',});
this.searchResultCollection= new SearchResultCollection([driver1, driver2, driver3, driver4]);
var searchResultView = new SearchResultView({el:this.$el.find("#searchResult"),collection:this.searchResultCollection});
this.$("#content").html(searchResultView.render());
//this.$el.find("#searchResult").html(searchResultView.render());
}
}); // Our module now returns our view
return DriverSearchView;
});
I don't see an issue with your workflow. Reason you are not seeing your console.logs, is because you are returning an object literal containing your initialize function without actually calling the function. Is there a reason you need to return an object from your initialize module?
Anyhow, I am not entirely sure how you calling your app.js file, but try the following if you do not need to return an object back from your initialize module
define(
[
'jquery',
'underscore',
'backbone',
'app/router',
// 'datatable'
],
function ($, _, backbone, router){ //,datatable) {
var initialize = (function () {
console.log("App1");
debugger;
console.log("App2");
router.initialize();
console.log("App3");
})();
// Depending what you want to do here, you could just
// console.log("Router initialized...");
// router.initialize();
});

When is the right time to fetch a collection in Backbone js?

So I'm working on a backbone app, and trying to modularize things as much as I can using require.js. This has been my guide.
I'm having trouble getting my view to always fetch my collection. If I access my app from the base url (myapp.com/), and go to the route of my view, the collection is fetched. If I do not go to the view, and instead access it from myapp.com/#/campaigns, then the collection is not fetched.
Here is some relevant code.
router.js
define([
'jQuery',
'Underscore',
'Backbone',
'views/home/main',
'views/campaigns/list'
], function($, _, Backbone, mainHomeView, campaignListView ){
var AppRouter = Backbone.Router.extend({
routes: {
// Define some URL routes
'campaigns': 'showCampaigns',
// Default
'*actions': 'defaultAction'
},
showCampaigns: function(){
campaignListView.render();
},
defaultAction: function(actions){
// We have no matching route, lets display the home page
//mainHomeView.render();
}
});
var initialize = function(){
var app_router = new AppRouter;
Backbone.history.start();
};
return {
initialize: initialize
};
});
collections/campaigns.js
define([
'jQuery',
'Underscore',
'Backbone',
'models/campaigns'
], function($, _, Backbone, campaignsModel){
var campaignsCollection = Backbone.Collection.extend({
model: campaignsModel,
url: '/campaigns',
initialize: function(){
}
});
return new campaignsCollection;
});
views/campaigns/list.js
define([
'jQuery',
'Underscore',
'Backbone',
'collections/campaigns'
], function($, _, Backbone, campaignsCollection){
var campaignListView = Backbone.View.extend({
el:$('#container'),
initialize:function(){
this.collection = campaignsCollection;
this.collection.fetch();
},
render: function(){
var data = {
campaigns: this.collection,
_: _
};
$('#container').html('Campaigns length: '+data.campaigns.models.length);
}
});
return new campaignListView;
});
Any ideas on what I'm doing wrong? I believe it has something to do with calling this.collection.fetch() in the initalize function of the view. If that is the issue, where should I put fetch()?
The problem in your code is that your campaignListView fetch the collection when it is initialized and it is initialized only once. Your render method which is actually called from the router doesn't call your initialize method of campaignListView, when you change theurl your second time.
You have two options here :
1. return the class not the instance of your campaignListView and initialize it in the router :
// in your router code
showCampaigns: function(){
var campaignListViewInstance = new campaignListView(); // don't forget the brackets after your initialize function
campaignListViewInstance.render();
},
// in your campaignListView code :
return campaignListView; // without "new"
This will initialize the code everytime the router is hit.
2. place your fetch in the render method
// in your campaignListView code :
initialize:function(){
this.collection = campaignsCollection;
},
render: function(){
this.collection.fetch({success: function(collection) {
var data = {
campaigns: collection,
_: _
};
$('#container').html('Campaigns length: '+data.campaigns.models.length);
}); // this will be fetched everytime you render and also it has success callback
}
Also be aware that you should replace all your instance creating functions with brackets at the end
return new campaignListView; --> return new campaignListView();
return new campaignsCollection; --> return new campaignsCollection();
Another problem you will face is the async work of fetch. You should use success or event driven rendering of your collection, because fetch works in the background and you will have no data when you immediately call render after calling fetch.
+1 for your AMD approach.
I should really update the tutorial but it's better to not return instantiated modules. Maybe try checking out http://backboneboilerplate.com

Structuring my backbone.js application

I'm in the process of creating a Backbone.js app using Require.js. Each view file corresponds to one resource (e.g. 'News'). Within each view file, I declare a backbone
view for each action ('index', 'new', etc). At the bottom of the view file I receive
the necessary info from the router and then decide which view to instantiate (based on the info passed in from the router).
This all works well, but it requires lots of code and doesn't seem to be the 'backbone.js way'. For one thing, I'm rellying on the url to manage state. For another, I'm not using _.bind which pops up in a lot of backbone.js examples. In other words, I don't think I'm doing it right, and my code base smells... Any thoughts on how to structure my app better?
router.js
define([
'jquery',
'underscore',
'backbone',
'views/news'],
function($, _, Backbone, newsView){
var AppRouter = Backbone.Router.extend({
routes:{
'news':'news',
'news/:action':'news',
'news/:action/:id':'news'
},
news: function(action, id){
newsView(this, action, id).render();
}
});
var intialize = function(){
new AppRouter;
Backbone.history.start()
};
return{
initialize: initialize;
};
}
news.js ('views/news')
define([
'jquery',
'underscore',
'backbone',
'collections/news',
'text!templates/news/index.html',
'text!templates/news/form.html'
], function($, _, Backbone, newsCollection, newsIndexTemplate, newsFormTemplate){
var indexNewsView = Backbone.View.extend({
el: $("#content"),
initialize: function(router){
...
},
render: function(){
...
}
});
var newNewsView = Backbone.View.extend({
el: $("#modal"),
render: function(){
...
}
});
...
/*
* SUB ROUTER ACTIONS
*/
var defaultAction = function(router){
return new newsIndexView(router);
}
var subRouter = {
undefined: function(router){return defaultAction(router);},
'index': function(router){ return defaultAction(router);},
'new': function(){
return new newNewsView()
},
'create': function(router){
unsavedModel = {
title : $(".modal-body form input[name=title]").val(),
body : $(".modal-body form textarea").val()
};
return new createNewsView(router, unsavedModel);
},
'edit': function(router, id){
return new editNewsView(router, id);
},
'update': function(router, id){
unsavedModel = {
title : $(".modal-body form input[name=title]").val(),
body : $(".modal-body form textarea").val()
};
return new updateNewsView(router, id, unsavedModel);
},
}
return function(router, action, id){
var re = /^(index)$|^(edit)$|^(update)$|^(new)$|^(create)$/
if(action != undefined && !re.test(action)){
router.navigate('/news',true);
}
return subRouter[action](router, id);
}
});
While I feel like it's important to emphasize that there isn't really a "Backbone.js way", it does seem like you're replicating work Backbone should be doing for you.
I agree that it makes sense to have a specialized Router for each independent section of your application. But it looks at first glance like what you're doing in your "sub-router" section is just recreating the Backbone.Router functionality. Your AppRouter doesn't need to deal with /news URLs at all; you can just initialize a NewsRouter with news-specific routes, and it will deal with news-related URLs:
var NewsRouter = Backbone.Router.extend({
routes:{
'news': 'index',
'news/create': 'create',
'news/update/:id': 'update',
'news/edit/:id': 'edit'
},
index: function() { ... },
create: function() { ... },
// etc
});
As long as this is initialized before you call Backbone.history.start(), it will capture URL requests for its routes, and you never have to deal with the AppRouter. You also don't need to deal with the ugly bit of code at the bottom of your view - that's basically just doing what the core Backbone.Router does for you.
I'm using require.js and backbone as well I think the main difference that i'd suggest is that each file should return just one view, model, router or collection.
so my main html page requires my main router. That router is a module that requires a few views based on each of it's routes, and a bootstrapped model. Each router method passes the relevant bootstrapped model piece to the relevant view.
From there it stays really clean as long as each file is just 1 backbone thing (model, collection, view, router) and requires just the elements it uses. This makes for a lot of js files (I have about 100 for my current project) but that's where require.js optimization comes into play.
I hope that helps.
Why don't you structure your routes like this:
routes:{
'news':'news',
'news/edit/:id':'editNews',
'news/new':'newNews',
...
}

Resources