Using variables amidst html in node.js - node.js

Here's the deal:
I already have node.js server which communicates with a database;
I need to create a webpage for people to access and check leader-boards
from the database (the server queries the database to show the users
with top 10 scores);
The example below shows a server responding with html;
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<!doctype html>\n<html lang="en">\n' +
'<head>\n<meta charset="utf-8">\n<title>Test web page on node.js</title>\n' +
'<style type="text/css">* {font-family:arial, sans-serif;}</style>\n' +
'</head>\n<body>\n<h1>Euro 2012 teams</h1>\n' +
'<div id="content"><p>The teams in Group D for Euro 2012 are:</p><ul><li>England</li><li>France</li><li>Sweden</li><li>Ukraine</li></ul></div>' +
'\n</body>\n</html>');
res.end();
}).listen(8888, 'address');
console.log('Server running at http://address:8888');
How would I go about to do something similar but instead the list would be the result of a query to the database?
Note:
I already know how to query, I only need to know how to have the results shows in the HTML code above
Thanks in advance

Most modern web applications solve this problem by using a template engine. You asked for something similar, and this is likely the better way to go about it.
A template allows you to have a file or simply a string with variable placeholders and then you can pass a model (in this case, your return value from the database) to the template, render it, and send that to the client.
Depending on the database you're using, you will have to likely start making your queries using Node.js's asynchronous patterns. Generally when you make a query in Node.js you will follow a pattern similar to
...
db.create({"name" : "rawr"},
function(err, data) {
if (err) {
// handle error
}
// build your template
// res.send the compiled HTML
});
...
Of course this all depends on the database that you're using, but generally it will look something like that. If you're comfortable with HTML I recommend looking into Handlebars.js but also loking at the link provided above for a few more and find one you like. Without a few more details such as constraints or database being used, this is about as much as I can say.

Related

Sending nested request to node.js web server

I am about to teach creating a simple web server in node.js to my students. I am doing it initially using the http module and returning a static page. The server code looks like this:
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
getFile(response);
}).listen(8080);
function getFile(response) {
var fileName = __dirname + "/public/index.html";
fs.readFile(fileName, function(err, contents) {
if (!err) {
response.end(contents);
} else {
response.end();
console.log("ERROR ERROR ERROR");
}
});
}
index.html looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Static Page</title>
</head>
</body>
<h1>Returned static page</h1>
<p>This is the content returned from node as the default file</p>
<img src="./images/portablePhone.png" />
</body>
</html>
As I would expect, I am getting the index.html page display without the image (because I am not handling the mime-type). This is fine; what is confusing me is, when I look at the network traffic, I would expect to have the index.html returned three times (the initial request, the image request and one for favicon.ico request). This should happen, because the only thing the web server should ever return is the index.html page in the current folder. I logged the __dirname and fileName var and they came out correctly on each request and there were indeed three requests.
So my question is, what am I missing? Why am I not seeing three index.html response objects in the network monitor on Chrome? I know one of the students will ask and I'd like to have the right answer for him.
what is confusing me is, when I look at the network traffic, I would
expect to have the index.html returned three times (the initial
request, the image request and one for favicon.ico request)
When I run your app, I see exactly three network requests in the network tab in the Chrome debugger, exactly as you proposed and exactly as the HTML page and the web server are coded to do. One for the initial page request, one for the image and one for favicon.ico.
The image doesn't work because you don't actually serve an image (you are serving index.html for all requests) - but perhaps you already know that.
So my question is, what am I missing? Why am I not seeing three
index.html response objects in the network monitor on Chrome?
Here's my screenshot from the network tab of the Chrome debugger when I run your app:
The code that you actually wrote (originally, can't be sure you won't edit the question) just serves an index.html. There is nothing in there that could read any other file (like an image).
I don't think you should teach students that syntax/mechanism because it is outdated. For starters, do not teach them to indent with tabs or four spaces. Indent with 2 spaces for JavaScript. Also, it just doesn't make sense to teach ES5 at this point. They should learn ES2015 or later (ES6/ECMAScript 2016/whatever they call it). For the current version of Node out of the box (6.6 as of writing), this would be the equivalent of what you wrote:
const http = require('http');
const fs = require('fs-promise');
http.createServer( (request, response) => {
fs.readFile(`${__dirname}/public/index.html`)
.then( data => {response.end(data)} )
.catch(console.error);
}).listen(8080);
But what you seem to be trying to do is create a gallery script.
Another thing about Node is, there are more than 300,000 modules available. So it just absolutely does not make sense to start from 0 and ignore all 300,000+ modules.
Also, within about three months, 6 at the most, async/await will land in Node 7 without requiring babel. And people will argue that kids will be confused if they don't have enough time toiling with promises, but I don't think I buy that. I think you should just teach them how to set up babel and use async/await. Overall its going to make more sense and they will learn a much clearer way to do things. And then the next time you teach the class you won't need babel probably.
So this is one way I would make a simple gallery script that doesn't ignore all of the modules on npm and uses up-to-date syntax:
import {readFile} from 'fs-promise';
import listFilepaths from 'list-filepaths';
import Koa from 'koa';
const app = new Koa();
app.use(async (ctx) => {
if (ctx.request.querystring.indexOf('.jpg')>0) {
const fname = ctx.request.querystring.split('=')[1];
ctx.body = await readFile(`images/${fname}`);
} else {
let images = await listFilepaths('./images',{relative:true});
images = images.map(i=>i.replace('images/',''));
ctx.body = `${images.map(i=> `<img src = "/?i=${i}" />` )}`;
}
});
app.listen(3000);

