let's start off with the following 4 jade files.
layout.jade
html
head
block cms_head
body
block cms_body
home_page.jade
extends layout
block append cms_head
title home page
block append cms_body
p superman ate so many different apples
plugin_a.jade
block append cms_body
p i got plugged in... genius
plugin_for_jquery.jade
block append cms_head
script(src="/jquery-1.8.3.js")
so i can easily render the home page by doing:
app.get("/", function(req, res){
res.render("home_page");
});
which is fantastic, however in order to allow for plugability in my app, i would like to allow their views to take advantage of existing templates, and append/prepend/replace content to whatever the plugin needs to show.
what i am trying to do is render home_page.jade, then render plugin_a.jade and plugin_for_jquery.jade ... the basic idea is there can be a variable number of plugins, and each plugin adding it's own content to the view.
I tried this (obviously it didn't work)
for plugin in plugins
include= plugin.name
So any ideas on how i can do that?
as far as i know there is no possability in jade to readout the directory
but using node:
var fs = require("fs");
app.get("/", function(req, res){
fs.readdir(DIR_OF_PLUGINS, function(err, list){
res.render("home_page", {"list": list});
});
});
and in homepage.jade
- for(plugin in list)
include = plugin
btw: you should build a router and a functions document with something like
//router.js // or app.js
var funct = require("routes/functions")
app.get('/', funct.home);
//functions.js
exports.home = function(req, res){
Related
I used NodeJs with Handlebars and thought about switching to PugJs because some functionality is native - using Handlebars requires some helper functions / partials.
In Handlebars I have to define a layout and pass in the template. In PugJs I created two example routes
First route file:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('index', {
title: 'Home',
template: 'main'
});
});
module.exports = router;
Second route file:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('index', {
title: 'Page2',
template: 'pageTwo'
});
});
module.exports = router;
As you can see I always have to render my index file and pass in the desired pug file as a template variable.
index.pug
doctype html
html
include ./header.pug
body
include ./templates/#{template}.pug
header.pug
head
title #{title} template!
main.pug
script(src='./client/main.js')
h1 main content here
pageTwo.pug
p pageTwo
When rendering the pug files I get this error
ENOENT: no such file or directory, open
'...\views\templates\#{template}.pug'
How can I replace #{template} with the correct template variable?
Dynamic template selection isn't a feature of pug, I believe it has something to do with how pug pre-compiles everything into a JavaScript function that stays in-memory inside node.js when it runs. The benefit of that is super-fast page rendering, so we're happy to keep using pug and just work around this.
Speaking of which, you can accomplish what you want to do using conditional logic:
doctype html
html
include ./header.pug
body
if template === 'pageOne'
include ./templates/pageOne.pug
else if template === 'pageTwo'
include ./templates/pageTwo.pug
else
include ./templates/home.pug
You should also look into the extends feature to simplify adding the header into your templates and mixins to resuse code between templates. You might find that these features could provide a better solution to your requirements with some redesign.
I'm wiriting a very complex CMS with Node, Express and Dust with Consolidate.
The CMS have Modules and Widgets:
Modules are things that respond to routes and render pages
Widgets are fragments of these pages.
The problem is that each Module and Widget have your own views folder and Express only allows you to set one "views directory", and in the Dust docs I couldn't even find in which directory Dust looks up for templates.
My folder structure look like this:
root
modules
module-1
module-n
module-n-controller.js
module-n-routes.js
module-n-view.dust
widgets
widget-1
widget-n
widget-n-controller.js
widget-n-routes.js
widget-n-view.dust
So, module-n-controller.js does something like this:
// just pretend that this data came from the widget-n-controller.js
var data = {
"widget" : {
"view": "./widgets/widget-n/widget-n-view",
"data": widgetNData
}
}
res.render('./modules/module-n/module-n-view', data);
Then, in my module-n-view.dust, I want to do something like this:
{#widget}
{>"{view}" data=data/}
{/widget}
So the question is: How do I set the path to these views, and, is res.render the right method to do this or should I use dust.render?
PS: I tried dust.render and I got "Error, can't find template" with every variation of the path.
STEP 1
root/app.js
var app = require("express")(),
cons = require("consolidate");
// Do this to get DustJS rendering .html files
// So, in the file structure above, you can rename the views extensions from .dust to .html
app.engine('html', cons.dust);
app.set('view engine', 'html');
// Set the views directory to the root directory of the application
// This way you can access views into any directory inside your application
app.set('views', '.');
root/widgets/widget-n/widget-n-controller.js
module.exports.index = function (req, res, next) {
return {
"widget" : {
"view": __dirname + '/widget-n-view',
"data": widgetNDataFromDB()
}
};
}
root/modules/module-n/module-n-controller.js
module.exports.index = function(req, res, next){
var data = require("../../widgets/widget-n/widget-n-controller").index(req, res, next);
res.render(__dirname +'/module-n-view', data);
}
STEP 2 - VERY IMPORTANT FOR .HTML FILES!
This is necessary to get consolidate looking up for .html files when rendering partials.
I don't know why but, when rendering partials, consolidate seems to look up only .dust files, even if you have specified html as the app and view engine.
Skip this if you're using the .dust extension.
node_modules/consolidate/lib/consolidate.js
Search for this function:
exports.dust.render
Inside of this function you'll find the following:
var ext = 'dust'
, views = '.';
Just change it to:
var ext = 'html'
, views = '.';
It was really hard to figure it out... I really hope it helps somebody else out there.
Usually you render a Jade page in a route like this:
app.get('/page', function(req, res, next){
res.render('page.jade');
});
But I want to serve all Jade pages (automatically rendered), just like how one would serve static HTML
app.use(express.static('public'))
Is there a way to do something similar for Jade?
"static" means sending existing files unchanged directly from disk to the browser. Jade can be served this way but that is pretty unusual. Usually you want to render jade to HTML on the server which by definition is not "static", it's dynamic. You do it like this:
app.get('/home', function (req, res) {
res.render('home'); // will render home.jade and send the HTML
});
If you want to serve the jade itself for rendering in the browser, just reference it directly in the url when loading it into the browser like:
$.get('/index.jade', function (jade) {
//...
});
https://github.com/runk/connect-jade-static
Usage
Assume the following structure of your project:
/views
/partials
/file.jade
Let's make jade files from /views/partials web accessable:
var jadeStatic = require('connect-jade-static');
app = express();
app.configure(function() {
app.use(jadeStatic({
baseDir: path.join(__dirname, '/views/partials'),
baseUrl: '/partials',
jade: { pretty: true }
}));
});
Now, if you start your web server and request /views/partials/file.html in browser you
should be able see the compiled jade template.
Connect-jade-static is good, but not the perfect solution for me.
To begin with, here are the reasons why I needed jade:
My app is a single page app, there are no HTMLs generated from templates at runtime. Yet, I am using jade to generate HTML files because:
Mixins: lots of repeated / similar code in my HTML is shortened by the use of mixins
Dropdowns: I know, lots of people use ng-repeat to fill the options in a select box. This is a waste of CPU when the list is static, e.g., list of countries. The right thing to do is have the select options filled in within the HTML or partial. But then, a long list of options makes the HTML / jade hard to read. Also, very likely, the list of countries is already available elsewhere, and it doesn’t make sense to duplicate this list.
So, I decided to generate most of my HTML partials using jade at build time. But, this became a pain during development, because of the need to re-build HTMLs when the jade file changes. Yes, I could have used connect-jade-static, but I really don’t want to generate the HTMLs at run time — they are indeed static files.
So, this is what I did:
Added a 'use' before the usual use of express.static
Within this, I check for the timestamps of jade and the corresponding html file
If the jade file is newer, regenerate the html file
Call next() after the regeneration, or immediately, if regeneration is not required.
next() will fall-through to express.static, where the generated HTML will be served
Wrap the ‘use’ around a “if !production” condition, and in the build scripts, generate all the HTML files required.
This way, I can also use all the goodies express.static (like custom headers) provides and still use jade to generate these.
Some code snippets:
var express = require('express');
var fs = require('fs')
var jade = require('jade');
var urlutil = require('url');
var pathutil = require('path');
var countries = require('./countries.js');
var staticDir = 'static'; // really static files like .css and .js
var staticGenDir = 'static.gen'; // generated static files, like .html
var staticSrcDir = 'static.src'; // source for generated static files, .jade
if (process.argv[2] != 'prod') {
app.use(‘/static', function(req, res, next) {
var u = urlutil.parse(req.url);
if (pathutil.extname(u.pathname) == '.html') {
var basename = u.pathname.split('.')[0];
var htmlFile = staticGenDir + basename + '.html';
var jadeFile = staticSrcDir + basename + '.jade';
var hstat = fs.existsSync(htmlFile) ? fs.statSync(htmlFile) : null;
var jstat = fs.existsSync(jadeFile) ? fs.statSync(jadeFile) : null;
if ( jstat && (!hstat || (jstat.mtime.getTime() > hstat.mtime.getTime())) ) {
var out = jade.renderFile(jadeFile, {pretty: true, countries: countries});
fs.writeFile(htmlFile, out, function() {
next();
});
} else {
next();
}
} else {
next();
}
});
}
app.use('/static', express.static(staticDir)); // serve files from really static if exists
app.use('/static', express.static(staticGenDir)); // if not, look in generated static dir
In reality, I have a js file containing not just countries, but various other lists shared between node, javascript and jade.
Hope this helps someone looking for an alternative.
I'd like to set my CSS path in my express application in order to use this one in my jade layouts. However, I don't know how to do, I try to use "app.set('cssPath', __dirname+'/public/admin/css/')" but it doesn't work because I can not use "app.get()" in my external controllers.
My layout _layout.jade :
!!! 5
html(lang='fr')
head
meta(charset='UTF-8')
link(href='admin/css/screen.css', media='screen, projection', rel='stylesheet', type='text/css')
body
.container
h1 Wellcome to Forest Administrator
.space20
block content
.clear.space20
script(type='text/javascript', src='admin/js/jquery.js')
My page edit.jade :
extends ../_layout
block content
.block.half.first
h2 Add a post
And I'd like to use something like :
link(href='+myCssPath+', media='screen, projection', rel='stylesheet', type='text/css')
Not sure if I get what you want to do but You can use
res.locals.cssPath = 'string with the path';
And cssPath will be available in your template.
Besides that you don't need __dirname+'/public/. Part because when the page is rendered for the browser /public/ will be /
[edit] If you want to have that variable available in all you routes, but declaring it just once, you can create a small middleware like
var express = require('express');
var app = express();
var app.configure(function(){
app.use(express.bodyParser());
app.use(express.methodOverride());
// .. and your other tipical configuration
//this small middleware for variables that must available in all paths
app.use(function(req, res, next) {
res.locals.cssPath = 'path to the css directory';
next();
});
});
//From here your typical route declarations
app.get('/', function(req, res) { res.render('someView'); });
I have a "partial" template that I want to use both client-side and server-side.
Is there some method or filter or something that's very similar to include except that instead of executing the template immediately, it returns a client-compiled function which I could then assign to a JS variable and use throughout my script?
At present, I'm doing this:
exports.list = function(req, res){
res.render('file/list', {
...
fileItemTemplate: jade.compile(fs.readFileSync(path.join(req.app.get('views'),'file','file-item.jade')), {client: true})
});
};
And then in my template I have:
ul#folder-list.thumbnails
each file in files
include file-item
...
script(type='text/javascript')
var fileItemTemplate = !{fileItemTemplate};
And in this way I can render some items to HTML on page-load, and then add some more in later by rendering the partial as data comes in.
This works, but it doesn't feel very DRY because I have to read in the file, and deal with filepaths and stuff in the route, and then essentially redeclare the exact same variable client-side.
Is there a nice way of doing this?
Something like this would be ideal:
script(type='text/javascript')
var fileItemTemplate = !{compile file-item};
A possible solution could be JadeAsset. See also the discussion here.
You can hook assets into Express:
assets.on('complete', function() {
var app = express.createServer();
app.configure(function() {
app.use(assets); // that's all you need to do
});
app.listen(8000);
});
To create your Jade assets:
var assets = new AssetRack([
new rack.JadeAsset({
url: '/templates.js',
dirname: __dirname + '/templates'
})
]);