Skip to content

ga-wdi-boston/wdi_8_express_jade

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Server-Side Rendering

Traditionally servers generated the content that was viewed in a browser. Another words all the HTML/CSS and JS that were to rendered and executed in the browser was sent to the browser when the page was initially loaded.

For the user to get new content they would make an HTTP Request to the server and the server would generate the content for the complete page.

There are some good reasons to generate content on the server. Some of these are:

  • Load time. Many apps load tons of JS when they are first loaded in the browser. It can take awhile for this javascript to load and execute.

    Often, the server will generate initial content a user views when they go to a site. Then, after this initial load Ajax will be used to update the site with new and updated content.

  • Predictable performance. Some browers are hopelessly slow and buggy. Older browser are still used in many places all over the world. I'm talking crazy old, Netscape Navigator, IE4 old. Not everyone has modern browsers or devices that run JS efficiently.

  • Caching We can cache content generated with from the server. And caches can be smoking fast.

    • Delivering content to the user right from the user's machine, the Browser cache.

    • Delivering content from a machine geographically close to the user. From a machine in the next town over! This is why we use Content Delivery Networks (CDN).

    • Delivering content without executing code in the web application. Save the generated HTML in a file on the server and give it to the browser as needed.

  • RESTful The web is a highly scalable distributed system. Part of it's RESTful architectural style is that it acts like a big state machine. Where users navigate to each page/node by following links/edges.

    This is easier to implement when server are generating content.

  • SEO (Search Engine Optimization) Google and other denizens of the net crawl sites and allow user to find your site based on your site's content.

    This has tradionally been done by sending HTTP Requests to the server and having the content generated by the server processed by such crawlers.

    There are ways to do SEO for sites that don't generate server side content, but it's somewhat more complex.

The Modules

In this excercise, we will be using two modules: Jade and File System.

Jade is a server-side view engines. Much like we used handlebars on our front end to bind data to a template. We will be creating templates as .jade files, and passing them data in our express routes.

File System is a module that allows you to read and write to files in your node server.

We will mostly be focusing on Jade in this lesson, but we will make use of FS later on.

Let's go ahead and install these:

Setup

npm install
npm install jade

https://nodejs.org/api/fs.html

Add Express, Mongoose and Body Parser

Nothing new here. Just a reminder.

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/contacts');

var express = require('express');
var app = express();

var bodyParser = require('body-parser');
var jsonParser = bodyParser.json();

Add Jade and File System

Now, we need to import these modules into our app.js. Near the top of our page, let's add:

var jade = require('jade');
var fs = require('fs');

// we set our view engine here
app.set('view engine', 'jade');
app.set('views', './templates');

Notice that we use app.set to tell express that we are going to use the jade templating engine.

And that the jade views will be located in the templates directory.

Add the Contact model.

// Add the Contact model
var Contact = require('./lib/contacts.js');

// Add the Utilities module, lot's of helpful function be here.
var util = require('util');

Spin off our previous routes in a separate router

We want our server to both render json AND render html depending on which route a user hits. We can accomplish this the way we talked about earlier, where we specifically type out each individual route with its appropriate base, or we can spin off our API functionality into a separate router. Let's go behind door number 2.

First, we create our new router:

var apiRouter = express.Router();

Next, let's attach all of our previous routes to our new router instead of our express app. Your new routes will look like something like this:

apiRouter.get('/', function(req, res){
  res.json({name:'Hello World!'});
});

Finally, we need to mount our new apiRouter on our express app and apply our '/api' base. This syntax is taken straight from Thursday's lesson:

app.use('/api', apiRouter);

Let's Generate HTML on this Server.

Up until now we've been generating only JSON on the server. But, tradionally web apps generate HTML, CSS and JS on the server. The generated code and content is sent to the browser in the body of the HTTP Response.

This will create a route for '/'.

app.get('/', function(req, res) {
  res.render( 'index', {name: "Max", message: 'Welcome to our contacts page! I hope you have a good stay.'});
});

The render method is added by the Jade templating module.

It will:

  • The first argument, 'index', will find the index.jade file in the templates directory. Remember we told Express which directory the templates lived in above.

  • The second argument, the object literal {name: ...}, will be passed to the Jade template Engine and be made availabe inside of the index.jade template.

  • The Jade Template Engine will process the index.jade file and generate HTML. More below on how it's processed.

  • The HTML generated from the last step will be returned to the browser in the HTTP Response.

Add this to the templates/index.html

