Jade dynamic data - node.js

Using node.js I am passing some variables to jade view:
res.render('index', {
locals: {
name: user.name,
hashpassword: JSON.stringify(user.hashPass),
languages: JSON.stringify(langs)}
});
In jade file I have:
body
#heading
h1 nodechat
label !{locals.languages} // working - printing whole json string
#content
- var laangs = !{locals.languages} //not working here!
//SyntaxError: Unexpected token .
- each item in laangs
label= item.EnglishName
The problem is that I cannot pass locals.languages to a variable in jade file. If I assign it to a single html element (like label), it's working, but when I try with var = that doesn't work.
What may be the problem?

See my change below...
body
#heading
h1 nodechat
label !{locals.languages} // working - printing whole json string
#content
//- Do it like this...You're already in JavaScript land after the -
- var laangs = locals.languages
- each item in laangs
label= item.EnglishName
Change !{locals.languages} into locals.languages

Related

Node JS: given a html string, how can I get the content inside of all <script> tags, manipulate and replace it?

Overview:
I am working on a project that has dozens of .Liquid (Shopify) snippets with <script> tags inside of them containing JS code.
They're similar to HTML, they look something like this:
{% assign variable = 'test' %}
<p>hey {{variable}}</p>
<script>console.log("hey")</script>
{% schema %}
{
...json stuff
}
{% endschema %}
Issue:
Basically what I wanna do is get the content inside <script>, manipulate it and replace with the new manipulated one.
I managed to do this using cheerio, but it ends up messing up the Liquid variables since it doesn't recognize them.
My previous code was looking something like this:
let html = cheerio.load(code, { _useHtmlParser2: true });
const { data: js } = html("script").get()[0].children[0];
html("script").get()[0].children[0].data = await minifyJS(js);
const result = html.html();
Expected Behavior:
I need to:
Find all script tags in a HTML string;
Get the code inside of the <script> tag;
Manipulate this code (minify, essentially);
Replace it with the now minified code.
I am trying to avoid using regex, but I can't foresee any other solutions.
Any suggestion is greatly appreciated.
Thank you!
To get the content inside tags you can use Regular Expressions
<script(.|\n)*?<\/script>
This is just the regex
let str = <Whatever string or data you want to extract script tags>;
let result = let result = str.match(/<script(.|\n)*?<\/script>
/g);
console.log(result);
in result you will get the content inside the script tag

Gathering document fragments at rendring time using `pug`

I use pug to generate HTML email messages from a template:
doctype html
html
head
title Hello #{name}
body
...
The title is the subject of the email.
Currently, I extract the title text content by parsing the HTML document rendered by pug. But it doesn't seem to be a very efficient way of doing.
Is there some feature or hook available in pug to collect part of the document while rendering it? I considered pug filters, but as far as I understand, those are not suitable since they are triggered at compile time. Not while rendering the document.
I came to a solution using a mixin:
mixin collect(name)
-
// This is just an ugly hack to
// capture the inner block rendered
// text
const savedHtml = pug_html;
pug_html = "";
if (block) block();
const innerHtml = pug_html;
self[name]=innerHtml;
pug_html = savedHtml+innerHtml;
html
head
title
+collect('title')
| Hello #{self.name}
var pug = require("pug");
const compiledFunction = pug.compileFile('template.pug', {debug:true,self:true});
console.log(compiledFunction(out={
name: 'Timothy',
}));
console.log(JSON.stringify(out));
Displaying:
<html><head><title>Hello Timothy</title></head></html>
{"name":"Timothy","title":"Hello Timothy"}
The code of the collect() mixin is not particularly pretty because as far as I know it there is no elegant way to capture the block() output. So I had to tackle into the internal undocumented pug_html variable.
Or is there a cleaner way to achieve that?

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.

Access current request in Express/Jade view

