Back to the index of articles and examples
This sample has several features, more than I intended initially, but here they go.
It has a source table on the left containing items and their prices. They can be dragged and dropped into the table on the right which is an invoice. Part numbers are unique. If the same item is dropped in the destination, the quantity is increased in one. Initially all invoiced items have a quantity of 1. This cell is editable with the DataTable's in-line cell editor. Any quantity can be entered, even fractional and the totals immediately refreshed.
There is also a discount cell which can also be edited and the given discount immediately applied.
First obvious thing is the drag and drop capability. It has DataTables both as source and as destination.
The rows drop wherever you place them in the destination table. The code is mostly a copy of a
sample by Dav Glass.
An array recordRefs
is built for the destination DataTable that points to each record,
indexed by the unique part number. When an item is about to be dropped, this array is checked and if there
is already a record for that item in the invoice, the qty
field is incremented by one.
The updated record will be highlighted for a little while so the user sees what has happened.
The subtotal, discount and total rows are placed in the TFOOT
element of the HTML table and are not under the control
of the DataTable, it doesn't know they are there, it doesn't mess with them, it leaves them alone so we are free to
use that section. Initially I draw this by setting the HTML for this into the innerHTML
property of the TFOOT
element.
In Firefox it worked, in IE it didn't so I had to go through all the DOM nonsense which works for both.
Funny title that. The discount field is actually off the DataTable, nevertheless I created an instance of my RegExpCellEditor
(see below) and tricked it to accept that cell as a real DataTable one. I had to bypass some of the methods that verified that
it really is a DataTable cell that failed, but I tried to keep to the public methods as much as I could.
Search the source for a 'click' event listener set on elDiscount
element to see how I managed
One of the TextboxCellEditor
properties is validator
which is all and good, but a little late.
The function validates the string you entered into the input box once you have hit the Ok button.
At that point, if the validation fails, the value will be returned as null
and you loose what you entered and what was initially there.
In the meantime you were able to enter any sort of funny characters and only at the end you get busted.
It would be much better if the validation happened while the keys are being pressed.
I made my own cell editor, YAHOO.widget.RegExpCellEditor
which takes an regExp
property
against which the entry is validated at each keypress. I tested it on FireFox and IE, I'm not sure how it would work in others.
You try, here it is:
The little box above is an editable DataTable with just one field, an American-style Social Security Number (SSN). The column definition for that field looks like this:
{ key:'SSN', formatter:function(el, oRecord, oColumn, oData) { el.innerHTML = oData; if (/^\d{3}-\d{2}-\d{4}$/.test(oData)) { YAHOO.uti.Dom.removeClass(el,'yellow'); } else { YAHOO.uti.Dom.addClass(el,'yellow'); } }, editor:new YAHOO.widget.RegExpCellEditor({ regExp:/^\d{0,3}-?\d{0,2}-?\d{0,4}$/, finalRegExp:'^\\d{3}-\\d{2}-\\d{4}$', failedRegExpClassName:'yellow' }) }
The column has a formatter
function which simply copies the data into the cell
and it also tests it against a regular expression for a valid SSN and changes the style of the cell accordingly.
The yellow
style simply sets a yellow background color.
The editor
is set to my RegExpCellEditor
editor and has a bunch of properties set.
The first is regExp
which can be set to either a regular expression or a string which will be converted into one.
It is set to take 0 to 3 digits, an optional hyphen, then up to two more digits, another optional hyphen and up to four digits.
This expression is full of optionals, actually, an empty string is valid.
This is because while you are entering the data you have to admit partial entries.
The second expression, finalRegExp
has no optionals.
It requires a specific number of digits and it requires the hyphens.
It is the same as used in the formatter
and it does the same.
When the value tested against that regular expression fails, the style will be set to that given in failedRegExpClassName
.
It can also take a regular expression or a string.
Be careful with the backslashes, they need to be doubled when given as a string.
If gigen as a regular expression, it can also take the modifiers at the end, that cannot be done with strings.
Notice that the final regular expression does not prevent invalid data to be accepted, that is the job of the validator
function, it just allows for a visual clue to the user that the data is not yet complete. Also notice that the validator
function is also the place to do the final conversion of the text entered in the editor into the correct internal data type.
In the editable boxes for the invoice DataTable I have set validator:YAHOO.widget.DataTable.validateNumber
to turn
the quantities and discount into actual numbers.
It is important that the regular expressions starts with a caret (^) and end with a dollar sign ($) so that it checks the full contents of the field from start to end. I might have forced those two in the function but I can't imagine what regExp magic someone might want to make with it so I left them out.
I've been an absolute miser with names. No DataSource got named, no column definitions got named. As much as possible I defined functions in-line not giving them names. Is this a good style? I'm not sure. It can be done and it is perfectly legal JavaScript. Whether it is good style or not, that's kind of philosophical. I just wanted to see if it worked, and it does. I certainly have nothing against modular programming and code reuse, but if some piece of code won't be used ever again, breaking up functionality into zillion of little functions with enormously long self-explanatory names (which often fail to explain enough, anyway) and then jumping jump back and forth through the code to find where each little piece was defined is not my thing.
The whole code for this example is enclosed in an anonymous function that, since it doesn't have a name, can't be called
from anywhere, but it has a set of parenthesis at the end so it will be executed as soon as it has been defined (there is a further
set of parenthesis enclosing the whole thing just because there is an ambiguity in the parser that gets it thoroughly confused,
and the extra parenthesis helps the parser). Anyway, the cool thing about this is that all the variables declared within that
anonymous function are local to that function, meaning, they can't be seen from outside. That allows for much shorter variable names
since they don't have to be qualified with an existing full namespace (such as YAHOO.example
) or one that you have to
define. Nevertheless, code within that function has full access to those variables declared outside of the function, which is the
global namespace. Thus, any reference to YAHOO within the anonymous function will be resolved to the global YAHOO namespace.
A method such as YAHOO.widget.DataTable.prototype.refreshRow
, though declared within the anonymous function,
gets into the global YAHOO namespace since the first part, YAHOO
, is already global and the anonymous function has
access to it and all the rest falls within it.
This name hiding game also allows us to define handy shortcuts to often used named entities. It is quite anoying to write
YAHOO.widget.DataTable
over and over again. Within the anonymous function we can declare DT
to
be a reference (or alias, if you wish) to the whole of YAHOO.widget.DataTable
. This will not only save time for you when
writing code but save time for the interpreter in trying to resolve such a complex hierarchy of names. DT
, then, becomes
a shortcut which benefits both you and the interpreter and since it is declared as a local variable within the anonymous function, it doesn't
contaminate the global namespace.
There is a whole bunch of such shortcuts at the beginning of the code. Just as the use of an underscore ( _ ) character at the begining
of a variable name is conventionally reserved for private variables, the dollar sign ( $ ) is reserved for such shortcuts and, specially
the $()
function (yes, it is a completely valid name) is reserved for YAHOO.util.Dom.get()
making it the
shortest shortcut, the shortcutests?.