Tuesday, February 21, 2017

Object-Oriented JavaScript in the UI [1]

JavaScript is an object-oriented language, though most of the development with it that I've seen doesn't take advantage of that. Granted, the syntax and language-structure of JavaScript didn't lend itself well to real OO coding-styles until ECMAScript 6 came out with an official(?) class definition syntax, along with extends capabilities. There's still no interface or abstract class declaration, though, as far as I've been able to tell. The underlying ability to create classes, extend them, make them (kinda) abstract, and to generate interface-like behavior has been available in the language for a long time, though, even if the methods to achieve those ends were odd, cryptic, or could be implemented in several different ways.

I look forward to the new class and extends being standard in all the active browsers on the market. It's looking good for all the modern browsers, at least on the desktop, but support on mobile and older desktop browsers is not as good. With around 20% of the market share still showing Internet Explorer 8, 9, 10 and 11 (as of the end of 2016 at netmarketshare.com), I'm not going to hold out high hopes for adoption of the new JavaScript capabilities for a while yet...

But, since it is an object-oriented language, that means that there is at least some possibility for writing JavaScript code that follows good, solid OO-design principles — Things like design patterns, for example...

A Basic Form/UI and Requirements

So, indulge me in a little conscientous role-playing. Say there's a need for a UI and functionality to manage a list of jobs in a web-application, and a somewhat functional POC UI has to be built out.

In this scenario, a user is on some sort of dashboard page, and can see a list of current jobs that looks something like this:

Current Jobs [# of jobs displayed here]
Job Number Job Title Job Contact
Job-Number #1 Job-Title #1 Job-Contact #1
Job-Number #2 Job-Title #2 Job-Contact #2
...
Job-Number #10 Job-Title #10 Job-Contact #10
The bold headings will (eventually) be clickable to toggle a sort of the listed items by that column's values, and the X buttons will allow the user to remove a job from the list.

They can also enter data for a new job using a form that looks something like this:

Create a New Job


Once the fields are populated and the user creates the job, it should be inserted into the job-list, paying attention to the current sort-order so as not to just be added to the end of the list. The Current Jobs header's job-count should also update when a job is added or removed.

There is also some talk about showing a distinct list of job-names and a separate list of job-contacts elsewhere on the dashboard (it's still on the same page, however):

All Jobs
Job-Title #1
Job-Title #2
...
Job-Title #3
All Job Contacts
Job-Contact #1
Job-Contact #2
...
Job-Contact #3

Finally, for reasons unspecified (something, something, UI design, something):

  • The Create a New Job form is hidden until the Add A New Job button is clicked;
  • When the Create a New Job form is made visible, the Add A New Job button is hidden or disabled;
  • When the OK or Cancel buttons are clicked, the Add A New Job button is shown or enabled; and
  • When the OK or Cancel buttons are clicked, the Create a New Job form is hidden and all of its fields are cleared;

All of these processes would normally involve some sort of server connection, probably an AJAX/XHR call, that returned some data-set to be added to the active jobs, but the basic mechanism can be established without that server-side connection, as can the removal mechanism, so for the POC, all that needs to happen is being able to demonstrate the job-addition, -removal and -counting functionality that the UI needs.

There are a lot of moving parts here, so I'm going to spend this post mostly just delving into the design of the UI components. Getting into the weeds will follow starting with my next post. There may be several — as I'm writing this, I don't really know how much code this is going to entail. I'm also going not going to worry about member-scopes for this write-up. That is, I'm going to stick to public members across the board until or unless I can think of a good reason to try and make things private, and I can come up with a way to implement it — I know it can be done with one object-construction pattern, but I'm not sure if it can with the pattern that I'm currently planning to use...

What That's Going To Look Like

After some analysis, and deciding on the architecture (yes, it really is architecture, even if it is simple and in JavaScript), I can accomplish the POC with seven classes:

  • Job to represent individual job-items;
  • JobManager to keep track of job-data, handle addition and removal of jobs from the data-set, and act as an originator of updates in an Observer relationship with various display-entities;
  • JobListView to manage the display of the big job list portion of the UI;
  • JobContactList to manage the display of job contact-names;
  • JobNameList to manage the display of job-names;
  • JobCounter to manage the display of job-counters; and
  • JobAdderForm to provide the functionality for adding a job to the JobManager.
In my opinion, the POC doesn't really need to have classes built out (or members added to any of the items listed above) to handle the eye-candy of showing and hiding things when various buttons are clicked. That can be handled with some simple event-setup, so I'm going to set those aside for now.

I'm going to write the code for these classes in plain-old-JavaScript, with an eye towards making sure it'll run on older browsers. Converting the code into something that uses jQuery (or any of the other multitude of JavaScript frameworks should not be difficult for those so inclined. For now, though, I'm going to keep it as free-standing as I can, so noting but standard, local JS implementation.

I will, however, try to make the code reasonably smart — allowing it to do at least some automatic attachment to elements in the page markup.

The Job class

A Job is a dumb data object — It's really just a data-structure, with no methods or logic behind it other than a constructor. That constructor, at least for a POC-level effort, doesn't need to do anything more than set instance properties from their corresponding values in the parameters, so I won't go into the class' interface in any detail. If this were to be implemented in a system to be deployed, I'd want to add some type- and value-checking to those property-setting processes, and some error-handling as well, in all likelihood, but that's more effort than I'm going to put in here and now. Here's the class-diagram for a Job:

While I was working through the design and implementation, I determined that I had at least an occasional need to be able to dump a string-value for a Job from time to time, so I added a toString method to it after the initial class-diagram had been drawn up. It's nothing too fancy, returning an XML-like tag along the lines of
<Job number="[number]" name="[name]" contact="[contact]" />
but it did prove invaluable when I found that I needed to be able to see what was happening while debugging some methods in other classes.

The JobManager class

First up, I'm going to define the class that will be used to keep track of all of the jobs in the UI. That class, JobManager is mostly a data-repository, but one that participates in an Observer pattern relationship with any number of display-oriented objects. As a class-diagram, it looks like this (at first blush — as development goes on, I may want/need to add or remove members):

The interface of the class, in some detail:
Properties
_displays [private]
The collection of display-related objects that will be notified when the instance's jobsChanged method is executed.
_jobs [private]
The collection of Job objects that the instance is keeping track of.
Methods
.ctor( jobs )
The object constructor/initializer. Returns a new instance if one does not already exist, otherwise returns the previously-created instance.
addDisplay( display )
Adds a display-related object to the instance's displays collection
addJob( job, updateNow )
Adds a Job object to the instance's jobs collection.
compareJobs( job1, job2 )
Compares two Job (or equivalent) objects, returning true if they have the same field-values, false otherwise
getSortedJobs( sortKey, sortOrder )
Gets a copy of the main job-list (because each different display may need a different sort-order!), sorted by the sortField specified, in the order specified by sortOrder.
jobsChanged()
Calls the onJobsChanged methods of all the display-related objects in the instance's displays.
removeDisplay( display )
Removes the specified display-related object from the instance's displays collection, which means that it will no longer get updates from jobsChanged.
removeJob( job, updateNow )
Removes the specified Job object from the instance's jobs collection
The JobManager should probably be a Singleton: The underlying rationale for this is that there should (probably) be only one job-data set that's being tracked and manipulated. The .ctor (constructor) method is where I'm planning to manage that need.

The JobListView class

JobListView is a display-control mechanism that is part of an Observer realtionship with the JobManager on the page. When a job is added to or removed from the JobManager, it will update the displayed list of Jobs, keeping the specified sort-order for items in the display. It also provides a process for attaching sort-control functionality to arbitrary elements on the page so that, when clicked, the display will be re-sorted according to the sort-criteria relevant to the sort-control.

The interface of the class, in some detail:
Properties
_jobManager [private]
The JobManager where the display's jobs can be found and manipulated.
_listElement [private]
The display-element that all of the list-members will be shown in.
_listItemTemplate [static]
The HTML template to be used in generating a list-member's display in the list.
sortControls
An array that keeps track of all the sort-controls for the display.
sortField
The name of the Job property that is used in the sort-process for the display.
sortOrder
The sort-direction that is used in the sort-process for the display.
_top [private]
The display-element that wraps all of the UI elements for the display; the display's root tag, as it were.
Methods
.ctor( parameters )
The object constructor/initializer. The parameters argument is an object that is expected to contain the following fields:
listElement
The element that will contain the list-items, which will be cleared of all child nodes and re-populated as needed/arbitrarily.
sortField
The name of the Job data-field to be used for sorting list-members in the display.
sortOrder
The sort-order to be used for sorting list-members in the display.
top
The element that wraps all of the UI managed by this object.
clearDisplay()
Removes all child element from the list-view.
displayItems()
Populates the listElement with zero-to-many job-displays using the jobManager's jobs and the current sort-field and -order.
getJobDisplay( job )
Generates and returns a sequence of nodes based on the class-level HTML template, populated with the specified job's data, ready to be appended to the list-display element.
registerSortControl( element, field )
Registers a sort-control element with the instance.
setSortCriteria( field, order )
Sets the display's sort criteria (which field, and in what order), and triggers a display-update with those criteria.
setListItemTemplate( markup ) [static]
Sets the class-level display-list member HTML template to use to display item data
Event-like Methods
onJobsChanged()
Clears the current list-display of all members, and re-creates it with the current data. Called by JobManager.jobsChanged as part of their Observer-pattern relationship.

The JobContactList class

JobContactList is a display-control object (like JobListView) that displays a sorted list of all job-contacts across all the jobs in the JobManager.

Properties
_jobManager [private]
The JobManager where the display's jobs can be found.
_listElement [private]
The display-element that all of the list-members will be shown in.
_listItemTemplate [static]
The HTML template to be used in generating a list-member's display in the list.
sortField
Description
sortOrder
Description
Methods
.ctor( parameters )
The object constructor/initializer.
listElement
The element that will contain the list-items, which will be cleared of all child nodes and re-populated as needed/arbitrarily.
top
The element that wraps all of the UI managed by this object.
clearDisplay()
Removes all child element from the list-view.
displayItems()
Populates the listElement with zero-to-many job-displays using the jobManager's jobs and the current sort-field and -order.
getJobDisplay( job )
Generates and returns a sequence of nodes based on the class-level HTML template, populated with the specified job's data, ready to be appended to the list-display element.
Event-like Methods
onJobsChanged()
Clears the current list-display of all members, and re-creates it with the current data. Called by JobManager.jobsChanged as part of their Observer-pattern relationship.

The JobNameList class

Apart from the fact that it focuses on displaying the name values of all the Jobs in the JobManager, JobNameList is pretty much identical to JobContactList.

Properties
_jobManager [private]
The JobManager where the display's jobs can be found.
_listElement [private]
The display-element that all of the list-members will be shown in.
_listItemTemplate [static]
The HTML template to be used in generating a list-member's display in the list.
sortField
Description
sortOrder
Description
Methods
.ctor( parameters )
The object constructor/initializer.
listElement
The element that will contain the list-items, which will be cleared of all child nodes and re-populated as needed/arbitrarily.
top
The element that wraps all of the UI managed by this object.
clearDisplay()
Removes all child element from the list-view.
displayItems()
Populates the listElement with zero-to-many job-displays using the jobManager's jobs and the current sort-field and -order.
getJobDisplay( job )
Generates and returns a sequence of nodes based on the class-level HTML template, populated with the specified job's data, ready to be appended to the list-display element.
Event-like Methods
onJobsChanged()
Clears the current list-display of all members, and re-creates it with the current data. Called by JobManager.jobsChanged as part of their Observer-pattern relationship.

The JobCounter class

Displays a count of current Jobs in the JobManager that updates as jobs are added or removed.

Properties
_element [private]
The document element whose content will be updated as jobs are added to or removed from the JobManager.
_jobManager [private]
The JobManager where the display's jobs can be found.
Methods
.ctor( element )
The object constructor/initializer.
Event-like Methods
onJobsChanged()
Updates the _element witht he current count of jobs. Called by JobManager.jobsChanged as part of their Observer-pattern relationship.

The JobAdderForm class

JobAdderForm defines a class that keeps track of all of the form-fields and action-elements (buttons) involved in the UI, connects those elements with other elements and objects (like the JobManager) to allow the form in the UI to add jobs to the JobManager. It also proves some of the eye-candy noted in the initial POC requirements (showing and hiding the job-adding form). I wasn't going to bother with that initially, but it was simple to implement, and it just made sense to wire the show/hide functionality up to the UI elements rather than assuming that it would be handled later. It could always be changed...

Properties
_cancelControl [private]
The element (a button) that the user clicks to cancel a job-creation.
_contactField [private]
The form-field that is the source for a new Job's contact value.
_createControl [private]
The element (a button) that the user clicks to submit a job-creation.
_form [private]
The outermost element that contains all of the _*Field elements of the form. Also the chunk of the UI that will be shown or hidden by various actions (buttons).
_jobManager [private]
The JobManager that new jobs will be added to when they are created.
_nameField [private]
The form-field that is the source for a new Job's name value.
_numberField [private]
The form-field that is the source for a new Job's number value.
_startControl [private]
The element (a button) that the user clicks sow the job-creation form.
_wrapper [private]
The element that wraps all of the UI — including the _form and all _*Control and _*Field elements
Methods
.ctor( wrapper, form )
The object constructor/initializer.
addJobToManager()
Builds the data-structure for a new job, then hands it off to the JobManager to be added.
clearForm()
Clears the values of all of the _*Field elements
hide()[scope]
Hides the _form element
show()[scope]
Shows the _form element

Next time, I'll start digging in to the actual implementation of these classes, and show how I'm integrating them into a process that allows the page mark-up to control what the UI looks like while keeping that separate from the logic embedded in these classes.

No comments:

Post a Comment