3
\$\begingroup\$

I've been trying to understand the original MVC implementation (the Xerox Parc's one). I'm sure it has flaws, but it's simple code to practice/learn the original MVC.

Working example

View (index.html):

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Person</title>
    </head>
    <body>
        <h1 id="person-name" contenteditable="true"></h1>
        <p>Birth Date: <span id="person-birth-date" contenteditable="true"></span></p>
        <p>Age: <span id="person-age"></span></p>
        <code id="json-model-representation"></code>
        <script src="model-person.js"></script>
        <script src="controller-person.js"></script>
        <script src="main.js"></script>
    </body>
</html>

Model (model-person.js):

var ModelPerson = (function () {
    'use strict';

    var ModelPerson = function (name, birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    };

    ModelPerson.prototype.getName = function () {
        return this.name;
    };

    ModelPerson.prototype.setName = function (name) {
        this.name = name;
    };

    ModelPerson.prototype.getBirthDate = function () {
        return this.birthDate;
    };

    ModelPerson.prototype.setBirthDate = function (birthDate) {
        this.birthDate = birthDate;
    };

    ModelPerson.prototype.getAge = function () {
        var dateDifference = new Date(Date.now() - this.birthDate.getTime());
        return Math.abs(dateDifference.getFullYear() - 1970);
    };

    return ModelPerson;
}());

Controller (controller-person.js):

var ControllerPerson = (function () {
    'use strict';
    var controller;

    var ControllerPerson = function (modelPerson) {
        controller = this;
        controller.modelPerson = modelPerson;
    };

    ControllerPerson.prototype.initialize = function () {
        document.getElementById('person-name').addEventListener('blur', function () {
            controller.modelPerson.setName(this.textContent);
            controller.updateView();
        });

        document.getElementById('person-birth-date').addEventListener('blur', function () {
            controller.modelPerson.setBirthDate(new Date(this.textContent));
            controller.updateView();
        });

        controller.updateView();
    };

    ControllerPerson.prototype.updateView = function () {
        document.getElementById('person-name').textContent = this.modelPerson.getName();
        document.getElementById('person-birth-date').textContent = this.modelPerson.getBirthDate();
        document.getElementById('person-age').textContent = this.modelPerson.getAge();
        document.getElementById('json-model-representation').textContent = JSON.stringify(controller.modelPerson);
    };

    return ControllerPerson;
}());

Instantiation (main.js):

(function () {
    'use strict';
    var modelPerson = new ModelPerson('John Doe', new Date('1987-09-11')),
        controllerPerson = new ControllerPerson(modelPerson);

    controllerPerson.initialize(); }());

My model is the mental object model. My controller handles events and update the Model. The view is "dumb" and just presents the content.

Do you think it's like the original MVC? What's wrong? What am I missing?

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

I am assuming you are used to getters/setters because you are experienced in another language, please don't do this. Performance matters, there is no good reason to download all those functions for no good reason, model-person.js should be

var ModelPerson = (function () {
    'use strict';

    var ModelPerson = function (name, birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    };

    ModelPerson.prototype.getAge = function () {
        var dateDifference = new Date(Date.now() - this.birthDate.getTime());
        return Math.abs(dateDifference.getFullYear() - 1970);
    };

    return ModelPerson;
}());

Also, updateView does not belong in the controller, it should be part of your view class which should contain all view related logic.

Furthermore, I think in the original pattern, the model actually updates the view, meaning that the controller should not be done the one doing controller.updateView();, that should be handled implicitly by controller.modelPerson.setName(this.textContent);

Personally, I like your approach better, I think the controller should decide when the UI gets updated.

Finally, there is the matter of the element id's like person-name, you use them more than once so you should use a constant. Now the question is, should this constant be defined in Model, View or Controller ? I personally define these id's in the View class and then the controller asks the View what the element is, so that it can attach the listener. I dont think the MVC pattern goes into this detail, so it's up to you.

Other than that, your code is very readable and easy to follow.

\$\endgroup\$
0
\$\begingroup\$

Look into PureMVC. In it, Views are termed "Mediators" which is a better idea conceptually.

The Mediator is the thing that takes an underlying UI, or small set of UI items, and gives it an external interface so a controller can come around and be unaware (as best as is possible) to how and what is being done on the screen.

In your case, a controller would like to say to the mediator something as simple as .updatePerson(person) and be on his way. There is a balancing act between the default behavior of a mediator and the power you would like to give to the controller.

Considerations that come into play are the anticipated longevity of the mediator (reuseablity) - which would dictate a 'dumber' mediator exposing many adjustments, and a customized mediator with built in behavior and therefore less reusability.

The principle is to make mediators as dumb as possible, but no dumber. The controllers are the smart ones of the system, and really ARE your 'app', with the model coming in second place.

Mediators responsibilities: To create/paint itself (manage the underlying UI control), to react to data being fed to it, and to emit UI events in a sensible manner that will invoke controllers who see the source of specialized event messages and act accordingly.

