Nodejs EJS helper functions? - node.js

Is there a way to register helper functions to EJS templates, so that they can be called from any EJS template? So, it should work something like this.
app.js
ejs.helpers.sayHi = function(name) {
return 'Hello ' + name;
});
index.ejs
<%= sayHi('Bob') %>

Yes, in Express 3 you can add helpers to app.locals. Ex:
app.locals.somevar = "hello world";
app.locals.someHelper = function(name) {
return ("hello " + name);
}
These would be accessible inside your views like this:
<% somevar %>
<% someHelper('world') %>
Note: Express 2.5 did helpers differently.

I have another solution to this, and I think it has some advantages:
Don't polute your code exporting filters.
Access any method without the need to export them all.
Better ejs usage (no | pipes).
On your controller:
exports.index = function(req, res) {
// send your function to ejs
res.render('index', { sayHi: sayHi });
}
function sayHi(name) {
return 'Hello ' + name;
};
Now you can use sayHi function inside your ejs:
<html>
<h1><%= sayHi('Nice Monkey!') %></h1>
</html>
You can use this method to send modules to ejs, for example, you could send 'moment' module to format or parse dates.

Here's an example filter...I'm not familiar with helpers.
var ejs = require('ejs');
ejs.filters.pluralize = function(num, str){
return num == 1 ? str : str+'s';
};
<%=: items.length | pluralize:'Item' %>
Will produce "Item" if it's 1, or if 0 or > 1, produces "Items"
app.js
ejs.filters.sayHi = function(name) {
return 'Hello ' + name;
});
index.ejs
<%=: 'Bob' | sayHi %>

I am using:
In helpers/helper.js
var func = {
sayhi: function(name) {
return "Hello " + name;
}, 
foo: function(date) {
//do somethings
}    
};
module.exports = func;
In router:
router.get('/', function(req, res, next) {
res.render('home/index', {
helper: require('../helpers/helper'),
title: 'Express'
});
});
In template:
<%= helper.sayhi("Dung Vu") %>
goodluck

Related

How to call an object not from a file I use require.js

