Skip to content

This guide will walk you through the basics of jade templating by creating a simple recipes website using Node.js Express and Jade

Notifications You must be signed in to change notification settings

mspcontent/jade-templating

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Creating a Cooking Website with Express, Stylus and Jade

#Objective

In this lab we will use Express to create Node recipes a full fledged website which can scale to mobile browsers as well as to the desktop. By the end of this lab you will know

1. How to write html templates with jade
2. How to render a view with a jade template and a model using a route

#Getting Started

Visual Studio (Windows)

Download and install Node.js

Download and install Visual Studio and Node Tools for Visual Studio

To get started open up the StartProject/ExpressWebsite.sln in Visual Studio 2013. You will notice a few things different about this project than before.

The solution contains a few more things than before:

Right click on the npm manager and click 'Install Missing npm packages:

This is the equivalent of opening the command line at your root folder of your project and doing:

npm install

Visual Studio Online (Mac/Windows/Linux)

Visual Studio Online works on all platforms and provides javascript validation, syntax highlting, file diffs and more. It will instantly deploy your app to the cloud.

Get an Azure Account by either getting a free trial, a token from me which looks like this:

If you're working on a Startup signup for Bizspark or tweet at me [@sedouard](http://twitter.com/sedouard] with your plan and I'll get you a bizspark token for free Azure usage for 3 years.

After you get your subscription create a new website:

Then go the config tab, select Edit in Visual Studio Online to Enabled.

Because you will be starting with the StartProject folder, change the virtual directory setting to have / point to **\wwwroot\StartProject:

Click save at the bottom.

Go back to the Dashboard and click Edit in Visual Studio Online

Delete hostingstart.html and clone this repository.

Clone this repository using the Git menu on the left toolbar:

In the console do:

cd ./StartProject
npm install

To run the EndProject folder, change the Virtual Directory of / to \wwwroot\EndProject* and modify the rootlevel ./package.json start property to point to ./EndProject/app.js:

{
  "name": "ExpressWebsite",
  "version": "0.0.0",
  "description": "ExpressWebsite",
  "main": "app.js",
  "author": {
    "name": "sedouard",
    "email": ""
  },
  "scripts": {
    "_comment": "ATTENTION: Change to EndProject/app.js to run the finish app!",
    "start" : "node StartProject/app.js"  
  }
}

Click the 'Run Button'

Command Line + Text Editor (Mac/Windows/Linux)

Download and install Node.js

If you just want to do things with a plain old editor like Sublime and command line and run locally, clone this repository navigate to the repository and do:

cd ./StartProject
npm install
node app.js

It is also easy to deploy to an Azure Website with github by linking your github repository. Ensure to change your virtual directory as noted in the Visual Studio Online instructions above.

Jade Files

Jade is a language used to create HTML templates on the server. This allows us to write HTML much easier because it avoid having to write as many brackets and also allows for us to bind a view to the underlying data model (similar to what Angular.js does in the browser).

Stylus Files

Stylus is the Jade for CSS. It allows us to write CSS much easier than the raw CSS language.

App.js

You will notice that there is considerably more stuff in your app.js than in the last lab. Lets take a peek at the setup:

The first new thing that happens is we set the views section of the app to the current directory, __dirname/views folder:

	//mounts the 'views' directory so that it is reachable by the client
	app.set('views', path.join(__dirname, 'views'));

We then tell Express that our view engine that we will be using is jade. Express can be used with a variety of view engines, but jade is the most popular choice:

	app.set('view engine', 'jade');

This will set the favicon of the website to the default express.js image:

	app.use(express.favicon());

We will set that we want to use stylus and that the stylus styling files are in the public folder:

	app.use(require('stylus').middleware(path.join(__dirname, 'public')));

Lastly, we set our public folder to be publicly accessible by the client. After this call, you can access anything in this folder by browsing to it (such as /stylesheets/style.styl)

	app.use(express.static(path.join(__dirname, 'public')));

Run the application and you'll see the placeholder 'Express' title:

##Templating HTML with Jade

Jade is a simple light-weight html templating engine which makes it easier to write and reuse HTML.

The current solution has 2 jade view files:

layout.jade:

