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

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

Related

Cascading Template Use in mustache-express?

My intent is to define a page_layout template and a form template as well. Within the route handler i was going to parse the inner (form) template and inject into the greater layout than return.
After several plot twists i'm able to use the response.app to get access to my mustache template engine and parse a view, but the HTML is getting escaped :(
let router = require('express').Router();
let mustache = require('mustache');
router.get('/plant', function(request,response) {
response.app.render('plantForm', {pageTitle: "Plant"},function (err, html) {
response.render ('layout', { pageContent : html});
});
});
yields the outer template with escaped html where I expect content:
<div class="form-group">
<label for="plantNameInput">Plant Type</label>
Perhaps I'm misusing the technology?
From the Mustache documentation:
All variables are HTML escaped by default. If you want to return unescaped HTML, use the triple mustache: {{{name}}}.

POST FORM in node and receive data response back to same webpage

I have a webpage that takes form details, POSTS the data and should then show the results. I'm using express for my routing.
This all works fine by resending the data with the HTML template after the POST but I think there must be a better way by hiding the "results" HTML section then just showing it once the data is known from the form. I've shown a cutdown version of my pages below.
On first load, the page says "your result is undefined", which I would expect but is ugly.
I could remove the "result" section and create a 2nd HTML page to resend from the POST route with it in which would work but I think there must be a better way.
I want to hide the result section on 1st page load then make it appear on the button submit with the result data. I can get the section hide/unhide but I can't get the data results back to display them. On button submit the form results just appear in the weburl www.mywebsite.com/?data almost like a GET request
I have tried using FormData and npm 'form-data' in a POST but can't get it working following these examples https://javascript.info/formdata and https://www.npmjs.com/package/form-data.
My structure in Node is
Router.js file
return res.send(htmlFormTemplate({}));
});
router.post('/css',
[],
async (req, res) => {
let {data} = req.body;
///
result= do some calculation on {data}
///
return res.send(htmlFormTemplate({result}));
});
The htmlFormTemplate is a js file
module.exports = ({result}) => {
return `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form class="box" method ="POST">
<inputname="data" />
<button>Submit</button>
</form>
<script>
///tried form processing here
</script>
<section id="Results">
<ul><li>Your result is ${result}</li></ul>
</section>
</body>
</html>
`;
};
I'm self-taught and new so hope this makes sense and thanks for any help/ideas
You can check if the result variable is null before it gets to the section div:
${ result === null ? '' :
`<section id="Results">
<ul><li>Your result is ${result}</li></ul>
</section>`}
Like this, it wont show the result div if result if null.
There is a very simple to solve this problem,
just use some templating engine for ex EJS, its very easy to use and will help you better,
and your result is undefined because your using a promise and it might have happened that the response might have not come and you loaded the page. Just use await
return await res.send(htmlFormTemplate({result}));

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

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

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?

Resources