Setup Link Functionality with Functional Programming

See the demo » Grab the code from Github »

I lost my WordPress blog due to a sloppy, “newbie-to-databases” error. I’ve been rebuilding it slowly with Jekyll, adding new functionality here and there.

One thing I added was on the homepage, where post snippets inside divs go to a certain link when they get clicked. To apply that functionality to ALL those links, functional programming made sense.

The file structure looks like this, roughly:

 
├── build
|   ├── 01-post
|       └── index.html
|   ├── 02-post
|       └── index.html
|   ├── 03-post
|       └── index.html
|   ├── 04-post
|       └── index.html
|   └── bundle.js
|   └── index.html
|   └── styles.css
├── js-build
|   ├── helpers.js
|   └── index.js
├── node_modules
├── .babelrc
├── package.json
└── webpack.config.js
 

The best way to start understand the code is by looking at build/index.html:

 
<!-- build/index.html -->

<!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>Functional Programming Click Tutorial</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <div class="post-link-hook" data-url="01-post">
   The link to the first post.
  </div>

  <div class="post-link-hook" data-url="02-post">
   The link to the second post.
  </div>

  <div class="post-link-hook" data-url="03-post">
   The link to the third post.
  </div>

  <div class="post-link-hook" data-url="04-post">
   The link to the fourth post.
  </div>

 <script type="text/javascript" src="bundle.js"></script>

</body>
</html>
  

Note that all the div tags have a post-link-hook class and a data-url attribute. Our JavaScript will use both of these things make the divs clickable links without the need for an a tag.

The core JavaScript is in build/bundle.js file but we’ll discuss it shortly. The styles are in the build/styles.css and they’re pretty basic:


/* build/styles.css */

.post-link-hook {
  width: 400px;
  height: 72px;
  margin-bottom: 20px;
  padding:6px;

  border: 1px solid #000;
  border-radius: 5px;
  cursor: pointer;
}
 

Very basic styles here that are applied to all the div tags. I should point out that divs have a cursor: pointer setting, allowing for a link hand cursor to appear when they’re hovered over, making them act like a tags.

Our code uses ES6 things like const, let, arrow functions and object destructuring. The (current) popular way to make this code cross-browser compatible is with webpack and Babel.

The setup for webpack/Babel starts in package.json:


// package.json

{
  "name": "kaidez.com",
  "version": "3.0.0",
  "description": "Personal blog of Kai Gittens",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.0",
    "webpack": "^3.5.5"
  }
}
 

The three key things here are:

  1. The main property which is optional here. It's the entry point for the webpack build, which works even if this property isn't here. But doing so is a de facto webpack best practice in terms of documenting what the entry point is.
  2. The scripts property which contains two tasks: build which builds out the production-ready build/bundle.js file via webpack, and watch, which watches for changes to the your .js source files and runs that build. These source files are in the js-build directory listed above and the tasks can be run using either Yarn or npm...I used Yarn to run watch.
  3. The devDependencies property which provides the packages needed to let webpack build ES6 out to the more cross-browser friendly ES5 syntax.

The .babelrc file uses Babel’s default settings to transform ES6 syntax to ES5 syntax.


// .babelrc
{
  "presets": ["env"]
}

webpack.config.js looks like this:


// webpack.config.js

const path = require('path');

module.exports = {
  module : {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
         loader: 'babel-loader'
        }
      }
    ]
  },
  entry: './js-build/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build')
  }
};

webpack will look at the entry point of the file, js-build/index.js, which lists any dependency files needed to build out our site’s JavaScript code. It will then transform any ES6 into ES5, and save things out in a file named build/bundle.js.

That js-build/index.js entry point file is real simple due to the small amount of code here. Entry point files are always much more detailed, but this simple one looks like this:


// js-build/index.js

import { divClick } from "./helpers"

// Run divClick code
divClick

