Gathering document fragments at rendring time using `pug` - node.js

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?

Related

Why is JSON.stringify needed to retain an object's value when importing it from node.js into an EJS template?

Environment: Node.js, Express, EJS
When JSON.stringify() is used to process objects passed from node.js to an EJS template the objects retain their original values. Although it works I find this result unexpected. JSON.stringify turns objects into strings. Why does this appear to work in reverse in this instance?
In the Node.js file:
app.get('/', function(req, res) {
let myArray = [1, 5];
let myObject = {
cats: 2,
dogs: 0
}
res.render('index', { myArray, myObject });
})
EJS:
<script>
let importedArray = <%- JSON.stringify(myArray) %>;
let importedObject = <%- JSON.stringify(myObject) %>;
</script>
Rendered version in browser:
Although I find this result unexpected it works perfectly fine.
<script>
let importedArray = [1,5];
let importedObject = {"cats":2,"dogs":0};
</script>
Rendered after both JSON.stringify() are removed in EJS file:
The values are lost and the browser throws an error. I would have thought the unescaped output tag <%- would be enough but it's not.
<script>
let importedArray = 1,5;
let importedObject = [object Object];
</script>
Because when you're trying to specify the source code for a script that will live inside a <script> tag inside a web page, you need to generate RAW Javascript source code that will make your object in the web page.
So, you need some method of turning your live server-side Javascript object back into Javascript source code that describes the same object. JSON.stringify() is one such way to generate that Javascript source.
If you don't use something like JSON.stringify() and just pass your actual Javascript object, the EJS will see that it's not a string and it will call obj.toString() on it to try to get a string representation of it. Unfortunately, the implemention of .toString() for a Javascript object just generates "[object Object]" which is completely useless in an EJS template. So, you can't do it that way - you have to manually generate the correct Javascript source code string. And, JSON.stringify() is one such way to do that.
because ejs only render string text, and when use toString on a json, it will get '[object Object]' instead of your real content

Pass variable from ajax to jade/pug

Am obtaining the values from nodejs via ajax/fetch and need to pass the same to Pug.
Any help here would be much appreciated
sample.pug
button#searchVal Search
script(type='text/javascript', src='/lib/onClick.js')
br
br
table#table(div='')
each row in slaJobs
tr
th#cbs-tab-header(div='') !{row.jobname}
th#cbs-tab-header(div='') !{row.job_type}
th#cbs-tab-header(div='') !{row.autosys_instance}
dummy.js
.then(function(data) {
console.log(data.jobsHeader)
console.log(data.slaJobs)
})
Need to set value of data.slaJobs from js to slaJobs of Pug
if you can afford to refresh the pug page for the result:
make a request with ajax to some route in node.js.
in the route handler - where you render the pug view - you can pass arguments to your view template.
so when the pug page will be rendered - It will already contain your data.
if you can't afford to refresh:
1. script tag in your template. handle the response and inject the data to the HTML page like any other AJAX
if you are getting data via ajax you should iterate the payload data in a forEach loop and generate HTML into current document. Actually pug is rendered on the server and then the output as HTML will be given to the user.
You can't show your data in your pug view engine while it is already rendered and shown to the user.
a sample:
fetch('localhost:3000/products')
.then(data => data.json())
.then(data => {
var div= document.getElementById('div');
data.forEach(item=>{
var p = document.createElement('p');
p.textContent = item[0] + ' ' + item[1];
div.appendChild(p);
}
})

handlebars - add content to head of view from partial

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!!!

Cheerio how to ignore elements of a certain tag

I am scraping the body of the webpage:
axios.get(url)
.then(function(response){
var $ = cheerio.load(response.data);
var body = $('body').text();
});
The problem is, I want to exclude contents from the <footer> tag. How do I do that?
cheerio creates a pseudo-DOM when it parses the HTML. You can manipulate that DOM similar to how you would manipulate the DOM in a browser. In your specific case, you could remove items from the DOM using any number of methods such as
.remove()
.replaceWith()
.empty()
.html()
So, the basic idea is that you would use a selector to find the footer element and then remove it as in:
$('footer').remove();
Then, fetch the text after you've removed those elements:
var body = $('body').text();

split string in nodejs

I am working on node js with mongodb. I am getting the value of doc in view file.
{{#each doc}}
<div class="abstract" data-reactid=".1ejbmifi4u8.1.1.0.1.2.0:$35.0.0.1.2.0" id="content">
{{this.content}}</div>
{{/each}}
this will print the value of content.
I want to print only 40 characters of this content on view page and then want to implement "read more" to go to full content page.
Guessing by the syntax, you are using Handlebars or some similar derivative. If it's not Handlebars, you'll have to modify the below a little to match your framework, but it should be similar. Leave a comment if it's not and I'll edit.
Handlebars supports what is known as helpers which allow you to manipulate data fed into your views.
You could write a helper named, for example, excerpt, like so:
Handlebars.registerHelper('excerpt', function(data, url) {
if (data.length > 40) {
return new Handlebars.SafeString(
data.substring(0, 40) + '… Read more"
);
}
return data;
});
You can then use it like {{excerpt this.content this.readMoreUrl}}, where this.readMoreUrl is whichever property provides the relevant URL.
I am not familiar with the JavaScript MVC but You can do something like this in JS:
content = this.content
if(content.length > 40)
content_to_print = content.substr(0,40)
content_to_print = content_to_print+' Read More...'
Hope this helps!

Resources