What is the simplest way to link between .hbs files using handlebars? - javascript

I am a complete beginner to handlebars and am trying to modify the simple handlebars template taken from the example on glitch.com
I would like to be able to link between .hbs files as I would link between .html files but when I try however I am given the message cannot GET followed by whatever file I give to it.
Here is a grab of my overall structure for ref;
Here is the index.hbs files I am working with
<!DOCTYPE html>
<html>
{{> head }}
<body>
Link to second page
</body>
</html>
Which I would like to link to (for example) this secondpage.hbs file;
<!DOCTYPE html>
<html>
{{> head }}
<body>
Link back to index
</body>
</html>
Here is the code in my server.js file
// Generic node.js express init:
const express = require('express');
const app = express();
app.use(express.static('public'));
const hbs = require('hbs');
hbs.registerPartials(__dirname + '/views/partials/');
app.set('view engine', 'hbs');
app.set('views', __dirname + '/views');
app.get("/", (request, response) => {
let dt = new Date();
let data = {
projectName: process.env.PROJECT_DOMAIN,
luckyNumber: Math.floor(Math.random()*1000),
serverTime: new Date(),
ip: (request.headers["x-forwarded-for"]||"").split(",")[0]
};
data.json = JSON.stringify(data, null, 2);
response.render('index', data);
});
let listener = app.listen(process.env.PORT, () => {
console.log('Your app is listening on port ' + listener.address().port);
});
and the code in my watch.json
{
"install": {
"include": [
"^package\\.json$",
"^\\.env$"
]
},
"restart": {
"exclude": [
"^public/",
"^dist/"
],
"include": [
"\\.js$",
"\\.hbs$",
"\\.json"
]
},
"throttle": 100
}
If any of the details of the other files is necessary to assist let me know and I can provide.
I appreciate I am probably thinking about this in the wrong way, I have looked at handlebars in more detail and experimented with helpers etc. but it seems overly complicated for what I am trying to achieve, I thought you could write basic html within an hbs file? I am looking for the most straightforward, generic solution to the problem of linking between views in handlebars.
FWIW I want to use handlebars in a pretty simple fashion, basically just looking to have the equivalent of php includes using partials instead, so if there is a better way to approach the creation of the app with that in mind I would be grateful for advice.

Your code looks alright. What is the problem exactly? When you add {{> head}} partial to the index.hbs doesn't it render properly?
EDIT:
Okay, you have mainly 2 problems with your code:
You have no route defined on express linking to your /secondpage endpoint.
You are trying to link to a file Link instead of linking to an URL endpoint Link.
To fix your code you would have to define the endpoint linking to the handlebars file, so you need to change your server.js file to something like this.
const express = require('express');
const hbs = require('hbs');
const app = express();
app.use(express.static('public'));
app.set('view engine', 'hbs');
app.set('views', __dirname + '/views');
hbs.registerPartials(__dirname + '/views/partials/');
// 1st Page Route (URL Endpoint)
app.get('/', (request, response) => {
const data = {
projectName: process.env.PROJECT_DOMAIN,
luckyNumber: Math.floor(Math.random() * 1000),
serverTime: new Date(),
ip: (request.headers['x-forwarded-for'] || '').split(',')[0],
};
data.json = JSON.stringify(data, null, 2);
response.render('index', data);
});
// 2nd Page Route (URL Endpoint)
app.get('/secondpage', (request, response) => {
response.render('secondpage');
});
const listener = app.listen(process.env.PORT, () => {
console.log('Your app is listening on port ' + listener.address().port);
});
And then you need to fix your HTML links to this on index.hbs:
<!DOCTYPE html>
<html>
{{> head }}
<body>
Link to second page
</body>
</html>
And this on secondpage.hbs:
<!DOCTYPE html>
<html>
{{> head }}
<body>
Link back to index
</body>
</html>
Hope this helps you.