html
  head
    title #{name}'s Contact Warehouse
  body
    h1 #{name}'s Contact Warehouse
    h4 #{message}
  • White space is VERY important in Jade. It sets the structure of the HTML document that is produced.

  • Proper indentation is a MUST. Errors will occur otherwise.

  • The layout of the jade file reflect the parent-child relationships that exist in the DOM.

    • The body tag is the parent of h1 tag, h1 tag is a sibling of the h4 tag, etc.
  • The pound curly-brace syntax substitutes a variable's value into the generated HTML.

    • Like string-interprelation in Ruby, ay.

Getting Trickier with Jade

Jade allows us to do some interesting things with templates. Let's explore a few of its features.

Jade's extend and block functions allow us to wrap one jade template around another.

Let's see this practice in action. Touch a new template called layout.jade.

Our Layout:

html
  head
    title Contact Warehouse

  body
    header
      h1 Contact Warehouse
      block main

Notice the block main line.

Now let's modify our index.jade:

extend layout
block main
  h1 #{name}'s Contact Warehouse
  h4 #{message}

When we call res.render on our index.jade file now, Jade will wrap our layout.jade around this template and substitute our index code where we had block main.

LAB, YOU DO

Read the Jade Tutorial and/or watch Jade Tutorial, Video.

Create a route in your articles app for '/'. Of course it will have a handler function.

The first argument to render would be for the name of the jade file to process, index.

The second argument to render would be an object literal, {name: 'Joe', message: "Welcome to Joe's blog"},

Don't forget to create a jade template file in the templates directory. Yes, it will look like the templates/index.jade we created above

Go to / in you browser to check that the proper HTML was generated and returned to your browser.

Once you do this, create a layout.jade and extend it onto your index.jade template.

Looping and connecting with javascript in Jade

Rendering our list of contacts will take a few more tricks of the trade.

First, we need to create our route. We are indexing all our contacts, so our route will be to '/contacts'. Inside of our route, we will pull all of our contacts from our database, and pass them into our .render method to be compiled with our template.

app.get('/contacts', function(req, res) {
  Contact.find({}, function(error, contactList) {
    res.render( 'contacts', {contacts: contactList});
  });
});

Now, we need to create our contacts.jade template itself. We first extend our layout into our contacts template. Then, we need to iterate through all of our contacts. The syntax for doing so is:

for contact in contacts

Our second hiccup comes when we go to render the various address of our contacts. Not only do we want to iterate through these, but we want to check to see if they exist, and if not, we don't want to render any references to them.

Jade allows you to use javascript right into your templates. We won't go too deeply into the specifics of this, but we will use conditional logic to check to see whether or not our contacts have certain sets of contact information. Here's email addresses for example:

if (contact.emailAddresses.length > 0)
  h4 Email Addresses:
  //- // iterating through email addresses
  for email in contact.emailAddresses
    p #{email.emailAddressType}: #{email.emailAddress}

We check to see if the array has any elements (remember, an empty array is truthy), then we render iterate through all of our email addresses and render them accordingly.

All together, our template will look like:

extend layout
block main
  h1 Contacts
  .contacts
    for contact in contacts
      h3 Name: #{contact.firstName} #{contact.lastName}
      h4 Title: #{contact.title}
      //- // checking to see if we have email addresses
      if (contact.emailAddresses.length > 0)
        h4 Email Addresses:
        //- // iterating through email addresses
        for email in contact.emailAddresses
          p #{email.emailAddressType}: #{email.emailAddress}
      if (contact.phoneNumbers.length > 0)
        h4 Phone Numbers:
        //- // iterating through phone numbers
        for phone in contact.phoneNumbers
          p #{phone.phoneNumberType}: #{phone.phoneNumber}
      if (contact.addresses.length > 0)
        h4 Addresses:
        //- // iterating through addresses
        for address in contact.addresses
          p #{address.street},
          p #{address.city}, #{address.state} #{address.zipCode}
          p #{address.country}

Now, let's go to http://localhost:3000/contacts and make sure that it works!

LAB, YOU DO

Create a route in your articles app for '/articles'.

The first argument to render would be for the name of the jade file to process, articles.

The second arguement would be the an object literal containing the array of articles that you would have pulled from your database.

Then, create your articles.jade template by following the pattern outlined above.

Go to /articles in you browser to check that the proper HTML was generated and returned to your browser.

Creating new contacts the single-page way

Now that we are showing all of our contacts, it is time to create some new ones. Not only that, but we want to have the new contacts appear on our contacts index without us having to refresh the page. Let's start with our form.

