how to access object in template in Backbone [duplicate] - templates

I've watched some videos on the topic of backbone js. This is an example straight from the video. It is from 2012, so I'm thinking backbone rules/library have changed, but I can't figure out why this does not work at the moment. In the video, the person shows it running in the JS Fiddle, but I can't get it to work. (I've included the necessary libraries in JS Fiddle, i.e. underscore, backbone and jQuery)
var V = Backbone.View.extend({
el:'body',
render: function () {
var data = { lat: -27, lon: 153 };
this.$el.html(_.template('<%= lat %> <%= lon%>', data));
return this;
}
});
var v = new V();
v.render();
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>

You used to be able to parse and fill in an Underscore template in one go like this:
var html = _.template(template_string, data);
But as of Underscore 1.7.0, the second argument to _.template contains template options:
template _.template(templateString, [settings])
Compiles JavaScript templates into functions that can be evaluated for rendering. [...] The settings argument should be a hash containing any _.templateSettings that should be overridden.
You have to compile the template using _.template and then execute the returned function to get your filled in template:
var tmpl = _.template(template_string);
var html = tmpl(data);
// or as a one-liner, note where all the parentheses are
var html = _.template(template_string)(data);
In your case, it would look something like this:
var V = Backbone.View.extend({
el:'body',
render: function () {
var data = { lat: -27, lon: 153 };
var tmpl = _.template('<%= lat %> <%= lon %>');
this.$el.html(tmpl(data));
return this;
}
});
var v = new V();
v.render();
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.0/backbone-min.js"></script>

This can be useful
1: If you have more then one template or sometime you are using external template so it can be useful for you inside method you can write reusable code
var V = Backbone.View.extend({
el:'body',
temp: function (str) {
// reusable code
return _.template(str);
},
render: function () {
var data = { lat: -27, lon: 153 };
// calling your view method temp
var tmpl = this.temp('<%= lat %> <%= lon %>');
this.$el.html(tmpl(data));
return this;
}
});
var v = new V();
v.render();

Related

testing directive, dom manipulation not occurring - Angularjs/jasmine

I am running my first test on a directive, and while it seems that the directive is being invoked (there is a GET request being made for the directive's template url), the DOM manipulation that is supposed to occur in the link function is not - well it's that or I'm just not testing it correctly.
// Generated by CoffeeScript 1.6.3
(function() {
var sprangularDirectives;
sprangularDirectives = angular.module('sprangularDirectives', []);
sprangularDirectives.directive('productDirective', function() {
return {
scope: {
product: '='
},
templateUrl: 'partials/product/_product.html',
link: function(scope, el, attrs) {
return el.attr('testattr', 'isthisclasshere');
}
};
});
}).call(this);
Test:
'use strict';
describe('productDirective', function() {
var scope, el, directive, $httpBackend, compiler, compiled, html;
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(function() {
html = '<div data-product-directive product="currentProduct"></div>';
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
// jasmine.getHTMLFixtures().fixturesPath='base/partials/product';
$httpBackend.when('GET', 'partials/product/_product.html').respond(
' <div class="product">'
+' {{ currentProduct.name }}'
+' {{ currentProduct.page }}'
+' </div>'
);
scope = $injector.get('$rootScope');
el = angular.element(html);
compiler = $injector.get('$compile');
compiled = compiler(el);
compiled(scope);
scope.$digest();
});
});
it('Should have an isolate scope', function() {
scope.currentProduct = {name: 'testing'};
console.log(el.attr('testattr'))
console.log(el.isolateScope())
expect(el.scope().product.name).toBe('testing');
});
});
console.log(el.attr('testattr')) returns undefined...even though when I boot my browser up it's there. Some help would be awesome :) thanks
The element you are using is the pre-compiled element reference. The element you want is returned from the "compiled(scope)" method call:
compiler = $injector.get('$compile');
compiled = compiler(el);
var element = compiled(scope); // <-- This guy!
I use this snippet as a testing helper method:
var compileTemplate = function (scope, rawTemplate) {
var template = angular.element(rawTemplate)
var element = $compile(template)(scope)
scope.$digest()
var new_scope = element.scope()
return [element, new_scope]
}

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

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);

