Project Process and Documentation

In my previous post, I described a default architecture for a UI application. In this post, I will introduce the concept for a system that requires a UI application (as well as other software packages) and briefly describe a process for discovering system requirements (user stories, scenarios, and feature specifications). Once the system requirements are defined, and using system architecture guidelines like the one in the previous post, product requirements can be defined.

The first step in a project’s life-cycle is the establishment of a project charter. Given that this is a fictitious project, there isn’t a real project charter. However, I did create a wiki (warning: be sure to have an ad blocker running) to keep track of some overly simplistic artifacts to use as a place to keep my ideas straight as I walk through the process of developing this conceptual project. Included in that wiki, is the Circulade Project Charter.

The overview of the Circulade system is to create a platform for enabling people trying to ship cargo to connect with carriers trying to fill their cargo containers for a route. This connection will allow shippers to find better shipping rates by finding containers with available space that can be purchased at a discounted price compared to standard shipping rates. Carriers will be able to make more money by selling space that would otherwise be left empty on an already planned shipment.

Once a project charter has been approved by the stakeholders, the project initiation process begins. However, since I want to keep focused on the deliverables of a project, I won’t be describing that process. For more details on project management, see the following:

To use terms from the PRINCE2 methodology, this post will be focused on the procedures in the Managing Product Delivery process. The “work package” to be delivered is the System Requirements for the project. This is accomplished by applying the business analysis process. The initial delivery of the System Requirements will consist of the most prominent User Stories and their most prominent Scenarios along with a list of Features the project will be expected to deliver and the Quality of Service requirements that will constrain the delivery of those features.

Business Analysis Process

System requirements specify the features a system must have in order to assist users with completing processes. These processes are described as Scenarios which detail a specific set of steps that are used to accomplish a User Story’s goal. Scenarios and User Stories are referred to as Business Requirements.

Business Requirements

User Stories

It is beyond the scope of this post to list all of these requirements, especially since this is a fictional system. However, the wiki contains some examples of the user stories used to produce this project.

If this were a real business, a Business Analyst would work with subject matter experts to elicit and document User Stories. More in-depth descriptions of user stories can be found at the following links:

For each user story, the analyst further examines the processes and procedures required to accomplish the goal of the story. This examination results in Scenario descriptions.

Scenarios

A scenario describes the process an actor must step through in order to achieve a goal . It specifies what information is needed, what decisions must be made based on that information and what to do with the result of those decisions. Another way of thinking about scenarios is to consider them “user flows” or “use cases”.

For existing organizations with detailed business process and procedure documentation, this activity is much easier, as the scenarios are essentially the process descriptions or are slightly modified versions of them as projects are usually intended to provide a new or improved process for the organization.

Again, some simplistic examples can be found on the Circulade wiki.

For more detailed examinations of the scenario writing process, read the following articles:

System Requirements

Features

Based on the Project Charter, User Stories, and Scenarios, a business analyst can specify the features a system must provide in order to enable the users to more efficiently accomplish their goals. For each Feature, a set of Feature Requirements will be defined.

This post provides a good example of the difference between a Feature and a Feature Requirement.

Quality of Service

QoS requirements are non-functional requirements such as availability, maintainability, extensibility, etc. These are important to capture in order to meet the expectations of the users and the stakeholders. The FURPS+ system is a useful way of thinking about these types of requirements in order to elicit these needs from the stakeholders and subject matter experts.

For the Circulade project, the system requirements examples are on the wiki.


Solutions Architecture Process

With the first batch of system requirements defined, a solutions architect, along with a user experience architect, should analyze the business requirements in order to identify any subsystems, or natural boundaries, within the organization and use these bounded contexts to define deployable packages of software/hardware necessary to fulfill the business requirements of the subsystems. This analysis is best handled by using the principles described by Domain-Driven Design. For a deep dive into these principles, see the following resources:

Once a bounded context has been identified and the software/hardware requirements are being determined, if the context requires a user facing application (and almost all of them do), the architects design the wireframes of the UI screens and detail the information needed for display and manipulation.

The solutions architect uses the system requirements analysis along with the wireframes and information details to specify the product requirements as a set of interface definitions.

The next post will explore the product requirements for the Circulade system’s public context.

Observing Events in an MVC Application

Continuing from the last post, I’m going to expand on the Profile “application” that was started. For this stage, the requirements will change to now allow the user to edit the Name.

I’ll restate again something that I wasn’t clear about in the first post. These posts are an exploration of which problems frameworks like React/Redux, Angular, Ember, etc. solve by coding myself into those problems and then identifying how different frameworks attempt to solve these problems.

To recap, the application displays a Profile that consists of a name, a profile image and a list of hobbies. The last post finished with a “minimum viable product” that looked something like the screen capture below.

final-0.0.0

In this fictitious software company, the change control board has met and the highest priority feature has been assigned: enable the user to edit their profile name. Our finished product will look like:

final-0.0.1

Retroactively Adding Tests

Something that wasn’t done in the previous post was to create unit tests. Working for a mid-sized business such as Fictitious Company, we use the QUnit as our testing framework. We have several JavaScript applications and we’ve not felt enough pain from it to warrant moving to Jasmine or Mocha or any of the other many options. Using this framework requires the application code to be placed in a separate file from the hosting document. So the contents of the script tag from the previous post have been moved into an app.js file. A new file has been added to the project called tests.html which will serve as the host for the test harness.

app.js:

var DATA = {    
    name: 'John Smith',
    imgURL: 'http://lorempixel.com/100/100/',
    hobbyList: ['coding', 'writing', 'skiing']
};

var App = {
    main: (function () {
        window.addEventListener('load', function () {
            var profile = new App.View.Profile(DATA);
            profile.render(document.body);
        });
    })()
 };