Touch a new file in your templates folder called contact-form.jade.

Jade allows you to add attributes to html tags with this syntax:

form(name="new-contact")

With this is mind, we will generate our form as so:

h1 Add new contact
form(name="new-contact")
    p First Name:
    input(type="text", name="first-name")
    p Last Name:
    input(type="text", name="last-name")
    p Title:
    input(type="text", name="title")
    input(type="submit", value="contact")

In addition to allowing you to extend a template onto another one, jade allows you to import the body of one template into another with include.

Let's to back to our contacts.jade file and add the line include form.jade *inline with block main to include the the form in our template.

Then, let's start up our server and make sure the form is included in our contacts view.

Dawg, I put javascript inside of your javascript . . .

Next, let's create another jade template called 'ajax.jade'. In this template, let's create a script tag that will generate our client-side javascript. Yes, we are using server-side javascript to generate client-side javascript.

The syntax to create a script tag in jade goes as follows:

script(type="text/javascript").

All of our javascript withh go underneath and tabbed one line over from this tag.

Our ajax call includes nothing unique to jade or node:

  $(document).ready(function(){
    $('form[name="new-contact"]').on('submit', function(event){
      event.preventDefault();
      var contact = {
        firstName: $('input[name="first-name"]').val(),
        lastName: $('input[name="last-name"]').val(),
        title: $('input[name="title"]').val()
      };
      $.ajax({
        method: 'POST',
        url: 'http://localhost:3000/contacts',
        data: JSON.stringify(contact),
        contentType: "application/json; charset=utf-8"
      }).done(function(response){
        $('.contacts').append(response);
      })
    });
  });

Please notice a detail though: our callback is expecting to recieve a string of html back. How do we do this? Let's go back to our app.js to find out.

Reading files with fs

We start with our route handlers:

app.post('/contacts', jsonParser);
app.post('/contacts', function(req, res) {

});

We are expecting json, so we include our jsonParser middleware.

Next, we write our mongoose code to create a new contact document:

  Contact.create(req.body, function(error, contact) {
    if (error) {
      console.log(error);
      res.sendStatus(400);
    } else {
    //  nothing here yet!!!
      });
    };
  });

We handle any errors that may come up in the creation process, but we have yet to tell our server to do anything after the contact document gets created.

Here's where our File System module comes into play. We are going to use it to read our contact template:

fs.readFile('./templates/contact.jade', 'utf8', function (err, data) {
  if (err){
    res.sendStatus(400);
  };
  // nothing to see here
});

.readFile is a method of fs that does exactly what you'd expect. We pass it a path to the file we want it to read, the type of encoding of the file, and a callback that takes both an error and the contents of the file as 'data'. In the callback, we handle the error first, then we will compile the template using jade. That comes next.

Return to handlebars island

Compiling our template may sound intimidating, but the syntax is almost identical to what we used with handlebars. We pass the jade template (stored as the variable 'data') into jade.compile, which produces our .contactCompiler function. Then, we call our compiler function on our newly created contact, which returns our stringified json. Finally we send the data back to our ajax request with a created header.

var contactCompiler = jade.compile(data);
var html = contactCompiler(contact);
res.send(html);
res.status(201);

All together, our route looks like:

app.post('/contacts', jsonParser);
app.post('/contacts', function(req, res) {
  Contact.create(req.body, function(error, contact) {
    if (error) {
      console.log(error);
      res.sendStatus(400);
    } else {
      fs.readFile('./templates/contact.jade', 'utf8', function (err, data) {
        if (err){
          res.sendStatus(400);
        };
        var contactCompiler = jade.compile(data);
        var html = contactCompiler(contact);
        res.json(html);
        res.status(201);
      });
    };
  });
});

Whoops, we almost forgot our contact.jade template! In keeping our list uniform, lets just copy the code out of our contacts.jade and make our contact.jade look like:

h3 Name: #{firstName} #{lastName}
h4 Title: #{title}
if (emailAddresses.length > 0)
  h4 Email Addresses:
  for email in emailAddresses
    p #{email.emailAddressType}: #{email.emailAddress}
if (phoneNumbers.length > 0)
  h4 Phone Numbers:
  for phone in phoneNumbers
    p #{phone.phoneNumberType}: #{phone.phoneNumber}
if (addresses.length > 0)
  h4 Addresses:
  for address in addresses
    p #{address.street},
    p #{address.city}, #{address.state} #{address.zipCode}
    p #{address.country}

