Injecting precompiled handlebars template in Handlebars.template method - handlebars.js

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

Related

angularjs get paramater from url

i'm currently working on a asp.net mvc site in which we are using angularjs for model binding. i have a controller setup but i need to grab the id from the url to pass it to the service. my url looks like:
http://localhost/myapp/section/5
I need to grab the 5 out of the url, we are not using angular for routing, is there anyway to grab that through angular? Otherwise i could use .net to inject that into a global js variable and use angular to read the id from there.
I setup my angular controller as below:
myModule.controller('SectionController', ['$scope', 'sectionRepository', '$routeParams', SectionController]);
function SectionController($scope, sectionRepository, $routeParams) {
var vm = this;
alert($routeParams.id);
the alert returns 'undefined', I'm assuming because I never setup the routes in angular, is there a way to do it without the setup of routes, as we don't want to use angular for routing.
You can use the $location service to grab the URL. From there, just parse it.
function SectionController($location) {
var url = $location.url();
//regex is slow, you should use substring/slice instead
//var regex = /(?:section\/)[0-9]+/;
var id = url.substring(url.indexOf("section/") + "section/".length);
alert(id);
}

ASP.NET MVC Reference script file with version wildcard (without bundling)

In a ASP.NET MVC 4 project, I'd like to reference a versioned script file like this:
// Just some pseudo-code:
<script src="#Latest("~/Scripts/jquery-{0}.min.js")"></script>
// Resolves to the currently referenced script file
<script src="/Scripts/jquery-1.10.2.min.js"></script>
so that when a new Script version is updated via NuGet, the reference is updated automatically. I know of the bundling-and-minification feature, but it's just to much. I just want the little part which resolves the wildcards. My files are already minified, and also I don't want the bundles.
Do you have some smart ideas how to solve this?
Even though it's a little over kill to use the Bundling in MVC, but I think that will be your best bet. It's already been done and proven so why spend more time to write some proprietary code.
That being said, if you want a simple sample of what you can do, then you can try the following.
public static class Util
{
private const string _scriptFolder = "Scripts";
public static string GetScripts(string expression)
{
var path = HttpRuntime.AppDomainAppPath;
var files = Directory.GetFiles(path + _scriptFolder).Select(x => Path.GetFileName(x)).ToList();
string script = string.Empty;
expression = expression.Replace(".", #"\.").Replace("{0}", "(\\d+\\.?)+");
Regex r = new Regex(#expression, RegexOptions.IgnoreCase);
foreach (var f in files)
{
Match m = r.Match(f);
while (m.Success)
{
script = m.Captures[0].ToString();
m = m.NextMatch();
}
}
return script;
}
}
This will return you the last match in your Scripts director or it will return empty string.
Using this call
#Html.Raw(MvcApplication1.Util.GetScripts("jquery-{0}.min.js"))
Will get you this result if 1.8.2 is the last file that matched your string.
jquery-1.8.2.min.js
Hope this will help you get started.

Alfresco Object is not available in extension module in alfresco share

I am trying to override the javascript controller node-header.js of components\node-details with the extension module of alfresco share
This is my node-header.get.js
<import resource="classpath:/alfresco/templates/org/alfresco/import/alfresco-util.js">
for (var i=0; i<model.widgets.length; i++)
{
if (model.widgets[i].id == "NodeHeader")
{
if(model.widgets[i].options.nodeRef!=null)
{
var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef);
if(jsNode.hasAspect("custom:intranetFile")){
model.widgets[i].options.showFavourite = false;
model.widgets[i].options.showLikes = false;
}
}
}
}
I am getting this error
Error Message: 05270002 Failed to execute script
'classpath*:webscripts/custom/nodeheader/hidelikesync/node-header.get.js':
05270001 ReferenceError: "Alfresco" is not defined.
(jar:file:/C:/Alfresco/Alfresco42/tomcat/webapps/share/WEB-INF/lib/customshare.jar!/webscripts/custom/nodeheader/hidelikesync/node-header.get.js#1555)
Error lies in this line
var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef);
as Alfresco object is not available how can I get it?
Based on my answer yesterday on the share-extras-devel list:
Your issue is that you are mixing up your web script JS with client-side JavaScript. Alfresco.util.Node is a client-side helper class and is therefore available to client-side JS running in the web browser, but not to your web script code which runs on the server.
If you look at the source of alfresco-util.js, which you are including, you will see that there is a helper class there but it is called AlfrescoUtil.
To get some information on this given node I would suggest that you want to use the static method AlfrescoUtil.getNodeDetails() from that class, e.g.
var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
The structure of the jsNode object will be as per the JSON returned by the doclist-v2 webscripts, so you should be able to check for the presence of your custom aspect in the aspects array property.
If you check the source of alfresco-util.js you will see that additional parameters are also supported by getNodeDetails(). It seems to me you can also pass in an optional site name, plus some options if you wish.

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.

Manually use precompiled handlebars templates

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.

Resources