1. What is it?
MVC is a pattern/framework decouple application into 3 parts: modal, view, and controller.
- Modal: the single source of truth of your app, responsible for data input and data output;
- View: the UI part displayed to users, responsible for display;
- Controller: bind views to modal organically in a desired way, responsible for handling Modal(automatically resulting in View update)
[Note]: MVC is based on Observer Pattern
2. Why use it?
As app grow, the business logic between data and view become complicated. There could be a good chance that several component use same piece of data. It would be painful to call vanilla JS to update every component one by one.
It would be better if there is a way that we can update all those components in a batch automatically when that data piece changes. Here come in the MVC pattern.
View
is html code snippet actually on frontend;Modal
defines all single source of true for their viewsController
will- binds all
Views
to their correspondingModal
, so that Views will update automatically when the Modal changes. - via callback passed in, it updates
Modal
, leading toViews
get updated
- binds all
3. How to use it?
The general way:
- Declare
modal
class prototype, and its APIs, which will be called bycontroller
- Specify to-be-bound
modal
forview
components - pass intention callback to
controller
to specify how to updatemodal
4. Evolve to MVC pattern (eg: Timer)
We will implement a very simple widget to show how to evolve an app to MVC pattern. The widget is to display whatever we get from API or from callback, and show it inside a div
.
No Pattern
1
2
3
4
5
6
7// View Part:
<div id="div1"></div>
// Logic Part:
const data = "This is the data"
const div = document.getElementById('div1');
div.innerHTML = data;
Using
Observer Pattern
Now we don’t want to update view by ourselves. Instead, we hope data update will trigger view change automatically.
Observer Pattern
can be used here to let the view observe the modal, so whenever modal changes, view will get updated automatically.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33function Model(value) {
this._value = typeof value === 'undefined' ? '' : value;
this._listeners = [];
}
Model.prototype.set = function (value) {
var self = this;
self._value = value;
// model value change will notify regisered callbacks
// According to JS running principle, we apply callback asynchronously: WON'T Freeze UI
// or use requestAnimationFrame, rather than setTimout
setTimeout(function () {
self._listeners.forEach(function (listener) {
listener.call(self, value);
});
});
};
Model.prototype.watch = function (listener) {
// register callback
this._listeners.push(listener);
};
// View:
<div id="div1"></div>
// Logic:
(function () {
var model = new Model();
var div1 = document.getElementById('div1');
model.watch(function (value) {
div1.innerHTML = value;
});
model.set('hello, this is a div');
})();
Bind
View
andModal
There is a drawback in the above approach: we need to make modal to
watch
each related views manually. But the truth is that all those work are duplicated, and conveys the same functionality: bind views to modal, or let modal watch related views.We can encapsulate this feature onto modal actually, as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Model.prototype.bind = function (node) {
// put `watch` and `common callback` inside bind
this.watch(function (value) {
node.innerHTML = value;
});
};
// View:
<div id="div1"></div>
<div id="div2"></div>
// Logic:
(function () {
var model = new Model();
model.bind(document.getElementById('div1'));
model.bind(document.getElementById('div2'));
model.set('this is a div');
})();
MVC Pattern
Now, it is better. We bind view and modal😄~ And we can bind as many as views to a modal as we want😱.
But still, we need to manually bind view to modal. Is there a way our code can handle it for us, as long as we tell the code which modal the view want to bind.
Yeah, You might already get it! We can specify modal piece for the view by using HTML attribute, like
<div bind='modal1' />
. Thus, the code can know the desired modal current view wants to bind.We will create an util object that can
- firstly, automatically bind views to their modals,
- secondly, receive a callback to update modal in programmer’s desired way
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function Controller(callback) {
var models = {};
// find all views with `bind` attr
var views = document.querySelectorAll('[bind]');
// convert views into array
views = Array.prototype.slice.call(views, 0);
views.forEach(function (view) {
var modelName = view.getAttribute('bind');
// get the modal for this view, or create new one for it
models[modelName] = models[modelName] || new Model();
// bind view and its modal
models[modelName].bind(view);
});
// update modals using specified callback
callback.call(this, models);
}
// View:
<div id="div1" bind="model1"></div>
<div id="div2" bind="model1"></div>
// Controller:
new Controller(function (models) {
var model1 = models.model1;
model1.set('this is a div');
});The whole MVC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30function Model(value) {
this._value = typeof value === 'undefined' ? '' : value;
this._listeners = [];
}
Model.prototype.set = function (value) {
var self = this;
self._value = value;
setTimeout(function () {
self._listeners.forEach(function (listener) {
listener.call(self, value);
});
});
};
Model.prototype.watch = function (listener) {
this._listeners.push(listener);
};
Model.prototype.bind = function (node) {
this.watch(function (value) {
node.innerHTML = value;
});
};
function Controller(callback) {
var models = {};
var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0);
views.forEach(function (view) {
var modelName = view.getAttribute('bind');
(models[modelName] = models[modelName] || new Model()).bind(view);
});
callback.call(this, models);
}
5. Test our MVC framework
We will use our MVC framework to implement a Timer as follows
1 | // View: |
6. Thoughts
For almost all frameworks like Redux
, Flux
. It use similiar ways to handle View and Modal. Here we use Observer Pattern
to implement MVC
.
Based on my understanding, Redux
and Flux
use Publisher-Subscriber Pattern
will handles events better than Observer Pattern
.