I call to object AjaxForm on my html page
<script>
require(['app', '/assets/build/js/app/ajaxform.js'], function (a) {
a.AjaxForm.initialize({$_modx->getPlaceholder('AjaxForm.params')});
});
</script>
But I get error
Cannot read property 'AjaxForm' of undefined
My ajaxform.js file
define('app', ['jquery'], function ($) {
'use strict';
var AjaxForm = {
initialize: function (afConfig) {
$(document).ready(function () {
$.jGrowl.defaults.closerTemplate = '<div>[ ' + afConfig['closeMessage'] + ' ]</div>';
});
.......
Are you returning AjaxForm? I think you are missing this:
define('app', ['jquery'], function ($) {
'use strict';
var AjaxForm = {
initialize: function (afConfig) {
$(document).ready(function () {
$.jGrowl.defaults.closerTemplate = '<div>[ ' + afConfig['closeMessage'] + ' ]</div>';
});
}
}
return AjaxForm;
});
Then, in your require, you do:
<script>
require(['app', '/assets/build/js/app/ajaxform.js'], function (AjaxForm) {
AjaxForm.initialize({$_modx->getPlaceholder('AjaxForm.params')});
});
</script>
Hope it helps
I did this:
requirejs(["app", "app/ajaxform"], function () {
require(['ajaxform'], function (e) {
e.initialize({$_modx->getPlaceholder('AjaxForm.params')});
});
});
And in the file ajaxform.js
define('ajaxform', ['jquery','jgrowl','jqueryform'], function ($,jGrowl,ajaxSubmit) {
....
return AjaxForm;

Generating an array in the view and passing it into the controller

It's a little strange but I cannot think of a better way to get it done.
First of all this is my code:
The router to get the view in the first place
router.get('/', function(req, res, next) {
Account.findOne(
{
_id: req.user._id,
},
function(err, acc) {
if (err) {
console.log(err);
}
// console.log(acc.websites);
res.render('reports/index', {
title: 'Reports!',
websites: acc.websites,
user: req.user,
});
}
);
});
The view:
<% include ./../partials/header.ejs %>
<h1 class="text-center">This is your report page</h1>
<form method="post">
<% for(let i=0; i<websites.length; i++){ let website = websites[i]; %>
<fieldset>
<label for="website<%=i%>" class="col-sm-2">Website <%=i+1%></label>
<input name="website<%=i%>" id="website<%=i%>" value="<%=website%>" type="text" />
</fieldset>
<% } %>
Generate report
</form>
<% include ./../partials/footer.ejs %>
The router, that's supposed to fire up after the on click.
router.get('/reports', function(req, res, next) {
if (req.user.isPremium == false) {
// Free user - Single report
var builtWithCall = `https://api.builtwith.com/free1/api.json?KEY=00000000-0000-0000-0000-000000000000&LOOKUP=${website}`;
let website = req.body.website0;
console.log(website);
}
});
How it works: The controller finds the account, grabs an array from inside of it, sends it to the view. The view prints it out and allows to make some changes to the values.
And now is where the problems begin. I need to gather the new values into an array and send it to the next router, which will then use them to call a bunch of APIs and print out the data. How do I gather the array and pass it to the controller? Also, should I use GET or POST?
With your logic, the name attribute will have the following form: name=website[0...n]. With that in mind, we can filter out the keys to gather all the website[n] into an array you seek:
const example = {
website0: 'example',
website1: 'example',
website2: 'example',
website3: 'example',
shouldBeIgnored: 'ignoreMe',
ignore: 'shouldIgnore'
}
const websites = Object.keys(example).filter(key => key.startsWith('website'))
console.log(websites)
So you're controller can be:
router.get('/reports', (req, res, next) => {
if (!req.user.isPremium) {
// Free user - Single report
const builtWithCall = `https://api.builtwith.com/free1/api.json?KEY=00000000-0000-0000-0000-000000000000&LOOKUP=${website}`;
const websites = Object.keys(req.body).filter(key => key.startsWith('website'));
console.log(websites);
}
});

Template actual data context

I have troubles with data context;
Here is my code (unfortunatly, meteorpad is broken)
router.js(I use iron:router)
Router.configure({
layoutTemplate: 'layout'
});
Router.route('home',{
path: '/',
action: function(){
this.redirect('sections', {page: 0});
}
});
Router.route('sections', {
path: '/sections/:page',
data: function(){
var data = {};
data.params = {};
data.params.page = this.params.page?this.params.page:0;
return data;
}
});
template.html
<template name="layout">
{{>yield}}
</template>
<template name="sections">
Page: {{params.page}}
<br>
Page 0
Page 1
Page 2
<br>
<button>what page?</button>
</template>
template.js
Template.sections.onRendered(function(){
let scope = this;
$("button").on("click", function(){
alert("page: " + scope.data.params.page);
});
});
When I click button, button-handler has the scope, which had the template, when rendered, but not actual in this moment;
thanks to #user3374348
method Blaze.getData(scope.view) returns actual data context.
template.js
Template.sections.onRendered(function(){
let scope = this;
$("button").on("click", function(){
alert("page: " + Blaze.getData(scope.view).params.page);
});
});

How to: Use ejs without express

I'm starting with node in general, and I'm attempting to do a site without express.
I would none the less want to use ejs to inject my html and this is where my problem is...
How do I attach the ejs.render(...) to the response?
PS: I know it might be a better option to use express, but I want to know how it works underneath before bridging it.
Something like:
var ejs = require("ejs");
function index (response, request, sequelize) {
response.writeHead(200, {"Content-Type": "text/html"});
test_data = "test data";
response.end(ejs.render("./views/home.html",test_data));
}
exports.index = index;
But that works ^_^
Thanks!
First of all, You need install ejs -> $ npm install ejs --save
Simple example:
main.ejs:
<p> <%= exampleRenderEjs %> </p>
server.ejs
var ejs = require('ejs');
var fs = require('fs');
var htmlContent = fs.readFileSync(__dirname + '/main.ejs', 'utf8');
var htmlRenderized = ejs.render(htmlContent, {filename: 'main.ejs', exampleRenderEjs: 'Hello World!'});
console.log(htmlRenderized);
There is a project called Consolidate.js which provides a common API for many template engines. This ensures they can all be interchangeable. If you want to render templates directly, you want to be compatible with this API.
Sample code from the Consolidate.js README:
var cons = require('consolidate');
cons.swig('views/page.html', { user: 'tobi' }, function(err, html){
if (err) throw err;
console.log(html); // Or write to your `res` object here
});
This sample is for Swig, but similar code works for EJS or any of the compatible engines.
Example from some book:
template.ejs
<html>
<head>
<style type="text/css">
.entry_title { font-weight: bold; }
.entry_date { font-style: italic; }
.entry_body { margin-bottom: 1em; }
</style>
</head>
<body>
<% entries.map(entry => { %>
<div class="entry_title"><%= entry.title %></div>
<div class="entry_date"><%= entry.date %></div>
<div class="entry_body"><%= entry.body %></div>
<% }); %>
</body>
</html>
function blogPage(entries) {
const values = { entries };
const template = fs.readFileSync('./template.ejs', 'utf8');
return ejs.render(template, values);
}
const server = http.createServer((req, res) => {
const entries = getEntries();
const output = blogPage(entries);
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(output);
console.log('run server');
});
server.listen(8000);

Newbie questions about partials

I have a simple page with head, menu, content and footer. I need to divide them into separate files. After reading through express documentation i (created 4 templates and) wrote something like this:
app.get('/', function(req, res) {
var response = [null, null, null, null]
, everyNotNull = function(elem) {
return (elem !== null);
}, sendResponse = function(type, str) {
switch (type) {
case 'head' : response[0] = str; break;
case 'menu' : response[1] = str; break;
case 'content' : response[2] = str; break;
case 'footer' : response[3] = str; break;
}
if (response.every(everyNotNull)) {
res.send(response.join(''));
}
};
res.partial('head', {'title' : 'page title'}, function(err, str) {
sendResponse('head', str);
});
res.partial('menu', {'active' : '/'}, function(err, str) {
sendResponse('menu', str);
});
res.partial('index', {'title' : 'Home'}, function(err, str) {
sendResponse('content', str);
});
res.partial('footer', {'nowdate' : (new Date()).getFullYear()}, function(err, str) {
sendResponse('footer', str);
});
});
Though it works it seems a bit dirty to me. Is there a better way to use partial templates?
You were right to suspect something was missing, you're doing unnecessary work there.
Express will stitch the templates together for you, just call res.render() and the name of the view you want to call. The layout and partials should get pulled in automatically.
In my apps I usually use partials as below. Just replace references to EJS with whichever template engine you're using (Jade, moustache, etc):
./lib/app.js
app.get('/', function(req, res) {
var model = {
layout:'customLayout', // defaults to layout.(ejs|jade|whatever)
locals:{
foo:'bar'
}
};
res.render('index',model);
});
./views/layout.ejs
<html>
<head><%- partial('partials/head') %></head>
<body>
<%- partial('partials/menu') %>
<%- body %>
<%- partial('partials/footer') %>
</body>
</html>
./views/index.ejs
<h1>Index page</h1>
./views/partials/menu.ejs
<div><a href='... </div>
./views/partials/head.ejs
<script>...</script>
etc.

Resources