Multiple sections in Handlebars template - handlebars.js

I’m just getting to know Handlebars a bit better as a templating solution and have hit a problem that I don’t know how to solve.
I’ve added sections to my layout, one for the header and one for the footer for dynamically inserting scripts from my views. However, only the first section ever renders. The second one (regardless of order) is always omitted.
My layout is a simple HTML page:
<!doctype html>
<html>
<head>
<title>Test site</title>
{{{_sections.head}}}
</head>
<body>
<header>
//Logo and stuff here
</header>
{{{body}}}
<script src="//code.jquery.com/jquery-2.0.2.min.js"></script>
{{{_sections.footer}}}
</body>
</html>
And in my layout file I have:
{{#section 'head'}}
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script>
{{/section}}
//basic HTML here
{{#section 'footer'}}
<script>
alert("this doesn’t fire if its second!");
</script>
{{/section}}
The header section appears on the page but the footer does not. The thing is, if I put the footer at the top of the page (i.e. before the {{section ‘head’}} that then renders but the head section no longer renders.
In my app.js I’m setting up the section functionality as follows:
var handlebars = require('express3-handlebars')
.create({
defaultLayout: 'main',
helpers: {
section: function (name, options) {
if (!this._sections) {
this._sections = {};
this._sections[name] = options.fn(this);
return null;
}
}
}
});
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
Any ideas what I’m doing wrong here or how to add support for both sections?
Thanks

I think your sections are overwriting each other. Try changing your handlebars create to the following:
var handlebars = require('express3-handlebars')
.create({
defaultLayout: 'main',
helpers: {
section: function (name, options) {
if (!this._sections) {
this._sections = {};
}
this._sections[name] = options.fn(this);
return null;
}
}
});

Related

Google App Script include css from another file

I'm trying out Google app scripts for the first time and I'm having a nightmare trying to get it to read my Stylesheet.
I've read dozens of pages and they all say the same thing, but it just doesn't work.
This is my code.gs :-
return HtmlService.createHtmlOutputFromFile('index');
}
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
This is my index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= include('Stylesheet'); ?>
</head>
<body>
Hello!!!!<br>
This is a test to see how Google scripts work<br>
</body>
</html>
This is my Stylesheet
<style>
html{
min-height:100%;
background-color:blue;
}
</style>
If I put the style tags inside the index file, it works fine, but when I try to include it, the include script just gets added to my page. I've tried using createTemplateFromFile as well, but it has the same result. It seems like index.html is ignoring the script identifier <?!
Please could someone tell me what I'm doing wrong as every page I've looked at says to do it this way!!!!!
Thanks
I assume it could be because your are not calling .evaluate()
return HtmlService.createTemplateFromFile('index').evaluate();
https://developers.google.com/apps-script/guides/html/best-practices#code.gs
Seems like it's been missed to add evaluate() function.
As per the HTML Service: Templated HTML , one should be using particular scriptlet as per the requirement.
I'd recommend a generalized solution like below:
function include(fileName, params) {
return render(fileName, params).getContent();
}
function render(path, params) {
let htmlBody = HtmlService.createTemplateFromFile(path);
// for prop drilling
htmlBody.allParams = {};
if (params) {
htmlBody.allParams = params;
Object.keys(params).forEach(key => {
htmlBody[key] = params[key];
});
}
return htmlBody.evaluate();
}

CSS Issue with Node.JS / EJS

I know similar questions have been asked before, but I've had a good look through & unfortunately none of the answers are helping me.
My CSS file is being ignored in certain circumstances.
So in my app.js file I have this code, defining my view engine setup
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
In my index.js file I have the following the code for UserList page
/* GET Userlist page. */
router.get('/userlist', function(req, res) {
var db = req.db; // (1) Extract the db object we passed to our HTTP request
var collection = db.get('usercollection'); // (2) Tell our app which collection we want to use
// (3) Find (query) results are returned to the docs variable
collection.find({},{},function(e,docs){
res.render('userlist', { "userlist" : docs }); // (4) Render userlist by passing returend results to said variable
});
});
Finally, my userlist.ejs page looks like this:
<!DOCTYPE html>
<html>
<head>
<title>User List</title>
<link rel='stylesheet' href='/stylesheets/style.css' type="text/css" />
</head>
<body>
<h1>User List</h1>
<ul>
<%
var list = '';
for (i = 0; i < userlist.length; i++) {
list += '<li>' + userlist[i].username + '</li>';
}
return list;
%>
</ul>
</body>
</html>
But when I run my page the CSS file is not loaded. However if I exclude this code:
<%
var list = '';
for (i = 0; i < userlist.length; i++) {
list += '<li>' + userlist[i].username + '</li>';
}
return list;
%>
The CSS file is loaded and applied without issue. Can anyone tell me why this is please? Apologies for the newbie question, but I've been trying to figure this out for ages.
I should mention the 'h1' tags are ignored too. The only thing rendered is the list items.
Not sure if its relevant, but my app is connecting to MongoDB to return the user data.
Any assistance would be very much appreciated!
Thank you!
Make sure that your CSS file is either defined as an endpoint in your index.js file or make sure that public/stylesheets/style.css exists so it can be loaded through the app.use(express.static(path.join(__dirname, 'public'))); command.

Loading Google Places Autocomplete Async Angular 2

I am trying to instantiate a Google Places Autocomplete input within an Angular 2 component. I use this code to do it:
loadGoogle() {
let autocomplete = new google.maps.places.Autocomplete((this.ref.nativeElement), { types: ['geocode'] });
let that = this
//add event listener to google autocomplete and capture address input
google.maps.event.addListener(autocomplete, 'place_changed', function() {
let place = autocomplete.getPlace();
that.place = place;
that.placesearch = jQuery('#pac-input').val();
});
autocomplete.addListener()
}
Normally, I believe, I would use the callback function provided by the Google API to ensure that it is loaded before this function runs, but I do not have access to it within a component's scope. I am able to load the autocomplete input 90% of the time, but on slower connections I sometimes error out with
google is not defined
Has anyone figured out how to ensure the Google API is loaded within a component before instantiating.
Not sure whether this will help, but I just use a simple script tag in my index.html to load Google API and I never get any error. I believe you do the same as well. I post my codes here, hope it helps.
Note: I use Webpack to load other scripts, except for Google Map API.
<html>
<head>
<base href="/">
<title>Let's Go Holiday</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Google Map -->
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<your-key>&libraries=places"></script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
And then in your component:
...
declare var google: any;
export class SearchBoxComponent implements OnInit {
ngOnInit() {
// Initialize the search box and autocomplete
let searchBox: any = document.getElementById('search-box');
let options = {
types: [
// return only geocoding results, rather than business results.
'geocode',
],
componentRestrictions: { country: 'my' }
};
var autocomplete = new google.maps.places.Autocomplete(searchBox, options);
// Add listener to the place changed event
autocomplete.addListener('place_changed', () => {
let place = autocomplete.getPlace();
let lat = place.geometry.location.lat();
let lng = place.geometry.location.lng();
let address = place.formatted_address;
this.placeChanged(lat, lng, address);
});
}
...
}
I used it the same way as explained above but as per google page speed i was getting this suggestion,
Remove render-blocking JavaScript:
http://maps.googleapis.com/…es=geometry,places&region=IN&language=en
So i changed my implementation,
<body>
<app-root></app-root>
<script src="http://maps.googleapis.com/maps/api/js?client=xxxxx2&libraries=geometry,places&region=IN&language=en" async></script>
</body>
/* Now in my component.ts */
triggerGoogleBasedFn(){
let _this = this;
let interval = setInterval(() => {
if(window['google']){
_this.getPlaces();
clearInterval(interval);
}
},300)
}
You can do one more thing, emit events once the value(google) is received,& trigger your google task
inside them.

Setting the document title with Meteor

I'm just starting with Meteor. In an app which is to be localized, I want to set the document title.
I am following the advice given by Bernát
In my barebones version, I have just 2 documents:
head.html
<head>
<meta charset="utf-8">
<title>{{localizedTitle}}</title>
</head>
ui.js
UI.registerHelper("localizedTitle", function() {
var title = "Localized Title"
document.title = title;
});
When the app loads, the document title is "{{localizedTitle}}". If I call UI._globalHelpers.localizedTitle() from the console, the correct title is shown.
What do I have to do to get the localized title to show when the page is loaded?
EDIT: This works for me, but it seems to be a bit of a hack. The title template does nothing but get itself rendered, which actually adds nothing to the interface.
body.html
<body>
{{> title}}
</body>
<template name="title">
</template>
title.js
Template.title.onRendered(function () {
document.title = getLocalizedString()
function getLocalizedString() {
return "Title : in English"
}
})
Following Bernát's answer, your global helper should not be called in the head's <title> tag, but within the <template> tag of the template where you wish to have a given title. In Meteor, <head> does not count as a template, therefore you cannot use Spacebars notation in it: it will just be considered as simple text.
Also, keep in mind that your helper will not return (i.e. print) anything to the page. document.title = "something" directly assigns "something" to your ` tag. So no need to call your helper inside it!
So, say you want to have the "Localized Title" title for a page using the localized template :
<template name="localized">
<h1>This is the localized page</h1>
{{localizedTitle}}
</template>
Here, your trick should work.
I've found it convenient to set the title in onAfterAction in my iron-router routes:
onAfterAction: function(){
document.title = 'foo'; // ex: your site's name
var routeName = Router.current().route.getName();
if ( routeName ) document.title = document.title + ': ' + routeName;
}
I think a more elegant solution is to make the title reactive and set it via a Session variable (other reactive data sources are of course also OK). Like that:
Template.body.onRendered(function() {
this.autorun(function() {
document.title = Session.get('documentTitle');
});
});
Now every time you set the 'documentTitle' variable with
Session.set('documentTitle', 'Awesome title');
the page title will change. No need for hacks and you can do this anywhere in your client code.
Try this instead:
UI.registerHelper('title', function() {
return 'LocalizedTitle'
});
we can use title where ever you want
you can use like this {{title}}

How to dynamically create properties in polymer as needed

I have a use case where I have a component (like a database) that I would like to expose all the information as bindable properties. However, only a few of those properties will be need by any particular client who uses it. There could be 1000's of entries in the database. How can I figure out which ones are actually needed by the client.
For example:
Polymer('database,
{
observer : {
name : function(oldVal, newVal) { onDataChanged('name', newVal);},
addr : function(oldVal, newVal) { onDataChanged('addr', newVal);},
tel.main : function(oldVal, newVal) { onDataChanged('tel.main',
etc....
}
});
In this case I would like to dynamically create observe handlers only for the data bindings that are actually needed on the fly.
If you are willing to have your clients extend your component to specify the desired database fields then you can dynamically create observers only for the fields they specify.
Example
Component
<link rel="import" href="../../webcomponents/bower_components/polymer/polymer.html">
<polymer-element name=demo-dynamicproperties >
<template>
<h2>dynamic properties</h2>
See the console for changes
</template>
<script>
Polymer({
// Validate that it is an attribute that is allowed
// For the example we will allow anything starting with validitem
isValidAttribute: function(name) {
return (name.match('^validitem'));
},
// Get attributes
created: function() {
for (name in this.publish) {
console.log("Trying: "+name);
// Verify that it is one of the dynamic attributes
if (this.isValidAttribute(name)) {
console.log("Accepting: "+name);
this[name]="Pull from DB";
// References:
// https://www.polymer-project.org/0.5/docs/polymer/node_bind.html
// https://github.com/Polymer/NodeBind/blob/master/tests/tests.js
// https://github.com/polymer/observe-js
var observer = new PathObserver(this,name);
observer.open(makeHandler(this,name));
}
}
/************* TEST **********************************/
// Verify that dynamic updates worked by updating
this.job('update_validitem1', function() {
this.validitem1="Updated after 10 seconds";
}, 10000);
/************ End Test ******************************/
}
});
// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
function makeHandler(element, property) {
function handler(newval,oldval) {
console.log("element" + element,"property:" + property,"from:"+oldval,"to:"+newval);
}
return handler;
}
</script>
</polymer-element>
Usage
<!DOCTYPE HTML>
<html lang="en">
<head>
<script src="../../webcomponents/bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="../../webcomponents/bower_components/polymer/polymer.html">
<link rel="import" href="demo-dynamicproperties.html">
<title>demo-dynamicproperties test page</title>
</head>
<body>
<polymer-element name=demo-usedb extends="demo-dynamicproperties" attributes="validitem1 validitem2 invaliditem" noscript>
</polymer-element>
<h1>Demo</h1>
<template is="auto-binding">
<h2>Dynamic tag</h2>
<demo-usedb validitem1="{{item1choice2}}" item2="setthis"></demo-usedb>
<h2>Input</h2>
<input type="text" value="{{item1choice2}}">
<h3>Produces</h3>
{{item1choice2}}
</template>
</body>
</html>
It looks like the answer to the questions is that it cannot be done. There does not appear to be any hooks or events that a component can use to get notified when properties are bound (or attempted to be bound) to it. I filed a bug/enhancement request here
https://github.com/Polymer/polymer/issues/1303
to request that this feature be supported in the future.

Resources