Controllers should be stateless (think of them as master functions), as such they are autonomous and atomic. Many MVC-ish systems bundle controllers or create a grand master controller. I think that utimately, this is bad. Every controller should be a 'class' and have its own file. The names of controllers (can, [more on that later]) correspond to event names, events from the mediators or the model.

Likewise, many MVCs use a 'grand View' trying to encapsulate the entire swaths of the UI in one manager, this too I think is bad. Most UI parts are complex enough to justify their own MEDIATORS, with the exception of perhaps simple buttons or a submenu item. In practice, I would use a mediator for every submenu, and have a mediator for every input box, but likely not for every text-display area.

Mediators do not have to correspond directly to UI controls like this, they can also address meta-concerns like overall control coloring, focus or other things that cross control boundaries. Take a layered approach. You could therefore use simple mediators that would display data, and then higher-level mediators that would color or highlight or decorate things depending on that data.

Controllers are the least reusable members of the system, but that doesn't mean they cannot be genericized enough to be reused. Which brings to mind a cheat that can be done to minimize having many mundane controllers...

Imagine a typical model change event. To be eligible for this type of technique the dataset for this particular model partition has to be small, so lets pick 'app settings' or subset thereof. Normally a model change event will invoke a controller whose job it is - is to figure out which mediators it needs to reflect the change in the UI. But if you use 'smart mediators' who also listen for events and understand how to implement the data packet, or parts of that which concern themselves, you can do without the controller on that level.

But you have to use a data packet in the message because we do not want the mediator to query the model as this would create a dependency. You might object and say the dependency is already there because the mediator has to understand the data packet (a representation of a model partition) -- true, but if you walk up to the model you have to know HOW TO ASK for your packet, which is a dependency. You don't know the path in the model to your particular partition, nor should you, so go away, go back to your life as a stupid mediator.

Data and the Model Lets say our model has People, Places and Things. When you get right down to it, on the core level, the model is just a CRUD/store, and your data should exist generically down there, be it XML or JSON -- even if the data is remote in a DB, you should be dealing with a layer that expresses everything in a generic format.

The next layer up, you have a CRUD for your data types, this is what I mean by model partitions. Here is where I am going with this -- imagine your 'Person' is in your UI, the user makes changes to person and emits an event.

First, what is the name of the event?? "PersonChange"? "UpdatePerson"? "UIPersonChange"?

Remember when I said the Mediators (aka Views) are supposed to be dumb as possible. This is done both for reuse, but also for simplification. The smarter a mediator is, the more it knows about the outside world, the more dependencies you are creating, and the tighter your coupling is getting. Its not death, but it is what we want to avoid.

So we try to not let a mediator know what it type of data 'has' -- in the hard coded sense, and by extension the mediator should not know what type of message to emit, until its creation. We try to parameterize these things as best as possible, keeping the mediator generic internally.

This means we should not be handing a mediator a 'PersonClass' but a clump of data that we (in the outside world) already know the mediator knows how to parse because of the parms we used to construct the mediator. (All of this is in my opinion of course, but see if you agree...)

If we let mediators know (in their guts in hardcode) about Persons, we are creating dependencies between the model and the mediators, we are going against the whole point of things. Foot, meet bullet.

Now, dont get me wrong, some of this type of thing is, to a degree, in a practical sense, unavoidable, but I want to use this to demonstrate a principle at work here...

Go back to the controllers. What makes them special? They are the dumping ground for dependencies. Read that again. Dependencies are part of life, but now at least they all reside in one place! Now we are on the flip side of the coin -- what are the concerns about controllers...

We want controllers to be as thin, lean and focused as possible. This is why from a controllers perspective, we want the mediators and model to be smarter so we dont have to do much work. Why do we want to be thin, are we just lazy? No, we want to be thin in order to make it legible what our core purpose is, and because we are really the most complex part of the system it is important to be uncluttered. If we have a weak mediator or a weak model partition then our code is going to get infected with fiddlings and each of those fiddlings is yet another dependency.

Bridges Whenever one component in an MVC has dealings with another we want that transaction to be as simple and straightforward as possible -- better known as minimizing your external interface. Everything you offer to the outside world is a dependency when it gets used, and dependencies are the enemy. They make a system brittle.

This is probably one of the top ten mistakes people make when making application code (library code is different though). You are down there in the code and you start offering things to the world "just in case". Hard to blame you since it FEELS like you are doing a "thorough job" and dammit, you deserve a pat on the back for covering all the bases. After all, you are on a roll now, and "knocking things out". Just look at all these options!

Before you know it the black box you created with all its knobs and dials, is getting hard to figure out - was it lever A then switch B?? Oh, nevermind. Then when you go to use it, once you DO figure it out (again), why you would feel downright rude if you didn't jack yourself into at least SOME of those bells and whistles you worked so hard on.

Pretty soon there are extension cords lying everywhere, and your budget on zip ties is going through the roof.

And you realize that you've created this beautiful woman, except that, every so often, she bites your head clean off, and there is really nothing you can do about it because --- well, its complicated.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.