Thursday, March 2, 2017

Object-Oriented JavaScript in the UI [4]

The last piece of this OO JavaScript UI is the bit that handles the form for adding jobs to the JobManager.

This is not a Real World Example...

In a real-world application scenario, this would probably be a lot more complicated — The current popular approaches would probably have it posting data to a web-service end-point that would return a JSON result-set that would be (or include) the validated job-data, possibly with some additional data indicating the success or failure of the post-attempt. That job-data would then be passed to the JobManager's addJob method, and the various displays would update accordingly.

The POC-level approach here doesn't do any of that, though there are a couple of places where that sort of connection to a server-side process could be introduced. Me, I think I would drop it into JobManager.addJob, but that's a knee-jerk thought, and other places might well be better.

At any rate, here's the last piece of the puzzle...

The JobAdderForm class

JobAdderForm attaches to the job-entry form in the main UI:

Create a New Job


It is responsible for accepting data for new jobs, building a data-structure that can be submitted to JobManager.addJob, and submitting it. Its implementation looks like this:

function JobAdderForm( wrapper, form )
{
    /*
     * Provides the functionality for adding a job to the JobManager
     * 
     * wrapper ........... (element or string, required) The element 
     *                     (or the ID of the element) that wraps all 
     *                     of the UI.
     */
    // Processing that needs to happen before definition, if any
    if ( typeof wrapper == 'string' )
    { wrapper = document.getElementById( wrapper ); }
    if ( typeof form == 'string' )
    { form = document.getElementById( form ); }
    // Instance properties:
    //  + Private properties
    var _cancelControl = null;
    var _contactField = null;
    var _createControl = null;
    var _form = form;
    var _nameField = null;
    var _numberField = null;
    var _startControl = null;
    var _wrapper = wrapper;
    var _jobManager = new JobManager();
    // - Find the job-fields and -actions elements, and assign 
    //   them as appropriate
    allChildren = wrapper.getElementsByTagName( '*' );
    for( ci=0; ci<allChildren.length; ci++ )
    {
        child = allChildren[ ci ];
        // data-jobfield
        jobField = child.getAttribute( 'data-jobfield' );
        switch( jobField )
        {
            case 'contact':
                _contactField = child;
                break;
            case 'name':
                _nameField = child;
                break;
            case 'number':
                _numberField = child;
                break;
        }
        // data-jobaction
        jobAction = child.getAttribute( 'data-jobaction' );
        switch( jobAction )
        {
            case 'cancelJob':
                _cancelControl = child;
                break;
            case 'createJob':
                _createControl = child;
                break;
            case 'startJob':
                _startControl = child;
                break;
        }
    }
    // TODO: This is where any validation that the REQUIRED fields 
    //       and actions exist should be done.
    // Initialize as needed based on incoming arguments
    // Instance methods:
    //  + public methods
    this.addJobToManager = function()
    {
        /*
         * Collects the job-data from the form, generates a new 
         * data-object with all the job-data in it, and adds it 
         * to the JobManager.
         */
        newJob = {
            'contact':_contactField.value,
            'name':_nameField.value,
            'number':_numberField.value,
            };
        _jobManager.addJob( newJob );
        this.clearForm();
    }
    this.clearForm = function()
    {
        /*
         * Clears all of the fields in the job-adder form
         */
        if ( _contactField )
        { _contactField.value = ''; }
        if ( _nameField )
        { _nameField.value = ''; }
        if ( _numberField )
        { _numberField.value = ''; }
    }
    this.hide = function()
    {
        /*
         * Hides the form
         */
        _form.style.display = 'none';
    }
    this.show = function()
    {
        /*
         * Shows the form
         */
        _form.style.display = 'block';
    }
    // Event-like methods of the instance
    // Processing that needs to happen before returning the instance, if any
    // - Attach events to controls
    if ( _cancelControl )
    {
        _cancelControl.jobAdderForm = this;
        _cancelControl.addEventListener( 'click', function( event )
            {
                this.jobAdderForm.clearForm();
                this.jobAdderForm.hide();
            }
        );
    }
    if ( _createControl )
    {
        _createControl.jobAdderForm = this;
        _createControl.addEventListener( 'click', function( event )
            {
                this.jobAdderForm.addJobToManager();
                this.jobAdderForm.hide();
            }
        );
    }
    if ( _startControl )
    {
        _startControl.jobAdderForm = this;
        _startControl.addEventListener( 'click', function( event )
            {
                this.jobAdderForm.show();
            }
        );
        // If there is a start-control, it's responsible for showing 
        // the form, so hide the form...
        this.hide();
    }
    // Return the instance
    return this;
}

The interface:

Properties
_cancelControl [private]
The UI control (a button, link, or other element) that cancels a job-submission.
_contactField [private]
The text-field that will contain the contact property-value of the new job.
_createControl [private]
The UI control (a button, link, or other element) that submits a job-submission.
_form [private]
The outermost element of the form that contains all of the _*Field elements and the _*Control elements associated with the form. This probably won't include the _startControl (since it should reside outside the _form, lest it be hidden).
_jobManager [private]
The JobManager object that new job-submissions will be sent to.
_nameField [private]
The text-field that will contain the name property-value of the new job.
_numberField [private]
The text-field that will contain the number property-value of the new job.
_startControl [private]
The UI control (a button, link, or other element) that starts the job-submission process (by showing the form).
_wrapper [private]
The element that contains the entire UI for the application, including the _form and all of the _*Control elements.
Methods
addJobToManager()
Gathers the values from the _*Field elements, builds a Job-compatible data-structure from them, and sends that to JobManager.addJob.
clearForm()
Clears the values of all the _*Field elements.
hide()
Hides the _form element.
show()
Shows the _form element.

JobAdderForm has no public properties — the wrapping UI element, the form and all of the fields and controls are auto-assimilated using a process much like the one built out for JobListView early in the initialization process of the object's construction. While it wouldn't be too difficult, I think, to allow items to be specified as part of a parameters argument (as has been the pattern elsewhere), but my gut feeling was to auto-connect everything involved. That's simpler from an integration standpoint (and it was less code for me to write, to boot).

All of its method are pretty straightforward, too. The only potential caveat is that as things stand right now, there is no checking performed with regards to whether the various _*Field fields are found and associated. Again, since this is pretty much a POC-level effort on my part, I simply didn't want to take the time to implement that level of initialization-validation.

Putting it All Together

With all of the markup-template in place (with all of the necessary data-* attributes), and the full object-library included, the only thing remaining is to initialize the set of objects. That's a fairly small chunk of code:

// Create some test jobs for inclusion into the initial view
testJobs = [
    { 'number':'OOK00001', 'name':'Job Number One', 'contact':'Joe Smith' },
    { 'number':'WOK00001', 'name':'Job Number Two', 'contact':'John Doe' },
    { 'number':'OOK00002', 'name':'The Third Job', 'contact':'Richard Roe' },
    { 'number':'WOK00002', 'name':'Fourth Is This Job', 'contact':'Pat Jones' },
    { 'number':'OOK00003', 'name':'The Fifth Job', 'contact':'Richard Roe' },
    { 'number':'WOK00003', 'name':'Sixth Is This Job', 'contact':'Pat Jones' },
];

// Create the main job-manager instance:
jobManager = new JobManager( testJobs );

// Initialize the main job-list view, with all the initial sort-settings
currentJobListParameters = {
    'top':'currentJobList',
    'listElement':'managedJobsList',
    'sortField':'number',
    'sortOrder':'up',
};
mainListView = new JobListView( currentJobListParameters );

// Create the job-contact-list view, with all the initial sort-settings
jobContactListParameters = {
    'top':'jobContactList',
    'listElement':'managedContactlist',
};
jobContactList = new JobContactList( jobContactListParameters );

// Create the job-name-list view, with all the initial sort-settings
jobNameListParameters = {
    'top':'jobNameList',
    'listElement':'managedNameList',
};
jobNameList = new JobNameList( jobNameListParameters );

// Set up the job-counter(s)
mainJobCounter = new JobCounter( 'jobCounter' );

// Set up the job-adder form
jobAdderForm = new JobAdderForm( 'ui-example', 'newJobForm' );

// After all the initialization is done, and all the objects' inter-relationships 
// are established, start the job-update cycle across all of the displays:
jobManager.jobsChanged();

And here it is, live:

Current Jobs [job-count]
Job Number Job Title Job Contact
number name contact
Create a New Job


All Jobs
name
All Job Contacts
contact


Is This an MVC...?

Eh, maybe.

If it isn't a Model-View-Controller, it certainly shares some of that pattern's structure. Consider:

JobManager is Model-like
It directly manages the data of the application;
It also directly manages all of the logic (such as there is) of the application, and could be modified to manage any rules around the data, if any were needed;
The display-objects (JobDisplayList, JobContactList, JobNameList, and JobCounter) are definitely Views
That's what they do: Displaying data from (or derived from) the JobManager
Where it gets questionable, I think, is in the Controller part of the pattern. There's no one, single controller-object in this mix, though the JobAdderForm arguably comes closest. Any of the various sort-controls in the JobDisplayList are also controller-like, and at some level, so is the entire page that the UI resides in.

If you want to play with the code, it's available for download. It runs well enough on my local environment as a local HTML page and JavaScript file (that's in the current version of Google Chrome, your mileage may vary from browser to browser):

6.9kB

No comments:

Post a Comment