Now that we successfully fetched all the data we need, it’s time to start building the interface.

Let’s add an Express server to serve a blank HTML page, in the / path:

const express = require('express')
const app = express()
app.set('view engine', 'pug')
app.set('views', path.join(__dirname, 'views'))

app.get('/', (req, res) => res.render('index'))
app.listen(3000, () => console.log('Server ready'))

and we create a views/index.pug file, with this content:

html
  body

Before this can work we must npm install Pug:

npm install pug

or add it to the package.json file in Glitch.

If you’re unfamiliar with Pug, it’s the version 2 of Jade. Before going on, find and read my introduction at flaviocopes.com/pug.

We’re ready to design the basic UI!

Remember the mockup I posted in the first lesson?

Let’s work on it.

This is not a CSS, design or HTML course, so forgive my poor design skills 😅

First we include create the HTML skeleton, using Pug. To experiment with Pug and see the resulting HTML, I recommend using PugHtml.

This Pug file:

html
  head
    link(rel='stylesheet', href='css/style.css')
  body
    #container
      #sidebar
      #main
        #select
        #stats
          #today
          #yesterday
          #monthly

will generate this HTML

<html>

<head>
    <link rel="stylesheet" href="css/style.css" />
</head>

<body>
    <div id="container">
        <div id="sidebar"></div>
        <div id="main">
            <div id="select"></div>
            <div id="stats">
                <div id="today"></div>
                <div id="yesterday"></div>
                <div id="monthly"></div>
            </div>
        </div>
    </div>
</body>

</html>

We apply this CSS, saving it to public/style.css:

#container {
  width: 800px;
  display: grid;
  grid-template-columns: 30% 70%;
}
#sidebar, #main {
  border: 1px solid lightgray;
  height: 450px;
}
#main {
  display: grid;
  grid-gap: 10px;
}

#select {
  border: 1px solid lightgray;
  height: 60px
}

#stats {
  display: grid;
  grid-template-columns: 33% 33% 33%;
  padding-left: 5px
}

#today,
#yesterday,
#monthly {
  border: 1px solid lightgray;
  height: 170px
}

This will give a nice structure to our page:

We are now going to fill those boxes with the data.

First, let’s add the list of sites to the sidebar, with an “All” at the top.

We must first add the data to our Pug template. We can do so by passing data in the res.render() function:

app.get('/', (req, res) => res.render('index', data))

Now we can reference each property of the data object in our template.

I mentioned we are going to put an “All” menu at the top, which is the default. However, we don’t sum those numbers yet in the backend.

Let’s do it.

Tweak the getAnalyticsData() method by adding, at the end of it, this snippet:

data.sums = data.aggregate.reduce(( acc, current ) => {
    return {
        today: {
            total: parseInt(current.today.total) + parseInt(acc.today.total),
            organic: parseInt(current.today.organic) + parseInt(acc.today.organic)
        },
        yesterday: {
            total: parseInt(current.yesterday.total) + parseInt(acc.yesterday.total),
            organic: parseInt(current.yesterday.organic) + parseInt(acc.yesterday.organic)
        },
        monthly: {
            total: parseInt(current.monthly.total) + parseInt(acc.monthly.total),
            organic: parseInt(current.monthly.organic) + parseInt(acc.monthly.organic)
        }
    }
  }, {
    today: { total: 0, organic: 0},
    yesterday: { total: 0, organic: 0},
    monthly: { total: 0, organic: 0}
  })

If you are unfamiliar with reduce, it’s a way to transform an array into a single value.

You know map already. Well, reduce is just like map as it iterates an array, but instead of returning a new array, it returns a single element. In this case, we provide an initial value:

{
	today: { total: 0, organic: 0},
	yesterday: { total: 0, organic: 0},
	monthly: { total: 0, organic: 0}
}

and we increment it on every iteration, adding the values of each site analytics to the sum.

The result is the same object, but with the values updated with the counts.

We also add to data the names of the sites, so they are easily accessible in our template:

  data.sites = data.aggregate.map(item => {
    return {
      name: item.property.name,
      id: item.property.id
    }
  })

Now we’re ready to add those values into the Pug template:

html
  head
    link(rel='stylesheet', href='style.css')
  body
    #container
      #sidebar
        ul
          li.active All
          each site in sites
            li= site.name