App.View = (function () { //the View 'class' library component
    var _library = {
            Profile: function (model) {
                         var _private = {
                                 div: document.createElement('div'),
                                 heading: document.createElement('h3'),
                                 img: document.createElement('img'),
                                 hobbies: new App.View.Hobbies(model.hobbyList)
                             },
                             _public = {
                                 render: function (container) {
                                             _private.heading.appendChild(document.createTextNode(model.name));
                                             _private.img.setAttribute('src', model.imgURL);
                                             _private.hobbies.render(_private.div);
                                             container.appendChild(_private.div);
                                         }
                             };
                         _private.div.appendChild(_private.heading);
                         _private.div.appendChild(_private.img);
                         _private.div.appendChild(_private.hobbies.element);
                         return _public;
                     },
            Hobbies: function (model) {
                         var _private = {
                                 div: document.createElement('div'),
                                 heading: document.createElement('h5'),
                                 list: document.createElement('ul')
                             },
                             _public = {
                                 element: _private.div,
                                 render: function (container) {
                                             model.map(function (hobby) {
                                                 var li = document.createElement('li');
                                                 li.appendChild(document.createTextNode(hobby));
                                                 _private.list.appendChild(li);
                                             });
                                             _private.div.appendChild(_private.list);
                                             container.appendChild(_private.div);
                                         }
                             };
                         _private.heading.appendChild(document.createTextNode('My hobbies:'));
                         _private.div.appendChild(_private.heading);
                         return _public;
                     }
        };
    return _library;
})();

