Replace string in a text file in node.js - javascript

I am using node.js. I want to read a file with some placeholder strings and replace them dynamically before I serve the file. This is not an HTML file, so a templating engine will not work.
How can I do this?

If a template engine is overkill just use string.replace().
temp = "Hello %NAME%, would you like some %DRINK%?";
temp = temp.replace("%NAME%","Michael Dillon");
temp = temp.replace("%DRINK%","tea");
console.log(temp);
With only a bit more work you could make a general purpose template function based on just the standard methods in the String object.

Templating engines are not only for html. If you are using Express, for instance, you can set your own headers and specify a content-type:
View:
var foo = "{{ bar }}";
Rendering:
app.get('/file.js', function(req, res, next) {
res.render('templateName', {
locals: {bar: 'quux'},
headers: {'content-type': 'text/javascript'}
});
})
Will yield:
var foo = "quux";
If you are not using Express, you can just render the template and send the response with any content-type you like.

Related

in PUG how the embedded HTML JavaScript access the data which is transferred from Controller [duplicate]

I'm having trouble with a variable (config) declared in a jade template file (index.jade) that isn't passed to a javascript file, which then makes my javascript crash. Here is the file (views/index.jade):
h1 #{title}
script(src='./socket.io/socket.io.js')
script(type='text/javascript')
var config = {};
config.address = '#{address}';
config.port = '#{port}';
script(src='./javascripts/app.js')
Here is a part of my app.js (server side):
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.set('address', 'localhost');
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Routes
app.get('/', function(req, res){
res.render('index', {
address: app.settings.address,
port: app.settings.port
});
});
if (!module.parent) {
app.listen(app.settings.port);
console.log("Server listening on port %d",
app.settings.port);
}
// Start my Socket.io app and pass in the socket
require('./socketapp').start(io.listen(app));
And here is a part of my javascript file that crashes (public/javascripts/app.js):
(function() {
var socket = new io.Socket(config.address, {port: config.port, rememberTransport: false});
I'm running the site on development mode (NODE_ENV=development) on localhost (my own machine). I'm using node-inspector for debugging, which told me that the config variable is undefined in public/javascripts/app.js.
Any ideas?? Thanks!!
It's a little late but...
script.
loginName="#{login}";
This is working fine in my script. In Express, I am doing this:
exports.index = function(req, res){
res.render( 'index', { layout:false, login: req.session.login } );
};
I guess the latest jade is different?
Merc.
edit: added "." after script to prevent Jade warning.
#{} is for escaped string interpolation which automatically escapes the input and is thus more suitable for plain strings rather than JS objects:
script var data = #{JSON.stringify(data)}
<script>var data = {"foo":"bar"} </script>
!{} is for unescaped code interpolation, which is more suitable for objects:
script var data = !{JSON.stringify(data)}
<script>var data = {"foo":"bar"} </script>
CAUTION: Unescaped code can be dangerous. You must be sure to sanitize any user inputs to avoid cross-site scripting (XSS).
E.g.:
{ foo: 'bar </script><script> alert("xss") //' }
will become:
<script>var data = {"foo":"bar </script><script> alert("xss") //"}</script>
Possible solution: Use .replace(/<\//g, '<\\/')
script var data = !{JSON.stringify(data).replace(/<\//g, '<\\/')}
<script>var data = {"foo":"bar<\/script><script>alert(\"xss\")//"}</script>
The idea is to prevent the attacker to:
Break out of the variable: JSON.stringify escapes the quotes
Break out of the script tag: if the variable contents (which you might not be able to control if comes from the database for ex.) has a </script> string, the replace statement will take care of it
https://github.com/pugjs/pug/blob/355d3dae/examples/dynamicscript.pug
In my case, I was attempting to pass an object into a template via an express route (akin to OPs setup). Then I wanted to pass that object into a function I was calling via a script tag in a pug template. Though lagginreflex's answer got me close, I ended up with the following:
script.
var data = JSON.parse('!{JSON.stringify(routeObj)}');
funcName(data)
This ensured the object was passed in as expected, rather than needing to deserialise in the function. Also, the other answers seemed to work fine with primitives, but when arrays etc. were passed along with the object they were parsed as string values.
If you're like me and you use this method of passing variables a lot, here's a write-less-code solution.
In your node.js route, pass the variables in an object called window, like this:
router.get('/', function (req, res, next) {
res.render('index', {
window: {
instance: instance
}
});
});
Then in your pug/jade layout file (just before the block content), you get them out like this:
if window
each object, key in window
script.
window.!{key} = !{JSON.stringify(object)};
As my layout.pug file gets loaded with each pug file, I don't need to 'import' my variables over and over.
This way all variables/objects passed to window 'magically' end up in the real window object of your browser where you can use them in Reactjs, Angular, ... or vanilla javascript.
See this question: JADE + EXPRESS: Iterating over object in inline JS code (client-side)?
I'm having the same problem. Jade does not pass local variables in (or do any templating at all) to javascript scripts, it simply passes the entire block in as literal text. If you use the local variables 'address' and 'port' in your Jade file above the script tag they should show up.
Possible solutions are listed in the question I linked to above, but you can either:
- pass every line in as unescaped text (!= at the beginning of every line), and simply put "-" before every line of javascript that uses a local variable, or:
- Pass variables in through a dom element and access through JQuery (ugly)
Is there no better way? It seems the creators of Jade do not want multiline javascript support, as shown by this thread in GitHub: https://github.com/visionmedia/jade/pull/405
Here's how I addressed this (using a MEAN derivative)
My variables:
{
NODE_ENV : development,
...
ui_varables {
var1: one,
var2: two
}
}
First I had to make sure that the necessary config variables were being passed. MEAN uses the node nconf package, and by default is set up to limit which variables get passed from the environment. I had to remedy that:
config/config.js:
original:
nconf.argv()
.env(['PORT', 'NODE_ENV', 'FORCE_DB_SYNC'] ) // Load only these environment variables
.defaults({
store: {
NODE_ENV: 'development'
}
});
after modifications:
nconf.argv()
.env('__') // Load ALL environment variables
// double-underscore replaces : as a way to denote hierarchy
.defaults({
store: {
NODE_ENV: 'development'
}
});
Now I can set my variables like this:
export ui_varables__var1=first-value
export ui_varables__var2=second-value
Note: I reset the "heirarchy indicator" to "__" (double underscore) because its default was ":", which makes variables more difficult to set from bash. See another post on this thread.
Now the jade part:
Next the values need to be rendered, so that javascript can pick them up on the client side. A straightforward way to write these values to the index file. Because this is a one-page app (angular), this page is always loaded first. I think ideally this should be a javascript include file (just to keep things clean), but this is good for a demo.
app/controllers/index.js:
'use strict';
var config = require('../../config/config');
exports.render = function(req, res) {
res.render('index', {
user: req.user ? JSON.stringify(req.user) : "null",
//new lines follow:
config_defaults : {
ui_defaults: JSON.stringify(config.configwriter_ui).replace(/<\//g, '<\\/') //NOTE: the replace is xss prevention
}
});
};
app/views/index.jade:
extends layouts/default
block content
section(ui-view)
script(type="text/javascript").
window.user = !{user};
//new line here
defaults = !{config_defaults.ui_defaults};
In my rendered html, this gives me a nice little script:
<script type="text/javascript">
window.user = null;
defaults = {"var1":"first-value","var2:"second-value"};
</script>
From this point it's easy for angular to utilize the code.

send html file and data using express

I want to send a html file and an object to the client from server using express such that when the file gets loaded it uses the object and structure dynamically using ajax.
I know that the html file can be send like this:
res.sendFile( __dirname + "/" + "main.html" )
object as:
res.json(obj);
But how to send them together?
In simple word, you can not send json and html together as we need to send content-type in header. You can either send html or json.
Another way, you can send html into josn with other object something like following
const fs = require('fs');
const html = fs.readFileSync( __dirname + '/main.html' );
res.json({html: html.toString(), data: obj});
There are a couple of ways you can do that. While not the best method, this is the one I used in my projects. I used ejs, a powerful and simple templating engine.
First, install it using npm
npm install ejs
Then, in your HTML add:
<html>
<body>
<script type="text/javascript">
var obj = JSON.parse(<%= objSentFromServer %>)
// do something with obj
</script>
</body>
</html>
Server Side:
let express = require('express')
let app = express()
let ejs = require('ejs')
let fs = require('fs')
let objectSentFromServer = ... // what you need to send
app.get('/', (req, res) => {
fs.readFile(__dirname + '/main.html', (err, html) => {
res.send(ejs.render(html, JSON.stringify(objectSentFromServer)))
})
})
app.listen(8080, (err) => { console.log(err) })
Of course, there are plenty of other ways.
You may not be able to achieve this by sending back plain HTML.
But you should be able to achieve this using a templating engine. With the help of templating engine, you should be able to render a view as well as pass JSON required to used in that template.
Reference: render view
Eg;
// pass a local variable to the view
res.render('user', { name: 'Tobi' }, function(err, html) {
// ...
});
Here is the complete list of Template Engines supported by Express JS

Can't Render EJS Template on Client

I'm coding an application on express, and I'm using ejs as a view/template engine.
At path /artists, I'm rendering the view artists.ejs which has artists covers. When clicking on a cover, I want an AJAX call to retrieve the corresponding data, place it in my template/view for artist artist.ejs and display this template in my HTML under the cover.
I've seen this related question but it has not solved my use case.
Everything seems clear, but I can't render the data with the template. I would like to compile the template server-side, send it to the client ready to use, and then fill it in when needed with the data received from the AJAX call.
What I've done:
When calling /artists, compile on server-side using ejs.compile(str, opt):
router.get('/artists', function(req, res) {
// Compile artist template
fs.readFile('views/artist.ejs', "utf-8", function(err, template) { // Convert template file to string
artist_template = ejs.compile(template); // Compile template
res.render('artists.ejs', {template: artist_template}); // render page with compiled template
});
I took care of converting the file into String, as ejs compiler only works with String (compared to Jade .compileFile)
Then on client-side, I grab the function:
<script>
var template = <%= template %>
</script>
Then on another script, I retrieve the data with an AJAX call:
$.get('/artists/'+artist_name, function(data) {
var html = template({artist: data});
$('#artist-page').html(html);
}
But when I make the call, I receive:
Uncaught ReferenceError: fn is not defined
When I call the template, fn, I receive:
Uncaught ReferenceError: opts is not defined.
Is the function fn hard-coded? I've read the EJS and Jade documentation but there was little relevant information in regards to my issue.
Do I perhaps need the template on client-side also?
I eventually found a workaround to my question, and I understood with your answer that you could proceed in 2 different ways:
1) What I did: read and save template as a string, then render it client-side with ejs Runtime script.
// In controller.js
var templates = {};
templates.template1 = fs.readFileSync(filePath1, 'utf-8'); // Read template as a string
templates.template2 = fs.readFileSync(filePath2, 'utf-8');
...
res.render('app.ejs', {templates: templates}); // Send templates in view
// In view app.ejs
<script type="text/javascript">
var templates = <%- JSON.stringify(templates) %>; // Get templates object (object of strings)
</script>
<script type="text/javascript" src="/JS/ejs.min.js"></script> <!-- Load ejs RunTime -->
// In site.js - javascript client/public file
$.get('/artists', function(data) {
var html = ejs.render(templates.template1, data); // Render ejs client side with EJS script (template1 corresponds to the artists template)
$('#artists-wrapper').html(html); // Sets HTML
});
Thus, I send all my templates on first page load, and then I render the requested page on the client side. The interest, according to what I've read, is that you only send JSON object (your data) through AJAX calls, and not the entire page, making your request light. Only the first load is heavy with all your templates.
2) What I would like to do according to #RyanZim answer: compiling templates server side into functions, send them, and then call them on the client side : template(data). If I understood well, there is no need of EJS client library in this case, and my templates are no longer strings but functions:
// In controller.js
var templates = {};
templates.template1 = ejs.compile(fs.readFileSync(filePath1, 'utf-8'), {client: true}); // Get template as a function
templates.template2 = ejs.compile(fs.readFileSync(filePath2, 'utf-8'), {client: true});
...
res.render('app.ejs', {templates: templates}); // Send templates in view
However, I can't get them in my view:
<script type="text/javascript">
var templates = <%- JSON.stringify(templates) %>; // Get templates object (object of functions)
</script>
is not working. they are functions on the server before I send them, but I don't know how to recover them. Do you have an idea ?
I tried a workaround, by changing them into String before sending them:
templates.template1 = templates.template1.toString();
Send them and then client side, transform them back in functions:
var template = new Function(templates.template1);
$.get('/artists', function(data) {
var html = template(data);
$('#artists-wrapper').html(html); // Sets HTML
});
But that won't work either.
Do you have an idea what I'm missing here?
And last, do you agree that compiling them server side before using the functions is better in terms of computation than rendering each template client-side?
Thanks for the help, and hope that will help anybody else!
You need to use the client option on the server side when you are compiling for the client. From the docs:
client When true, compiles a function that can be rendered
in the browser without needing to load the EJS Runtime
https://github.com/mde/ejs#options
Your server-side code snippet should be:
// Compile artist template
fs.readFile('views/artist.ejs', "utf-8", function(err, template) {
artist_template = ejs.compile(template, {client: true}); // Use client option
res.render('artists.ejs', {template: artist_template});
});

Extracting extra parameters from HTTP request

Let's say I send to a Node Server a request to get a JS file:
<script src="/blabla.js" id="545234677">
and on my Node side:
app.get('/blabla.js', function(req, res, next) {
console.log(req.params.id); //undefined
res.setHeader('content-type', 'text/javascript');
res.render('blabla'); //default
});
and finally I have this template file blabla.ejs:
var id = <%= id %>
Now, I am trying to get that id para but it's showing undefined. I am using Express and EJS, and once I get that ID I want to manipulate an EJS file and send back a JS file which adapts itself according to the ID my node app received.
Thank you.
The way to do this would probably be like this - In your HTML you would request the JS file like so:
<script src="/blabla.js?id=545234677">
And then for express you would do the following:
app.get('/blabla.js', function(req, res, next) {
console.log(req.query.id);
res.setHeader('content-type', 'text/javascript');
// Pass id into the renderer
res.render('blabla', {id: req.query.id}); //default
});
Although rendering is normally used to render HTML. There is probably a better way to do what you want in a more elegant way.
Note: req.params would work, but would make the url look like /blabla.js/545234677. See the documentation on req.params.
How about changing your script to
<script src="[filename][id].js"></script>
EG:<script src="blabla123.js"></script>
then you can do something using regular expressions like
app.get(/^\/([a-zA-z]*)([0-9]*).js/,function(req,res,next){
var fileName = req.params['0'],
id = req.params['1'];
console.log(fileName);
console.log(id);
// Now since you have both fileName and id as you required, you can
// use it to dynamically serve the content
// if(file=='' && id==''){//logic}
// If you want static file Name, modify the regular expression accordingly
})
Just modify the regular expression in above example as per your need and its done. Currently the above regex matches for any of
/.js
/[numeric].js
/[alphabet].js
/[alphabet][numerals].js
I hope this helps.

Initializing client javascript variables from nodejs

I am trying to send some extra data to a client using nodejs at the same time the server is sending html and/or javascript. I'm pretty new to web development and am probably overlooking some core concept.
Here's essentially what I would like to do.
require('http');
var someVar = 'Some data';
http.createServer(function(req, res){
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(someWebPage);
res.sendThisDataToClient(someVar);
res.end();
}).listen(4000);
And the client
var someVar = getDataSentWithThisPage();
// Do stuff
I did find a way to solve the specific problem I had in a different way although I would still like to know how to do this / if it is possible / if it is the javascript way.
You could use a templating engine and send your data like so:
res.render('index.html', { myVar : someVar }):
and in your index.html you'd have some expression evaluating myVar, for example <% myVar %> in ejs, or something like span=myVar in jade.

Categories