As you can see we add a loop to iterate the sites property of the data object we passed to the view.

We set the “All” element to have the active class, so we can style it properly later.

Let’s add the data. This is not a frontend course, so we’ll write both organic and total data in the HTML, and show/hide using CSS, defaulting to show total data first.

#main
  #select
  #stats
    #today
      h2 Today
      p.total #{sums.today.total}
      p.organic.hidden #{sums.today.organic}
    #yesterday
      h2 Yesterday
      p.total #{sums.yesterday.total}
      p.organic.hidden #{sums.yesterday.organic}
    #monthly
      h2 Last 30
      p.total #{sums.monthly.total}
      p.organic.hidden #{sums.monthly.organic}

The last pug thing we’ll add now is the links to select to show the total or organic filter:

#main
  #select
    a(class='button total active' href='#') Total
    a(class='button organic' href='#') Organic

We’re done with Pug for now. Let’s switch to some CSS and frontend JavaScript.

CSS, let’s style it

Let’s style the sidebar list, and create a special style if there is the active class on an element:

#sidebar ul {
  padding: 0;
  margin-top: 0;
}

#sidebar li {
  list-style-type: none;
  border-bottom: 1px solid lightgray;
  padding: 20px;
}

#sidebar li.active {
  background-color: gray;
  color: white;
}

Then let’s style the buttons to choose total or organic:

#select {
  width: 300px;
  margin: 0 auto;
  padding-top: 20px;
  text-align: center;
}

#select a {
  padding: 30px;
  color: #666;
}

#select a.active {
  font-weight: bold;
  text-decoration: none;
}

Finally, we hide elements with the hidden class:

.hidden {
  display: none;
}

Frontend JS

In terms of frontend JavaScript we need to do one thing here: when pressing the “Organic” select button, we’ll show the organic numbers.

Otherwise we show the total numbers, which is the default.

To do this, we add the hidden class to all items with the total class, and we remove that class to the organic class elements.

Also, we’ll add the active class the the select link that was clicked, and remove it from the other one. Let’s do it!

If you are a jQuery user, it might be tempting to use it, but we’ll use the native browser APIs instead.

How?

First we listen for the DOMContentLoaded event to make sure the DOM is loaded when we perform our operations.

Then we listen for click events on each of the 2 select button (Total/Organic):

document.addEventListener('DOMContentLoaded', (event) => {
  const buttons = document.querySelectorAll("#select .button")
  for (const button of buttons) {
    button.addEventListener('click', function(event) {

    })
  }
})

Inside this, we first remove the active class from both the buttons, and we add it to the one we clicked:

//...
button.addEventListener('click', function(event) {
  for (const button of buttons) {
    button.classList.remove('active')
  }

  this.classList.add('active')

Then we hide and show the organic or total stats, depending on the class contained in the button:

//...
const organicStats = document.querySelectorAll('#stats .organic')
const totalStats = document.querySelectorAll('#stats .total')

if (this.classList.contains('organic')) {
  for (const item of organicStats) {
    item.classList.remove('hidden')
  }
  for (const item of totalStats) {
    item.classList.add('hidden')
  }
} else {
  for (const item of organicStats) {
    item.classList.add('hidden')
  }
  for (const item of totalStats) {
    item.classList.remove('hidden')
  }
}

Here’s the full code of the JavaScript file:

document.addEventListener('DOMContentLoaded', (event) => {
  const buttons = document.querySelectorAll("#select .button")
  for (const button of buttons) {
    button.addEventListener('click', function(event) {
      for (const button of buttons) {
        button.classList.remove('active')
      }

      this.classList.add('active')

      const organicStats = document.querySelectorAll('#stats .organic')
      const totalStats = document.querySelectorAll('#stats .total')

      if (this.classList.contains('organic')) {
        for (const item of organicStats) {
          item.classList.remove('hidden')
        }
        for (const item of totalStats) {
          item.classList.add('hidden')
        }
      } else {
        for (const item of organicStats) {
          item.classList.add('hidden')
        }
        for (const item of totalStats) {
          item.classList.remove('hidden')
        }
      }

      event.preventDefault()
    })
  }
})

That’s it for now!

You can find the code up to now in https://glitch.com/edit/#!/node-course-project-analytics-dashboard-d.

In the next lesson we’ll add ability to only show the data of a single site, rather than the sum of all visits.


Go to the next lesson