Is it possible to use PhantomJS and Node to dynamically generate PDFs from templates?

Background / Need
I am working with a group on a web application using Node.JS and Express. We need to be able to generate reports which can be printed as hard copies and also hard copy forms. Preferably we'd like to dynamically generate PDFs on the server for both reports and hand written forms. We're currently using EJS templates on the server.
Options
I was thinking that it would be convenient to be able to use templates to be able to build forms/reports and generate a PDF from the resulting HTML, however my options to do this appear limited as far as I can find. I have looked at two different possible solutions:
PhantomJS -- (npm node-phantom module)
PDFKit
EDIT: I found another Node.JS module which is able to generate PDFs from HTML called node-wkhtml which relies on wkhtmltopdf. I am now comparing using node-phantom and node-wkhtml. I have been able to generate PDFs on a Node server with both of these and they both appear to be capable of doing what I need.
I have seen some examples of using PhantomJS to render PDF documents from websites, but all of the examples I have seen use a URL and do not feed it a string of HTML. I am not sure if I could make this work with templates in order to dynamically generate PDF reports.
When a request for a report comes in I was hoping to generate HTML from an EJS template, and use that to generate a PDF. Is there anyway for me to use Phantom to dynamically create a page completely on the server without making a request?
My other option is to use PDFkit which allows dynamic generation of PDFs, but it is a canvas-like API and doesn't really support any notion of templates as far as I can tell.
The Question
Does anyone know if I can use PhantomJS with Node to dynamically generate PDFs from HTML generated from a template? Or does anyone know any other solutions I can use to generate and serve printable reports/forms from my Node/Express back-end.
EJS seems to run fine in PhantomJS (after installing the path module). To load a page in PhantomJS given a string of HTML, do page.content = '<html><head>...';.
npm install ejs and npm install path, then:
var ejs = require('ejs'),
page = require('webpage').create();
var html = ejs.render('<h1><%= title %></h1>', {
title: 'wow'
});
page.content = html;
page.render('test.pdf');
phantom.exit();
(Run this script with phantomjs, not node.)
I am going to post an answer for anyone trying to do something similar with node-phantom. Because node-phantom controls the local installation of PhantomJS, it must use asynchronous methods for everything even when the corresponding PhantomJS operation is synchronous. When setting the content for a page to be rendered in PhantomJS you can simply do:
page.content = '<h1>Some Markup</h1>';
page.render('page.pdf');
However, using the node-phantom module within node you must use the page.set method. This is the code I used below.
'use strict';
var phantom = require('node-phantom');
var html = '<!DOCTYPE html><html><head><title>My Webpage</title></head>' +
'<body><h1>My Webpage</h1><p>This is my webpage. I hope you like it' +
'!</body></html>';
phantom.create(function (error, ph) {
ph.createPage(function (error, page) {
page.set('content', html, function (error) {
if (error) {
console.log('Error setting content: %s', error);
} else {
page.render('page.pdf', function (error) {
if (error) console.log('Error rendering PDF: %s', error);
});
}
ph.exit();
});
});
});
A really easy solution to this problem is the node-webshot module - you can put raw html directly in as an argument and it prints the pdf directly.

Do I only have to use a templating language with express render?

I am learning node and express from the simplest and when rendering views using res.render('view',{data:data}) is it only a template engine like jade that fits in view. can I not use a normal html?
You can, but this is a problem I ran into when I was learning Node. If you do not want to use a templating engine, you can still have Node just spit out the contents of your HTML file in a static way. For example (VERY BASIC EXAMLE):
var base = '/path/to/your/public_html',
fs = require('fs'),
http = require('http'),
sys = requrie('sys');
http.createServer(function (req,res) {
path = base + req.url;
console.log(path);
path.exists(path, function(exists) {
if(!exists) {
res.writeHead(404);
res.write('Bad request: 404\n');
res.end();
} else {
res.setHeader('Content-Type','text/html');
res.statusCode = 200;
var file = fs.createReadStream(path);
file.on("open",function() {
file.pipe(res);
});
file.on("error",function(err) {
console.log(err);
});
}
});
}).listen(80);
console.log('server on tcp/80');
The great thing about Node is that it forces you to separate templates from logic (to a certain level, you can squeeze a lot of logic into template anyway).
I didn't like Jade and used EJS until it turned out that client-side EJS is different from server-side and you cannot really re-use templates in the browser (as you would definitely want at some point, when you start rendering pages in the browser). You can re-use simple EJS templates but you cannot re-use templates with partials (as most of your templates will be).
After a lot of searching and trial-and-error I ended up using doT templates that are very fast (the fastest there is, actually), light-weight (only 140 lines of JavaScript), can be easily integrated in Express (by following the pattern of consolidate - you can't use consolidate directly with doT yet), can be used in the browser (the function to load partials will have to be different, but it is easy again).
doT seems to have features that I haven't seen in other templating engines, has a very elegant syntax that is the closest to handlebars (my favourite) but still allows normal JavaScript inside (that's why I chose EJS in the first place).