tests.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
        <title>Testing: Your First JavaScript Project</title>
        < script src="../app.js></ script >
        <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.20.0.css">
        < script src="http://code.jquery.com/qunit/qunit-1.20.0.js"></ script >
    </head>
    <body>
        window.addEventListener('load', function () {
            QUnit.test("Given , when ...", function (assert) {
            });
        };
    </body>
</html>

Looking at the code base from the last post, there is one object representing the Model component but it isn’t really anything more than a representation of a Data Transfer Object, two objects in the Views component, and there is no Controller (yet). Based on the previous “requirements”, the filled in tests might look like the following:

Testing the Hobbies View

In the simplistic example from the previous post, calling this a View object is a bit of hyperbole, but we’ll go with it anyway. The public interface of the Hobbies view also has two members, an element attribute and a render method. It is responsible for displaying the current state of the model’s hobbyList attribute. So to test, we need to verify that instantiating a View results in a DOM element that contains an h5 heading and an ul element with li elements for each of the hobbies specified in the model.

Starting at line 15 of the tests.html:

QUnit.test("Given an instance of the Hobbies view, when it is rendered...", function (assert) {
    var mockModel = {
            name: 'John Smith',
            imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring'],
            addEventListener: function (eventType, listener) {
            }
        },
        hobbiesView = new App.View.Hobbies(mockModel.hobbyList, document.createElement('div'));
    

    assert.equal(hobbiesView.element.children.length, 2, '...then it has 2 HTML elements.');
    assert.equal(hobbiesView.element.children[0].tagName, 'H5', "...then the first child is an H5.");
    assert.ok(hobbiesView.element.children[0].textContent === 'My hobbies:', "...the the H5 contains text: 'My hobbies:'");
    assert.equal(hobbiesView.element.children[1].tagName, 'UL', "...then the second child is an UL.");
    mockModel.hobbyList.forEach(function (hobby, index) {
        assert.ok(hobby === hobbiesView.element.children[1].children[index].textContent, "...then list items match.");
    });
});

To break that down, the testing style is in the given-when-then phrasing. This says which unit of the application and what behavior of that unit is being tested. In this case the rendering of the Hobbies view object. Which is fairly simple, it should contain 2 elements: a level 5 heading with the text “My hobbies:” and an unordered list of items that match the items in the model the view is presenting.

Adding the above to the code results in something like:

test-hobbies

Testing the Profile View

The public interface of the Profile view has a two members, the element attribute and the render method. It is responsible for displaying the current state of the model on the screen. So to test that we need to verify that instantiating a Profile results in a div element with a class of ‘profile-view’ that contains an h3 heading, an img element, and a div with a class of ‘hobbies-view’.

Immediately after the last test, add the following:

QUnit.test("Given an instance of the Profile view, when it is rendered...", function (assert) {
    var mockModel = {
            getName: function () {return 'John Smith';},
            imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring'],
            addEventListener: function (eventType, listener) {
            }
        },
        profileView = new App.View.Profile(mockModel, document.createElement('div'));


    assert.equal(profileView.element.children.length, 5, '...then it has 5 HTML elements.');
    assert.equal(profileView.element.children[0].tagName, 'H3', '...then the first child is an H3.');
    assert.equal(profileView.element.children[0].textContent, 'John Smith', '...then the H3 contains text: "John Smith".');
    assert.equal(profileView.element.children[1].tagName, 'INPUT', '...then the second child is an input element.');
    assert.equal(profileView.element.children[1].getAttribute('type'), 'text', '...then the input element is a text field.');
    assert.equal(profileView.element.children[1].classList.contains('hidden'), true, '...then the input element is not visible.');
    assert.equal(profileView.element.children[2].getAttribute('class'), 'scl-edit-save-cancel', '...then the third child is the SCL edit-save-cancel component.');
    assert.equal(profileView.element.children[3].tagName, 'IMG', "...then the fourth child is an image.");
    assert.equal(profileView.element.children[3].getAttribute('src'), 'http://lorempixel.com/100/100/', "...then the image url matches the model.");
    assert.equal(profileView.element.children[4].getAttribute('class'), 'hobbies-view', '...then the fifith child is a hobbies-view class.');
});

After modifying the app.js file to include setting the class attribute on the root element of each view, running the tests result in the something like:

test-profile

With all of the previous requirements covered and passing, it’s now time to write the tests that will drive the design for implementing the current set of requirements.

The use case:

Use Case: Edit a Profile Name

Primary Actor: Member (Registered User)

Scope: a fictitious system

Level: User goal

User Story:

As a customer I want to be able to update my profile name so that my identity in the system is accurate.

Preconditions:

  • The profile with editing enabled is presented to the member.

Triggers:

  • The member invokes an edit request on the profile name.

Postconditions:

  • The profile name is saved and an updated view is shown.

Basic flow:

  1. The system provides an editable area populated with the current value of the Profile Name.
  2. The member modifies the Profile Name value until satisfied.
  3. The member saves the edit.
  4. The system saves the new Profile Name, logs the edit event and finishes any necessary post processing.
  5. The system presents the updated view of the Profile Name to the member.

Extensions:

3a. Cancel the edit:

  1. The member invokes a cancel request on the edit profile name.
  2. The system discards any change the member has made, then goes to step 5.

After reviewing the use case that the Business Analysts have documented, it’s time to consult the Profile View section of the UI Specification Document that the User Experience Architect has defined:

7.2.1 Profile View

Description

The Profile View enables the user to review their profile to see how other users of the site will see their identity.

This element will inherit the core styling of the app.css file (see Appendix A).

Behavior

The behavior of this element is contained in its constituent elements.

Elements

7.2.1.1 Profile Name View

Description

The Profile Name View enables users to see and edit the current value of their profile name as it appears on the site.

Behavior

The behavior of the Profile Name View is contained in its elements.

Elements

7.2.1.1.1 Profile Name Label

Description

A text label that displays the current value of the user’s profile name. Positioned at the top left of the Profile Name View as a level 3 heading.

Behavior

N/A

Elements

N/A

7.2.1.1.2 Profile Name Input Field

Description

A text entry field that accepts character string input from the user.
Positioned at the top left of the Profile Name View.

Behavior

Default HTML input @type=text behavior. Defaults to being hidden from view.

Elements

N/A

7.2.1.1.3 Edit-Save-Cancel Component

Description

The Fictitious Company’s Standard Component Library’s Edit-Save-Control.
See Reference 2 for a complete description.

Behavior

See Reference 2 for default behaviors.

For this specification, clicking the Edit button will hide the Profile Name Label and reveal the Profile Name Input Field.

Clicking the Save button will hide the Profile Name Input Field, update the supporting data model with the value entered in the Profile Name Input Field, and reveal the Profile Name Label.

Clicking the Cancel button will hide the Profile Name Input Field and reveal the Profile Name Label.

Elements

See Reference 2.

7.2.1.2 Profile Picture View

Description

The Profile Picture View enables users to see the avatar image associated with their profile. The image will be 100 x 100 pixels.

Behavior

N/A

Elements

N/A

7.2.1.3 Profile Hobbies View

Description

The Profile Hobbies View displays a list of hobbies that the user provided when they joined the site.

Behavior

N/A

Elements

7.2.1.3.1 Profile Hobbies Header

Description

A text label displays the value: “My hobbies:” as a level 5 heading.

Behavior

N/A

Elements

N/A

7.2.1.3.2 Profile Hobbies List

Description

A bulleted list of each hobby.

Behavior

N/A

Elements

N/A

Time to create the feature branch and begin coding; or is it?

Looking back to the UI specification, there is a requirement to use the company’s Standard Component Library for the Edit button that is required on the Profile Name section of the Profile view.

A Standard Component Library (SCL) is a way to keep the “look and feel” of the user interfaces consistent across the various applications that the company has implemented. It helps the user base have a sense of familiarity with an app even if it’s the first time they’ve used it. It also helps the development team with producing new applications a little faster since they don’t have to build everything from scratch with each new application/project.

The Fictitious Company’s SCL has an Edit-Save-Cancel control that allows the user to indicate they would like to Edit a value on the screen, the Edit button is hidden and in its place the Save and Cancel buttons are displayed. This control is to be used to edit the Profile Name. The first step toward implementing will be to configure the Profile View to include the Edit-Save-Control component.

Packaging and utilizing a library from a package management tool is a subject for a different post, but the SCL would normally be brought into the application with a dependency manager. For this post, the library is just going to be added via a script tag.

< script >../scl.js</ script>

The View has a dependency on this library, so that gets passed into it:

App.View = (function (scl) { //the View 'class' library component
    var _library = {
            Profile: function (model) {
                         var _private = {
                                 div: document.createElement('div'),
                                 heading: document.createElement('h3'),
                                 img: document.createElement('img'),
                                 hobbies: new App.View.Hobbies(model.hobbyList)
                             },
                             _public = {
                                 element: _private.div,
                                 render: function (container) {
                                             _private.heading.appendChild(document.createTextNode(model.name));
                                             _private.img.setAttribute('src', model.imgURL);
                                             _private.hobbies.render(_private.div);
                                             container.appendChild(_private.div);
                                         }
                             };
                         _private.div.setAttribute('class', 'profile-view');
                         _private.div.appendChild(_private.heading);
                         _private.div.appendChild(_private.img);
                         _private.div.appendChild(_private.hobbies.element);
                         return _public;
                     },
            Hobbies: function (model) {
                         var _private = {
                                 div: document.createElement('div'),
                                 heading: document.createElement('h5'),
                                 list: document.createElement('ul')
                             },
                             _public = {
                                 element: _private.div,
                                 render: function (container) {
                                             model.map(function (hobby) {
                                                 var li = document.createElement('li');
                                                 li.appendChild(document.createTextNode(hobby));
                                                 _private.list.appendChild(li);
                                             });
                                             container.appendChild(_private.div);
                                         }
                             };
                         _private.div.setAttribute('class', "hobbies-view");
                         _private.heading.appendChild(document.createTextNode('My hobbies:'));
                         _private.div.appendChild(_private.heading);
                         _private.div.appendChild(_private.list);
                         return _public;
                     }
        };
    return _library;
})(FC.SCL);

The library is declared in the Fictitious Company namespace FC as SCL. The View library will refer to it as scl.

Now we can start implementing the view as specified in section 7.2.1.1.2 of the UI Spec.

The Profile View needs a new element: a profile name input field. First thing to do is write a test for it:

QUnit.test("Given an instance of the Profile view, when it is rendered...", function (assert) {
    var dto = {
            name: 'John Smith',
            imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring']
        },
        v = new App.View.Profile(dto);

    v.render(document.createElement('div'));

    assert.equal(v.element.children.length, 4, '...then it has 3 HTML elements.');
    assert.equal(v.element.children[0].tagName, 'H3', '...then the first child is an H3.');
    assert.equal(v.element.children[0].textContent, 'John Smith', '...then the H3 contains text: "John Smith".');
    assert.equal(v.element.children[1].tagName, 'INPUT', '...then the second child is an input field.');
    assert.equal(v.element.children[1].getAttribute('type'), 'text', '...then the input element is a text field.');
    assert.equal(v.element.children[1].getAttribute('style'), 'display:none', '...then the input element is not visible.');
    assert.equal(v.element.children[2].tagName, 'IMG', "...then the second child is an image.");
    assert.equal(v.element.children[2].getAttribute('src'), 'http://lorempixel.com/100/100/', "...then the image url matches the model.");
    assert.equal(v.element.children[3].getAttribute('class'), 'hobbies-view', '...then the third child is a hobbies-view class.');
});

Then run the test and see the page fill up with red:

test-input-fail

Now we update the Profile to make it pass:

Profile: function (model) {
             var _private = {
                     div: document.createElement('div'),
                     heading: document.createElement('h3'),
                     input: document.createElement('input'),
                     img: document.createElement('img'),
                     hobbies: new App.View.Hobbies(model.hobbyList)
                 },
                 _public = {
                     element: _private.div,
                     render: function (container) {
                                 _private.heading.appendChild(document.createTextNode(model.name));
                                 _private.input.value = model.name;
                                 _private.img.setAttribute('src', model.imgURL);
                                 _private.hobbies.render(_private.div);
                                 container.appendChild(_private.div);
                             }
                 };
             _private.div.setAttribute('class', 'profile-view');
             
             _private.div.appendChild(_private.heading);
             
             _private.input.setAttribute('type', 'text');
             _private.input.setAttribute('style', 'display:none');
             _private.div.appendChild(_private.input);
             
             _private.div.appendChild(_private.img);
             
             _private.div.appendChild(_private.hobbies.element);
             
             return _public;
         },

And now are test runner is nice and green again!

test-input-pass

The next specification was to add the Edit-Save-Cancel component to the Profile. Again, we go back to the test script:

QUnit.test("Given an instance of the Profile view, when it is rendered...", function (assert) {
    var dto = {
            name: 'John Smith',
            imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring']
        },
        v = new App.View.Profile(dto);

    v.render(document.createElement('div'));

    assert.equal(v.element.children.length, 5, '...then it has 5 HTML elements.');
    assert.equal(v.element.children[0].tagName, 'H3', '...then the first child is an H3.');
    assert.equal(v.element.children[0].textContent, 'John Smith', '...then the H3 contains text: "John Smith".');
    assert.equal(v.element.children[1].tagName, 'INPUT', '...then the second child is an input element.');
    assert.equal(v.element.children[1].getAttribute('type'), 'text', '...then the input element is a text field.');
    assert.equal(v.element.children[1].getAttribute('style'), 'display:none', '...then the input element is not visible.');
    assert.equal(v.element.children[2].getAttribute('class'), 'scl-edit-save-cancel', '...then the third child is the SCL edit-save-cancel component.');
    assert.equal(v.element.children[3].tagName, 'IMG', "...then the fourth child is an image.");
    assert.equal(v.element.children[3].getAttribute('src'), 'http://lorempixel.com/100/100/', "...then the image url matches the model.");
    assert.equal(v.element.children[4].getAttribute('class'), 'hobbies-view', '...then the fifith child is a hobbies-view class.');
});

Which results in the predictable test run:

test-esc-fail

Update the code to make the tests pass:

Profile: function (model) {
             var _private = {
                     div: document.createElement('div'),
                     heading: document.createElement('h3'),
                     input: document.createElement('input'),
                     img: document.createElement('img'),
                     hobbies: new App.View.Hobbies(model.hobbyList)
                 },
                 _public = {
                     element: _private.div,
                     render: function (container) {
                                 _private.heading.appendChild(document.createTextNode(model.name));
                                 _private.input.value = model.name;
                                 _private.img.setAttribute('src', model.imgURL);
                                 _private.hobbies.render(_private.div);
                                 container.appendChild(_private.div);
                             }
                 };
             _private.div.setAttribute('class', 'profile-view');
             
             _private.div.appendChild(_private.heading);
             
             _private.input.setAttribute('type', 'text');
             _private.input.setAttribute('style', 'display:none');
             _private.div.appendChild(_private.input);

             _private.input.editComponent = new scl.EditSaveCancel(_private.div);
             
             _private.div.appendChild(_private.img);
             
             _private.div.appendChild(_private.hobbies.element);
             
             return _public;
         },

Which passes:

test-esc-pass

And the application looks almost as specified:

preview-with-edit

The layout will be addressed at the end. The next requirement in the specification 7.2.1.1.3: Behavior is to swap the H3 with an input when the Edit button is clicked. Time for a new test:

QUnit.test("Given a rendered Profile view, when its Edit button is clicked...", function (assert) {
    var dto = {
            name: 'John Smith',
            imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring']
        },
        v = new App.View.Profile(dto),
        editButton, inputField, nameLabel;

    v.render(document.createElement('div'));
    
    editButton = v.element.getElementsByClassName('scl-edit-button')[0];
    editButton.click();
    
    inputField = v.element.children[1];
    nameLabel = v.element.children[0];

    assert.equal(inputField.classList.contains('visible'), true, '...then the profile name input field is visible.');
    assert.equal(nameLabel.classList.contains('hidden'), true, '...then the profile name label is not visible.');
});

Run the test.

edit-click-fail

A user initiated event is going to occur – the click event on the Edit button. The View is responsible for responding to user actions. So we need to “wire up” the Profile view to listen for a click event on the Edit button. The SCL component we’re using provides an addEventListener method that allows us to define a function that can be executed when the component dispatches an editClicked event.

Profile: function (model) {
             var _private = {
                     div: document.createElement('div'),
                     heading: document.createElement('h3'),
                     input: document.createElement('input'),
                     img: document.createElement('img'),
                     hobbies: new App.View.Hobbies(model.hobbyList),
                     editButtonClickHandler: function () {
                        _private.heading.classList.remove('visible');
                        _private.heading.classList.add('hidden');
                        _private.input.classList.remove('hidden');
                        _private.input.classList.add('visible');
                     }
                 },
                 _public = {
                     element: _private.div,
                     render: function (container) {
                                 _private.heading.appendChild(document.createTextNode(model.name));
                                 _private.input.value = model.name;
                                 _private.img.setAttribute('src', model.imgURL);
                                 _private.hobbies.render(_private.div);
                                 container.appendChild(_private.div);
                             }
                 };
             _private.div.setAttribute('class', 'profile-view');
             
             _private.div.appendChild(_private.heading);
             
             _private.input.setAttribute('type', 'text');
             _private.input.setAttribute('class', 'hidden');
             _private.div.appendChild(_private.input);

             _private.input.editComponent = new scl.EditSaveCancel(_private.div);
             _private.input.editComponent.addEventListener('editClicked', _private.editButtonClickHandler);
             
             _private.div.appendChild(_private.img);
             
             _private.div.appendChild(_private.hobbies.element);
             
             return _public;
         },

Run the test.

edit-click-pass

The next requirement states:

Clicking the Save button will hide the Profile Name Input Field, update the supporting data model with the value entered in the Profile Name Input Field, and reveal the Profile Name Label.

There’s a requirement to modify the data model. We’re only testing the Profile View right now, so to verify that we’re accomplishing that aspect of the requirement the test will focus on verifying that the input field is hidden and the label is visible. After that test is passing, we’ll focus on the Model updates.

QUnit.test("Given a rendered Profile view, when its Save button is clicked...", function (assert) {
    var dto = {
            name: 'John Smith',
            imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring']
        },
        v = new App.View.Profile(dto),
        saveButton, inputField, nameLabel;

    v.render(document.createElement('div'));
    v.element.getElementsByClassName('scl-edit-button')[0].click();

    saveButton = v.element.getElementsByClassName('scl-save-button')[0];
    saveButton.click();

    inputField = v.element.children[1];
    nameLabel = v.element.children[0];

    assert.equal(inputField.classList.contains('hidden'), true, '...then the profile name input field is not visible.');
    assert.equal(nameLabel.classList.contains('visible'), true, '...then the profile name label is visible.');
});

(This post is getting really, really long, so I’m going to skip the screen shots of the test runner for the failing tests from here on.)

The application is supposed to be following the MVC design pattern as mentioned in the previous post. To this point, there has been no Controller. Now that the Model is going to be updated, an object needs to be responsible for that logic. So we’ll build a Profile Controller that will observe events in the View and update the Model accordingly:

Profile: function (model) {
             var _private = {
                     div: document.createElement('div'),
                     heading: document.createElement('h3'),
                     input: document.createElement('input'),
                     img: document.createElement('img'),
                     hobbies: new App.View.Hobbies(model.hobbyList),
                     editButtonClickHandler: function () {
                        _private.heading.classList.remove('visible');
                        _private.heading.classList.add('hidden');
                        _private.input.classList.remove('hidden');
                        _private.input.classList.add('visible');
                     },
                     saveButtonClickHandler: function () {
                        _private.heading.classList.add('visible');
                        _private.heading.classList.remove('hidden');
                        _private.input.classList.add('hidden');
                        _private.input.classList.remove('visible');
                     }
                 },
                 _public = {
                     element: _private.div,
                     render: function (container) {
                                 _private.heading.appendChild(document.createTextNode(model.name));
                                 _private.input.value = model.name;
                                 _private.img.setAttribute('src', model.imgURL);
                                 _private.hobbies.render(_private.div);
                                 container.appendChild(_private.div);
                             }
                 };
             _private.div.setAttribute('class', 'profile-view');
             
             _private.div.appendChild(_private.heading);
             
             _private.input.setAttribute('type', 'text');
             _private.input.setAttribute('class', 'hidden');
             _private.div.appendChild(_private.input);

             _private.input.editComponent = new scl.EditSaveCancel(_private.div);
             _private.input.editComponent.addEventListener('editClicked', _private.editButtonClickHandler);

             _private.input.editComponent.addEventListener('saveClicked', _private.saveButtonClickHandler);
             
             _private.div.appendChild(_private.img);
             
             _private.div.appendChild(_private.hobbies.element);
             
             return _public;
         },

Run the test.

save-click-pass

Lather, rinse, repeat for the cancel button.

Now the last piece of the puzzle is that the View is supposed to display the current value of the Model’s name attribute when displaying the H3 element. But if you execute this code, and change the value in the input field, the value is not updated in the header. This is because the View is not “listening” to the Model and the Model is not publishing any events for the View to listen for anyway.

Update the tests to ensure that we verify that the View renders the correctly when the Profile Name is modified. Then modify the Model so that it can dispatch events and modify the View so that it listens for the nameChanged event coming from the Model.

QUnit.test("Given a rendered Profile view, when the value in the Profile Name Input Field is modified and Saved...", function (assert) {
    var dto = {
        name: 'John Smith',
        imgURL: 'http://lorempixel.com/100/100/',
            hobbyList: ['coding', 'testing', 'refactoring']
        },
        v = new App.View.Profile(dto),
        saveButton, inputField, nameLabel;

    v.render(document.createElement('div'));
    v.element.getElementsByClassName('scl-edit-button')[0].click();
    saveButton = v.element.getElementsByClassName('scl-save-button')[0];

    inputField = v.element.children[1];
    nameLabel = v.element.children[0];

    inputField.value = 'New Value';
    saveButton.click();

    assert.equal(nameLabel.textContent, 'New Value', '...then the profile name label displays the modified value.');
    assert.equal(dto.name, 'New Value', '...then the name attribute of the model is set to the modified value.');
});

We are trying to adhere to the MVC pattern. However, the model that we have is just a DTO. The View can’t subscribe to any events on this model, so we first have to redefine the Model to provide the addEventListener method. We also don’t have a mechanism for mutating the state of the Model – which means that we have no mechanism for dispatching those events.

App.Model = (function () {
    var _library = {
        Profile: function (name, imgSrc, hobbies) {
            var _private = {
                    name: name,
                    imgURL: imgSrc,
                    hobbyList: hobbies,
                    handlers: {
                        profileNameChanged: []
                    },
                    dispatchEvent: function(evt) {
                        _private.handlers[evt.type].forEach(function (handler) {
                            handler(evt);
                        });
                    }
                },
                _public = {
                    getName: function () {
                        return _private.name;
                    },
                    setName: function (value) {
                        if (value !== _private.name) {
                            var profileNameChanged = new CustomEvent('profileNameChanged', {detail:{oldValue:_private.name, newValue:value}});
                            _private.name = value;
                            _private.dispatchEvent(profileNameChanged);
                        }
                    },
                    addEventListener: function (eventType, listener) {
                        _private.handlers[eventType].push(listener);
                    },
                    imgURL: _private.imgURL,
                    hobbyList: _private.hobbyList
                };
            return _public;
        }
    };

    return _library;
})();

After redefining what the Model.Profile’s interface is, we need to go refactor the previous test cases so that the mock Model.Profile object provides the same interface. See the finished code at the end of the post or in my GitLab repo.

With event listeners, testing was a little awkward. The mock objects needed to either receive and event listener or dispatch events to one. Here’s an example of verifying that an object successfully subscribed an event listener:

QUnit.test("Given a Profile view, when the view is constructed...", function (assert) {
    var mockModel = {
            getName: function () { return 'foo'; },
            addEventListener: function (eventType, listener) {
                assert.equal(eventType, 'profileNameChanged', "...then the Profile subscribes to the Model's 'profileNameChanged' event.");
            },
            hobbyList: []
        },
        profile = new App.View.Profile(mockModel, document.createElement('div'));
});

And here is an example of testing that an object dispatches events:

QUnit.test("Given a Profile model, when a subscriber exists and the name is changed...", function (assert) {
    var profile = new App.Model.Profile(),
        subscriber = function (evt) {
            assert.equal(evt.detail.newValue, 'My New Name', '...then the subscriber is notified of the event.');
        };

    profile.addEventListener('profileNameChanged', subscriber);
    profile.setName('My New Name');
});

Now the last piece is to modify the CSS so that the elements are laid out according to the UI Specification:

.visible {
    display: inline-block;
}

.hidden {
    display: none;
}

.profile-view h3  {
    float: left;
    margin: 3px;
}

.profile-view .scl-edit-save-cancel {
    float: left;
}

.profile-view input {
    float: left;
}

.profile-view img {
    display: block;
    clear: both;
}

Which results in:

final-with-css final-with-css-editmode

And there it is, an implementation of the MVC pattern without the need for a framework – yet. We are making use of a library, which was modified to keep it consistent with the concepts presented so far. The render method has been refactored out of the Views and replaced with an event based architecture. This allows the Model objects to store the logic for determining if a state change has occurred, and when it does, it dispatches events to the View object(s) that handle those events by updating only those elements that display the mutating attributes of the Model.

One thing to notice is the public addEventListener method and the private handlers and dispatch members. This code was duplicated in all of the objects. It would have been better to define an object that manages that work and have the M-V-C objects inherit that behavior. The next post will introduce a Publish-Subscribe pattern from the Fictitious Company’s standard libraries as well as a little more clean up in the View library. Data access has also not yet been explored and will introduce even more complexity to the system.

Full code is available at: https://gitlab.com/pct/YourFirstJavaScriptProject-0.0.1/tree/master/

 

My Reaction to React

EDIT: Some people were rightly complaining about the code snippets being screen captures rather than <code> blocks. I’ve updated accordingly.

Also, many have rightfully commented that the code in this post is not realistic and if used in a production environment would be nightmarish for maintainers and likely even for scalability. I didn’t clearly articulate what my intentions were, so I’ll try to do better in this update: I am a proponent of using libraries and frameworks. They allow a developer to “stand on the shoulders of giants” so to speak. This post and the ones that I’m going to publish that build on this are an exploration of which problems frameworks like React/Redux, Angular, Ember, etc. solve by coding myself into those problems and then identifying how different frameworks attempt to solve these problems.

The interwebs are buzzing about React.js and redux and flux and angular and ember and…

It’s rather tiring to try and learn all of these.

I thought that I’d make a parallel post to this post: Building Your First React.js App.

If you want to use React, fine, I am not trying to convince anyone not to. I just want to show that components in a JavaScript application are quite simple to do and do not require any framework at all to do so.

The concept is to follow the MVC pattern. Where the Models are being observed by the Views which are observed by the Controllers. A user interacts with a View, that event is handled by the Controller which updates the Model accordingly. The View observes the Model and handles those events by rendering itself accordingly. In the aforementioned post, there is no controller, nor events. So I’m going to keep my example as simplistic as the one provided. In a future post, I plan on expanding this idea example to utilize Models and Controllers.

Below is my parallel universe version of Per Harald Borgen’s post:

This article will take you through every step of building the simplest JavaScript app possible. Even simpler than a to-do list.

Why so simple? Because I’ve found that when I’m trying to learn a new technology, even the simplest features can add unnecessary complexity.

If you’re overwhelmed with the wide variety of frameworks and libraries being published, this tutorial is for you.

There are no prerequisites other than basic JavaScript skills. Though if you’d like to read some basic theory before getting started with the coding, you can read the article below and then return.

MDN: JavaScript Guide

Looking at the final project

To claim you’ll be building an app is actually an exaggeration. It’s only a profile page, as you can see below. (The image is taken randomly from http://lorempixel.com/)

Step 1: Splitting the page into components

An application is built around components; everything you see on the screen is a part of a View component. Before we start coding, it’s a good idea to create a sketch of the views, as we’ve done above.

The main component — which wrap all other components — is marked in red. Since we’re targeting this application to run in a browser, we’ll call this one document.body or Body.

Once we’ve figured out that Body is our main viewport, we’ll need to ask ourselves: which view is a direct child of the Body?

I’d argue that the name and the profile image can be grouped into one View, which we’ll call Profile (green rectangle), and the Hobbies section can be another View (blue rectangle).

The structure of our components can also be visualized like this:

- Body
    - Profile
    - Hobbies

We could split the Views further; like ProfileImage and HobbyItem, though we’ll stop here for the sake of simplicity.

Step 2: Hello World

Before you begin coding, you’ll need to download the source file. It’s available at this GitLab repo. Simply copy or clone it and open the index.html file in the browser. (The full code is available in the finished_project.html file.)

I’ve setup the file properly, so you’ll see no links to any unnecessary libraries in the <head/> section of the file. Your code will start at line 9.

For a proper JavaScript project, you wouldn’t use this non-minified setup. But I don’t want this tutorial to be about anything else than coding, so we’ll keep it simple.

Let’s write our first View:

var App = {};

App.View = (function () { //the View component - a 'class library' of views
    var _library = {
        Hello: (function () {
            var _private = {
                div: document.createElement('div')
                },
                _public = {
                    render: function (container) {
                        _private.div.appendChild(document.createTextNode('Hello World'));
                        container.appendChild(_private.div);
                    }
                };
            return _public;
        })()
    };
    return _library;
})();

As you can see on the first line, we created an object namespace called App. We then created a View object as a component to house all of the views that are going to be in our application. Finally, we declare the Hello object using the Definitive Module Pattern and the Immediately Invoked Function Expression.

Each View object can have as many members you want, though the most important one is the render method. In the render method, you’ll pass in a reference to the DOM element that will contain the output of this view and then append the contents of the view to that container. In our case, we simply want a div tag with the text “Hello World”.

Then follow up with this inside the App object:

var App = {
    main: (function () {
        window.addEventListener('load', function () {
            App.View.Hello.render(document.body);
        });
    })()
};

This is how we specify where on the page we want the Hello view to be rendered. This is done by adding a load event handler to the window that calls Hello.render, passing in the document.body as the container parameter. I throw this into a function called main, but that is just a convention that I use due to familiarity with C-based programming languages.

The syntax shouldn’t look too weird, we’re just declaring an object that utilizes the DOM API and adhering to a couple of tried and tested software development patterns.

Load the page in a browser and you’ll see ‘Hello World’ printed out on the screen.

Step 3: More components

Let’s add some more views. Looking back at our application overview, we see that the App component has got two views called Profile and Hobbies.

Let’s write out these two views. We’ll begin with Profile:

Profile (function () {
    var _private = {
        div: document.createElement('div'),
        heading: document.createElement('h3'),
        img: document.createElement('img')
    },
    _public = {
        element: _private.div,
        render: function (container) {
            _private.heading.appendChild(document.createTextNode('My name'));
            _private.img.setAttribute('src', 'example.jpg');
            container.appendChild(_private.div);
        }
    };
    _private.div.appendChild(_private.heading);
    _private.div.appendChild(_private.img);
    return _public;
})(),

There is actually nothing new here. Just a bit more content in the render function than it was in the Hello view.

Let’s write the Hobbies component:

Hobbies (function () {
    var _private = {
        div: document.createElement('div'),
        heading: document.createElement('h5')
    },
    _public = {
        render: function (container) {
            container.appendChild(_private.div);
        }
    };
    _private.heading.appendChild(document.createTextNode('My hobbies:'));
    _private.div.appendChild(_private.heading);
    return _public;
})()

If you refresh the page again though, you won’t see any of these components.

This is because nothing has told these views to render to the screen. We need to update our main function to render the Profile and Hobbies view instead of the Hello view.

This is what we’ll need to do:

var App = {
    main: (function () {
        window.addEventListener('load', function () {
            App.View.Profile.render(document.body);
            App.View.Hobbies.render(document.body);
        });
    })()
};

If you refresh the page again you’ll see that all the content appears on the page. (Though the image wont appear, as we’ve only added a dummy link to it).

Step 4: Get the data

Now that we have the basic structure setup, we’re ready to add the correct data to our project.

A good practice when implementing the MVC pattern is something called a one directional data flow, meaning that the data is passed down from parent to child components.

Above all the components, paste in the following code:

var DATA = {    
    name: 'John Smith',
    imgURL: 'http://lorempixel.com/200/200/',
    hobbyList: ['coding', 'writing', 'skiing']
}

You can imagine this data being fetched from an API of something.

The next thing you’ll need to do is add this data to the App components as its model.

Data in the MVC pattern is maintained in the Model objects and mutated by the Controller objects. In this simplistic example, I’m going to overlook that aspect and just deal with the single object. In a future post, I’ll explore a more complex data model for the application.

Below, you’ll see how you pass the data into the views, by simply changing the constructor method of the views to accept a model, we’ll initialize our view objects and then render them.

var App = {
    main: (function () {
        window.addEventListener('load', function () {
            var profile = new App.View.Profile(DATA),
                hobbies = new App.View.Hobbies(DATA);
            profile.render(document.body);
            hobbies.render(profile.element);
        });
    })()
};

There’s no new syntax, no new Domain Specific Language, just plain old JavaScript.

Now we’re able to access this data from within the View objects through model.[member-name]. We’ll also restructure the views a bit to make the Hobbies a child of the Profile view. This makes it so the application just initializes an instance of the Profile view and data is where it needs to be.

We use the profileImage and name in the Profile view while only the hobbyList array is passed into the Hobbies view. This is because the Hobbies component doesn’t need the rest of the data; it’s simply going to display a list of hobbies.

Let’s look at how we’ll need to rewrite the Profile view in order to use the data we’ve passed down to it:

Profile: function (model) {
             var _private = {
                     div: document.createElement('div'),
                     heading: document.createElement('h3'),
                     img: document.createElement('img'),
                     hobbies: new App.View.Hobbies(model.hobbyList)
                 },
                 _public = {
                     render: function (container) {
                         _private.heading.appendChild(document.createTextNode(model.name));
                         _private.img.setAttribute('src', model.imgURL);
                         _private.hobbies.render(_private.div);
                         container.appendChild(_private.div);
                     }
                 };
             _private.div.appendChild(_private.heading);
             _private.div.appendChild(_private.img);
             _private.div.appendChild(_private.hobbies.element);
             return _public;
         },

I removed the IIFE pattern and added the Hobbies view to the Profile‘s members. Then access the members of the passed in model in order to present the resource’s current state to the user.

In the Hobbies component we’ll need to use a technique for looping through the of hobbies.

Hobbies: function (model) {
             var _private = {
                     div: document.createElement('div'),
                     heading: document.createElement('h5'),
                     list: document.createElement('ul')
                 },
                 _public = {
                     element: _private.div,
                     render: function (container) {
                                 model.map(function (hobby) {
                                     var li = document.createElement('li');
                                     li.appendChild(document.createTextNode(hobby));
                                     _private.list.appendChild(li);
                                 });
                                 _private.div.appendChild(_private.list);
                                 container.appendChild(_private.div);
                             }
                 };
             _private.heading.appendChild(document.createTextNode('My hobbies:'));
             _private.div.appendChild(_private.heading);
             return _public;
         }

As you can see, we’re looping through the hobbies array stored in model. We’re using the array prototype method map, which creates a new array based on whatever we return within the callback function.

Notice that I didn’t create a key attribute on the list items. There is no reason to at this point in the development of this application as nothing outside of the Hobbies view is concerned with this list of data as it is presented on the screen.

This is the full code:

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Your First JavaScript Project</title>
</head>
type="text/javascript">
/*Add your application code here*/
var DATA = {    
    name: 'John Smith',
    imgURL: 'http://lorempixel.com/100/100/',
    hobbyList: ['coding', 'writing', 'skiing']
};

var App = {
    main: (function () {
        window.addEventListener('load', function () {
            var profile = new App.View.Profile(DATA);
            profile.render(document.body);
        });
    })()
};

App.View = (function () { //the View 'class' library component
    var _library = {
            Profile: function (model) {
                         var _private = {
                                 div: document.createElement('div'),
                                 heading: document.createElement('h3'),
                                 img: document.createElement('img'),
                                 hobbies: new App.View.Hobbies(model.hobbyList)
                             },
                             _public = {
                                 render: function (container) {
                                             _private.heading.appendChild(document.createTextNode(model.name));
                                             _private.img.setAttribute('src', model.imgURL);
                                             _private.hobbies.render(_private.div);
                                             container.appendChild(_private.div);
                                         }
                             };
                         _private.div.appendChild(_private.heading);
                         _private.div.appendChild(_private.img);
                         _private.div.appendChild(_private.hobbies.element);
                         return _public;
                     },
            Hobbies: function (model) {
                         var _private = {
                                 div: document.createElement('div'),
                                 heading: document.createElement('h5'),
                                 list: document.createElement('ul')
                             },
                             _public = {
                                 element: _private.div,
                                 render: function (container) {
                                             model.map(function (hobby) {
                                                 var li = document.createElement('li');
                                                 li.appendChild(document.createTextNode(hobby));
                                                 _private.list.appendChild(li);
                                             });
                                             _private.div.appendChild(_private.list);
                                             container.appendChild(_private.div);
                                         }
                             };
                         _private.heading.appendChild(document.createTextNode('My hobbies:'));
                         _private.div.appendChild(_private.heading);
                         return _public;
                     }
        };
    return _library;
})();

</html>

 

An analogy of an IT department as a pro football team

The captain of the cheerleading squad is now the Head Coach…
The sports journalist who wrote an especially damning article about the team last season is now the Defensive Coordinator…
The water boy is now the Offensive Coordinator…
The veteran Offensive Coordinator has been demoted to Offensive Line Coach, but he may not last because the cheerleaders think he’s ugly…
The Offensive Line is stacked with rookies and Arena League subs…
There is one good receiver and they’ve put me in as quarterback.
It’s going to be an interesting season.