I’m using ES6 destructuring to import divClick from this file’s only dependency: helpers.js. And helpers.js contains the core code for our link functionality:


// js-build/helper.js

const getPostDiv =  document.querySelectorAll(".post-link-hook");

const doEventOnElement = (element, getEvent, fn) => {
  for (let i = 0; i < element.length; i++) {
    element[i].addEventListener(getEvent, event => {
      fn(element[i])
    })
  }
}

function goToPage(el) {
  window.location = el.dataset.url
}

export const divClick = doEventOnElement(getPostDiv, 'click', goToPage)

Breaking this code down…


const getPostDiv =  document.querySelectorAll(".post-link-hook");
...

getPostDiv refers to any page elements of with a class name of post-link-hook, which is all the divs in index.html. getPostDiv returns this group of elements as an array.


...
const doEventOnElement = (element, getEvent, fn) => {
  for (let i = 0; i < element.length; i++) {
    element[i].addEventListener(getEvent, event => {
     fn(element[i])
    })
  }
}
...

doEventOnElement is a function that takes our elements array and builds functionality where performing an event on each element (like click) runs a function.

  • The element parameter refers to the element array
  • The getEvent parameter refers to the event
  • The fn parameter refers to the function

doEventOnElement loops through the array items and places an event listener on each item. The event listener runs the event which, again, is defined by the getEvent parameter.

doEventOnElement also allows a callback function to run our other function defined by fn. And fn takes a parameter as well: the element parameter we defined in the beginning.

I get that this MIGHT be confusing, but walking through the goToPage function may clarify things:


...
function goToPage(el) {
  window.location = el.dataset.url
}
...

goToPage will be the function that runs when a div is clicked. Its job is to go to a particular web page.

It does this by accepting a parameter el, which is expected to be a page element. el is expected to have a data-url attribute that can be accessed by looking at el.dataset.url which, by the way, all our div tags have.

Once goToPage resolves all that, goToPage loads el.dataset.url as a relative URL with the help of the window.location property. It will then go to that page in the browser.


...
export const divClick = doEventOnElement(getPostDiv, 'click', goToPage)

We create a constant called divClick. It’s exported out as dependency, which we saw in js-build/index.js.

divClick contains an instance of doEventOnElement() which takes three parameters:

  1. getPostDiv which is the constant defined earlier and represents the elements that should be affected by our code. It's an array list of all the div tags with the post-link-hook class.
  2. 'click' is the event that should affect the div tags. i.e. when we click on divs something should happen.
  3. goToPage is the "something" that should happen. It's the function that looks for the URLs stored in each element's data-url attribute and loads the URL into the browser.

It’s important to note that we’re passing a function as a parameter here, which is a big deal in functional programming. Whenever you see or hear the phrase “functions are first class objects in JavaScript”, passing functions as parameters is one of the many reasons why that’s true.

And it’s VERY important to note what happens when that function actually gets passed as a parameter. In the context of our code, that means looking at doEventOnElement().

The last parameter passed to doEventOnElement() is fn. And in our code, that’s the goToPage() function.

So when fn runs in the for loop like this…


fn(element[i])

It looks like this when the fn param is set…


goToPage(element[i])

We built goToPage() to expect an element: that will be defined by element[i]. And since we set the element parameter to be getPostDiv, the function in the for loop look like this…


goToPage(getPostDiv[i])

So as the loop iterates over an element array, it will look like this when it hits the array’s second item…


goToPage(getPostDiv[1])

And from there, it will look at the data-url attribute in the getPostDiv[1] and treat it as a link.

This was something that took me some time to grasp while learning functional programming. It can be a bit mind-bending but understanding it is key.

In closing and like I said in my last post, I try to implement JavaScript functional programming wherever I can, even if it’s just for practice. Code like this may be too much for the task at hand, but I’m glad I did it: practice or no practice.

Feel free to ask questions or make suggestions.

Would you like to Tweet this page?