Now, let's try it! Fire up your server, navigate over to http://localhost:3000/contacts and try to create a contact! If the contact doesn't appear, you done goofed.

LAB, YOU DO

Create a post route in your articles app for '/articles'.

Create form and script jade templates.

Build out your single page upload functionality.

Go to /articles, create an article, and watch it get rendered like magic.

Serving Static Files

Our site looks pretty hideous as is. We have no styling, and if we wanted to use more javascript, our script tag would become cumbersome and hard to manage.

When we use a grunt server, or our ruby webrick server, our whole directory is served. Node is more selective. If we want to serve up static files (Perhaps a stylesheet? Maybe some javascripts?) along with our render html, we need to explicitly tell our app to do so.

Express has built in middleware to help us accomplish this task. Calling express.static on the path to a directory of static files we want to serve returns middleware that allows us to serve said files.

app.use(express.static(__dirname + '/public'));

In nodeland, __dirname is a reference to the directory in which the executed file resides. Therefore, in the line above, we create the middleware that allows us to serve static files from a directory called 'public', then tell our Express app to use the middleware.

Now, our response will include whatever files we put in our 'public' directory.

Touch a 'public' directory in the root of your app, then touch a directory called 'scripts' inside of it. Next, touch a file called 'app.js' inside of your scripts directory. We're going to move our ajax call from a script tag to this file. Let's copy and past our javascript from our ajax.jade file to our newly created scripts/app.js file.

Finally, we need to link to our new javascript file into our layout.jade.

Add this line:

script(src='scripts/app.js')

to the bottom of your layout, one tab over from your body tag.

Then, restart your server, open up your browser, and make sure that your app still works.

CSS on Steroids with Stylus and Nib

At this point, we could style our app with css stylesheet. However, a node module provides us with a more flexible, powerful option.

Stylus is a css pre-processer. It allows us to write css with variables, mixins, functions; you name it. It also frees us from having to use semi-colons or colons in our css. Anyone who has experience with SASS or LESS will find stylus familiar.

We will also be using a library for stylus call Nib. Once imported into our stylesheet, it gives us a number of premade mixins. Most importantly however, it frees us from having to use vendor prefixes with our styles. Isn't that nice?

First, we need to install both of these packages. Go ahead and run:

npm install stylus
npm install nib

Now that we have these packages in our repository, we need to import them into our server:

var stylus = require('stylus');
var nib = require('nib');

Next, we create a function that configures how Stylus processes raw .styl files. This function is taken straight from the Stylus documentation, save for one minor addition. We expose Stylus to Nib in the last line of our function.

// creates a compile function that calls the stylus and nib middlewear in our stack
function compile(str, path) {
  return stylus(str)
    .set('filename', path)
    .use(nib())
};

Afterwards, we need to tell our Express app to use stylus as middleware. As we pass in the middleware into our app's handler chain, we configure it by telling it both where to look for .styl files, and what function to use to compile them. We are using the function we just created.

// we set up express to use our stylus middlewear and pass in our compile function as an object here
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));

Now, we are all set to create a .styl file! Touch a directory inside of 'public' called 'stylesheets'. Inside of this new directory, touch a file called 'style.styl'.

We won't get rabbit-holed into the functionality that Stylus provides, but I invite you to check out their documentation and get exploring yourself.

Instead, I have created a quick-and-dirty .styl stylesheet:

@import 'nib'

container-color = #BEB9B5
background-color = #C25B56

@import url(http://fonts.googleapis.com/css?family=Open+Sans);

body
  background-color background-color
  font-family

#hero
  margin-left 20px

.container
  width 600px
  margin 50px auto
  font-family 'Open Sans', sans-serif
  overflow hidden
  background-color container-color

.contacts
  float left
  width 360px
  padding 20px

.form
  float right
  width 140px
  padding 20px

Either paste this into your style.styl file, or make your own styles. IDGAF.

Now, when we run our server, Stylus will compile our style.styl file and create+write equivalent css to a file in the same directory called style.css.

We aren't done yet though. We still need to link to our to-be-created stylesheet in our layout.jade.

With this line insert into the head of our template, we should see style and color breathed into our contacts page:

link(rel='stylesheet', href='/stylesheets/style.css')

LAB, YOU DO

Style your articles app!

First, create a 'public' folder and configure your app to serve it with your rendered html.

Next Stylus and Nib and include them as middleware in your Express app.

Then, create your .styl file and go to town!

Remember to add all appropriate tags to your layout.jade file.

References

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published