Backbone.js - preload external template with Require.js text plugin - templates

I want to load external HTML template for Backbone view with Require.js text! plugin and make template path custom (get template path from View 'template' property). The problem is - template loads asynchronously and on render I still not have it. Here is my code:
custom View
define([
'views/abstract/base-view',
], function (BaseView) {
var AppView = BaseView.extend({
el: '#app-view',
template: 'app-view',
initialize: function () {
this.on('template:loaded', this.render, this);
BaseView.prototype.initialize.apply(this, arguments);
},
render: function () {
this.$el.empty();
var html = this.template();
this.$el.html(html);
}
});
return AppView;
});
and abstract View to inherit
define([], function () {
var AbstractView = Backbone.View.extend({
initialize: function(){
this.setTemplate();
},
setTemplate: function(){
var that = this;
require(['3p/text!templates/' + this.template + '.tpl'], function(tpl) {
that.template = _.template(tpl);
that.trigger('template:loaded');
});
}
});
return AbstractView;
});
It works but I don't like to listen to 'template:loaded' event for render. Any suggestions?
Thanks!

I had the same problem.
To avoid the async behaviour, there is no another way than add in some file the requires that will be dynamic and load all of them before.
I made something similar to an inject. I loaded in a require all the dynamic templates and put in a shared dictionary the paths of the template. Then I use _.template(require(inject["templateNAME"])) and I get the template at the moment.
The idea is something like the next:
define(["require"], function (require) {
require(["text!path/of/the/template",
... ]);
window.inject = {};
inject.templateNAME = "text!path/of/the/template";
Then you will use something like this:
var tpl = require(inject[this.templateName]);
this.template = _.template(tpl);

Related

How to make external template for backbone view

I am new to things like angular and backbone and am trying to understand the structure better. I built some views but if I leave any spaces in the template chunk it breaks everything.
var HomeView = Backbone.View.extend({
template: '<h1>Home</h1><p>This is the first test. I think this is the way.</p>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var AboutView = Backbone.View.extend({
template: '<h1>About</h1><p>This is the second test. I think this is the way.</p>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var CoolView = Backbone.View.extend({
template: '<h1>Cool</h1><p>This is the third test. I think this is the way.</p>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homeRoute',
'home': 'homeRoute',
'about': 'aboutRoute',
'cool': 'coolRoute',
},
homeRoute: function () {
var homeView = new HomeView();
$("#content").html(homeView.el);
},
aboutRoute: function () {
var aboutView = new AboutView();
$("#content").html(aboutView.el);
},
coolRoute: function () {
var coolView = new CoolView();
$("#content").html(coolView.el);
}
});
var appRouter = new AppRouter();
Backbone.history.start();
Is there a way to make this pull from external templates outside of the javascript. What's best practices if the pages are very elaborate?
Here is my jsfiddle link.
https://jsfiddle.net/galnova/k4ox8yap/14/
You can specify your template in your html
<script id='CoolViewTpl' type='template'>
<h1>Cool</h1><p>This is the third test. I think this is the way.</p>
</script>
Then in your render func, simply select the template by ID and get the content.
var CoolView = Backbone.View.extend({
template: "#CoolViewTpl",
initialize: function () {
this.render();
},
render: function () {
this.$el.html($(this.template).html());
}
});
If you are using a javascript module loader like RequireJs (which you'll probably find yourself doing when the application gets more complicated!) then the templates can be loaded from an external source using the RequireJs text plugin.
For example you might have a file called home.js which could look like:
require([ "backbone", "underscore", "text!templates/template.html" ],
function( Backbone, _, template ) {
return Backbone.View.extend({
template: _.template( template ),
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
}
);
Then an app.js file could contain your application logic and require your views:
require([ "backbone", "jquery", "home.js" ],
function( Backbone, $, HomeView ) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homeRoute',
'home': 'homeRoute',
// etc
},
homeRoute: function () {
var homeView = new HomeView();
$("#content").html(homeView.el);
}, // etc

Access view helper directly with Ember.Handlebars.helpers.view in ember 1.10.0

This code worked in ember 1.7.0:
var ViewTemplateHelper = Ember.Handlebars.makeBoundHelper(function(templateString, options) {
var dummy = Ember.View.extend({
classNames: ['view-template'],
template: Ember.Handlebars.compile(templateString)
});
var view = dummy.create();
if (options && options.hash) {
options.hash.content = template;
}
// Hack to prevent appendChild error
if (options.data.view._state === 'inDOM') {
options.data.view.rerender();
options.data.view.renderToBuffer();
}
return Ember.Handlebars.helpers.view.call(this, view, options); // undefined is not a function
});
export
default ViewTemplateHelper;
But now in ember 1.10.0 is gives the undefined is not a function error.
I tried to use Ember.Handlebars.helpers.view.helperFunction.call.
What do I miss?
The solution for this problem was not in a helper, but to use a component instead.
// components/content-element.js
import Ember from 'ember';
export
default Ember.Component.extend({
updateLayout: function() {
var store = this.container.lookup('store:main');
var projectId = this.get('project.id');
store.find('contentElement', {
key: this.get('key'),
project_id: projectId
}).then(function(contentElement) {
if (!Ember.isEmpty(contentElement.get('firstObject.value'))) {
var template = contentElement.get('firstObject.value');
var compiled = Ember.Handlebars.compile(template);
this.set('layout', compiled);
this.rerender();
}
}.bind(this));
}.observes('key').on('init')
});
We use the model contentElement for our templates. After setting the layout to the compiled Handlebars you have to run this.rerender();
For components you have to bind all to be used variables like this:
{{content-element key="name.of.element" project=project}}
In this case we use project in our dynamic template so we bound it. The key is used to get the right contentElement from the store.

How to use this.template if app.js is not in document ready?

I tried to follow http://ricostacruz.com/backbone-patterns/#inline_templates to avoid http://ricostacruz.com/backbone-patterns/#abuse however I have a typical view like this:
// in app.js
App.MyView = Backbone.View.extend({
className: "ui-widget-content",
template: _.template($("#myTemplate").html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
}
Then I just include app.js like this
<script src="./js/jquery-1.7.2.min.js"></script>
<script src="./js/jquery-ui-1.8.20.custom.min.js"></script>
<script src="./js/underscore.js"></script>
<script src="./js/backbone.js"></script>
<script src="./js/app.js"></script>
Browser complains that the $("#myTemplate") line in App.MyView.template is null (because document is not ready?). What shall I do?
Why not lazy-load your templates? Compile the template on first use and cache the compiled template in the view's "class". You could even add your base view to handle this caching with something like this:
var BV = Backbone.View.extend({
template: function(data) {
if(!this.constructor.prototype._template)
this.constructor.prototype._template = _.template($('#' + this.tmpl_id).html());
return this._template(data);
}
});
Then you could have things like this:
var V = BV.extend({
tmpl_id: 'tmpl',
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
and the #tmpl template would be compiled the first time it was used and it would be compiled at most once.
Demo: http://jsfiddle.net/ambiguous/hrnqC/
Notice the no wrap (head) in the demo and have a look at the console to see what is being compiled and how often.
my quick fix for this was to compile the template on view initialisation..
App.MyView = Backbone.View.extend({
className: "ui-widget-content",
template: '#myTemplate',
initialize: function(){
this.template = _.template($(this.template).html());
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
then everything else still works the same and you can put your render method in a base class..
The easiest thing to do is simply not cache the template:
App.MyView = Backbone.View.extend({
className: "ui-widget-content",
render: function()
{
var template = _.template($("#myTemplate").html())
this.$el.html(template(this.model.toJSON()));
}

Emberjs Handlebars precompiling

My Emberjs app is running slowly so I wanted to precompile my template to ease the runtime a bit. However I'm lost on how to proceed. I read http://handlebarsjs.com/precompilation.html and Emberjs introduction but no, all I could do was just creating a template file as instructed on the site, and I cannot figure out what and how to do with this template file in Emberjs.
How can I precompile templates in Emberjs? What should I do with the template file to use it in Emberjs?
To clarify, Thomas' example as-written is still doing the template compilation at run-time. I think his point, though, was that after you've loaded your precompiled Ember-Handlebars templates you can do this:
MyApp.MyView = Ember.View.extend({
template: Ember.TEMPLATES.mytemplate,
})
The problem with using Handlebars' built-in precompiler is that Ember's Handlebars implementation adds some functionality on top of what Handlebars itself provides, so you'll want to install the ember-precompile package, which provides basically the same interface as the handlebars command-line utility, but using Ember's Handlebars implementation.
This will avoid you having to change all your templateNames to templates and having to add in the Ember.TEMPLATES... in each view, since it automatically updates Ember's built-in template cache.
So, assuming you've already loaded your pre-complied templates.js file as output from ember-precompile templates/*.handlebars -f templates/templates.js, here's a more complete example snippet of a worker import/initialization order:
<script src="/lib/handlebars-1.0.0.beta.6.js"></script>
<script src="/lib/ember-1.0.pre.js"></script>
<script src="/lib/ember-data-latest.js"></script>
<script>
var App = Ember.Application.create();
</script>
<script src="/templates/templates.js"></script>
<script src="/js/models.js"></script>
<script src="/js/views.js"></script>
<script src="/js/controllers.js"></script>
<script src="/js/router.js"></script>
<script>
App.initialize();
</script>
You could also use Grunt.js and a handlebars template compiler. I've used the "grunt-ember-templates" plugin and it works well.
http://gruntjs.com/
https://npmjs.org/package/grunt-ember-templates
Here is a gist showing how to precompile handlebars templates and add the result to the Ember.TEMPLATES object, which Ember consults to resolve named templates.
https://gist.github.com/2013669
I'm using Gulp for builds, and precompiling templates looks like this:
var handlebars = require('gulp-ember-handlebars');
var concat = require('gulp-concat');
var SRC = {
TEMPLATES: ['app/templates/**/*.{hbs,html}']
};
gulp.task('templates', function() {
return gulp.src(SRC.TEMPLATES)
.pipe(handlebars({outputType: 'browser'}))
.pipe(concat('templates.js'))
.pipe(gulp.dest(DEST.SCRIPTS));
});
Then I use the Handlebars runtime library rather than the full version.
Ember-Handlebars: https://www.npmjs.org/package/gulp-ember-handlebars
You can precompile in the client's browser, as Thomas Bartelmess stated.
You can also precompile using handlebars via nodejs (taken from my very own Jakefile):
var Handlebars = require('handlebars');
precompile = (function () {
//Lovingly extracted from Ember's sources.
var objectCreate = Object.create || function (parent) {
function F() {}
F.prototype = parent;
return new F();
},
Compiler = function () {},
JavaScriptCompiler = function () {};
Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
Compiler.prototype.compiler = Compiler;
JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
JavaScriptCompiler.prototype.compiler = JavaScriptCompiler;
JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
JavaScriptCompiler.prototype.initializeBuffer = function () {
return "''";
};
JavaScriptCompiler.prototype.appendToBuffer = function (string) {
return "data.buffer.push(" + string + ");";
};
Compiler.prototype.mustache = function (mustache) {
if (mustache.params.length || mustache.hash) {
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
} else {
var id = new Handlebars.AST.IdNode(['_triageMustache']);
if (!mustache.escaped) {
mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
}
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
}
};
return function precompile(string) {
var ast = Handlebars.parse(string);
var options = {
knownHelpers : {
action : true,
unbound : true,
bindAttr : true,
template : true,
view : true,
_triageMustache : true
},
data : true,
stringParams : true
};
var environment = new Compiler().compile(ast, options);
return new JavaScriptCompiler().compile(environment, options, undefined, true);
};
}());
strPrecompiledTemplate = item.handlebarsTemplateFolders.map(function (dir) {
console.info("\tProcessing " + dir);
return readdirRecursiveSync(dir).map(function (file) {
console.info("\t\t" + file);
var content = fs.readFileSync(file, 'utf-8');
content = Handlebars.precompile(content);
file = file.replace(/\.[^\.]+$/, '').replace(/^src\//g, '').substr(dir.length).replace(/^\/+/, '');
// Pay attention: The wrap in Ember.Handlebars.template() is important!
return "Ember.TEMPLATES['"+file+"'] = Ember.Handlebars.template("+content+");";
}).join("\r\n");
}).join("\r\n");
You can set the precompiled handlebars output to the template property (not templateName) on you ember view. This is what ember also does under the hood
MyApp.MyView = Ember.View.extend({
templateName: "myViewWhatever",
template: Ember.Handlebars.compile('<p>{{blah}}</p>'),
})

Is it possible to load a Handlebars template via Ajax?

I would like to load additional templates on the fly. Is it possible?
You can register new templates in Ember.TEMPLATES. They will then be available to views.
An excerpt from my code (jQuery Ajax handler):
success: function(data) {
$(data).filter('script[type="text/x-handlebars"]').each(function() {
templateName = $(this).attr('data-template-name');
Ember.TEMPLATES[templateName] = Ember.Handlebars.compile($(this).html());
});
}
That's it.
I was just looking for the same thing and am about to have a play with the snippet below
credit: borismus on github https://gist.github.com/2165681
<script>
/*
* Loads a handlebars.js template at a given URL. Takes an optional name, in which case,
* the template is added and is reference-able via templateName.
*/
function loadTemplate(url, name, callback) {
var contents = $.get(url, function(templateText) {
var compiledTemplate = Ember.Handlebars.compile(templateText);
if (name) {
Ember.TEMPLATES[name] = compiledTemplate
} else {
Ember.View.create({ template: compiledTemplate }).append();
}
if (callback) {
callback();
}
});
}
</script>
I am using requirejs along with text plugin to load handlebar templates dynamically.
r.js optimizer will compile the handlerbar template to text file, which can be loaded easily using requirejs or even ajax