//Layout.jade defines the layout of the entire website. It will contain the navbar and
//extension views will populate pageTitle and pageContent blocks
doctype html
html
  head
  title= title

  //indicate that our site is mobile optimized
  meta(name='viewport', content='width=device-width, initial-scale=1.0')

  //reference our site-specific css
  link(rel='stylesheet', href='/stylesheets/style.css')

  //add bootstrap dependencies from CDNjs. Using a CDN to distribute these files puts less pressure
  //on our web server and will be delivered faster to the client than our own server.
  script(src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js')
  script(src='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/js/bootstrap.min.js')
  link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/css/bootstrap-theme.min.css')
  link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/css/bootstrap.min.css')
  body
  block pageContent

You can see that the layout.jade sort-of looks like HTML, except there aren't any brackets. Instead jade uses tabs.

Notice how we have something called block pageContent. This tells jade that layout.jade will place any block named pageContent from an extension view in its place.

This same jade looks like this in when it is rendered HTML in the browser:

Index.jade is the default view of the page. It is an extension view of layout.jade (because of the 'extends keyword). This means that it can place its content in the pageContent block inside layout.jade:

index.jade

extends layout

block pageContent
  h1= title
  p Welcome to #{title}

##Creating the Home Page

Layout.jade is pulling a references to Bootstrap which you used in BartNOW to create your responsive UI elements.

Each page on our website will contain a pageTitle and a pageContent block. Since we don't have a pageTitle block specified in Layout.jade, we'll add one above the pageContent block. Remember, jade uses tabs/whitespace to determine where html elements begin and end so be careful with the indentation:

layout.jade

body
  block pageTitle
  block pageContent

Now run the website again:

You'll notice that nothing happens. This is because index.jade, the default page doesn't define the block pageTitle. Take a look at index.jade. Notice that this is an extension view of layout. That means that what's in pageContent will be placed in the pageContent block in layout.jade.

index.jade

extends layout

block pageContent
  h1= title
  p Welcome to #{title}

We can add a new block with a bootstrap jumbotron UI element:

block pageTitle
  .jumbotron
    h1 Node recipes
    h2 Welcome! Here you will find a variety of scruptious recipes for you to make

The block keyword doesn't map to anything in HTML and is only used by jade. This jade code translates to this HTML:

	<div class="jumbotron">
		<h1>Node recipes</h1>
		<h2>Welcome! Here you will find a variety of scruptious recipes for you to make</h2>
	</div>

Tabs in jade signify child elements in the HTML. You don't have to worry about closing any tabs. .jumbotron could be div.jumbotron but jade defaults to the div element type if you don't specify one. Jade lets you short hand class='jumbotron' by simply just using .classname instead.

Your entire index.jade should now be:

extends layout

block pageTitle
  .jumbotron
    h1 Node recipes
    h2 Welcome! Here you will find a variety of scruptious recipes for you to make

block pageContent
  h1= title
  p Welcome to #{title}

Run the Website and now we have a landing page message:

We don't want our site to have that pesky 'Express' tag on it so let's remove the pageContent block from our default view index.jade. It should now look like:

extends layout

block pageTitle
  .jumbotron
    h1 Node recipes
    h2 Welcome! Here you will find a variety of scruptious recipes for you to make

Now we should just have the jumbotron:

##Creating a Navigation Bar

Because layout.jade specifies how our web pages are laid out, it makes sense to put the bootstrap navbar here because every page should have a navigation bar:

The navbar will have 4 sections, Home Page button, BBQ, Brunch, Dessert. Each of these are the name of the kinds of recipes will eventually display on each page:

The HTML needed to create the navbar is:

    <nav role="navigation" class="navbar navbar-default">
		<div class="container-fluid">
			<div class="navbar-header">
				<button data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" class="navbar-toggle">
					<span class="sronly"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
				</button>
				<a href="/#" class="navbar-brand">Node Recipes</a>
			</div>
			<div id="bs-example-navbar-collapse-1" class="collapse navbar-collapse">
				<ul class="nav navbar-nav">
					<li><a href="/recipes/bbq">BBQ</a></li>
					<li><a href="/recipes/brunch">Brunch</a></li>
					<li><a href="/recipes/dessert">Dessert</a></li>
				</ul>
			</div>
		</div>
	</nav>

The jade which produces this is (add this above block pageTitle in layout.jade):

    block navBar
        nav(class='navbar navbar-default', role='navigation')
            .container-fluid
                .navbar-header
                    button(class='navbar-toggle', data-toggle='collapse', data-target='#bs-example-navbar-collapse-1')
                        span.sronly
                        span.icon-bar
                        span.icon-bar
                        span.icon-bar
                    a(class='navbar-brand', href='/#') Node Recipes
                #bs-example-navbar-collapse-1(class='collapse navbar-collapse')
                    ul(class='nav navbar-nav')
                        li
                            a(href='/recipes/bbq') BBQ
                        li
                            a(href='/recipes/brunch') Brunch
                        li
                            a(href='/recipes/dessert') Dessert

As you can see it is a lot easier to write jade than it is raw HTML. Remember block navBar is just a jade specific marker which specifies the start of a new block. It doesn't render to any HTML.

Notice how each li element points to a route on your server:

	ul(class='nav navbar-nav')
	    li
	        a(href='/recipes/bbq') BBQ
	    li
	        a(href='/recipes/brunch') Brunch
	    li
	        a(href='/recipes/dessert') Dessert

Now run your web site:

If you click on any of the items in the navbar you'll see that you get a page not found error:

In the next section we will implement the views for each of the recipe kinds.

##Creating the Recipes Router

To create a new Recipe route add a new javascript file, recipes.js to the routes folder:

Create a new route handler

recipes.js

var express = require('express');
var router = express.Router();

/* Renders Recipe view */
router.get('/:id', function(req, res) {
	
});

module.exports = router;

This route handler will handle all (HTTP GET) request from your browser. We will come back to how '/:id' defines our route.

Add a reference to data.js, our data source for recipes:

recipes.js

var recipes = require('../data/recipeData.js');
var express = require('express');
var router = express.Router();

/* Renders Recipe view */
router.get('/:id', function(req, res) {
	
});

module.exports = router;

Now in app.js add a reference to ./routes/recipes' router we just made:

app.js

	var recipes = require('./routes/recipes.js');

Then add the router that will use the base route '/recipes' as assign it to the recipes.list handler:

app.js

	//default route
	app.use('/', routes);
	app.use('/recipes', recipes);

The last piece we need is to create a new view template recipes.jade file under the views folder:

Your views folder should look like this now:

Let's go back and take a look at data/recipeData.js:

	/**
    This is the data source for your application. In the real world, you would either get this data from a data base or
    from an API 
	**/
	exports.recipeTypeName = {
	    bbq : 'Barbeque',
	    dessert : 'Dessert',
	    brunch : 'Brunch'
	}
	exports.bbq = [
	    {
	    name: 'Make-It-Mine Pork Kabobs',
	    ingredients: ["1 pound boneless pork loin or tenderloin",
	            "1 Onion",
	            "1 Zuchhini",
	            "1 Shitake Mushroom"
	        ],
	    photo : 'http://images.media-allrecipes.com/userphotos/250x250/01/07/80/1078019.jpg'
	},
	{
	    name: 'San Diego Grilled Chicken',
	    ingredients: ["1 pound boneless pork loin or tenderloin",
	            "1 Onion",
	            "1 Zuchhini",
	            "1 Shitake Mushroom"
	        ],
	    photo : 'http://images.media-allrecipes.com/userphotos/250x250/00/62/02/620268.jpg'
	},
	...
	]

	exports.brunch = [
	    {
	    name: 'Breakfast Scones',
	    ingredients: ["1 pound boneless pork loin or tenderloin",
	            "1 Onion",
	            "1 Zuchhini",
	            "1 Shitake Mushroom"
	        ],
	    photo : 'http://images.media-allrecipes.com/userphotos/250x250/00/01/71/17102.jpg'
	},
	{
	    name: 'Veggie-Bean Brunch Casserole',
	    ingredients: ["1 pound boneless pork loin or tenderloin",
	            "1 Onion",
	            "1 Zuchhini",
	            "1 Shitake Mushroom"
	        ],
	    photo : 'http://images.media-allrecipes.com/userphotos/250x250/00/88/68/886877.jpg'
	},
	...
	]

	exports.dessert = [
    {
	    name: 'Red, White and Blue Strawberry Shortcake',
	    ingredients: ["1 Cake",
	            "1 Red",
	            "1 Whit",
	            "1 Shitake Mushroom"
	        ],
	    photo : 'http://images.media-allrecipes.com/userphotos/250x250/00/97/60/976034.jpg'
	},
	{
	    name: 'All American Trifle',
	    ingredients: ["1 pound boneless pork loin or tenderloin",
	            "1 Onion",
	            "1 Zuchhini",
	            "1 Shitake Mushroom"
	        ],
	    photo : 'http://images.media-allrecipes.com/userphotos/250x250/01/17/91/1179163.jpg'
	},
	...
	]

In lieu of using a data source like a database or an API, we will use this file as our data source for recipes. recipesData contains 4 exported properties:

  1. recipesData.recipeTypeName - A key/value pair that maps the recipe type name like 'BBQ' to Barbecue
  2. recipesData.bbq - A collection of Barbecue recipes
  3. recipesData.brunch - A collection of Brunch recipes
  4. recipesData.dessert - A collection of Dessert recipes

Now that we have an idea of what our data looks like we can use express to render a recipe collection to the recipe.jade view. We will back the recipe view with the model:

	{
		recipes: {
			list: <Collection of recipe objects from recipesData.js>,
			kind: <name of the recipe kind>
		}
	}

In the route handler recipes.js we will use the res.render function will allow us to render a view, with a model to the page:

	var recipes = require('../data/recipesData.js');
	
	exports.list = function (req, res) {
	
	    var kind = req.params.id;
	    
	    res.render('recipes', {
	        recipes: {
	            list: recipes[kind],
	            kind: recipes.recipeTypeName[kind]
	        }
	    });
	}

This will render the jade view recipe and back that the view with the data in recipes object.

Run the website and you'll notice when you click on any of the navbar items you get a blank page:

This is because the view template recipes.jade is blank.

#Creating the Recipes View Template

This is the easy part! We've already created our route, and attached our view to our data model. Now all we have to do is specify the jade template code so that the recipe shows up on our page.

The first thing we need to do is to declare that recipes.jade is an extension view of layout.jade:

layout.jade

	extends layout

Now that we've done this, running the website again will show you that we get the navBar from layout.jade:

However the page is still blank. First we should add a pageTitle block. Layout.jade will place the html in that block first:

extends layout

block pageTitle
  //this will get displayed in the pageTitle block in layout.jade.
  //This makes a reference backing data model of this page
  h1 #{recipes.kind}

The #{recipes.kind} is a jade variable, and it will pull backing data model's recipes.kind value as the title of the page.

Run the website again and you will see the clicking on a tab will change the title of the page. This is because the underlying data for the template is changing even though the actual template, recipes.jade does not:

Now the most important part of course is where we will display the recipe data. First we need to specify a block pageContent which also comes from layout.jade. This block will be displayed after the pageTitle block.

extends layout
block pageContent

  ul

Now we will use another one of jade's important features. Jade allows us to create programmatic loops to repeat generating repetitive pieces of html. We can use these loops to iterate though the recipes.list recipe collection in the backing data model.

extends layout
block pageContent

  ul
  each recipe in recipes.list
    h2 #{recipe.name}

Run your website and see how it looks:

The each jade statement will actually iterate through a collection in the underlying data model. In this case, jade is going to print out with an h2 header the name field on every item in the recipes.list collection.

We can do a bit better on the UI layout by using the bootstrap well UI widget.

extends layout
block pageContent

  ul
  each recipe in recipes.list
  //create a new well for each recipe model
    .well
      h2 #{recipe.name}
      img(src='#{recipe.photo}')

Reload your webpage and you'll see that a well is created to contain the recipe. We also added the photo using another jade variable #{recipe.photo} and which pulls the photo from the recipe data model. This is then placed as the src attribute to an img html element:

Now let's add the ingredients:

extends layout
block pageContent

  ul
  each recipe in recipes.list
    //create a new well for each recipe model
    .well
      h2 #{recipe.name}
      img(src='#{recipe.photo}')
      h3 Ingredients
      h5
      ul
      each ingredient in recipe.ingredients
        li=ingredient

Within the main each loop which iterates through reach recipe, we create new li elements within a ul element by iterating through the recipe.ingredients collection for each recipe.

Reload the page again and you'll see that you get the ingredients listed!

Switching between tabs changes the underlying data model and reloads the same template:

One more thing to note, this page is already mobile optimized thanks to bootstrap. This code in your layout.jade does this trick:

layout.jade

	//indicate that our site is mobile optimized
    meta(name='viewport', content='width=device-width, initial-scale=1.0')

This tells mobile browsers that your website is optimized for mobile. You can test how your site reacts to a smaller window by just re-sizing it:

That's it! Congratulations you've created your first Node.js website!

About

This guide will walk you through the basics of jade templating by creating a simple recipes website using Node.js Express and Jade

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 96.6%
  • CSS 3.4%