Node JS

Using PUG (JADE) create wizard for your apps – Node JS & Express

This blog post is to show the steps for creating a wizard using PUG. If you are new to PUG read the API docs. It’s always a best practice to keep the code clean. Having said that, it’s a bit complex to manage if we haven’t followed naming conventions. PUG supports includes which helps us to keep the JavaScripts and Style (CSS) as a separate files!

Gist Code Files are here. Copy all files at one go!

For a demo let me create my project root folder named Wizard which is structured as shown below. Look at the views folder in which we created JS and STYLE folders which holds javascript and css respectively. Index and UserInformation.pug file will be added later!

Install Express, pug and body-parser packages which are available in npm.

npm install

Source JSON

{
  "name": "wizard",
  "version": "1.0.0",
  "description": "PUG Wizard Demo!",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Chendrayan Venkatesan",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.3",
    "pug": "^2.0.3"
  }
}

We are set partially! Let’s begin to play with PUG! If you are good at HTML and CSS it’s easy to build pages easily and if not refer W3Schools to pick logic! HTML source from the same site.

STYLE SHEET

* {
    box-sizing: border-box;
}
body {
    background-color: #f1f1f1;
}
#regForm {
    background-color: #ffffff;
    margin: 100px auto;
    font-family: Raleway;
    padding: 40px;
    width: 70%;
    min-width: 300px;
}
h1 {
    text-align: center;
}
input {
    padding: 10px;
    width: 100%;
    font-size: 17px;
    font-family: Raleway;
    border: 1px solid #aaaaaa;
}

/* Mark input boxes that gets an error on validation: */
input.invalid {
    background-color: #ffdddd;
}

/* Hide all steps by default: */
.tab {
    display: none;
}

button {
    background-color: #4CAF50;
    color: #ffffff;
    border: none;
    padding: 10px 20px;
    font-size: 17px;
    font-family: Raleway;
    cursor: pointer;
}

button:hover {
    opacity: 0.8;
}

#prevBtn {
    background-color: #bbbbbb;
}

/* Make circles that indicate the steps of the form: */
.step {
    height: 15px;
    width: 15px;
    margin: 0 2px;
    background-color: #bbbbbb;
    border: none;
    border-radius: 50%;
    display: inline-block;
    opacity: 0.5;
}

.step.active {
    opacity: 1;
}

/* Mark the steps that are finished and valid: */
.step.finish {
    background-color: #4CAF50;
}

JAVASCRIPT

var currentTab = 0; // Current tab is set to be the first tab (0)
showTab(currentTab); // Display the crurrent tab

function nextPrev(n) {
    // This function will figure out which tab to display
    var x = document.getElementsByClassName("tab");
    // Exit the function if any field in the current tab is invalid:
    if (n == 1 && !validateForm()) return false;
    // Hide the current tab:
    x[currentTab].style.display = "none";
    // Increase or decrease the current tab by 1:
    currentTab = currentTab + n;
    // if you have reached the end of the form...
    if (currentTab >= x.length) {
        // ... the form gets submitted:
        document.getElementById("regForm").submit();
        return false;
    }
    // Otherwise, display the correct tab:
    showTab(currentTab);
}

function validateForm() {
    // This function deals with validation of the form fields
    var x, y, i, valid = true;
    x = document.getElementsByClassName("tab");
    y = x[currentTab].getElementsByTagName("input");
    // A loop that checks every input field in the current tab:
    for (i = 0; i < y.length; i++) {
        // If a field is empty...
        if (y[i].value == "") {
            // add an "invalid" class to the field:
            y[i].className += " invalid";
            // and set the current valid status to false
            valid = false;
        }
    }
    // If the valid status is true, mark the step as finished and valid:
    if (valid) {
        document.getElementsByClassName("step")[currentTab].className += " finish";
    }
    return valid; // return the valid status
}

function fixStepIndicator(n) {
    // This function removes the "active" class of all steps...
    var i, x = document.getElementsByClassName("step");
    for (i = 0; i < x.length; i++) {
        x[i].className = x[i].className.replace(" active", "");
    }
    //... and adds the "active" class on the current step:
    x[n].className += " active";
}

The functionality is very straight forward – Go forward and backward! In our index.pug file we simple call style:include and script:include

So our index code looks like below!

INDEX

<!DOCTYPE html>
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title Wizard
        style
            include ./style/style.css
        link(rel="stylesheet", href="https://fonts.googleapis.com/css?family=Raleway")
        body
            form#regForm(action='/userInformation',method="post")
                h1 PUG Wizard Demo
                // One "tab" for each step in the form:
                .tab
                    p
                        input(placeholder='First name...', oninput="this.className = ''", name='fname')
                    p
                        input(placeholder='Last name...', oninput="this.className = ''", name='lname')
                .tab
                    p
                        input(placeholder='E-mail...', oninput="this.className = ''", name='email')
                    p
                        input(placeholder='Phone...', oninput="this.className = ''", name='phone')
                .tab
                    p
                        input(placeholder='dd', oninput="this.className = ''", name='dd')
                    p
                        input(placeholder='mm', oninput="this.className = ''", name='nn')
                    p
                        input(placeholder='yyyy', oninput="this.className = ''", name='yyyy')
                .tab
                    p
                        input(placeholder='Username...', oninput="this.className = ''", name='uname')
                    p
                        input(placeholder='Password...', oninput="this.className = ''", name='pword', type='password')
                div(style='overflow:auto;')
                    div(style='float:right;')
                        button#prevBtn(type='button', onclick='nextPrev(-1)') Previous
                        button#nextBtn(type='button', onclick='nextPrev(1)') Next
                // Circles which indicates the steps of the form:
                div(style='text-align:center;margin-top:40px;')
                    span.step
                    span.step
                    span.step
                    span.step
        script
            include ./js/button.js

For our demo we need to collect user information and show as text in another page for which we created a POST route named userinformation.pug

USERINFORMATION

<!DOCTYPE html>
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title Wizard
        style
            include ./style/style.css
        link(rel="stylesheet", href="https://fonts.googleapis.com/css?family=Raleway")
        body
            p
            label(for="fname") First Name
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=fname)
            p
            label(for="lname") Last Name
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=lname)
            p
            label(for="email") Email
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=email)
            p
            label(for="phone") Phone
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=phone)
            p
            label(for="dd") Date
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=dd)
            p
            label(for="nn") Month
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=nn)
            p
            label(for="yyyy") Year
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=yyyy)
            p
            label(for="uname") User Name
            p
            input(placeholder='First name...', oninput="this.className = ''", name='fname' , value=uname)

We have created two routes with GET and POST which renders index and userinformation files respectively. Look at the server.js code – For a demo I haven’t created separate route folder!

SERVER JS

var express = require('express');
var app = express();
var path = require('path');
var bodyparser = require('body-parser');

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(bodyparser.urlencoded({ extended: true }));

app.get("/", function (request, response) {
    response.render('index')
});

app.post("/userInformation", function (request, response) {
    response.render('userInformation', {
        fname: request.body['fname'],
        lname: request.body['lname'],
        email: request.body['email'],
        phone: request.body['phone'],
        dd: request.body['dd'],
        nn: request.body['nn'],
        yyyy: request.body['yyyy'],
        uname: request.body['uname'],
        pword: request.body['pword'],
    })
});

app.listen(3000)
console.log("Your Application is running on port 3000");

Enjoy PUG!

 

Leave a Reply

Your email address will not be published. Required fields are marked *