KendoUI: Render template with external variable

I'm loading up some stuff via AJAX, and rendering it through kendo, but want to add something to it that's not in the AJAX-loaded data but set as a var in js, something like this:
<script id="some-template" type="text/x-kendo-template">
<div style="background-image: url('#= commonBackground #');">
<h2>#= title #</h2>
</div>
</script>
And then call this:
$('button').on('click', function(e){
e.preventDefault();
var commonBackground = '/images/background.png';
var someData = {
title: 'Lorem Ipsum'
};
var someTemplate = kendo.template($('some-template').html());
$('#target').append(someTemplate(someData));
});
Now I know this is horrible code, but I hope I'm getting the point across of what I'm trying to do here. I'm unsure how I should reference commonBackground from my template, any help with this? Documentation didn't make me any wiser...
Your variable commonBackground needs to be global.
Either define it as:
var commonBackground = '/images/background.png';
$(document).ready(function () {
...
});
or:
var commonBackground = undefined;
$(document).ready(function () {
...
$('button').on('click', function(e){
...
commonBackground = '/images/background.png';
...
});
});
IMPORTANT: Why is not commonBackground defined inside the template data as title is? I think that it is easier / cleaner doing:
$('#button').on('click', function (e) {
e.preventDefault();
var someData = {
title : 'Lorem Ipsum',
commonBackground: 'background.png'
};
var someTemplate = kendo.template($('#some-template').html());
$('#target').append(someTemplate(someData));
});

Dojo Widget Templates

