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.

Drag and drop between tables

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.

Summary in TFOOT element

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.

Off-table in-line cell editor

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

Editor with Regular Expression validator

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:

	formatter:function(el, oRecord, oColumn, oData) {
		el.innerHTML = oData;
		if (/^\d{3}-\d{2}-\d{4}$/.test(oData)) {
		} else {
	editor:new YAHOO.widget.RegExpCellEditor({

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.

Stingy with names

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.

Shortcuts and hidden names

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?.