Create PDF with NodeJS, Jade and PhantomJS - css

I want to create a PDF from a template in Jade using PhantomJS, I can create the PDF document, but the CSS is not applying, I create two routes the first one render the template to the web browser and the second one generate de PDF with the exact same jade template, in the browser all is right, but the PDF don't apply CSS.
My app.js is:
var express = require('express'),
routes = require('./routes'),
http = require('http'),
path = require('path'),
bodyParser = require('body-parser');
var app = express();
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/cotizacion/generar', routes.cotizacion.generar);
app.get('/api/cotizacion/generar', routes.cotizacion.testGenerar);
app.all('*', function(req, res) {
res.send(404);
});
http.createServer(app).listen(app.get('port'), function(){
console.info('Express server listening on port ' + app.get('port'));
});
The controller file is cotizacion.js:
var phantom = require('phantom');
exports.generar = function(req, res, next){
if(!req.body.cotizacion){
return next(new Error('No se enviaron datos de cotización'));
}
var cotizacion = req.body.cotizacion;
req.app.render('test', {cotizacion: cotizacion}, function(err, html){
generarPDF(cotizacion, html, function(){
res.send("1");
});
});
};
exports.testGenerar = function(req, res, next){
res.render('test', {});
};
generarPDF = function(cotizacion, html, callback){
phantom.create(function(ph){
ph.createPage(function(page){
page.set('paperSize', { format: 'Letter' });
page.set('content', html);
page.render(__dirname + '/test.pdf', function(err){
ph.exit();
callback();
});
});
});
}
And finally my test.jade is:
doctype html
html
head
title Cotización PDF
link(rel='stylesheet', href='/css/pdf.css')
body
h1 Test
The following works, but is a little annoying
doctype html
html
head
title Cotización PDF
| <style type="text/css">
| body{ color: green; }
| </style>
body
h1 Test
The project structure is:
/
app.js
public/
css/
pdf.css
routes/
index.js
cotizador.js
views/
test.jade

Ok here is our solution for similar problem. Below JADE template, notice hrefs with a reference to JSON object properties:
doctype html
html(lang="en")
head
link(rel='stylesheet', type="text/css", media="all", href=report.statics.bootstrap)
link(rel='stylesheet', type="text/css", media="all", href=report.statics.custom)
body
Here is a code snippet, used for providing to the template CSS paths:
var createStatics = function (req) {
var hostName = url.format({protocol: req.protocol,
host: req.get('host')
});
return {
bootstrap: hostName + '/assets/reports/bootstrap-report.css',
custom: hostName + '/assets/reports/custom.css',
}
}
Next here is a JADE template compilation:
var fn = jade.compile(template);
Transportation object, used for passing required by JADE template utility objects:
var report = {
statics: statics,
title: 'Simple report title'
};
Rendering HTML report and retrieved as a stream...
var output = fn({
report: report
});

Related

Using express to serve public static files

