A working example of MVC

To illustrate how an implementation of MVC might look in JavaScript, let's build a very simple program. It will be a basic mutable number application that renders a simple UI where the user can see the current number and choose to update it via either incrementing or decrementing its value.

First, we can implement the logic and containment of our data using a Model:

class MutableNumberModel {
constructor(value) {
this.value = value;
}
increment() {
this.value++;
this.onChangeCallback();
}
decrement() {
this.value--;
this.onChangeCallback();
}
registerChangeCallback(onChangeCallback) {
this.onChangeCallback = onChangeCallback;
}
}

In addition to storing the value itself, this class also accepts and relies upon a callback function called onChangeCallback. This callback function will be provided by the Controller and will be called whenever the value changes. This is necessary so that we can kick off a re-render of the View if the Model changes.

Next, we need to build the Controller, which will act as a very simple bridge (or glue) between view and model. It registers the necessary callbacks to know when either the user requests a change via view or the underlying data of the model changes:

class MutableNumberController {

constructor(model, view) {

this.model = model;
this.view = view;

this.model.registerChangeCallback(
() => this.view.renderUpdate()
);
this.view.registerIncrementCallback(
() => this.model.increment()
);
this.view.registerDecrementCallback(
() => this.model.decrement()
);
}

}

Our view is responsible for retrieving data from model and rendering it to the user. To do this, it creates a DOM hierarchy in which the data will sit. It also listens for and escalates user events to controller when either the increment or decrement button is clicked:

class MutableNumberView {

constructor(model, controller) {
this.model = model;
this.controller = controller;
}

registerIncrementCallback(onIncrementCallback) {
this.onIncrementCallback = onIncrementCallback;
}

registerDecrementCallback(onDecrementCallback) {
this.onDecrementCallback = onDecrementCallback;
}

renderUpdate() {
this.numberSpan.textContent = this.model.value;
}

renderInitial() {

this.container = document.createElement('div');
this.numberSpan = document.createElement('span');
this.incrementButton = document.createElement('button');
this.decrementButton = document.createElement('button');

this.incrementButton.textContent = '+';
this.decrementButton.textContent = '-';

this.incrementButton.onclick =
() => this.onIncrementCallback();
this.decrementButton.onclick =
() => this.onDecrementCallback();

this.container.appendChild(this.numberSpan);
this.container.appendChild(this.incrementButton);
this.container.appendChild(this.decrementButton);

this.renderUpdate();

return this.container;

}

}
This is quite a lengthy View as we're having to create its DOM representation manually. Many modern frameworks (React, Angular, Svelte, and so on) allow you to declaratively express your hierarchy using either plain HTML or a hybrid syntax such as JSX (a syntax extension to JavaScript itself that permits XML-like tags within JavaScript code).

This View has two rendering methods: renderInitial will carry out the initial render, which sets up the DOM elements, and then the renderUpdate method is responsible for updating the number whenever it changes.

Tying this all together, our simple program would be initialized like so:

const model = new MutableNumberModel(5);
const view = new MutableNumberView(model);
const controller = new MutableNumberController(model, view);

document.body.appendChild(view.renderInitial());

view is given access to model so that it can retrieve the data to render. controller is given to both model and view so that it can glue them together by setting up the appropriate callbacks.

In the case of a user clicking the + (increment) button, the following process would kick off:

  1. The DOM click event from incrementButton is received by the View
  2. The View fires its onIncrementCallback(), listened to by the Controller
  3. The Controller instructs the Model to increment()
  4. The Model calls its mutation callback, that is, onChangeCallback, listened to by the Controller
  5. The Controller instructs the View to re-render

You may be wondering why we bother with the separation between the Controller and the Model. Why can't the View just communicate with the Model directly and vice versa? Well, it can! But if we did that, we'd be polluting both our View and our Model with more logic and hence more complexity. We could equally just place everything in the View and have no Model, but you can imagine how unwieldy that would get. Fundamentally, the degree and quantity of separation will vary with every project you pursue. At its core, MVC teaches us about the general idea of how to separate the problem domain from its presentation. How we wield this separation is up to us.

Since 1978, when MVC was first coined, many adaptations of it have surfaced, but its central theme of separation between Model and View has persisted through the decades. Consider the architectural design of a React application. It includes Components, which contain the logic for rendering state, and typically will include several domain-specific reducers, which take actions (for example, user has clicked something!) and derive state from those actions.

This architecture looks surprisingly similar to traditional MVC:

MVC, as a general guiding pattern, has impacted the design of countless frameworks and code bases throughout the last few decades, and it will continue to do so. Not every adaptation, reproduction, or MVC will abide by the original description posed in 1978 but, usually, these adaptations will stay true to the centrally important theme of separating a Model from its View and of having a View be a reflection (or even, a derivation) of a Model.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset