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 div
s 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 div
s 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 div
s 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:
-
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. -
The
scripts
property which contains two tasks:build
which builds out the production-readybuild/bundle.js
file via webpack, andwatch
, which watches for changes to the your.js
source files and runs that build. These source files are in thejs-build
directory listed above and the tasks can be run using either Yarn or npm...I used Yarn to runwatch
. -
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 div
s 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:
-
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 thediv
tags with thepost-link-hook
class. -
'click'
is the event that should affect thediv
tags. i.e. when we click ondiv
s something should happen. -
goToPage
is the "something" that should happen. It's the function that looks for the URLs stored in each element'sdata-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.