Classic Front End MVC with Vanilla Javascript

Patrick Ackerman
6 min readFeb 9, 2017

--

(The first in a series of JavaScript oriented design pattern articles)

Most of us are familiar with SPA MVC frameworks like Angular that have become increasingly popular over the past few years. However, it is possible to implement MVC architectural patterns on the front-end without the aid of frameworks and libraries (without even JQuery!). Inspired by some recent work I did with Java Swing, I set out to see if I could transfer many of the same patterns to pure, vanilla JavaScript.

Model-View-Controller

Model-View-Controller is one of the most well known architectural patterns, and is the basis of many JavaScript frameworks (such as Angular) and several server-rendered web frameworks (the more pedantic among us would argue that Angular is MVVM or the nebulous MV*, but it still serves as a relevant point of reference). The Model is a container for our state, and also performs business/domain logic. The View is the output displayed to our user. The Controller serves as a mediator between our Model and View, and also stores some non-domain application state.

Building Our View

Most old-school non-browser oriented GUIs are built with nested component objects that are built and styled entirely within the code itself (see Java Swing), so a View object can be abstracted entirely within a class definition. For a browser-based, vanilla JavaScript implementation, we have HTML and the Document Object Model to worry about, so there will be two pieces to our view: the HTML itself and a View class that encapsulates relevant DOM selectors.

Our example implementation will be extremely trivial, so the HTML is very simple. The most important thing is probably the H1 element with the ID of “heading” (which we will not give any inner text by default):

<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8">
<meta name=”viewport” content=”width=device-width”>
<title>MVC Experiment</title>
<script src="./mvc-script.js"></script>
</head>
<body>
<h1 id=”heading”></h1>
</body>
</html>

You could probably get away with just putting this script in-line, rather than saving as a separate file, but that’s up to you. It also might be easiest to just run in something like JSBin or CodePen.

Our view class will start out looking like this:

function View(controller){
this.controller = controller;
this.heading = document.getElementById(‘heading’);
}

As you can see, we select the relevant DOM node and store it. We won’t do anything else with it yet. We also pass a controller in as a dependency, which will be very important later on. The controller will be what listens for events on our relevant DOM nodes, but first we have to build one. Let’s take a detour and build our model, first.

The Model

For now, much like the rest of our code, our model will be very simple:

function Model(){
this.heading = "Hello";
}

It consists of a value for the heading, which will be what we use to set our h1 element’s innerText. It’s hard coded currently, but we can change it later to take a value in as part of the constructor.

The model will be injected into our controller so that the controller can manipulate the model as prompted by the view’s events.

So now that we have a model and a view, it is finally time to start building our controller.

The EventListener Interface

JavaScript, as a dynamically-typed interpreted language, does not have enforceable interfaces the way a statically-typed language like Java does, but that doesn’t prevent a programmer from establishing their own unenforceable contracts. You can have various classes that implement an agreed upon interface, you just can’t get the same compile-time assurances you would with the statically-typed languages (unless you use TypeScript, but that is not in the scope of this article).

That said, there are a few implicit interfaces scattered around DOM and Web API interactions. The most relevant of those to us today is the EventListener interface.

Generally speaking, when adding an event listener to a DOM node, most people will use a callback function, either anonymous or broken out into it’s own function. However, you also have the option of passing an object that implements the EventListener interface. The EventListener interface consists of one method:

void handleEvent(
in Event event
);

So any object that has a handleEvent method can be registered as a listener, and it will be passed the Eventobject as its single argument. When the event occurs, the handleEvent method will be invoked, and the specified procedures will take place.

Our Controller

Our Controller will implement the EventListener interface so that we can register it as a listener for DOM events in our view. So our initial controller will have three methods:

function Controller(model){
var self = this;
this.model = model;
//EVENTLISTENER INTERFACE
this.handleEvent = function(e){
e.stopPropagation();
switch(e.type){
case "click":
self.clickHandler(e.target);
break;
default:
console.log(e.target);
}
}
//GET MODEL HEADING
this.getModelHeading = function(){
return self.model.heading;
}
//CHANGE THE MODEL
this.clickHandler = function(target){
self.model.heading = 'World';
target.innerText = self.getModelHeading();
}
}

We have the handleEvent method which delegates to other methods based on a switch statement (though there is only one for now). If the event type is click then the model is changed, and the element (represented as the target parameter) is changed to reflect the model. We also have a method for retrieving the value of the heading property of the model at any given time.

Let’s go back and modify the View class to add the controller as a click event listener on our h1 element and have it populate it’s innerText with the initial state of the model:

function View(controller){
this.controller = controller;
this.heading = document.getElementById(‘heading’);
this.heading.innerText = controller.getModelHeading();
this.heading.addEventListener('click', controller);
}

Hooking Things Up

Now that we have our classes defined, we need to create and inject them in the correct order:

function main(){
var model = new Model();
var controller = new Controller(model);
var view = new View(controller);
}

The model is instantiated first, injected into the constructor for the controller, and then the controller is injected into the view. Our model does not know about the view, and our view does not know about the model, but the controller can interact with both since it is referenced within the view, and the model is referenced within the controller.

All we should have to do now is invoke main() and we should be able to click ‘Hello’ and change it to ‘World’.

Initial State
After Click Event

Sure, this could have been accomplished in one line of code, but the idea is that this pattern can be expanded. We can listen to inputs and other events, and update the model to store values or trigger other functionality within the controller. With a few changes to constructors, you can encapsulate and expand your needed functionality.

Or you could just use a framework.

Next Steps

We can expand on these building blocks a good deal to explore some relevant Gang of Four patterns. In a follow-up article, we’ll take a look at using the Observer pattern to implement a unidirectional data flow by implementing certain interfaces on the model and the view. We’ll also talk about the State pattern and ways that we can toggle between ‘Hello’ and ‘World’ by abstracting those values into State objects.

(Part II)

--

--