handlebars - add content to head of view from partial - node.js

I am using express-handlebars in my project and have the following problem:
Question
I want to be able to add <script> oder such tags to my overall views head from a partial that is called inside the view.
Example:
The view
{{#layout/master}}
{{#*inline "head-block"}}
<script src="some/source/of/script">
{{/inline}}
...
{{>myPartial}}
{{/layout/master}}
The view is extending another partial (layouts/master) that I use as a layout. It adds its content to that ones head block through the inline partial notation, which works fine
the Partial "myPartial
<script src="another/script/src/bla"></script>
<h1> HELLO </h1>
Now I would like that particular script tag in there to be added to my views head-block. I tried going via #root notation but can only reference context there. Not change anything.
I know I could use jquery or similar to just add the content by referencing the documents head and such. But I wanted to know if this is possible at all via Handlebars.
I do doubt it is in any way. But if you have any ideas or suggestions, please do send them my way! Many thanks!!!

UPDATE
This wont work if you have more than one thing injected into your layout / view. Since this happens when the browser loads the page, it creates some kind of raceconditions where the helpers has to collect the things that have to be injected into the parent file. If its not quick enough, the DOMTree will be built before the helper resolves. So all in all, this solution is NOT what I hoped for. I will research more and try to find a better one...
Here is how I did it. Thanks to Marcel Wasilewski who commented on the post and pointed me to the right thing!
I used the handlebars-extend-block helper. I did not install the package, as it is not compatible with express-handlebars directly (Disclaimer: There is one package that says it is, but it only threw errors for me)
So I just used his helpers that he defines, copied them from the github (I am of course linking to his repo and crediting him!) like so:
var helpers = function() {
// ALL CREDIT FOR THIS CODE GOES TO:
// https://www.npmjs.com/package/handlebars-extend-block
// https://github.com/defunctzombie/handlebars-extend-block
var blocks = Object.create(null);
return {
extend: function (name,context) {
var block = blocks[name];
if (!block) {
block = blocks[name] = [];
}
block.push(context.fn(this));
},
block: function (name) {
var val = (blocks[name] || []).join('\n');
// clear the block
blocks[name] = [];
return val;
}
}
};
module.exports.helpers = helpers;
I then required them into my express handlebars instance like so:
let hbsInstance = exphbs.create({
extname: 'hbs',
helpers: require('../folder/toHelpers/helpersFile').helpers() ,
partialsDir: partialDirs
});
Went into my central layout/master file that`is extended by my view Partial and added this to its <head> section
{{{block 'layout-partial-hook'}}}
(The triple braces are required because the content is HTML. Else handlebars wont recognize that)
Then in the partial itself I added things like so:
{{#extend "layout-partial-hook"}}
<link rel="stylesheet" href="/css/index.css"/>
{{/extend}}
And that did the trick! Thanks!!!

Related

Changing script files used on the front end of a node backed website

I'm making a solitaire game using NodeJs and Express backend and phaser.io frontend. It will offer different layouts of cards to play.
I have the game working "just fine" alpha state, but with a single layout of cards setup in HTML like:
<script src="js/gameboards/data.js"></script>
<script src="js/tableau.js"></script>
where data.js is the file that describes the card layout, and tableau.js is the game logic. I have several different files in /gameboards and for the testing/building, I just change the filename when I want to change the layout.
data.js is not a JSON file, it's a JS object
let gameboard = {
info: {
title: "Standard",
description: "6 columns of 5 cards"
},
[...]
deal: function() {
for (let i = 0; i < this.vars.allstacks; i++) {
etc...
That contains simple object data as well as object methods that define patterns and repetition (like multiple stacks, pyramids, etc) so it can't really be made into a JSON or other straight data file.
What I want to do is present a list of anchors/links to the player of the layout options. They'll click the link to get sent into /game with the layout they chose.
The method I thought of was to have /index send POST-data containing the layoutname. Then, in /game
router.post('/', function(req, res, next) { ... })
with the HTML template and res.render containing
<script src="js/gameboards/<layoutname>.js"></script>
to call the right file.
Is there a better way of doing this than what I listed above? It seems kludgey to me as if there would be a more 'nodey' and elegant way to do it.
To do it in the way you describe you need to enable a templating engine like handlebars or Pug. For pug I do something like this:
In server:
const cdnAction = process.env.S3_CDN_ACTIVE;
app.route('*')
.get( (req, res) => {
const cdnCSSPath = `https://s3.amazonaws.com/${process.env.S3_BUCKET_CDN}/audience.css.gz`;
res.render('home', { cdnActive, cdnPath, cdnCSSPath });
});
In pug file 'home.pug' rendered above:
head
if cdnActive === 'true'
link(href=cdnCSSPath rel='stylesheet' type='text/css')
else
link(href='/style/embed.css' rel='stylesheet' type='text/css')

Look up layouts and include files on multiple paths in express/ejs

I am building a node application based on express using ejs as template engine.
To support different looks for the site I would like to put files in folders named base holding vanilla stuff and an overlay per style/theme/client. I want the system to lookup files in overlay first, and only if not found use what is in base.
For static content like images and css files this works using the static middleware twice, first for the overlay, then for base.
I want to do the same for templates rendered through ejs. I have found:
Multiple View paths on Node.js + Express
And BananaAcids answer provided in that thread almost works for me as long as I call simple ejs views. If I want to use layouts or includes it breaks down for overlaid views because the base directory is now overlay and layouts that are unchanged from base are no longer found.
A simplified example follows.
File base/layouts/root.ejs:
<!DOCTYPE html>
<html lang="en">
<body>
<!-- Main content of pages using this layout goes here -->
<%- body %>
</body>
</html>
File base/index.ejs:
<% layout('layouts/root') -%>
<p>
A page in base using the root layout
</p>
File overlay/index.ejs:
<% layout('layouts/root') -%>
<p>
Totally different page in the overlay.
</p>
Using BananaAcids approach and setting both paths as view-sources express/ejs now correctly locates overlay/index.ejs as the view to render but as I did not also overlay layouts/root it fails because the resulting file overlay/layouts/root.ejs does not exist.
Is there a way of patching my way further down into ejs so that I can help it locate this file in base/layout/root.ejs instead?
Thank you for reading this and any brain cycles you have expended on it.
Here's what I've used to monkey patch Express (4.x) to add layout support:
/*
Usage:
Set a global/default layout with:
app.set('view layout', 'foo');
Set a layout per-render (overrides global layout) with:
res.render('foo', { layout: 'bar' });
Or disable a layout if a global layout is set with:
res.render('foo', { layout: false });
If no layout is provided using either of the above methods,
then the view will be rendered as-is like normal.
Inside your layout, the variable `body` holds the rendered partial/child view.
Installation:
Call `mpResponse();` before doing `require('express');` in your application.
*/
function mpResponse() {
var expressResponse = require('express/lib/response'),
expressResRender = expressResponse.render;
expressResponse.render = function(view, options, fn) {
options = options || {};
var self = this,
req = this.req,
app = req.app,
layout,
cb;
// support callback function as second arg
if (typeof options === 'function')
fn = options, options = {};
// merge res.locals
options._locals = self.locals;
// default callback to respond
fn = fn || function(err, str) {
if (err) return req.next(err);
self.send(str);
};
if (typeof options.layout === 'string')
layout = options.layout;
else if (options.layout !== false
&& typeof app.get('view layout') === 'string')
layout = app.get('view layout');
if (layout) {
cb = function(err, str) {
if (err) return req.next(err);
options.body = str;
expressResRender.call(self, layout, options, fn);
};
} else
cb = fn;
// render
app.render(view, options, cb);
};
}
I patched EJS to support the multiple views folder feature added in Express v.4.10. There is currently a pending pull request you can find here: https://github.com/mde/ejs/pull/120. If you still need this solution for your project you could include my fork into your package.json as a EJS replacement:
{
...
"dependencies": {
"ejs": "git://github.com/MarcelloDiSimone/ejs.git#feature/multi-views"
}
}
..or you plus one the pull request and hope it'll be accepted soon.

Marionette, how to change view's template on fly

I'm doing a single page webApp with Marionette and RequireJs, basically it is a Layout view nested with a lots of ItemView and compositeView. I want the page change to a new design by one click, so I want to ask a best practice about changing the view's template.
say, I got my view like this, and its templates are included by requirejs's text plugin:
define([
'text!templates/designNo1/template.html'
], function(template) {
var MyView = Backbone.Marionette.ItemView.extend({
/*Template*/
template: template,
initialize: function() {
......
},
onRender: function() {
......
}
});
return SkillView;
});
every view and theirs subviews are defined like this. and their templates located in "template/designNo1" folder. now I got a set of new design located in "template/designNo2", and I want apply it to the page, but I found there is no chance to do this, because the views are already loaded by RequireJs and the template's path are hard-coded. of course, I can override the view's template when create the view's instance, but if I do so, I must load all the new design in the upper-module which create the instance, that looks bad, and the new design are keep coming, it gonna be a mess soon.
so, any advice?
From what I can tell, it sounds like you are wanting to change the template on a view depending on different circumstances. Marionette's ItemView has a getTemplate() function that is called when rendering it. You can specify a function that determines which template to render from there.
Another approach might be to simply change the template of the view and re-render it. You could do that on a click event easily:
HTML
<div class="content"></div>
<button class="btn">Change Template</button>
Javascript
var template1 = '<div>Template 1</div>';
var template2 = '<div>Template 2</div>';
var ItemView = Backbone.Marionette.ItemView.extend({
template: template1
});
var itemView = new ItemView({ el: '.content' });
itemView.render();
$('.btn').click(function(e) {
e.preventDefault();
itemView.template = template2;
itemView.render();
});

Node.js, Express, Jade - Separate layout files

I'm working on some project with Node.js, Express and Jade, where I'd like to seperate layout files. Inside the main file is already separated header, but I don't know how to do this for sublayout where I need to pass data. In this case I need to pass data to widgets for every view on page, but in the route would be too many things to load data into widgets instead of some easy solution which I'm looking for.
I could do this thing on the way which I described above - to load data in view with every request, but this is somehow time & cpu consuming.
Another way I'm thinking of is to create some sublayout for widgets in which I'd load data once and then would be available all the time without calling data from DB in all requests. What's the best way to do that?
I work with mustache but I think you can use a similar strategy that I do.In most of the mustache templates that I use there is a common header and footer section.Along with the scripts and css files.I have created a separate partials file that exports these partials
.For instance my partial file looks like this.
exports.partials = function (isAuthenticated)
{
var menu;
isAuthenticated ?
menu = {
header: '',
footer: ' '
} :
menu = {
header: '',
footer: ''
}
return menu;
};
exports.staticResources = {
bootstrap :'//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css',
fonts : '//netdna.bootstrapcdn.com/font-awesome/3.0/css/font-awesome.css',
jquery : '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'
};
I have another method called generatePartials which as the name suggest generate the partials for my templates
exports.generatePartials = function(isAuthenticated){
var menu = resources.partials(isAuthenticated);
var partials = {
header : menu.header,
footer : menu.footer,
bootstrap : resources.staticResources.bootstrap,
fonts :resources.staticResources.fonts,
jquery :resources.staticResources.jquery,
};
return partials;
};
Now while rendering the template all I have to do is this
app.get('/routeName',function (req, res){
var partials = require('../helpers').generatePartials(req.isAuthenticated());
return res.render('viewName.html', partials);
};
And that's it.

Client side + Server side templating, feels wrong to me, how to optimize?

In the web app I am making, I use the classical Express+Jade to render client pages and also expose some REST API (let's say : "user list API").
These client pages use provided API to retrieve "user list" and display it. To display it, I use the handlebars template library once the data is retrieved.
It seems a bit dirty to me, using two template engines, parsing the code twice, how to make it better ?
Note : I already optimized the thing by sending the initial data within the client page by inserting it a script variable. This variable is then displayed the same way data received by API would be. The API is only used in case of data refresh.
UPDATE : using jade both server and client side is a good idea but how to seperate / specify ? Wich part of the rendered template should be done by when serving the page and what part will be used by the client ?
That's very easy to use Client side + Server side templating.When we are building some web apps,we should use ajax to get some data and use the callback function to deal with it.So we should render these data on the client side.
The question is how to render them on client side?
Now We just need a client side jade.js.
Follow this document : https://github.com/visionmedia/jade#readme
First
git clone https://github.com/visionmedia/jade.git
Second
$ make jade.js ( in fact the project has already compile the file for us )
so we just need to copy this file to the path that we use.
Third
read my demo below :
<script type='text/javascript' language='javascript' src="lib/jquery-1.8.2.min.js"></script>
<script type='text/javascript' language='javascript' src="lib/jade/jade.js"></script>
<script type='template' id='test'>
ul
li hello world
li #{item}
li #{item}
li #{item}
</script>
<script>
var compileText = $("#test").text();
console.log( typeof( compileText ) );
var fn = jade.compile( compileText , { layout : false } );
var out = fn( {item : "this is item " } );
console.log( out );
$("body").append( out );
</script>
Now you can see the output result in the body
hello world
this is item
this is item
this is item
After reading this demo I think that you would know how to seperate jade server side and client side.If you can understand which one compile the jade template,then all the questions are easy.
Maybe you would have another question now.How to write some jade template codes in *.jade?The document also provide us a way to do it.This Tutorial may help you.
index.jade
!!!5
html
head
title hello world
body
ul#list
script#list-template(type='template')
|- for( var i in data )
| li(class='list') \#{ data[i].name }
|- }
index.js
/* you javascript code */
var compileText = $('#list-template').text();
var compile = jade.compile( compileText , { layout : false } );
var data = [{ "name" : "Ben" } , {"name" : "Jack" } , {"name" : "Rose" }];
var outputText = compile( data );
$("#list").append( outputText );
Use http://github.com/flatiron/plates template engine which will work both on the client side and server side.
A few weeks ago I wrote an npm package for Handlebars templates to share them between client and server. It's pretty basic, but it's been working really well for me so far:
https://github.com/jwietelmann/node-handlebars-precompiler
Edit: I'm separately using "hbs" as the package for server-side rendering. The precompiler just delivers precompiled templates to my public javascripts directory whenever I update my hbs views.

Resources