Manually use precompiled handlebars templates - handlebars.js

How to manually use the precompiled handlebars.js templates?
Let's say, we have
source = "<p>Hello, my name is {{name}}</p>"
data = { name: "Joe" }
Currently, I have
template = Handlebars.compile(source)
render: -> template(data)
The source is coming from the database, and in order to cut down on the compilation time, I want to use a compilation step, precompiling the template server side with Handlebars.precompile(source) and then using something like:
template = precompiled_template
render: -> precompiled_template(data)
The precompiled_template is a string with function definition, so that doesn't work.
Also, I've found that Hanlebars.compile(source)() == Handlebars.precompile(source), but after browsing the source codes of handlebars, it's compilers and runtime, I'm still not sure how to achieve this.

if you did not find the right question till now, the answer is pretty simple.
Handlebars comes with a C pre-compiler on the command line, if you can access your shell you can simple just compile your templates each separated or merge them together into one file.
you can install Handlebars via npm / or build it on your system.
on the shell you can access the help file
$> Handlebars [ENTER]
You will see a help file like >
- f --output Output File etc etc ..
- m --min Minimize Output
$> Handlebars mysupertemplate.handlebars -f compiled.js -m ("-m" if
you want to minify the js file)
To run Handlebars.compile in the browser is a huge loss in performance, so it's worth a try to precompile on the server before sending the file to the browser.
To register Handlebars templates in your browser you have to load them like this:
var obj = {"foo":"bar"}
var template = require("./mytemplate-file.js") // require.js example
template = template(Handlebars) // Pass Handlebars Only if the template goes mad asking for his Father
var html = Handlebars.templates[template-name](obj)
For example if you have more then one template registered in the "templates-file" you will be able to access after the require call all templates by name using
var html = Handlebars.templates["templateName"]({"foo":"bar"});
You can go even further by register all the know helper within the file and / or making custom helpers for partials like so..
*// This will be the helper in you app.js file*
Handlebars.registerHelper("mypartials", function(partialName, data) {
var partial = Handlebars.registerPartial(partialName) || data.fn
Handlebars.partials[partialName] = partial
})
And in your template file you can put this...
{{#mypartial "divPartial"}}
<div id="block"><h2>{{foo}}</h2><p>{{bar}}</p></div>
{{/mypartial}}
{{#mypartial "formPartial"}}
<form id="foo"><input type="text" value="{{foo}}" name="{{bar}}"></form>
{{/mypartial}}
Now you can access this files by calling
var html = Handlebars.partials["divPartial"]({"foo":"bar","bar":"foo"})
var formHtml = Handlebars.partials["formPartial"]({"bar":"bar","foo":"foo"})
Hope this helped a bit ..

This speed test http://jsperf.com/handlebars-compile-vs-precompile/3 gave the answer.
Apparently, one solution is to eval() that resulting string and it will work.
The code is
var data = { name: "Greg" };
var source = "<p>Howdy, {{ name }}</p>";
eval("var templateFunction = " + Handlebars.precompile(source));
var template = Handlebars.template(templateFunction);
template(data);
=> "<p>Howdy, Greg</p>"
Of course one needs to be careful with eval and probably a better solution exists.

Related

Would it make sense to provide a sample for arbitrary code generation?

i am new to handlebars. I am seeking for a general way to create code from templates via some kind of input data. I found handlebars quite good for this purpose. But wheras the documentation is centered on server side / browser online templating (html). My runtime would be the shell. I read and tried handlebars-cmd... with limited success. I don't know how to include my own helpers or libraries like handlebars-helpers in handlebars-cmd.
Can anybody help me with an example?
I was able to write a sample.
I hope this small piece of code can help others to run and test their templates as well as their helpers and partials ... perhaps it is worth to include this in a example directory or the documentation...
i would like to share my first rough sample that runs in node.js
just with
npm i handlebars
npm i handlebars-helpers
npm i minimist
node hbrscmd.js --in some.json --template template.hbs --out myresult.txt
content of hbrscmd.js:
var hbs = require('handlebars');
var helpers = require('handlebars-helpers')({
handlebars: hbs
});
var fs = require('fs');
var args = require('minimist')(process.argv.slice(2));
if (args.hasOwnProperty("v")) {
console.log(args);
}
var datastring = fs.readFileSync(args.in, 'utf8');
var data = JSON.parse(datastring);
if (args.hasOwnProperty("v")) {
console.log(data);
}
var templatefile = fs.readFileSync(args.template, 'utf8');
//console.log(templatefile);
var template = hbs.compile(templatefile);
var output = template(data);
fs.writeFileSync(args.out, output);
this works as i like.
No checking is made.

Injecting precompiled handlebars template in Handlebars.template method

Hi I am currently using Express Handlebars setup [https://github.com/ericf/express-handlebars][1] to render compiled and pre-compiled templates. In case of pre-compilation I have configured express middleware to get handlebars templates as precompiled and manually configured registration of handlebars pre-compiled templates like this:
hbs.getTemplates('views/partials/page/', {
cache : app.enabled('view cache'),
precompiled: true
}).then(function (templates) {
var templatesArr = hbs.handlebars.templates = hbs.handlebars.templates || {}
// extract all templates names and values
templates = Object.keys(hbs.handlebars.templates).map(function (name) {
//get precompiled template data
var templateSpec = templates[name]
//Inject precompiled data into Handlebars.template method
var precompileTemplateRef = hbs.handlebars.template(templateSpec)
//templateSpec is treated as String rather than Object
//and this is where the things break because
//handlebars.template expects Object
//Register precompileTemplateRef with Handlebars so that
//precompiled template can be extracted for later use by using
//template name
templatesArr[name] = precompileTemplateRef
});
When I run the express server, hbs.handlebars.template doesn't get executed because templateSpec mentioned above is received as a string rather than object.
Even using JSON.parse(templateSpec) doesn't work because precompiled output is not JSON BUT an Object literal.
Precompiled output looks something like this:
{"1":function(container,depth0,helpers,partials,data) {var helper;ret.......
I want to know if its possible to inject just the pre-compiled output at run time into Handlebars.template(templateSpec) OR Do I have to create precompiled template in file system which has registration with Handlebars.templates.
In case of compiled templates there are no issues.
Many thanks in advance
This is an open issue with Handlebars as per the following links:
https://github.com/wycats/handlebars.js/issues/922
https://github.com/wycats/handlebars.js/issues/1033
var templateSpec = (new Function('return ' + templates[name])());
Will resolve the problem

document.querySelectorAll not defined

I am following a calculator tutorial from here: http://thecodeplayer.com/walkthrough/javascript-css3-calculator
However I am using nitrous as the IDE and Meteor. In this part of the code in the js file:
// Get all the keys from document
var keys = **document.querySelectorAll**("#calculator span");
var operators = ['+', '-', 'x', 'รท'];
var decimalAdded = false;
The 'document.querySelector All' part comes up with a not defined error. I have tried replacing it with the more Meteor friendly 'template. find' however then it just says that template is not defined. Any help would be very much appreciated. :)

Reference variables in a separate less file using SquishIt (support dynamic LESS content)?

We are using bootstrap for our project in MVC4. So far, we were referencing the bootstrap.less file in our main layout page and it worked great. However, a new requirement has come along that requires us to have customized look for each of our department pages (each department has its own layout that use the main layout)
bootstrap.less has this structure:
#import variables.less // defines all the variables
#import others // all imports like mixins.less, reset.less etc
since we need to inject our variable override, we created another less file:
bootstrap-without-variables.less //contains all the imports without the variables.less from bootstrap.less
The reason for this separation is to inject our variable override so that we can customize the bootstrap styles for our pages.
We are using SquishIt to bundle the less files into a bundle.
Here is the Bundle:
Bundle.Css()
.Add("~/bootstrap/variables.less")
.Add("~/variable-override.less") // custom override
.Add("~/bootstrap/bootstrap-without-variables.less")
.MvcRender("styles_combined_#.js");
This does not work at all. If I remove the variables.less and reference that in bootstrap-without-variables.less (which now becomes similar to bootstrap.less), it works perfectly fine.
The issue, I think, is that each file is evaluated and converted to css independently before combining them together.
Is there a way to tell the bundler to first bundle the files into one and then to evaluate and convert to css or a better solution to this problem?
Like mentioned by AlexCuse, there was no way for me to do what I mentioned above using only SquishIt. However, as mentioned in https://github.com/jetheredge/SquishIt/issues/170, there is an overload to AddString() that lets you add dynamic less content.
For example,
.AddString("LessString",".less") // .less being the extension
This works perfectly fine as long as the LessString does not contain any imports (#import). So, I downloaded the source from https://github.com/jetheredge/SquishIt and started diving into the code. Going through the code I found that the content loaded through AddString() has the CurrentDirectory set to the path of my IIS ("c:\windows\system32\inetsrv"). As a result of which, the imports were throwing
FileNotFoundException (You are importing a file ending in .less that
cannot be found.)
So, I needed a way to set the current directory (reference location from where my imports will be searched)
Here is what I did:
STEP1: Extended Asset to have a property called CurrentDirectory
internal string CurrentDirectory { get; set; }
STEP2: Added a third optional parameter to the AddString() overload
public T AddString(string content, string extension, string currentDirectory = null)
{
return AddString(content, extension, true, currentDirectory);
}
STEP3: Updated the AddString() to add the current directory to the Asset
T AddString(string content, string extension, bool minify, string currentDirectory = null)
{
if (bundleState.Assets.All(ac => ac.Content != content))
bundleState.Assets.Add(new Asset { Content = content, Extension = extension, Minify = minify, CurrentDirectory = currentDirectory });
return (T)this;
}
STEP4: Modify the PreprocessArbitary (For Release) on BundleBase to set the current directory
protected string PreprocessArbitrary(Asset asset)
{
if (!asset.IsArbitrary) throw new InvalidOperationException("PreprocessArbitrary can only be called on Arbitrary assets.");
var filename = "dummy." + (asset.Extension ?? defaultExtension);
var preprocessors = FindPreprocessors(filename);
return asset.CurrentDirectory != null ?
directoryWrapper.ExecuteInDirectory(asset.CurrentDirectory, () => MinifyIfNeeded(PreprocessContent(filename, preprocessors, asset.Content), asset.Minify)) :
MinifyIfNeeded(PreprocessContent(filename, preprocessors, asset.Content), asset.Minify);
}
For Debug, modify the RenderDebug to set the current directory
if (asset.IsArbitrary)
{
var filename = "dummy" + asset.Extension;
var preprocessors = FindPreprocessors(filename);
var processedContent = asset.CurrentDirectory != null ?
directoryWrapper.ExecuteInDirectory(asset.CurrentDirectory, () => PreprocessContent(filename, preprocessors, asset.Content)) :
PreprocessContent(filename, preprocessors, asset.Content);
sb.AppendLine(string.Format(tagFormat, processedContent));
}
Here is my how I add dynamic or static less files now:
.AddString("#import 'content/bootstrap/variables.less';", ".less", AppDomain.CurrentDomain.BaseDirectory)
For my above requirement, I read the variables.less into a string builder, then add my variable-override.less and finally add the bootstrap-without-variables.less to the string builder.
It has worked for me so far. I tested following scenarios:
normal less files with imports, e.g. .Add("~/content/styles.less")
inline less without imports, e.g. .AddString(LessString, ".less")
dynamic less files with imports, e.g. .AddString("#import content/bootstrap/variables.less';", ".less", AppDomain.CurrentDomain.BaseDirectory)
I'll try to do a pull request soon. I hope this helps people looking to support dynamic LESS content with imports.
You are right, SquishIt does the less processing before combining files. There is no way to do what you ask using only SquishIt, but I suppose you could combine the files on your own on the way in (use the .AddString method to add your resulting .less content to the bundle). One of the alternatives may suit your needs better, I'm not sure.

Accessing "customconfiguration" inside GUI Extension

I'm doing a GUI Extension of User Interface (SiteEdit) by overriding the behaviour of one of the javascript files, to add some funcionality.
The javascript file is "/Scripts/Components/ExtComponentField.js" and the target is "SiteEdit" extending:
Tridion.Web.UI.Editors.SiteEdit.Views.Content
All works well with the extension, and I have what I wanted to have, but now I'm trying to use the
settings/customconfiguration/clientconfiguration
node of the extension config, to use some initialization parameters, but there is no way to access $config element in the javascript, and Tridion.Core.Configuration.Editors["myExt"].configuration is null.
I've seen using this customconfiguration in various javascripts like "Dashboard" or "Footprints", but is it possible to have it on "Content"? am I missing something on the extension config?
I'm afraid I didn't test this but you should be able to use:
Extensions.YourExt.getConfigurationItem = function (itemName, editorName)
{
var editor = $config.Editors[editorName].configuration;
if (editor)
{
var confXml = $xml.getNewXmlDocument(editor);
var confObj = $xml.toJson(confXml);
if (confObj[itemName])
return confObj[itemName];
else
return "";
}
}
You can then use it in the following way:
$this.getConfigurationItem("YOUR_CONFIG_ITEM_NAME", "YOUR_EDITOR_NAME").toString();
In your extension configuration (below the <theme> node) you can enter your own configuration values:
<customconfiguration>
<clientconfiguration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge">
<YOUR_CONFIG_ITEM_NAME>The value</YOUR_CONFIG_ITEM_NAME>
Can you confirm :)
I usually use a separate JS file with the following:
Type.registerNamespace("Extensions.Namespace");
Extensions.Namespace.getEditorConfigSection = function Editor$getEditorConfigSection() {
if (this._settings === undefined) {
var editor = $config.Editors["ThisEditorName"];
if (editor && editor.configuration && !String.isNullOrEmpty(editor.configuration)) {
var configSectionXmlDoc = $xml.getNewXmlDocument(editor.configuration);
this._settings = $xml.toJson(configSectionXmlDoc.documentElement);
}
}
return this._settings;
};
and in the configuration add it in a separate group:
<cfg:group name="Extensions.Namespace" merge="always">
<cfg:fileset>
<cfg:file type="script">/Scripts/Definitions.js</cfg:file>
</cfg:fileset>
</cfg:group>
Then wherever you need it, you can add the following dependency:
<cfg:dependency>Extensions.Namespace</cfg:dependency>
Then I usually use a function like this to get a certain configuration value:
Extensions.Namespace.Something.prototype._getMyConfigValue = function Something$_getMyConfigValue() {
var configSection = Extensions.Namespace.getEditorConfigSection();
if (configSection) {
return configSection.myconfigvalue;
}
};
The code contained in the "Content" group is running inside of the IFRAME which is hosting your published web page. As you can imagine, the amount of files included there should be minimized and so quite a lot of functionality is not available.
My suggestion would be to read the configuration only in the main window and then pass along the settings that you need to the code running in the IFRAME -- through the use of the Tridion.Utils.CrossDomainMessaging utility class ($xdm).

Resources