I'm trying to get my express to serve files from my public folder but I can't seem to get anything to work here. I have tried so many variations in my file paths yet I can't seem to get pug to cooperate with node. I can see the html at the very least from index but neither the css nor the music files want to load. And yes I have looked at many questions similar to mine. I still can't determine why it's not working. please help.
I get these errors in dev tools
Refused to apply style from 'http://localhost:3000/public/music.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
GET http://localhost:3000/public/Music/Pink_Floyd/WishYouWereHere/ShineOnYouCrazyDiamond(PartsI-V).mp3 404 (Not Found)
my file structure is like this:
js
-server.js
public
-Music
--artist
---album
----musicfiles.mp3
-music.css
views
-index.pug
My node.js code:
var jsmediatags = require('jsmediatags');
var express= require('express');
var app=express();
var pug=require('pug');
const path=require('path')
const fs=require('fs');
const { error } = require('console');
var tagsArray=Array();
const port=3000;
const host='localhost';
app.use('/public', express.static(path.join(__dirname, 'public')))
app.set('views','./views');
app.set('view engine', 'pug');
const server =app.listen(port, host, ()=>{
console.log("server started at "+host+" port:"+port);
});
process.on('SIGTERM', () => {
server.close(() => {
console.log('Process terminated')
})
})
jsmediatags.read("./public/Music/Pink_Floyd/WishYouWereHere/ShineOnYouCrazyDiamond(PartsI-V).mp3", {
onSuccess: function(tag) {
var tags=tag.tags;
tagsArray=[tags.artist,tags.track,tags.album,tags.title,tags.picture];
var artist=tags.artist;
var album=tags.album;
var title=tags.title;
var track=tags.track;
var base64Url=Buffer.from(tags.picture.data).toString("base64");
// var base64Url=Buffer.from(base64String).toString("base64");
var artInfo="data:"+tags.picture.format+";base64,"+base64Url;
console.log(`data:${tags.picture.format};base64,`);
app.get('/', (req, res)=>{
res.render("index", {
artist:tags.artist,
album: tags.album,
title: tags.title,
track: tags.track,
art: artInfo
});
console.log('done');
});
},
onError: function(error) {
console.log(':(', error.type, error.info);
}
});
My pug code:
html(lang="en")
head
meta(charset='UTF-8')
meta(http-equiv='X-UA-Compatible' content='IE=edge')
meta(name='viewport' content='width=device-width, initial-scale=1.0')
title Music App
link(rel='stylesheet' type='text/css' href='/public/music.css')
body
img#albumC(src=art alt='album art')
.playbox
.play
.reflect
.playRef
.pausebox
.pause
.PA.center
.PB.center
.reflect
.pause
.PAref.center
.PBref.center
.stopbox
.stop
.reflect
.stopRef
h2 Artist: #{artist}
h2 Album: #{album}
h2 Song: #{title}
h2 Track: #{track}
audio(controls src="/public/Music/Pink_Floyd/WishYouWereHere/ShineOnYouCrazyDiamond(PartsI-V).mp3")
From your directory sketch, it looks to me like your public directory is a sibling (not a sub-directory) to the js directory where server.js is. So, instead of this:
app.use('/public', express.static(path.join(__dirname, 'public')))
you would to go up a level first like this:
app.use('/public', express.static(path.join(__dirname, '../public')))
To go up a directory and then down to the public directory.
That would then match up with this tag you have:
link(rel='stylesheet' type='text/css' href='/public/music.css')

How to set file name and extension to the original when inserting a file with full url or URI in CollectionFS?

Model code:
ProfileImage = new FS.Collection('profileImage', {
stores: [
new FS.Store.FileSystem('profile-image')
],
filter: {
maxSize: 524288,
allow: {
extensions: ['png', 'jpg', 'jpeg'],
contentTypes: ['image/png', 'image/jpg', 'image/jpeg']
}
}
});
Insertion code:
ProfileImage.insert('http://graph.facebook.com/' + user.services.facebook.id + '/picture/?type=large', function(error, imageObj) {
console.log(imageObj);
});
with that code I get a file name like this: profileImage-iiGE2ouSifuu3iLjq-undefined .
the name is undefined and without extension at all.
Try this (copied from a project of mine. this works):
//this is the event for when you select an image
'change .yourfileinput': function (event, template) {
FS.Utility.eachFile(event, function (file) {
var yourFile = new FS.File(file);
Images.insert(yourFile, function (err, fileObj) {
var fileUrl = '/cfs/files/profile-image/'+fileObj._id;
Session.set('fileUrl', fileUrl);
});
});
},
//form submit event
"submit .your-form-class": function (event) {
var whichUser = Meteor.userId() //or you could just write this inside the method call instead of adding a variable
var profilePicture = Session.get('fileUrl');
Meteor.call("yourMethod", whichUser, profilePicture);
}
Of course, you need to play on it/customise to your needs. ^ is for default file path of cfs:filesystem.
I switched to gridfs though and I highly recommend it.
Edit your question with all your code if it doesn't work and we'll find a solution. File upload was the biggest problem I encountered along the way.

Server side rendering with Meteor and Meteorhacks:ssr and iron-router

