I have a problem using knockoutjs with custom template bindings.
Suppose I have a HTML body like this:
<div id="1">
<div data-bind="template:{name: '2', data: data}"></div>
</div>
<div id="2">
<h3 data-bind="text: caption"></h3>
</div>
JS code looks like this:
var ViewModel2 = function () {
this.caption = ko.observable("Caption");
}
var ViewModel1 = function () {
this.data = new ViewModel2();
}
ko.applyBindings(new ViewModel1(), document.getElementById("1"));
If we test this code, everything will work just fine;
See JSFiddle example: http://jsfiddle.net/4eTWW/33/
Now suppose we want to make our custom template binding. We'll use 'templatex' binding instead of 'template'.
In HTML we need to change just one line:
<div data-bind="templatex:{name: '2', data: data}"></div>
Next, let's add custom template binding to JS:
/*Custom binding*/
ko.bindingHandlers.templatex = {
init: function (element) {
ko.bindingHandlers.template.init.apply(this, arguments);
},
update: ko.bindingHandlers.template.update
}
See: http://jsfiddle.net/4eTWW/35/
But in this case we have an error, saying that it can't find 'caption' in the model.
Now let's add template {} to html bindings:
<div data-bind="template: {}, templatex:{name: '2', data: data}"></div>
See: http://jsfiddle.net/4eTWW/36/
And now everything works just fine.
It seems that while binding parent div it can't determine that child div is a template.
So how can I mark it as a template in my custom template binder?
Thanks.
You have wrong update handler, change to this:
ko.bindingHandlers.templatex= {
init: function(element) {
// do things
return ko.bindingHandlers.template.init.apply(this, arguments);
},
update: function(element) {
return ko.bindingHandlers.template.update.apply(this, arguments);
}
}
Here is working fiddle: http://jsfiddle.net/vyshniakov/4eTWW/39/
I don't think you can use a custom binding to create a new template engine. You need to register your custom engine with ko.setTemplateEngine().
From the knockoutjs source:
If you want to make a custom template engine,
[1] Inherit from the ko.templateEngine class (like ko.nativeTemplateEngine does)
[2] Override 'renderTemplateSource', supplying a function with this signature:
function (templateSource, bindingContext, options) {
// - templateSource.text() is the text of the template you should render
// - bindingContext.$data is the data you should pass into the template
// - you might also want to make bindingContext.$parent, bindingContext.$parents,
// and bindingContext.$root available in the template too
// - options gives you access to any other properties set on "data-bind: { template: options }"
//
// Return value: an array of DOM nodes
}
[3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
function (script) {
// Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
// For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
}
This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
and then you don't need to override 'createJavaScriptEvaluatorBlock'.
Example: http://jsfiddle.net/6pStz/ (see Note 7 on this page)
Related
I'm changing the value of a property in my controller and the helper fails to recompute them.
Sample code here:
My template looks like,
{{#if (my-helper info)}}
<span>Warning</span>
{{/if}}
In my controller,
changeAction: function() {
let that = this,
info = that.get("info");
set(info, "showWarning", true);
}
my helper,
import { helper as buildHelper } from '#ember/component/helper';
export default buildHelper(function(params) {
let that = this,
info = that.get("info");
if(info.showWarning ) {
return true;
} else {
return false
}
});
I see several issues with your code:
The template helper seems to get an object as it's first and only position param: {{my-helper info}} while info is { showWarning: true }. A template helper does recompute if the value passed it changes but not if a property of that value changes. A quick fix would be {{my-helper info.showWarning}}.
In your template helper your are trying to access the property on it's this context. As far as I know that's not supported. As you are using a positional param and it's the first one, it's available as first entry inparams array. So your template helper should look like:
export default buildHelper(function([info]) {
if(info.showWarning ) {
return true;
} else {
return false
}
});
What version of Ember are you using? If it's >= 3.1 you don't need to use this.get() in your controller. If you are using Ember < 3.1 you need to use info.get() also in template helper.
But as described before I would not recommend passing an object to a template helper as it's only updated if the object itself is replaced. Changing a property of it is not enough. You might be able to do so using Class-based Helpers but I wouldn't recommend to do so as it's error prune.
Using Meteor I got a template which consists of 2 other templates A & B.
A got a <select> Tag and B got a <canvas> on which something will drawn depending on the select of template A.
When the selection changes, A should call someMethod` of B, and this method should get some data from the db and then draw the canvas. How can I achieve this behaviour?
Simply using Template.B.someMethod = function (bla) ... and then in the JS of A calling Template.B.someMethod(hi) causes an error because someMethod accesses the DOM canvas Element of B which is not rendered when A changes for the first time at the loading.
What would be a fitting way to call the method? Or should I merge the two templates together?
no need to combine A and B templates. the purpose of the parent here is to coordinate data changes between A and B. i.e. you won't be calling functions on B, but rather giving it the data it needs so it can respond.
P (the parent) can push into A a function that A calls when its data changes via the select. P can then push that data into B through Meteor's normal reactivity. B can then respond to that change.
e.g.
<template name="P">
{{A changeHandler=getChangeHandler}}
{{B selection=selectedData}}
</template>
P.js:
Template.P.onCreated(function() {
this.data = new ReactiveVar();
});
Template.P.helpers({
getChangeHandler() {
let template = Template.instance();
return function(data) {
template.data.set(data);
}
},
selectedData() {
return Template.instance().data.get();
}
});
A.js:
Template.A.onCreated({
this.changeHandler = new ReactiveVar(Template.currentData().changeHandler); // i think this is right
});
and whenever A data changes:
let changeHandlerFn = template.changeHandler.get();
if (_.isFunction(changeHandlerFn)) {
changeHandlerFn(updatedData);
}
Besides #zim's answer, you can also use a variation of https://stackoverflow.com/a/43687311/5108796:
HTML (actually Spacebars)
<template name="Parent">
{{> Child1 sharedVar1=sharedVar}}
{{> Child2 sharedVar2=sharedVar}}
</template>
JavaScript
import { ReactiveVar } from 'meteor/reactive-var';
// Just initialize the variable. Could also be within the scope of a template.
var myReactiveVar = new ReactiveVar();
Template.Parent.helpers({
// This is what will be sent to Child1 and Child2.
sharedVar: function () {
return myReactiveVar;
}
});
Template.Child1.events({
'change select': function (event, template) {
// This will trigger a re-execution of Child2 autorun.
template.data.sharedVar1.set(myNewValue);
}
});
Template.Child2.onCreated(function () {
var sharedVar2 = this.data.sharedVar2;
this.autorun(function () {
// As usual, this is reactive.
var newValue = sharedVar2.get();
// Perform some operation using newValue…
});
});
(of course you can split these into several JS files)
When Blaze makes a call to a function defined inside a Template.xxx.helper, it passes one or more arguments. The first argument is an object which appears to be empty. What is it? What can it be used for?
Here's how to recreate my barebones test, using a Terminal window:
meteor create test
cd test
cat > test.html << EOF
<body>
{{> test}}
</body>
<template name="test">
<p>{{test1 "data"}}</p>
<p>{{test2 key="value"}}</p>
</template>
EOF
cat > test.js << EOF
if (Meteor.isClient) {
Template.test.helpers({
test1: function (argument) {
console.log(this, argument)
return "test1 helper: " + argument
}
, test2: function (argument) {
console.log(this, argument)
return "test2 helper: " + argument.hash.key
}
});
}
EOF
meteor run
Here's what I see in the browser console, after expanding the hash object:
Object {} "data"
Object {} S…s.kw {hash: Object}
hash: Object
key: "value"
__proto__: Object__proto__: Spacebars.kw
What is the this Object {}?
And in particular, is there a way I can use it to discover which HTML element triggered the call?
Inside a template helper, this is the data context of your template instance.
In your example the data context is not set, so it is returning an empty object. But that is not always the case. Imagine the following example:
<template name='parent'>
{{#with currentUser}}
{{> child}}
{{/with}}
</template>
In this case Meteor.user() has been set as the data context for the instance of Template.child, so Meteor.user() is bound to this in Template.child.helpers(). Which allows you to do the following:
Template.child.helpers({
greeting: function(){
console.log(this); // logs Meteor.user() || undefined
return 'Welcome back ' + this.username;
}
});
The data context can be set explicitly, via each or with blocks, or via parent template contexts. As in the example above you'll generally want to check against undefined when using this in helpers.
The short answer to your question whether this in a template helper can identify the DOM node(s) that invoke it is no. You may be able to dig it out of the helper's parameters via the prototype (I haven't checked), but I would consider this an antipattern. If you care where the helper is coming from, just include a parameter. Continuing the prior example:
<template name='child'>
<p>{{greeting}}</p>
<p>{{greeting 'special'}}</p>
</template>
And:
Template.child.helpers({
greeting: function(str){
if (str === 'special'){
return 'Welcome to first class Ambassador ' + this.username;
}
return 'Please take your seat in coach ' + this.username;
}
});
In my app, I am using the handlebars template plugin, I am little bit confused here, how can i integrate handlebars template to marionette Item View which is using separate template?
here is my code :
define([
'jquery',
'underscore',
'backbone',
'marionette',
'hbs!scripts/templates/login/loginTemp'], // this is my handlebars template.
function ($,_,Backbone,Marionette,loginTemplate) {
"use strict";
socialApp = window.socialApp || {};
socialApp.loginView = Backbone.Marionette.ItemView.extend({
tagName:'div',
className:'loginContainer',
template: '#loginTemplate' //this is template for login alone (from DOM )
});
return socialApp.loginView;
}
);
"loginTemp" - has all details what i require for login template.
You could override Marionette.TemplateCache.prototype.compileTemplate function to achieve needed behavior:
Marionette.TemplateCache.prototype.compileTemplate = function (yourRawTemplate) {
// In case if template is function
if (_.isFunction(yourRawTemplate)) {
return yourRawTemplate;
} else {
return Handlebars.compile(yourRawTemplate);
}
};
My "Marionette-Handlebars Boilerplate" might help you:
https://github.com/yuraji/marionette-handlebars-boilerplate
If you are using regions in your marionette application, such as
socialApp.addRegions({
loginRegion:'<id of the region>'
})
and if your template is written inside a script tag, then you can easily render your template in this manner:
//defining the view
socialApp.loginView = Marionette.ItemView.extend({
template:Handlebars.compile(document.getElementById('loginTemplate').innerHTML),
...
...//other code and view logic
});
and then,
var loginView = new socialApp.loginView(); //creating instance of the view
socialApp.loginRegion.show(loginView); //rendering it inside the region
I want to get handlebarjs template name using handlebar helper, how to do it? thanks! for example,
give:
a.hbs
<p>hello</p>
{{fileName}}
I want:
a.html
hello a
I think that the easy and the safe way, is to get the template of the current view.
And get your name, based in the identity, using Ember.TEMPLATES.
Since the TEMPLATES structure is { templateName: compiledTemplate }.
Ember.Handlebars.registerHelper('filename', function(options) {
var template = options.data.view.get('template');
for (var templateName in Ember.TEMPLATES) {
if (Ember.TEMPLATES[templateName] === template) {
return templateName;
}
}
});
Live demo http://jsbin.com/ucanam/674/edit