I need to get a template from Ember.TEMPLATES, compile it with a specified object and get its raw HTML value.
Ember.TEMPLATES content (generated using gruntjs) returns a function and seems to be already passed through Handlebars.template() function so for example I would have this:
Ember.TEMPLATES["test"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
var buffer = '', hashTypes, hashContexts, escapeExpression=this.escapeExpression;
data.buffer.push("<strong>hello world ");
hashTypes = {};
hashContexts = {};
data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "test", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
data.buffer.push("</strong>\n");
return buffer;
});
and would like to compile that template with new values from a JSON object.
I tried something like that based on what I've seen in Ember code:
var test = Ember.TEMPLATES['test'];
var compiled = test({ test: 'value' });
I thought it might work but it doesn't actually.
Basically I'd like to do like with standard handlebars :
Handlebars.compile('<strong>{{hello}}</strong>', { hello: 'world' });
Is there any way to compile a template with specified values, and get the HTML result using Emberjs?
Ember do some modifications in handlebars compiler to enable the use of computed properties, make templates update when model changes etc.
If you see the view render method, it does more than template(context), it use the context and some private custom data. So Handlebars.compile is diferent of Ember.Handlebars.compile and I think that compiled templates from Ember.Handlebars.compile, is not intended to be used outside of a Ember.View.
Marking script types with text/x-raw-handlebars, instead of text/x-handlebars make the template be compiled with Handlebars.compile.
The following sample will work, but without the ember features:
Template
<script type="text/x-raw-handlebars" data-template-name="custom-template">
First name: {{firstName}} <br/>Last name: {{lastName}}
</script>
Javascript
App = Ember.Application.create({
ready: function() {
var template = Ember.TEMPLATES['custom-template'];
var html = template({ firstName: 'Tom', lastName: 'Dale' });
$('body').append(html);
}
});
You can see this sample here http://jsfiddle.net/marciojunior/MC8QB/
Related
I can't solve this for almost four hours, and i can't find any helpful documentation for this kind of problems. This is the issue, I'm using pug/jade templates and i want to call function inside pug template to transform some data
This is the main template:
/** main template */
section
each pet in pets
.pet
.photo-column
img(src= pet.photo)
.info-column
h2= pet.name
span.species= (pet.species)
p Age: #{calculateAge(pet.birthYear)} //here I need to call calculateAge function
if pet.favFoods
h4.headline-bar Favorite Foods
ul.favorite-foods
each favFood in pet.favFoods
li!= favFood
/** end main template **/
This is the external function:
/** calculateAge.js **/
module.exports = function(birthYear) {
var age = new Date().getFullYear() - birthYear;
if (age > 0) {
return age + " years old";
} else {
return "Less than a year old";
}
};
/** end calculateAge.js **/
What shell I do to make this happen?
There may be better way to handle this, but I usually do it by importing the external module and then passing it as part of template context object. That means the code that renders the template should be something like:
const calculateAge = require("calculateAge"); // change path accordingly
router.get("/main", function(){
let pageInfo = {};
pageInfo.title = "Demo";
pageInfo.calculateAge = calculateAge;
res.render("main", pageInfo);
});
Now, you can access calculateAge in your template. If this module is used a lot in most of the templates, then you should pass it as part of res.locals or app.locals so that it is available for all templates without the need to append it for every path request.
In PUG options use locals property to import functions you want to use inside your templates.
const calculateAge = require('./calculate-age');
const config = {
pug: {
locals: {
calculateAge,
},
},
};
Then you can use it in all your templates like this (please note the extra unescaping tag !{}):
p Age: !{calculateAge(pet.birthYear)}
Like in raw HTML if you want JS to execute in a sepcific part you need a script tag to enclose the js you want to use.
span.species= (pet.species)
p Age:
.script
calculateAge(pet.birthYear)
if pet.favFoods
Make your function available in pug like this:
//assuming you're using express
app.set('view engine', 'pug');
app.locals.someFunction = input => input * 5;
// or
import {someOtherFunction} from "packageOrFile";
app.locals.someOtherFunction = someOtherFunction;
In your pug you then can do
span= someFunction(10)
span= someOtherFunction(123)
This is basically what mahish wrote in his comment, but it actually answers the question satisfactory and here's the documentation.
You can write javascript with .script tag
script.
$( document ).ready(function() {
calculateAge(params)
})
I am working on a code gen tool, and I am using Lodash to template the output files. When I try running the resultant function from _.template(XXX) I get
"AppData" (the name of the template variable) is not defined.
I have tried to call temp below with and without the AppData variable and both give me
"ReferenceError: AppData is not defined"
templates.forEach((template) => {
let temp = _.template(template, { AppData: AppData });
output = temp(AppData);
});
This is the AppData object:
let AppData = { clientId: clientId,
appId: applicationId,
intents: ['Testing1',
'Testing2',
'Testing3',
'Testing4'
]};
Here is one of the 3 templates:
<% _.each(AppData.intents, function( intent ){ %>
<transition event="<%=intent%>" target="<%=intent%>">
<state id="<%=intent%>">
<onentry>
<assign location="href_answer" expr="tfs.getKBAPIUrl(DNIS, environment, '<%=intent%>')"/>
<assign location="reason" expr="'<%=intent%>'"/>
<script><![CDATA[presentAnswer(href_answer,reason);]]></script>
</onentry>
</state>
<% }) %>
Stack is not letting me post the output, but it's the function representing the finished template.
Perhaps you should step back and get the lodash to first do the templating first and then once you get the templating work, then introduce more complexity...
var tfsStr = document.querySelector('#tfs-template').textContent;
var compiled = _.template(tfsStr, {
intents: AppData.intents,
_: _
});
I did a little codepen to test the templating
After much head smashing, I realized a simple issue.
I was passing the object incorrectly, and the templates were looking for the wrong thing. In the templates I removed my AppData. object notation and updated the code to look like this. The templates picked up the values correctly.
templates.forEach((template) => {
let temp = _.template(template);
output += temp(AppData);
});
A bit of a rookie mistake, first time using Lodash.
I'm trying to add some html into a tinyMCE textarea (tinyMCE can show html inside those editable textareas while allowing the user to manipulate them live).
So, lets say my template is:
var template = '<span>hello {{ user.name }}</span>'
I'm trying to:
compile the template with the current controller's scope.
stringify it into a varialbe (after it was compiled)
concat it to the end of the tinyMCE editor.
my controller's code is:
// [...]
var user = {};
user.name = "john";
$scope.user = user;
$templateRequest('<PATH_TO_TEMPLATE>').then(function() {
var linkingFunc = $compile(template);
// if I understand correctly, this should replace the user.name with "john"
var parsedTemplate = linkingFunc($scope);
console.log(parsedTemplate.prop('outerHTML'));
});
// [...]
The output of parsedTemplate.prop('outerHTML') is:
<span>hello {{ user.name }}</span>
Does it get rendered only if I inject it to the DOM after linking it to the scope?
is there a way to get it compiled in the javascript without rendering it back?
I'm trying to get some sort of var templateWITHOUTVariables which will euqal <span>hello john</span>
any ideas?
thanks!
Thanks to #FrailWords, I solved this using $timeout.
So my code looks like this:
var user = {};
user.name = "john";
$scope.user = user;
$templateRequest('<PATH_TO_TEMPLATE>').then(function() {
var parsedTemplate = $compile(template)($scope); // at this point, the template isn't processed yet
//timeout in order to get the template processed
$timeout(function() {
console.log(parsedTemplate.prop('innerHTML');
})
});
output:
<span>hello john</span>
When the $timeout executes, the $digest process already finished processing the parsedTemplate.
I am trying to create and initialize some sort of master view model that contains common view models that might be run on every page and page specific models that are appended on page load.
var MasterViewModel = {
commonViewModel1 : CommonViewModel1(),
commonViewModel2 : CommonViewModel1()
};
var commonInit = function() {
// Populate View Model Data
MasterViewModel.commonViewModel1 = initCommonViewModel1();
MasterViewModel.commonViewModel2 = initCommonViewModel2();
// Apply common view model bindings
ko.applyBindings(MasterViewModel);
};
var pageSpecificInit = function() {
// Populate Specific View Model Data
MasterViewModel.pageViewModel1 = initPageViewModel1();
MasterViewModel.pageViewModel2 = initPageViewModel2();
// Apply Page Specific Bindings
ko.applyBindings(MasterViewModel);
};
$(function() {
commonInit();
pageSpecificInit();
});
this is a crude example of what I am trying to do in the real application this is all namespaced and in separate files so that only page specific code is run. What is the best practice for doing this I somewhat based the above on http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html but when I do it in the application I get something like "cannot bind to pageViewModel1 undefined" should I setup my MasterViewModel differently to be more like
var MasterViewModel = {
commonViewModel1 : CommonViewModel1(),
commonViewModel2 : CommonViewModel1(),
pageViewModels : {}
};
var commonInit = function() {
// Populate View Model Data
MasterViewModel.commonViewModel1 = initCommonViewModel1();
MasterViewModel.commonViewModel2 = initCommonViewModel2();
// Apply common view model bindings
ko.applyBindings(MasterViewModel);
};
var pageSpecificInit = function() {
// Populate Specific View Model Data
MasterViewModel.pageViewModels.pageViewModel1 = initPageViewModel1();
MasterViewModel.pageViewModels.pageViewModel2 = initPageViewModel2();
// Apply Page Specific Bindings
ko.applyBindings(MasterViewModel.pageViewModels);
};
$(function() {
commonInit();
pageSpecificInit();
});
Your second example is more correct, but shouldn't you be binding the page-specific view models to a specific html element that you've surrounded with the stop binding comment?
ko.applyBindings(MasterViewModel.pageViewModels, $('#pageElement')[0]);
However, if you want to have nicely decoupled objects that can talk to each other, then you might want to look at Knockout Postbox
I'm loading a template with the following data:
"slides": [
{
"template": "video",
"data": {
"video": ""
}
},
{
"template": "image",
"data": {
"image": ""
}
}
]
in my template I want to loop over these slides and based on the configured template I want to load a partial
{{#each slides}}
{{> resources_templates_overlay_video }}
{{/each}}
How can I make this partial load dynamically (based on the configured template)?
I'm using the require-handlebars-plugin
As far as I can tell, hbs expects the partials to be known at compile time, which is way before you pass in your data. Let's work around that.
First, pull in your dynamic partials before rendering, something like:
// I required the main template here for simplicity, but it can be anywhere
var templates = ['hbs!resources/templates/maintemplate'], l = data.slides.length;
for (var i=0; i<l; i++ )
templates.push('hbs!resources/templates/overlay/'+data[i].template);
require(templates, function(template) {
var html = template(data);
});
And define a helper that will act as a dynamic partial
define(['Handlebars'], function (Handlebars) {
function dynamictemplate(template, context, opts) {
template = template.replace(/\//g, '_');
var f = Handlebars.partials[template];
if (!f) {
return "Partial not loaded";
}
return new Handlebars.SafeString(f(context));
}
Handlebars.registerHelper('dynamictemplate', dynamictemplate);
return dynamictemplate;
});
Finally, modify your main template to look like
{{#each slides}}
{{dynamictemplate this.template this.data}}
{{/each}}
I found the above answers a little hard to understand - they leak globals, have single character variables, and some odd naming. So here's my own answer, for my (and your) reference:
A dynamic partial using 'hbs', express.js default handlebars implementation:
I used this to make a simple blog making (article-name).md into /blog/(article-name), creating a dynamic partial:
// Create handlebars partials for each blog item
fs.readdirSync('blog').forEach(function(blogItem){
var slug = blogItem.replace('.md','')
var fileContents = fs.readFileSync('blog/'+blogItem, 'utf8')
var html = marked(fileContents)
var compiledTemplate = hbs.compile(html);
hbs.registerPartial(slug, compiledTemplate);
})
// Create 'showBlogItem' helper that acts as a dynamic partial
hbs.registerHelper('showBlogItem', function(slug, context, opts) {
var loadedPartial = hbs.handlebars.partials[slug];
return new hbs.handlebars.SafeString(loadedPartial(context));
});
Here's the route. It 404s if the partial doesn't exist, because the blog doesn't exist.
router.get('/blog/:slug', function(req, res){
var slug = req.param("slug")
var loadedPartial = hbs.handlebars.partials[slug];
if ( ! loadedPartial ) {
return res.status(404).json({ error: 'Article not found' })
}
res.render('blog', {
slug: slug
});
})
/views/blog.hbs looks like:
<div class="blog">
{{ showBlogItem slug }}
</div>
When Handlebars.partials[] returns a raw string it means the partial is not compiled.
I am not sure but my best guess is that Handlebars compiles the partial internally when it compiles the template which includes the partial. So when you use a helper to include a partial then Handlebars doesn't recognize it and it will not be compiled.
You can compile the partial yourself. Don't forget to register the compiled partial or you end up compiling every time the partial is required, which hurts performance. Something like this should work.
var template = Handlebars.partials['templatename'],
fnTemplate = null;
if (typeof template === 'function') {
fnTemplate = template;
} else {
// Compile the partial
fnTemplate = Handlebars.compile(partial);
// Register the compiled partial
Handlebars.registerPartial('templatename', fnTemplate);
}
return fnTemplate(context);