This came out recently: https://meteorhacks.com/server-side-rendering.html but there doesn't seem to be a full fledged example of how to use this with iron-router.
If I had a template like:
/private/post_page.html
{{title}}
{{#markdown}} {{body}} {{/markdown}}
How would I populate it with a single records attributes from a request for a specific ID ?
E.g page requested was localhost:3000/p/:idofposthere how to populate it with data and render it in iron-router for that route / server side?
Actually a bit easier than you think (I just followed Arunoda's SSR example and converted it to iron-router for you):
if(Meteor.isServer) {
Template.posts.getPosts = function(category) {
return Posts.find({category: category}, {limit: 20});
}
}
Router.map(function() {
this.route('home');
this.route('view_post', {
path: 'post/:id',
where:"server",
action : function() {
var html = SSR.render('posts', {category: 'meteor'})
var response = this.response;
response.writeHead(200, {'Content-Type':'text/html'});
response.end(html);
}
});
});
It only gets tricky if you share the same route for client and server side for example, if you want it to render client-side based on user agent.
Source: We use this strategy on our own apps.
UPDATE
While the above code is simply what the question is asking for, we can get this working to follow Google's Ajax spec by checking the ?_escaped_fragment_= query string before we reach the client..
Basically, what we mostly don't know about Iron-Router is that if you have identical routes declared for server and client, the server-side route is dispatched first and then the client-side.
Here's the main javascript (with annotations):
ssr_test.js
Router.configure({
layout: 'default'
});
Posts = new Mongo.Collection('posts');
// Just a test helper to verify if we area actually rendering from client or server.
UI.registerHelper('is_server', function(){
return Meteor.isServer ? 'from server' : 'from client';
});
myRouter = null;
if(Meteor.isServer) {
// watch out for common robot user-agent headers.. you can add more here.
// taken from MDG's spiderable package.
var userAgentRegExps = [
/^facebookexternalhit/i,
/^linkedinbot/i,
/^twitterbot/i
];
// Wire up the data context manually since we can't use data option
// in server side routes while overriding the default behaviour..
// not this way, at least (with SSR).
// use {{#with getPost}} to
Template.view_post_server.helpers({
'getPost' : function(id) {
return Posts.findOne({_id : id});
}
});
Router.map(function() {
this.route('view_post', {
path: 'post/:id', // post/:id i.e. post/123
where: 'server', // this route runs on the server
action : function() {
var request = this.request;
// Also taken from MDG's spiderable package.
if (/\?.*_escaped_fragment_=/.test(request.url) ||
_.any(userAgentRegExps, function (re) {
return re.test(request.headers['user-agent']); })) {
// The meat of the SSR rendering. We render a special template
var html = SSR.render('view_post_server', {id : this.params.id});
var response = this.response;
response.writeHead(200, {'Content-Type':'text/html'});
response.end(html);
} else {
this.next(); // proceed to the client if we don't need to use SSR.
}
}
});
});
}
if(Meteor.isClient) {
Router.map(function() {
this.route('home');
this.route('view_post', { // same route as the server-side version
path: 'post/:id', // and same request path to match
where: 'client', // but just run the following action on client
action : function() {
this.render('view_post'); // yup, just render the client-side only
}
});
});
}
ssr_test.html
<head>
<title>ssr_test</title>
<meta name="fragment" content="!">
</head>
<body></body>
<template name="default">
{{> yield}}
</template>
<template name="home">
</template>
<template name="view_post">
hello post {{is_server}}
</template>
<template name="view_post_server">
hello post server {{is_server}}
</template>
THE RESULT:
I uploaded the app at http://ssr_test.meteor.com/ to see it in action, But it seems to crash when using SSR. Sorry about that. Works fine if you just paste the above on Meteorpad though!
Screens:
Here's the Github Repo instead:
https://github.com/electricjesus/ssr_test
Clone and run!
SSR is lacking real life examples, but here is how I got it working.
if (Meteor.isServer)
{
Router.map(function() {
this.route('postserver', {
where: 'server',
path: '/p/:_id',
action: function() {
// compile
SSR.compileTemplate('postTemplate', Assets.getText('post_page.html'));
// query
var post = Posts.findOne(this.params._id);
// render
var html = SSR.render('postTemplate', {title: post.title, body: post.body});
// response
this.response.writeHead(200, {'Content-Type': 'text/html'});
this.response.write(html);
this.response.end();
}
});
});
}
Assets are documented here: http://docs.meteor.com/#assets.

Theme Development: Adding custom handle bar helpers

For the techno theme I wanted to make custom hb helpers and configuration available to users. To do this I applied an override to the [ghost root]/index.js.
The code below searches for index.js in the current theme folder and runs it.
var ghost = require('./core'),
errors = require('./core/server/errorHandling');
ghost()
.then(function (param) {
var settings = require('./core/server/api').settings;
settings
.read({key: 'activeTheme', context: {internal: true}})
.then(function (result) {
try {
require('./content/themes/' + result.value + '/index')();
}
catch (e) {
//No custom index found, or it wasn't a proper module.
}
});
})
.otherwise(function (err) {
errors.logErrorAndExit(err, err.context, err.help);
});
The theme level index.js injects custom blog variables (from a config file) and hb helpers.
var hbs = require('express-hbs'),
_ = require('lodash'),
downsize = require('downsize'),
blogVariable = require('../../../core/server/config/theme');
module.exports = function() {
//This block allows configuration to be available in the hb templates.
var blogConfig = blogVariable();
var config = require('./config') || {};
blogConfig.theme = config;
//console.log(JSON.stringify(blogConfig));
////Custom hb helpers////
hbs.registerHelper('excerpt', function (options) {
...
return new hbs.handlebars.SafeString(excerpt);
});
...
};
An example of using the custom blog variables is below.
<ul class="list-inline">
<li><i class="fa fa-fw fa-github"></i>
</li>
...
Is there a better way to do this in Ghost 0.4.2? I do not like having users override the ghost core index.js file.
There is a blog post explaining how to do this only by modifying the config.js file, and adding a file to the root directory. I agree with the author that this is more likely to be update-proof. http://zackehh.com/safely-creating-custom-handlebars-helpers/
Add:
require('./helpers')();
To the top of config.js
And add helpers to your helpers.js file like so:
var hbs = require('express-hbs');
module.exports = function(){
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
};
Unfortunately not, there was a post about this a little while ago on the forums, however you can add your own helpers.js file to the core folder for example...
var hbs = require('express-hbs')
// quick function for an example
registerHelper = function(){
hbs.registerHelper('ifNthItem', function(nthItem, currentCount, offset, options) {
if((currentCount+ offset)%(nthItem) == 0) {
return options.fn(this);
} else {
return options.inverse(this);
}
});
};
module.exports = registerHelper;
Then just link this into the index.js
var when = require('when'),
bootstrap = require('./bootstrap'),
scopa = require('./helpers');
scopa();
At least this way your not modifying the core index.js but the helpers.js instead.

Web-resources from Javascript

I need to get handlebars templates in Javascript.
So I create template file in tpl folder and wrote such line in ML:
<resource type="download" name="tpl/" location="/tpl"/>
if I put some image to this folder I can to get it from CSS:
.css
{
background: url(tpl/image.png);
}
if I want to get this image from js AJS.$("css").css("background", "url(tpl/image.png)") I have the error - file not found;
image file is for example.. In real I need to get template
AJS.$.ajax({
url: "tpl/backlog_coll.handlebars",
cache: true,
success: function(data) {
source = data;
template = Handlebars.compile(source);
$('#backlog_coll').html(template);
}
});
Here's how I would do that
I will write a function in a generally accessible js file (lets call it Global.js for now) as shown below
GLOBAL.JS
function fnGetTemplate(strName) {
if (Handlebars.templates === undefined || Handlebars.templates[strName] === undefined) {
$.ajax({
url : "tpl" + strName + ".handlebars",
success : function(data) {
if (Handlebars.templates === undefined) {
Handlebars.templates = {};
}
Handlebars.templates[strName] = Handlebars.compile(data);
},
async : false
});
}
return Handlebars.templates[strName];
}
This is assuming I have the handlebars js library (something like handlebars-v1.3.0.js) referred to appropriately.
Then inside the View where I need the template to show up, I would declare the template as shown below
template: fnGetTemplate("backlog_coll");
Inside the render function of my view I would call this template supplying the needed data as show below
render: function() {
this.$el.html(this.template(data));
}
Hope this helps

Resources