With reference to Simple Login implementation for Dojo MVC / - there is one point i don't understand. With regards to sample from phusick, the login dialog class does a call of dom.byId("dialog-template") - "dialog-template" is an id from the script which is the template for the dialog and should be present in an html template - not in the main html. So if I remove that, the call to dom.byId would fail
so my code structure is as follows
main.html ( calls Only main.js is called - nothing more)
main.js ( Contains the following)
require([
"dojo/_base/declare","dojo/_base/lang","dojo/on","dojo/dom","dojo/Evented",
"dojo/_base/Deferred","dojo/json","dijit/_Widget","dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin","dijit/Dialog",
"widgets/LoginDialog",
"widgets/LoginController",
"dijit/form/Form","dijit/form/ValidationTextBox","dijit/form/Button",
"dojo/domReady!"
], function(
declare,lang,on,dom,Evented,Deferred,JSON,
_Widget,
_TemplatedMixin,
_WidgetsInTemplateMixin,
Dialog,
LoginDialog,
LoginController
) {
// provide username & password in constructor
// since we do not have web service here to authenticate against
var loginController = new LoginController({username: "user", password: "user"});
var loginDialog = new LoginDialog({ controller: loginController});
loginDialog.startup();
loginDialog.show();
loginDialog.on("cancel", function() {
console.log("Login cancelled.");
});
loginDialog.on("error", function() {
console.log("Login error.");
});
loginDialog.on("success", function() {
console.log("Login success.");
console.log(JSON.stringify(this.form.get("value")));
});
});
Now LoginDialog.js and LoginDialogTemplate.html is the templatised widget for the dialog
and LoginController.js is the controller.
My LoginDialog.js is
define([
"dojo/_base/declare","dojo/_base/lang","dojo/on","dojo/dom","dojo/Evented","dojo/_base/Deferred","dojo/json",
"dijit/_Widget","dijit/_TemplatedMixin","dijit/_WidgetsInTemplateMixin",
"dijit/Dialog","dijit/form/Form","dijit/form/ValidationTextBox","dijit/form/Button",
"dojo/text!templates/loginDialogTemplate.html",
"dojo/text!templates/loginFormTemplate.html",
"dojo/domReady!"
], function(
declare,lang,on,dom,Evented,Deferred,JSON,
_Widget,
_TemplatedMixin,
_WidgetsInTemplateMixin,
Dialog,
Form,
Button,
template
) {
return declare([ Dialog, Evented], {
READY: 0,
BUSY: 1,
title: "Login Dialog",
message: "",
busyLabel: "Working...",
// Binding property values to DOM nodes in templates
// see: http://www.enterprisedojo.com/2010/10/02/lessons-in-widgetry-binding-property-values-to-dom-nodes-in-templates/
attributeMap: lang.delegate(dijit._Widget.prototype.attributeMap, {
message: {
node: "messageNode",
type: "innerHTML"
}
}),
constructor: function(/*Object*/ kwArgs) {
lang.mixin(this, kwArgs);
var dialogTemplate = dom.byId("dialog-template").textContent;
var formTemplate = dom.byId("login-form-template").textContent;
var template = lang.replace(dialogTemplate, {
form: formTemplate
});
var contentWidget = new (declare(
[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],
{
templateString: template
}
));
contentWidget.startup();
var content = this.content = contentWidget;
this.form = content.form;
// shortcuts
this.submitButton = content.submitButton;
this.cancelButton = content.cancelButton;
this.messageNode = content.messageNode;
},
postCreate: function() {
this.inherited(arguments);
this.readyState= this.READY;
this.okLabel = this.submitButton.get("label");
this.connect(this.submitButton, "onClick", "onSubmit");
this.connect(this.cancelButton, "onClick", "onCancel");
this.watch("readyState", lang.hitch(this, "_onReadyStateChange"));
this.form.watch("state", lang.hitch(this, "_onValidStateChange"));
this._onValidStateChange();
},
onSubmit: function() {
this.set("readyState", this.BUSY);
this.set("message", "");
var data = this.form.get("value");
var auth = this.controller.login(data);
Deferred.when(auth, lang.hitch(this, function(loginSuccess) {
if (loginSuccess === true) {
this.onLoginSuccess();
return;
}
this.onLoginError();
}));
},
onLoginSuccess: function() {
this.set("readyState", this.READY);
this.set("message", "Login sucessful.");
this.emit("success");
},
onLoginError: function() {
this.set("readyState", this.READY);
this.set("message", "Please try again.");
this.emit("error");
},
onCancel: function() {
this.emit("cancel");
},
_onValidStateChange: function() {
this.submitButton.set("disabled", !!this.form.get("state").length);
},
_onReadyStateChange: function() {
var isBusy = this.get("readyState") == this.BUSY;
this.submitButton.set("label", isBusy ? this.busyLabel : this.okLabel);
this.submitButton.set("disabled", isBusy);
}
});
});
My loginDialogTemplate.html is as follows
<script type="text/template" id="dialog-template">
<div style="width:300px;">
<div class="dijitDialogPaneContentArea">
<div data-dojo-attach-point="contentNode">
{form}
</div>
</div>
<div class="dijitDialogPaneActionBar">
<div
class="message"
data-dojo-attach-point="messageNode"
></div>
<button
data-dojo-type="dijit.form.Button"
data-dojo-props=""
data-dojo-attach-point="submitButton"
>
OK
</button>
<button
data-dojo-type="dijit.form.Button"
data-dojo-attach-point="cancelButton"
>
Cancel
</button>
</div>
</div>
</script>
Since the template has the id="dialog-template" so I guess when the widget calls the dom.byId("dialog-template"), it throws an error "TypeError: dom.byId(...) is null" at the line :-> var dialogTemplate = dom.byId("dialog-template").textContent;
So what am I doing wrong here?
If i use all the template scripts in the main html it works fine.
Asif,
Since you're passing in the templates in the define function, you don't need the dom.byId() to get the content. Try this:
Remove the elements from your HTML templates.
In LoginDialog.js, change your function arguments to:
...
Button,
dialogTemplate,
formTemplate
You'll need the formTemplate for the next change. I used 'dialogTemplate' instead of your 'template' so it's more obvious how it's replacing the code from the example. Next, change the beginning of the constructor to:
constructor: function(/*Object*/ kwArgs) {
lang.mixin(this, kwArgs);
//var dialogTemplate = dom.byId("dialog-template").textContent;
//var formTemplate = dom.byId("login-form-template").textContent;
var template = lang.replace(dialogTemplate, {
form: formTemplate
});
var contentWidget = new (declare(
...
I only left the commented code in so you can see what I changed. What it does is create a new template string called 'template' by substituting the {form} placeholder in your dialogTemplate HTML with the formTemplate you passed in. Then it's using that new template string to create the widget.

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()));
}