I have a layout Jade view that has a menu via unordered list, and I want to set the <li> to be <li class="active">...</li> when the current page is rendered in the browser.
I assume I will have to access the current request to determine when to set the attribute on the <li>
I can't find any examples of how to do this so hoping someone can help
Thanks
Try this before your call res.render() in your route:
res.locals.path = req.path;
res.render('/page');
or
res.render('/page', { path: req.path });
Then you would have to do a bunch of if/else statements in your view (as the above solution suggests).
- if(currentUrl === '/')
li(class='active')
a(href='/') Current Driver Standings
- else
li
a(href='/') Current Driver Standings
I however, prefer to do this on client side instead, to keep my template files free from as much logic as possible:
In page template file (this is ejs, not sure how to echo in jade):
<body data-path="<%= path %>">
Then with jQuery you can grab the path from body and attach an active class:
$(function(){
var path = $('body').attr('data-path');
$('nav li a[href='+path+']').parents('li').addClass('active');
});
Update: You can also just use var path = window.location.pathname instead of saving it to an attribute on body
//no need to save path to <body> tag first:
$(function(){
var path = window.location.pathname;
$('nav li a[href='+path+']').parents('li').addClass('active');
});
Here's a much neater way of doing it, server-side:
In your routes.js (or wherever) define an array of objects representing your nav like such:
var navLinks = [
{ label: 'Home', key: 'home', path: '' },
{ label: 'About', key: 'about', path: '/about' },
{ label: 'Contact', key: 'contact', path: '/contact' }
]
Pass the navLinks variable to your view, as well as the key of the item you'd like hilighted:
res.render('home', { title: 'Welcome!', section: 'home', navLinks: navLinks });
You can also add the navLinks variable to app.locals and save yourself always having to provide it explicitly to views.
Then in your jade template loop through the array of links and set the active class on the one whose key matches the provided section:
ul(class='nav nav-list')
- navLinks.forEach(function(link){
- var isActive = (link.key == section ? 'active' : '')
li(class=isActive)
a(href=link.path)= link.label
- })
Pass the req.originalUrl in your routes file. example: in your
/routes/about.js
router.get('/', function(req, res) {
res.render('about', {
url: req.originalUrl
});
});
Then, write if else condition on your jade template
if(url==='/about-us')
li(class='active')
a(href='about-us') About Us
else
li
a(href='about-us') About Us
you can use global variables in app.js like :
// Global vars
app.use( function ( req, res, next ) {
// rest of your code ...
res.locals.current_url = req.path;
// rest of your code ...
next();
} );
// then in your .jade file:
ul.navbar-nav.mr-auto
li(class="nav-item #{ current_url === '/page1' ? 'active' : ''}")
a.nav-link(href='/page1') Page1
like this you are able to use "current_url" globally all around your view files
I came up with this which works however I'm not sure if its best practice. Please let me know either way:
response.render("current/currentSchedule", {
title: "Current Race Schedule",
currentUrl: req.path,
});
ul(class='nav nav-list')
li(class='nav-header') Current Season
- if(currentUrl === '/')
li(class='active')
a(href='/') Current Driver Standings
- else
li
a(href='/') Current Driver Standings
- if(currentUrl === '/constructor-standings')
li(class='active')
a(href='/constructor-standings') Current Constructor Standings
- else
li
a(href='/constructor-standings') Current Constructor Standings
- if(currentUrl === '/current-schedule')
li(class='active')
a(href='/current-schedule') Current Race Schedule
- else
li
a(href='/current-schedule') Current Race Schedule

Use a variable in a Jade include

I'm working with Jade and Express and I would like to use a variable in my include statement. For example:
app.js
app.get('/admin', function (req, res) {
var Admin = require('./routes/admin/app').Admin;
res.render(Admin.view, {
title: 'Admin',
page: 'admin'
});
});
layout.jade
- var templates = page + '/templates/'
include templates
When I do this I get the error EBADF, Bad file descriptor 'templates.jade'
I even tried
include #{templates}
to no avail.
AFAIK JADE does not support dynamic including. What I suggest is to "include" outside the template, i.e.
app.js
app.get('/admin', function (req, res) {
var Admin = require('./routes/admin/app').Admin;
var page = 'admin';
var templates = page + '/templates/';
// render template and store the result in html variable
res.render(templates, function(err, html) {
res.render(Admin.view, {
title: 'Admin',
page: page,
html: html
});
});
});
layout.jade
|!{ html }
this also works:
//controller
var jade = require('jade');
res.render('show', {templateRender: jade.renderFile});
//template
!= templateRender('my/path/'+dynamic+'.jade', options)
This probably will not increase the performance that you would expect from using the 'view cache' setting (it's on by default in NODE_ENV === 'production'). Or even break the app (e.g. if files are not available on the hard drive while deploying new code). Also trying to use this trick in a client-side or isomorphic app will not work because the template can not be compiled.
Found this page googling for the same question, but in a different context, so thought I'd put my solution (read: workaround) here for posterity:
I wanted to surround my include with more context pulled from the variable, e.g. (simplified):
- var templates = page + '/templates/'
- var headid = page + 'head'
- var imgsrc = '/images/' + page
div(id=headid)
h1 #{page}
img(src=imgsrc)
div(id=page)
include templates
Since that doesn't work (Jade doesn't support dynamic includes, as noted by freakish), I hybridized with a mixin:
(Edit– a little more elegant than my previous workaround:)
mixin page1
include page1/templates
mixin page2
include page2/templates
...
- for (var i = 0; i < 3; i++)
- var page = 'page' + i
- var headid = page + 'head'
- var imgsrc = '/images/' + page
div(id=headid)
h1 #{page}
img(src=imgsrc)
div(id=page)
+page
My previous answer:
mixin templates(page)
- var headid = page + 'head'
- var imgsrc = '/images/' + page
div(id=headid)
h1 #{page}
img(src=imgsrc)
+templates('page1')
#page1
include page1/templates/
+templates('page2')
#page2
include page2/templates/
...
It's not elegant, and it won't work if you need to include more than a few things this way, but at least part of the Jade is dynamic.
Why do not use jade inheritance?
Render what you want at middleware level:
res.render('templates/' + template_name + '.jade')
Write common common.jade:
h1 This is a page
.container
block sublevel
h2 Default content
Then write file that extends common.jade:
extends common.jade
block sublevel
h2 Some things are here
It's 2019 and using variables in Pug (previously Jade) mixins has become simple.
When creating your mixin, you can give it parameters as per value(s) you're expecting to pass to the mixin. You can access any nested values using dot notation.
mixinFile.pug:
mixin myMixin(parameter1, parameter2, parameter3)
h2.MyHeading #{parameter1}
p.MyParagraph #{parameter2.myVariable}
.MyBox(id= parameter3.id)
index.pug:
include mixinFile
block content
+MyMixin(variable1, variable2, variable3)
You can read more in the official Pug documentation on Mixins.

Resources