Instead of using handlebars I used express-handlebars
Terminal: npm i express-handlebars
Handlebars is a Middleware and functions as a Twig (Template Engine) so for your server I'd suggest:
// Generic node.js express init:
const express = require('express');
const app = express();
app.use(express.static('public'));
const exphbs = require('express-handlebars');
app.set('views', __dirname + '/views');
// added this part
app.engine('.hbs', exphbs ({
defaultLayout: 'main',
layoutsDir: ('views', __dirname + 'layouts'),
partialsDir: ('views', __dirname 'partials'),
extname: '.hbs'
}));
app.set('view engine', 'hbs')
app.get("/", (request, response) => {
let dt = new Date();
let data = {
projectName: process.env.PROJECT_DOMAIN,
luckyNumber: Math.floor(Math.random()*1000),
serverTime: new Date(),
ip: (request.headers["x-forwarded-for"]||"").split(",")[0]
};
data.json = JSON.stringify(data, null, 2);
response.render('index', data);
});
let listener = app.listen(process.env.PORT, () => {
console.log('Your app is listening on port ' + listener.address().port);
});
By doing this, you should have a file in your layouts folder named main.hbs where you will have that dynamic approach you're looking for. Something that stays the same for all pages. I will insert here a suggestion, feel free to adapt for your code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- CUSTOM CSS -->
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
{{> navigation }}
<main class="container p-5">
{{{ body }}}
</main>
</body>
</html>
Now when you create a navigation.hbs in your partials folder you will have the same frontend in all pages in the navigation. This is because we defined in the server.js our default template to be main.hbs. Whilst for your body, the triple hash ({{{}}}) inserts the components of the other .hbs files that you define. Don't forget to create a index.hbs file inside the views folder.
I learned the basics of hbs by following this tutorial (Note it's in Spanish). The tutorial produces this open-source project (which I am including in case it is useful).

Related

Problem with express-handlebars section helpers

I can't make the section helper work as intended. The body of login.hbs was parsed normally, but the js section was never parsed. I have tried using helpers inside res.render() and put section() straight into engine() and it didn't work either
app.js
import express from "express"
import expressHbs from "express-handlebars"
const app = express();
app.use(express.urlencoded({ extended:true }));
app.engine('hbs', expressHbs.engine({
defaultLayout: 'main.hbs',
helpers: {
section(name, options) {
if (!this._sections)
this._sections = {};
this._sections[name] = options.fn(this);
return null;
},
},
}));
app.set('view engine','hbs');
app.set('views','./view');
app.get("/", (req, res) => {
res.render("login");
})
app.listen(3000, () => console.log("listening"));
main.hbs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
{{{body}}}
{{{_section.js}}}
</body>
</html>
login.hbs
<h1>Hello</h1>
{{#section "js"}}
<script>
console.log("hello world")
</script>
{{/section}}
Edit
I can't embed HTML either
login.hbs
<h1>Hello</h1>
{{#section "js"}}
<p>a paragraph</p>
{{/section}}
I tried to run it. The real reason why it is not rendering is because inside your main.hbs you need to have {{{_sections.js}}} inside the html body tags instead of just {{{_section.js}}} like you have it now. The script tag should run as well.
Sorry for the confusion with the previous answer.

Cannot post back calculation to server

The problem showing up when I click the button submit.The error coming out 404 not found and "Cannot POST/Index" in the website. Am I have logical problems on the code or problem that occur on the syntax. My program is doing without any http request, it's just a normal import express engine and integrate with html which I'm trying to do a basic post back function to express(server) and post the answer back to my angular html.
I'm trying to post the number back to the server for the calculation and doesn't know where is the error that make me could not doing Post Function. Please get the requirement file from me if the file that I uploaded is not completed.
app.component.html
In my HTML file do I need to add somethings for link the server.ts? Is there still any issue that I have to check on it?
<!doctype html>
<html lang="en">
<head>
<Title>Calculator</Title>
<meta charset="utf-8">
<title>Myapp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
</head>
<body>
<h1 align="center">Angular Calculator</h1>
<div class="container">
<div class="card">
<div class="card-body">
<form action="index" method="POST">
<input type="number" name="num1" class="form-control" placeholder="Number">
<input type="number" name="num2" class="form-control" placeholder="Number">
<select ng-model="operator" name="operator">
<option>+</option>
<option>*</option>
<option>-</option>
<option>/</option>
</select>
<button type="submit">Submit</button>
</form>
<p>Calculation Of the number is :{{ result }} </p>
</div>
</div>
</div>
</body>
</html>
server.ts file
This is the by default server file that generate by npm which I'm not sure the syntax of code any problems for my first testing of addition functions.
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import * as bodyParser from 'body-parser';
import * as express from 'express';
import {join} from 'path';
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', DIST_FOLDER);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express.static(DIST_FOLDER, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('/app.component.html', { req });
});
app.get('*', (req, res) => {
res.status(404).send('data requests are not supported');
});
//have body-parser is a piece of express middleware that reads a form's
//input and stores it as a javascript object accessible through
//can not to include body-parser but not to get the raw request, your body and header are not in the root object of request parameter
app.post('/',(req, res) => {
//var num1 = req.body.operator
var result=req.body;
console.log(req.body);
var operator = req.body.operator
if (operator == "+")
{
res.render('/app.component.html', {
result: parseInt(req.body.num1) + parseInt(req.body.num2),
});
}
})
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
The following line implies you are posting to /index:
<form action="index" method="POST">
For posting to your express service (which is hosted on port 4000 and hence the URL is http://localhost:4000), you have to define a submit behavior on your button. Check this for reference.
I think the problem is you don't include
'angular-route.js'
in your client-side code
you can't post to index <form action="index" method="POST">.
you need a server side file. like: <form action="form_process.php" method="POST">

EJS CSS Not loading front end

So i set the middleware and static folder for the .css file, but when i attempt to use it and load it in the EJS file i receive an error in the chrome debugger saying under "network" that it was cancelled.
app.js
// view engine
exapp.set('view engine', 'ejs');
exapp.set('views', path.join(__dirname, 'views'));
console.log('Static location: ' + path.join(__dirname, 'views'));
// body parser
exapp.use(bodyParser.json());
exapp.use(bodyParser.urlencoded({extended: false}));
// set static path
exapp.use(express.static(path.join(__dirname, 'public')));
console.log('Static location: ' + path.join(__dirname, 'public'));
exapp.use(express.static(path.join(__dirname, 'public/css')));
console.log('Static location: ' + path.join(__dirname, 'public/css'));
// get requests
exapp.get('/', function(request, response){
response.render('index');
})
exapp.get('/about', function(request, response){
response.render('about');
})
header.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Unicorn</title>
<link rel="stylesheet" href="/public/css/main.css" type="text/css" media="screen" charset="utf-8">
</head>
<body>
Picture of what i found in the chrome debugging thing
Fixed it
For some unknown reason i need to add '/public/css' as the first parameter to the use() function
Original
exapp.use(express.static(path.join(__dirname, 'public/css')));
Fixed
exapp.use('/public/css', express.static(path.join(__dirname, 'public/css')));
Hope this helps others :)

Interating through imported json in node.js and express, then displaying with jade

After looking at the examples and answers given in related questions here on StackExchange and trying to use them to resolve my issue, I'm finally posting my question. Grrr....
So My goal is to eventually access some complex json via api and REST. I was hoping to import (currently via require but eventually via Oauth'd RESTful API) json, parse it to discover the key/value pairs (including nested keys and values) and then at the very least create an object I could then display and have access to all elements. I hope that's making sense. Anyway, to begin to build that I thought I'd get some example json and require it. Well I initally tried some json from the API that I'm going to use but I'm afraid that it was causing issues (well, my inexperience with node, express and jade is really the cause) so I decided to simplify and grab some very simple json. A colorsArray. So .. now some code. Here's the console output including the error I get after error I get after attempting to render the web page. Please ignore the pathings because I'm using my php oriented Eclipse IDE to run nodeclipse (which is working awesome btw)
{ colorsArray:
[ { colorName: 'red', hexValue: '#f00' },
{ colorName: 'green', hexValue: '#0f0' },
{ colorName: 'blue', hexValue: '#00f' },
{ colorName: 'cyan', hexValue: '#0ff' },
{ colorName: 'magenta', hexValue: '#f0f' },
{ colorName: 'yellow', hexValue: '#ff0' },
{ colorName: 'black', hexValue: '#000' } ] }
Express server listening on port 3000
After some work (with advise from comments and answers below) I'm now getting the following in my browser
**Express**
Welcome to Express
[object Object]
I've update code in the sections below to reflect advice.
Here's my current app.js
/**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path');
//Load projects as JSON.
var ob = require('./simple.json');
console.log(ob);
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// set object for templates
app.locals('ob' , ob);
// development only
if ('development' === app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
and current index.js
/*
* GET home page.
*/
exports.index = function(req, res){
res.render('index', { title: 'Express' , ob: req.body});
};
index.jade
extends layout
block content
h1= title
p Welcome to #{title}
p #{ob}
for(var prop in ob)
p #{ob.colorName}: #{ob.hexValue}
and finally layout.jade
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
ADD-->Initially forgot to add the json file I'm trying to import
{
"colorsArray":[{
"colorName":"red",
"hexValue":"#f00"
},
{
"colorName":"green",
"hexValue":"#0f0"
},
{
"colorName":"blue",
"hexValue":"#00f"
},
{
"colorName":"cyan",
"hexValue":"#0ff"
},
{
"colorName":"magenta",
"hexValue":"#f0f"
},
{
"colorName":"yellow",
"hexValue":"#ff0"
},
{
"colorName":"black",
"hexValue":"#000"
}
]
}
So there you have it. Again, I'd LOVE to be able to get the json, parse it to identify the elements (including nested sub-elements) and have it as an object I can access for logic and display. But right now, I'd be giddy with being able to just display it in the jade and access the object's elements via a loop.
Thanks for taking time to look through this and I know this has been answered here in other examples but I've spent a week trying to employ those solutions with no luck
When node.js requires a json file it automatically parses it.
You are attempting to parse it a second time here:
exports.index = function(req, res){
var ob = JSON.parse(ob);
res.render('index', { title: 'Express' , ob: ob});
};
Try omitting JSON.parse and pass the object straight to the render method.

Isomorphic React - how to make react function as part of the window object

I've been building an isomorphic react app using node-jsx, browserify, reactify, etc. My code runs fine on the server, and components are mounted and rendered correctly. However, the react function doesn't seem to work, for instance, the handleClick function does not recognize alert(), or console.log() never prints out expected result on neither the server nor the client side console. Can anyone identify what's going on here?
UPDATE: Another thing I want to point out is, when I run the server and go to the browser, in the browser console(used chrome dev tool) I typed "window.React", it actually returned the React object. But console.log still doesn't do anything for click handler function.
views/index.ejs
<!doctype html>
<html>
<head>
<title>Shortened URL Generator</title>
<link href='/style.css' rel="stylesheet">
<link href="css/griddle.css" rel="stylesheet" />
</head>
<body>
<h1 id="main-title">Welcome to Shortened URL Generator</h1>
<div id="react-main-mount">
<%- reactOutput %>
</div>
<!-- comment out main.js to see server side only rendering -->
<script src="https://fb.me/react-with-addons-0.14.3.min.js"></script>
<script src="https://fb.me/react-dom-0.14.3.min.js"></script>
<script src="/main.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script src="//fb.me/JSXTransformer-0.12.0.js"></script>
<script type="text/javascript" src="scripts/griddle.js"></script>
</body>
</html>
routes/routes.js
var React = require('react/addons'),
ReactApp = React.createFactory(require('../components/ReactApp'));
module.exports = function(app) {
var storeUrls = {
"fb.com": "facebook.com"
};
app.get('/', function(req, res){
// React.renderToString takes your component
// and generates the markup
var reactHtml = React.renderToString(ReactApp({}));
// Output html rendered by react
// console.log(myAppHtml);
res.render('index.ejs', {reactOutput: reactHtml});
});
app.get('/:routeParam', function(req, res){
});
};
app/components/ReactApp.js
var TableComponent = require('./TableComponent');
var React = require('react/addons');
var urls = require('./url');
var Griddle = React.createFactory(require('griddle-react'));
var ReactApp = React.createClass({
componentDidMount: function () {
console.log("yes");
},
handleClick: function() {
// this.setState({liked: !this.state.liked});
var longUrl = this.refs.inputUrl;
urls.push({
"original url": longUrl,
"shortened url": "/"
})
console.log(longurl);
},
render: function () {
return (
<div>
<div id="form">
<form>
<section>Paste your long url here</section>
<input ref="inputUrl" value={this.props.value} type="text" placeholder="http://...." />
<button onclick={this.handleClick} type="submit" value="Submit">Shorten URL</button>
</form>
</div>
<div id="table-area">
<TableComponent />
</div>
</div>
)
}
});
module.exports = ReactApp;
app/main.js
var React = require('react/addons');
var ReactApp = require('./components/ReactApp');
var TableComponent = require('./components/TableComponent');
var mountNode = document.getElementById('react-main-mount');
var mountTable= document.getElementById('table-area');
React.render(new ReactApp({}), mountNode);
React.render(new TableComponent({}), mountTable);
server.js
var express = require('express'),
path = require('path'),
app = express(),
port = 5000,
bodyParser = require('body-parser');
require('node-jsx').install();
// Include static assets. Not advised for production
app.use(express.static(path.join(__dirname, 'public')));
// Set view path
app.set('views', path.join(__dirname, 'views'));
// set up ejs for templating. You can use whatever
app.set('view engine', 'ejs');
// Set up Routes for the application
require('./app/routes/routes.js')(app);
//Route not found -- Set 404
app.get('*', function(req, res) {
res.json({
'route': 'Sorry this page does not exist!'
});
});
app.listen(port);
console.log('Server is Up and Running at Port : ' + port);
Gulpfile.js
var gulp = require('gulp');
var source = require('vinyl-source-stream'),
browserify = require('browserify');
gulp.task('scripts', function(){
return browserify({
transform: [ 'reactify' ],
entries: 'app/main.js'
})
.bundle()
.pipe(source('main.js'))
.pipe(gulp.dest('./public/'));
});
gulp.task('default', ['scripts']);
document.getElementById('react-main-mount'); returns null if your script runs before that element is loaded in html.
What you should do is either include your script before the closing tag </body>, or run ReactDOM.render after DOMContentLoaded.
It can be a simple modification to your existing code:
if (typeof window !== 'undefined) {
window.React = require('react');
}
Do that for whatever you want make available globally, usually you'll want to do the same for ReactDOM too so you can call render directly in a html file. It's not usually recommended though. You might want to use the standalone downloads from React Downloads.
So I realized it has something to do with React.render in the main.js file. instead of
React.render(new ReactApp({}), mountNode);
it should be
React.render(<ReactApp/>, document.getElementById('react-main-mount'));
In this case, TableComponent does not need to be rendered, also require React without using the addon.
It works now. Thanks for everyone's help!

Categories