Get return value of `include` in jade template

What I basically try to accomplish is to re-use jade partials/templates when getting data through a socket connection. Non working example:
socket.on('company_created', function(company) {
var html = include _company;
$('#companies ul').append(html);
});
Normally I had to create a new li and set the content like so (which is working as expected):
$('#companies ul').append($('<li>').text(company.name));
This is okay for a simple list, but if I had complexer list and stuff, this could get messy pretty quick, plus I had to write plain HTML again, so I figured re-using my already existing jade templates with all their goodness would be awesome, but had not luck, yet.
Any clue?
PS: Please do not tell my to use Ember, Backbone, Derby, Meteor, Angular or whatsoever.
Thanks in advance!
You can compile your jade sources to JS with jade.compile. Then include these sources in the client-side javascript, include jade's runtime.min.js, and refer to your jade templates as to normal JS functions in your client-side code.
For example,
server.js
app.get('/templates/:template.js', function (req, res) {
var template = req.params.template;
response.end([
"window.templates = window.templates || {};",
"window.templates[\"" + template + "\"] = " + jade.compile(template + ".jade", { client: true; });
].join("\r\n"));
});
client.js
$(function() { $("#placeholder").html(window.templates["content"]({user: "Daniel" })); });
content.jade
h1: Hello #{user}!
index.jade
!!!
html
head
script(src='/lib/jquery/jquery.js')
script(src='/lib/jade/runtime.min.js')
script(src='/templates/content.js')
script(src='/scripts/client.js')
body
#placeholder
Note that the code above might be syntactically incorrect and is provided solely to illustrate the idea.
we have a build step that compiles them to functions sort of like penartur mentioned. I dont use extend or include (which dont work on the client anyway ATM), but personally I find we have absolutely no need for that on the client at all since the DOM provides all the separation we need.

Accessing jQuery object in JADE

During login, I save the userId in a jQuery cookie, so that I can access it by $.cookie('userId').
Now that I am trying to access it from Jade, as,
- if ($.cookie('userId') === userId)
I get an error
$ is not defined
How can I access the cookie ?
My code:
- if (($.cookie('userId')) === userId)
input(type='submit', value='Move')
else
input(type='submit', disabled, value='Move'')
You seem to misunderstand concepts. Let me give you a lecture. ;)
Basics. Cookie is stored on the client-side. So using jQuery $.cookie('userId') will retrieve it when this script is run on the client-side.
But here you use JADE templating engine on server-side. So you didn't even send the page to the client-side yet. So what cookie do you want to retrieve? There is no cookie, because you are at the server.
There's more. Running $.cookie('userId') throws the error, because you are on the server-side. There is no $ object. And there cannot be, because you cannot use jQuery on server-side (well, actually you can, but this is irrelevant at the moment). Did you define $ object in your route system? Or did you define helper $? I don't think so.
Express+JADE tutorial. Now let me give you a short tutorial. Say you have a view (in app.js)
app.get('/', function(req, res){
res.render('index.jade', { code: 500 });
});
Now in index.jade you can use code as a variable. So you can write your index.jade for example like this:
// some code
- if (code == 500)
input(type='submit', disabledm, value='Move')
- else
input(type='submit', value='Move')
// some code
and now it will work! You may also use JavaScript functions in template if you predefine them. For example (in your app.js)
app.helpers({
positive: function(no) { return no > 0; }
});
then in index.jade template you can use
- if (positive(-13))
input(type="submit")
and even combine these two concepts
- if (positive(code))
// do some stuff
To cookie or not to cookie. If you really need to use cookies (and you shouldn't except for sessions or CSRF for example) then you need to use client-side JavaScript. This means that you need to add (after jQuery)
script(src='my-path/my-script.js')
to the index.jade file and write my-script.js like this
$(document).ready(function() {
if ($.cookie('userId') == userId) {
// do something like append input to some holder
}
});
But there are several problems with that solution. First: what is userId (second part of equality)? You need to predefine it in your index.jade. For example using
script
var userId = #{ user.id };
and adding user object in your route. Second: cookies are local variables. Setting them requires additional server code which I do not thing you would like. This makes maintaining the app harder. And finally: storing a user's id in a cookie seems to be pointless. The final user don't even have to know it's own id. The server needs to know! Sessions system comes handy here.
Final note. The code you wrote shows us that you cannot distinguish between server-side and client-side JavaScript. The solution to your problem is to learn, learn and learn. Read tutorials and everything about JADE, Express and Node.js.
What #freakish said is right. You're trying server side vs. client side.
If the cookie is set you should be able to access it on server like so
app.use(express.cookieParser());
app.get('/', function(req, res){
// index is jade template
// local variable on jade template holds cookie value
// use req.cookies.userid
res.render('index', { userid: req.cookies.userid });
});

Resources