jQuery, Dynamic HTML and Dealing With eForms of Indeterminate Size.
Saturday, January 02 2010
The requirements, scope, and solution:
The requirement for this project is one you’ve probably seen many times. We need a web form that is capable of handling many detail lines for a single header which defines the one-to-many relationship between the header and its details. Simple examples of this type of form would be online order forms, invoices, statements and the like. The form must handle any number of details, whether one or hundreds, as efficiently as possible. What we will not be addressing in this Blog is the retrieval or the posting of the data. That part of the project would be highly dependent upon the resources in the environment for which it is being developed and will be outside of what we are building here.
By definition,the requirement to handle an indeterminate number of details as efficiently as possible, means that we will not be using static hidden rows of HTML which are “turned on” by setting their visibility properties. Rather, we will be:
- Building a simple form with a header block, totals section, and a detail row template.
- Binding an event to a button that allows us to:
- Select the HTML of our Row Template
- Inject the HTML into a details section of the form dynamically
- Bind events to the elements of the dynamic rows so that they self validate, format and total.
- Then we’ll wrap it all up into a small web application.
That’s a lot of ground to cover, so let’s get started.
To keep this example as simple and generic as possible, I will be creating a very simple HTML file. The layout (at the right) is a header section, line detail section, and total section. I have chosen table elements for this form because the data represented is inherently tabular. The actual HTML will show some additional layout which is hidden. The line detail section is made up of two parts, one of which is displayed when the section is expanded, and one of which is displayed when the section is collapsed (default). The intention is to allow the user to determine whether they prefer to enter details in the expanded or table sections of the form. There are a few graphics added for buttons to make the application a little more appealing. In the editor, the rendering of the HTML would appear like shown at the right.
Not shown at the right is the hidden tbody element ‘tRow_0’ that is the template for the row.
jQuery Selectors, CSS, Element IDs and Classes – Planning Tight Code.
The most important aspect of jQuery to master is the ‘selector’. It’ allows us to group DOM elements and apply appearances and/or behaviors to the group and apply or change them ‘on-the-fly’. This post is not intended to be a basic introduction to jQuery, but a quick overview is that jQuery which is usually called by it’s alias ‘$’ can use pattern matching, including regular expressions, to build a collection of DOM elements to which we will apply a property or behavior. A selector would look like this: $("#details input[id$=" + elemMask + "][value!=''].currencyInp").each(function().
This selector of DOM elements represents a huge savings in efforts of the developer. Additionally, by understanding and applying this capability you exercise significant control over the client with a minimum of added code – that equates to a better user experience without the performance penalty of ‘code bloat’.
Included References and Files.
As is the standard, I’ve included the references to the supporting CSS and Script files in the header of the HTML
<link href="css/EFormBase.css" rel="stylesheet" type="text/css" />
<link href="css/SampleForm.css" rel="stylesheet" type="text/css" />
<script type="text/vbscript" language="vbscript" src="scripts/SampleForm.vbs"></script>
To summarize the files and their purpose, the “EFormBase” files are predominantly used to apply the validation engine to the onSubmit event of the Form and provide some default CSS appearances.
The 2 “jQuery-1-3-2” files are the jQuery libraries and interface to my development environment.
The “SampleForm.vbs” and “SampleForm.js” files are the scripts that give life to the form.
Element IDs, CSS classes and a strategy to minimize code.
A quick look at our form, and we can see that as the number of detail rows grow, the need to optimize the code of the template becomes more critical both to the performance of the form and to the amount of coding that must be created and supported. Because we have chosen to template the row HTML and inject it into the form at run-time on the client as needed, we avoid the bandwidth overhead of downloading all of the HTML that would be caused by many rows. In addition, careful considerations was given to the element IDs and CSS classes to minimize the code needed to handle the appearance and behavior of the elements. For the table section of the form, which exists entirely in the row template, the column name is added to the row number in a was that is easily and reliably separable. In this case my cell IDs are in the form of ‘ColumnName_Row”. The illustration shows how the form has been divided into zones based on behavior and appearance.
The section circled in red and filled with light red are all currency input elements. They share both appearance and behavior, and as we will see later this behavior includes the ability to self total and format. The light blue filled text inputs in this region do not share the CSS class.
The section circled in dark blue is somewhat different. Each column has its own appearance, behavior and validation, and so, while the CSS classes are shared across rows, each column has its own class.
The total column and row each have their own class, but are read-only because they are handled entirely in code.
The other elements of the form also rely heavily on CSS but our focus during this post is the table details section of the form.
An Overview of SampleForm.js – Gluing Things Together.
We won’t be going through a line-by-line discussion of the code, but I hope to provide enough of an overview to make it easy to read the code and figure things out. The first thing to notice is the ‘loadme()’ function which is bound to the onload event of the form. The particular line of interest is the $('#btnLineAdd').bind('click', function(event).
This nugget of jQuery code binds the addRow function to the green button element with an ID of ‘btnLineAdd’. The addRow function, when triggered by this element, determines the next available row number, selects the HTML from the tbody ‘tRow_0’ element (our row template), replaces the row number ‘0’ with the row number being inserted, injects the code into the DOM form at the end of the detail section (jQuery append) then calls the function ‘bindThisRow(curRow)’ to bind the events for the elements of the row. A study of the bind statements and the selectors will explain which elements are selected and which events are bound to them. After the events are bound, we apply a little of the magic from the EFormBase.js file. A deep dive into that code is beyond this post. However, a quick explanation is that a validation engine is created and bound to the elements based on the column name portion of the element ID (e.g. Entity_1 would be validated based on the ‘Entity’ portion of the ID.) This represent a significant savings in code and effort. Properties for validation are configured through prototypes and instantiated upon creation. By the time we have returned from the addRow function, we have injected a dynamically created HTML row, attached events, and added a validation engine. All this was done with an surprisingly small code footprint.
A Summary and Encouragement.
So that wraps up this post and hopefully I’ve demonstrated enough of the jQuery goodness to encourage you to take the time to learn the tool and become comfortable with it. Just the time saved in coding of this form would be enough to justify the time spent in the learning curve. The future time savings when modifying or troubleshooting this form would be ‘frosting’. As always, I welcome comments or suggestions. Be kind. This wasn’t intended to be a ‘textbook example’ or the best possible. It’s just a sample I wanted to share to help make the learning and building of a little reusable code easier. I hope this helps … let me know.