I'm trying to template a template, like below:
{{{
{
"name" : "{{name}}",
"description" : "{{description}}"
}
}}}
{{{debug this}}}
<h1>{{name}}</h1>
Where I want to triple brackets to stay, but double brackets to be replaced with the JSON passed in. Anyone know the best way to do this without writing post-process JS code, and if not, is there a good nodeJS template engine for this type of scenario?
As described in this Question handlebars doesn't support changing the delimiters. But you can escape the double braces with a backslash like this:
HTML:
... \{{ myHandlbarsVar }} ...
You can switch delimiters to something that won't conflict with the triple mustaches, like erb-style tags:
{{=<% %>=}}
{{{
{
"name": "<% name %>",
"description": "<% description %>"
}
}}}
{{{debug this}}}
<%={{ }}=%>
Note that you can do this as many times as you like throughout your template. Any time you run into something that conflicts, pick a new set of delimiters :)
You can also assign Mustache.tags = ["[[", "]]"]; before your template compilation.
http://jsfiddle.net/fhwe4o8k/1/
e.g.
$(function () {
Mustache.tags = ["[[", "]]"];
var template = $('#test').html();
Mustache.parse(template);
var rendered = Mustache.render(template, {test: "Chris"});
$('#content-placeholder').html(rendered);
});
another option is create a helper for outputing curly brackets.
Handlebars.registerHelper('curly', function(object, open) {
return open ? '{' : '}';
});
and then use it in the template like this:
<script id="template" type="text/x-handlebars-template">
{{curly true}}{{name}}{{curly}}
</script>
which then outputs:
{Stack Over Flow Rocks}
The template contains HTML, so you could use HTML-Entities, for example the ASCII-codes 123 and 125 for braces:
{{{myValue}}}
I just wanted slightly different approach. I have tried few other other ways and here are few things which I didn't like about them:
Changing Angular default {{obj.property}} brackets to something else is a bad idea. Mainly beacause as soon as you start using third party components which are not aware of you non standard angular configuration, bindings in those third part components will stop working. Also worth mentioning that AngularJS team doesn't seem to want to go the route of allowing multiple binding notations, check this issue
I quite like Mustache templates and don't want to switch the whole project to something else because of this small issue.
Quite a few people recommend not to mix client and server side rendering. I don't fully agree, I believe if you are building a multipage website which has few pages with angular and some other which are static (something like about us or Terms and Conditions pages) it is perfectly fine to use server side templating to make maintaining those pages easier. But that said, for parts which are Angular you shouldn't mixing server side rendering.
Ok no my answer:
If you are using NodeJS and Express you should be to the following:
Replace bindings {{}} in your angular part with something like {[{}]} (or something completely unique)
Now in you route add a callback to you render method:
app.get('/', function(req, res){
res.render('home', {
title: 'Awesome Website',
description: 'Uber Awesome Website'
}, function(err, html){
var htmlWithReplacedLeftBracket = html.replace(/{\[{/g, '{{');
var finalHtml = htmlWithReplacedLeftBracket.replace(/}\]}/g, '}}');
res.send(finalHtml);
});
});
This should allow you to use Mustache together with AngularJS. One improvement you could do is extract that method into a separate module to reuse across all routes.
This is a good solution I have found for this type of problem where you can easily switch delimiters in the template settings in runtime:
http://olado.github.com/doT/
You can do the RegEx settings like this:
doT.templateSettings = {
evaluate: /\{\{([\s\S]+?)\}\}/g,
interpolate: /\{\{=([\s\S]+?)\}\}/g,
encode: /\{\{!([\s\S]+?)\}\}/g,
use: /\{\{#([\s\S]+?)\}\}/g,
define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
varname: 'it',
strip: true,
append: true,
selfcontained: false
};
Related
Simple problem that I must be overcomplicating...
I want to include a script file for any URL that contains /edit-post. Problem is, my editor URL will always be suffixed with a post title like this: /edit-post/some-post-title, therefore my Pug conditional never evaluates to true.
In other words, I'm looking for a conditional statement that will be true in both the following example conditions:
/edit-post/some-post-title
/edit-post/an-even-better-post-title
I have access to the path url variable already, and my conditionals are working fine when it's an exact match, so I'm trying to extend that to suffixed subdirectories. Here's what I'm starting with:
if path === '/edit-post'
//my script file
Is there maybe a way to include a regex expression or possibly some sort of "contains" statement in Pug?
I was thinking something like "/edit-post.*" or "edit-post[s/S]". Also got desperate and tried using .indexOf() which all you folks smarter than me will already know doesn't work in Pug templates!
Maybe there's a better way to achieve this altogether? Searched for hours. Tried a hundred combos. Pug's documentation is sparse. Plz send help! :)
Side note: I did find this neat little Pug/Jade conditional doc I've been using as a tester. Might be of use. http://learnjade.com/tour/conditionals/
Thanks in advance!
Actually I recommend You:
1) to rethink Your routing
2) create /layouts/admin for admin panel layout
3) create /admin/posts/edit view for edit purposes
But for Your question here is straightforward example:
router.get('/post/:slug/edit', (req, res) => {
Post
.findOne({slug: req.params.slug})
.exec((err, post) => {
res.render('post', {includeEdit: true, post});
});
});
Post is mongoose model
and in Your view file:
if includeEdit === true
include partials/edit
I've successfully implemented i18next, which by the way is a great library! Though I'm still in search for the "best practice". This is the setup I have right now, which in general I like:
var userLanguage = 'en'; // set at runtime
i18n.init({
lng : userLanguage,
shortcutFunction : 'defaultValue',
fallbackLng : false,
load : 'unspecific',
resGetPath : 'locales/__lng__/__ns__.json'
});
In the DOM I do stuff like this:
<span data-i18n="demo.myFirstExample">My first example</span>
And in JS I do stuff like this:
return i18n.t('demo.mySecondExample', 'My second example');
This means I maintain the English translation within the code itself. I do however maintain other languages using separate translation.json files, using i18next-parser:
gulp.task('i18next', function()
{
gulp.src('app/**')
.pipe(i18next({
locales : ['nl','de'],
output : '../locales'
}))
.pipe(gulp.dest('locales'));
});
It all works great. The only problem is that when I've set 'en' as the userLanguage, i18next insists on fetching the /locales/en/translation.json file, even though it doesn't contain any translations. To prevent a 404, I currently serve an empty json object {} in that file.
Is there a way to prevent loading the empty .json file at all?
Maybe I'm missing something here but couldn't you simply do this:
if (userLanguage != 'en') {
i18n.init({
lng : userLanguage,
shortcutFunction : 'defaultValue',
fallbackLng : false,
load : 'unspecific',
resGetPath : 'locales/__lng__/__ns__.json'
});
}
That way your script i18n wouldn't be initialized unless you actually needed the translation service.
i18next-parser author here, I will explain how I use i18next and hopefully it will help:
1/ I do not use defaultTranslation in the code. The reason is that it doesn't belong in the code. I understand the benefit of having the actual text but the code can get bloated quickly. The difficult part consists in defining intelligible translation keys. If you do that, you don't really need the defaultTranslation text anymore. The translation keys are self-explainatory.
2/ If you have a 404 on the /locales/en/translation.json, then probably that you don't have the file in your public directory or something similar. With gulp you can have multiple destination and do dest('locales').dest('public/locales') for instance.
3/ If there is no translation in the catalog, make sure you run the gulp task first. Regarding populating the catalog with the defaultTranslation you have, it is a tricky problem to solve with regexes. Think of this case <div data-i18n="key">Default <div>translation</div></div>. It needs to be able to parse the inner html and extract all the content. I just never took the time to implement it as I don't use it.
See http://i18next.com/pages/doc_init.html under "whitelist languages to be allowed on init" (can't fragment link on those docs...):
i18n.init({ lngWhitelist: ['de-DE', 'de', 'fr'] });
Only specified languages will be allowed to load.
That should solve your problem. Though I suppose a blacklist would be even better.
I'm new to Backbone, and am helping maintain an app. I'd like to make the default in all normal situations for Backbone to be escaping model data, to help avoid XSS attacks by default.
I know we can do this using
<%- someModelAttribute %>
and
model.escape('attr')
to escape data in our app, but I'd like to switch it so
<%= someModelAttribute %>
and
model.get('attr')
did the same as well.... So by default, all existing code and future code that uses these tags and methods is escaped by default. Then I'd like to introduce another model method like "model.getDataThatShouldBeSafeHtml" to make it 100% clear to developers when they're getting data that should include HTML.
So is there some way for me to switch the "<%=" tag and the "model.get" methods to be the same as their escape equivalents?
I only ask as I thought this might've been done somewhere before, or already be part of backbone, and I want to avoid rebuilding the wheel!
I found an easy way to make it so all templates by default are escaped. Since Backbone uses Underscore for it's templating engine, I googled around and found that you can customize the underscore delimiters using _.templateSettings, described here on the Underscore site. Note that if you make sure all of your html is written out using these templates, then you're covered for all XSS. So don't skip using templates in some simple scenarios, use them to avoid XSS in those cases as well!
You can test it out using this fiddle: http://jsfiddle.net/vx0pw2n0/
So all I did was make it so both <%= and <%- are used to display escaped data by default. The evaluate tag <% still remains, and can be used to output whatever HTML you want using the print statement. I also introduced a new tag, <%cleanHtml that can be used to output HTML without it being escaped, and without needing to say print(someVariable)
<script type="text/javascript">
//
// This is the important part - The part that changes what underscore uses
// for template delimiters.
//
_.templateSettings =
{
escape: /<%[=-]([\s\S]+?)%>/g,
interpolate: /<%cleanHtml([\s\S]+?)cleanHtml%>/g,
evaluate: /<%([\s\S]+?)%>/g
};
// Test it out
var t = _.template($('#t').html());
var html = t({ title: '<b>pancakes</b>' });
$("#target").html(html);
console.log(html);
</script>
<!-- Sample Underscore Template showing different ways of using it -->
<script id="t" type="text/x-underscore">
<div><%= title %></div>
<div><%- title %></div>
<div><%safeHtmlOnly title safeHtmlOnly%></div>
<div><% print(title) %></div>
</script>
<div id="target"></div>
Making this work globally
To get this to work globally, you may have to configure Underscore as a module, which can be done like so:
Implement as a dependency on Backbone
Configure Require.js modules
// When you initially setup require.js, add a new module to configure underscore
// Make it a dependency of backbone, so it'll always be loaded whenever
// backbone is used.
require.config({
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: ['underscoreConfig', 'underscore', 'jquery'],
exports: 'Backbone'
},
jquery: {
exports: 'jQuery'
}
}
});
underscoreConfig.js
define(['underscore'], function (_) {
'use strict';
_.templateSettings =
{
escape: /<%[=-]([\s\S]+?)%>/g,
interpolate: /<%cleanHtml([\s\S]+?)cleanHtml%>/g,
evaluate: /<%([\s\S]+?)%>/g
};
return _;
});
You could extend Backbone.Model to create a reusable base class that does this for you. Something like this (untested):
BaseModel = Backbone.Model.extend({
getSafeAttributes: function() {
var safe = {};
_.each(this.attributes, function(key, value) {
safe[key] = _.escape(value);
});
return safe;
}
});
I'm guessing your render functions look something like
this.$el.html(this.template(this.model.attributes));
So, instead of that, you'd write:
this.$el.html(this.template(this.model.getSafeAttributes()));
and just make sure your models extend the base class instead of Backbone.Model.
It's perfectly acceptable to modify backbone.js and underscore.js to achieve a similar outcome, but it does make it a pain to upgrade, which is why I'd go with the base class instead.
As far as I know backbone depends on the template engine provided by underscore.
How to use underscore.js as a template engine?
By underscore you can change the regex patterns to always escape, but it won't call the escape method of your model, so you have to create your own template engine to do that...
For example you can fork the templating code from underscore and develop it in your own way and use that instead of _.template().
We are trying to use NodeJs with HoganJs for server side templating. However we are also wanting to use AngularJs for our client side needs.
The problem is that both HoganJs and AngularJs use "{{" and "}}" to full fill their compiler needs. Because of this Hogan strips out if at all there is a angular's "{{", because of the way hogan works.
My question is is there a out of the box solution that allows me to use both Angular and Hogan together, without clashing with each other.
If not, does anyone knows what/where/how to tweak one of these to make them love each other and work gracefully.
Thanks in advance...
If you're using express, you can change hogan's delimiters like so:
var app = express();
app.locals.delimiters = '<% %>';
Place the above before :
app.set('view engine', 'hjs');
Now in your page.hjs file, for the data { template : "Template test" }, you can do :
<p>This is a <% template %></p>
Try with
Hogan.compile(text, {delimiters: '<% %>'});
so you can change the delimeters Hogan
uses by passing the compile method an option overriding them.
http://comments.gmane.org/gmane.comp.lang.javascript.express/1426
NB
imo using a template system is useless using angularjs because
of https://stackoverflow.com/a/20270422/356380
Alternative to changing Hogan delimeters as other answer shows... change Angular's! I did this while using doT (which also uses {{ and }}) and it works fine:
say you have this in your layout HTML:
<html ng-app="cooApp">
Add this script to call up Angular with custom delims (I'm also including reference to Angular just for clarification):
<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js'></script>
<script>
var cooApp = angular.module('cooApp', [], function($interpolateProvider) {
$interpolateProvider.startSymbol('{%');
$interpolateProvider.endSymbol('%}');
});
</script>
Now just use {% and %} for Angular stuff.
Without changing delimiters, on Angular 1.x you can use the ng-non-bindable directive for in the elements that uses HoganJS, Mustache or any other code of this kind:
Example:
<div>
{{angularjs_variable}}
<div ng-non-bindable>{{hogan_variable}}</div>
</div>
This is useful if the element contains what appears to be AngularJS directives and bindings but which should be ignored by AngularJS. [...]
my first question here - please go easy. I'm using express, express3-handlebars and i18next-node with node.js
The plan is to work with a different translation namespace depending on which view (i.e. which handlebars file) is currently being served. So if we're looking at the page called ie(.hbs), i18next will look in the namespace called ie(.json) for the relevant language. This makes organisation and coordination of translations easier.
This is how I'm currently doing it: first I send the current page into the handlebars template for rendering (even this seems unnecessary - handlebars doesn't automatically expose which file it's rendering?):
res.render( url_base_path, { layout: ("sub"), title: title, currentpage: url_base_path } );
and then I access the variable "greeting" to be translated in the namespace of the current page like so {{t "greeting" page=currentpage }} - the annoying thing is that there are 10's of these variables on each page. Don't Repeat Yourself, anybody?
't' is defined in the express3-handlebars create() function, like so, helpers: { t: t }
and the translate function looks like this
var t = function (i18next_key, options) {
var page, result;
page = options.hash.page;
result = i18next.t(page + ":" + i18next_key);
return new hbs.handlebars.SafeString(result);
};
for the sake of full disclosure, this is what my (english) namespace file for the current page looks like
{
"greeting": "Hello, it appears you're using Internet Explorer, an outdated web browser."
}
this works, but it seems like there should be a much simpler solution.
what i really want is to be able to just type {{t "greeting"}} into the handlebars template to achieve the same result. is this possible without overriding core handlebars functionality?
here is the i18next docs page
http://i18next.com/pages/doc_features.html
I've answered my own question - it turned out easier than I expected.
You can access the handlebars instance in my Handlebars translate helper (duh?), so in the helper: renderer = this (for clarity), then you can just access renderer.currentpage to get the name of the required namespace. Note, you still have to send currentpage in the render function (on res.render()), but I'm ok with that side of it.
I've done it like this (it's probably pretty slow like this at the moment, but it works exactly how I want it to):
// Set namespace for translation
options.ns = options.ns || bestNamespaceFor( i18n_key );
result = i18n.t(i18n_key, options);
return new hbs.handlebars.SafeString(result);
function bestNamespaceFor( i18n_key ){
if ( i18n.exists(i18n_key, { ns: renderer.currentpage }) ) return renderer.currentpage;
if ( i18n.exists(i18n_key, { ns: renderer.layout }) ) return renderer.layout;
if ( i18n.exists(i18n_key, { ns: "common" }) ) return "common";
Works perfectly with {{t "my translation key" }} and gets the right namespace no matter where I use it.