Home  >  

YUI DataTable Component

Author photo
AddThis Social Bookmark Button
yuidata.jpg
The DataTable component allows us to display and manipulate tabular data. It can be used to enhance an existing HTML table, produce one based on data fetched from a remote data source in JSON, XML or CSV format or a local (client side) source in a few more formats. The first option is handy when you don't want to completely redo the server side portion of an existing application which is already generating the HTML table and you want to enhance the UI or when you want to provide for users who might not have JavaScript enabled, so that they at least get a regular HTML table.

The DataTable has methods to add and delete rows and columns, to sort, resize, show, hide and move columns around, to edit cells in place with plain (textbox or textarea), enhanced (using the Calendar component for dates) or custom editors. In all, the DataTable has many of the UI features of a regular spreadsheet and it is often confused with one. A spreadsheet, though, allows you to put any type of data in any cell, there is a block of meta-data associated with each individual cell, the DataTable has meta-data associated with each column, just as with a database table.

In a previous article I wrote about how to get started with the YUI, how to load the libraries and find whether the DOM was loaded and stable before trying to do anything with it. We also saw how to hide your variables from the global scope so that you didn't have to get concerned with name collision. In the sample code, there was a comment saying 'Your code here', we will see what your code could be for a DataTable.

The Data Source

The DataTable fetches data through the DataSource component, which is the one that contains all the parsers for the different types of data formats supported. The DataSource component is the same one used by the Charts component (the AutoComplete component still uses an earlier incompatible version of DataSource). The DataSource produces an array with all the requested data for the DataTable. A few definitions allows the DataSource to fetch the data, extract its fields and parse the values.

var myDataSource = new YAHOO.util.DataSource('json_proxy.php?', {
     responseType: YAHOO.util.DataSource.TYPE_JSON,
     responseSchema: {
           resultsList: 'data',
           fields: [
               {key: 'id', parser: YAHOO.util.DataSource.parseNumber},
               'name',
               {key: 'date', parser: YAHOO.util.DataSource.parseDate}
           ]
      }
});

The code above would create a DataSource instance which will retrieve data remotely from the URL given in the first argument, it will expect the data to be in JSON format and the array of records under a property called data. It will then extract three fields, id, name and date. For data formats, such as plain text, where there are no field names, the field values will be read in the order given.

The name field, being a plain string, doesn't need any parser and you can simply give the name of the field. The first one needs to be parsed as a number, the last as a date so you have to tell which parser to use. The DataSource includes parsers for several kinds of data but you can also provide your own. A parser simply receives the value as it came in the data packet (usually a string) and should return it converted to whatever the closest native JavaScript type is. Though JavaScript can do automatic type conversion it not always does so. It often happens when asking the DataTable to sort a column, that a 12 will end up sorted before a 2 because the parser was missing and it compared them as strings not numbers.

Since the DataSource is able to fetch data asynchronously, once it is set up, you call the sendRequest method and give it, amongst other things, the callback function that will receive the data when it finally arrives and gets processed. If used with the DataTable, you just pass the DataSource instance already set up to the DataTable and it will do it for you. The DataTable will store the data received in its RecordSet object. You can fetch more or less fields than you will show. Some extra fields might be for internal use and you don't want them shown; some extra columns may be made up, usually UI elements such as buttons or checkboxes.

The Columns Definition

The next thing we need to do is to define which columns will be shown. We do that through an array usually called the columns definitions. The array contains one entry per visible column. Each column definition is an object literal with several properties. Only one is required, the key, which is the name by which this column will be recognized. The value of the key property in the column definition must match the value of the key property in the fields definition of the DataSource.

var myColumnDefs = [
    {key: 'id'},
    {key: 'name', label: 'User Name'},
    {key: 'date', label: 'Last Login', formatter: 'date'}
];

Here is a possible columns definition for the previous set of data. We will show all three fields, we change the column headers of the last two, otherwise it defaults to the key value, and we further specify that the last one should be formatted as a date. The formatter property can be either a string, as in this case, or a function. If a string, it should correspond to one of the named built-in formatters, if a function the DataTable will call it for each cell, passing it the value to be formatted, the DOM reference to the cell where it will be displayed and references to the Record and Column objects where it belongs. The formatter can thus access other fields in the same Record to decide how to format it (even hidden fields) or extra info in the Column. You are free to add your own properties to the columns definition, the DataTable will preserve those which can later be used, for example, in your custom formatter.

Since the formatter has access to the HTML cell, it can not only set its contents with the formatted value but can also change its style. For example, setting right alignment or changing colors, or it can also draw HTML elements such as links, buttons or image tags.

You can also define columns with no associated data field in the RecordSet. You will still give them a unique key name and you will have to provide a formatter otherwise it will simply show an empty cell.

There are many other column options such as sortable, resizeable, width or minWidth which are self-explanatory. You can also assign an editor to any column. Just like with formatter, you can use a named built-in editor or you can define your own. This is somewhat more involved than custom formatters so I won't cover it here. There are plain text editors (both textbox and textarea), calendars, dropdowns, multi-option checkboxes or radio buttons. You can provide extra options for some of these with the editorOptions property.

You can override the built-in column sorter with the sortOptions property and you can give a column a particular style with the className property.

The hidden property will render the column as hidden. This is different from not naming the column in the columns definition. When you don't name it, the column will never exist, when you do define it as hidden, it will render collapsed, just a narrow vertical strip, which can be shown at any time via the showColumn method.

Finally, columns can be nested. Any given column can have a children property which can contain a further array with the nested columns definition.

Creating the DataTable

We are now ready to create the DataTable instance.

 
var myDataTable = new YAHOO.widget.DataTable('tableContainer', 
	myColumnDefs, myDataSource, myOptions);

This will create and render the DataTable. The first argument should be a reference to an existing DOM object which will hold the DataTable or, as in this example, the value of the id attribute of an HTML element. We then provide the references to the columns definition and the DataSource object. These three are mandatory arguments: where to put it, what to put there and where to get it from. There is a fourth optional argument, an object with several configuration attributes for the whole DataTable. This is a common practice across the YUI library, the constructors of most complex components can accept a last optional argument which is an object literal to set multiple optional attributes at once. Usually they can also be set by other means, but it is often handy to do it all at once. We'll see those options in the next section, in the meantime, if you want to see it at work, you can check a couple of examples.

The first one uses an existing table as the basis to build an active DataTable. If you turn JavaScript off, you can still see the plain HTML table, completely undecorated and passive (actually, depending on the speed of your connection you might get to see it briefly before it is replaced). Admittedly, making it more palatable only involves setting a few styles. I don't mean to imply that without JavaScript decent tables cannot be built, but it is nevertheless true that if you mean to support users with and without JavaScript enabled, the visual design has to be duplicated to suit both situations, with the non-JavaScript alternative further complicated by all the extra elements (forms, buttons, links and such) that need to be added to get from the server the functionality that the YUI library provides on the client side. The CSS components of the YUI library can still be used in this situation so a little help could still be had.

The second example does require JavaScript to be active and uses a server providing data in JSON format. There is little HTML that needs to be provided for the active components, in this case a single container to draw the table in. I have been further skimpy in the use of variables. I didn't store the DataSource and columns definitions in separate variables but defined them in-line. Examples usually avoid this, as I did in the previous example, in order to guide the reader step by step, so I went the opposite way in this example to even things out. Both examples are well commented.

Variables explicitly declared within a function are local to that function and thus invisible from outside that function. We have used that feature of JavaScript in both examples. By the time I am declaring the DataTable, the variable dt is local to a function nested three levels deep from the global namespace. There is no chance that any variable that I explicitly declare inside it would ever clutter the global namespace. Variables used without explicitly declaring them first do go to the global namespace, that is why I have already repeated 'explicitly' three times in this paragraph: please do use var. This is important when working in large projects with code provided by your colleagues or third party libraries: don't clutter the global namespace.

There is, however, the need to share components on the page. We do that in this example by copying the reference to the DataTable we have just created from the local variable dt to YAHOO.example.myDataTable. As I mentioned in the a previous article, all the YUI library is stored inside variable YAHOO, which is further subdivided in several sections, widget, lang, util and a few others. These are no more than properties in the YAHOO object containing further objects. One of those, example, is already created and available for anybody to use. This one does not need to be declared, explicitly or not, YUI already has declared it, you can simply use it as we did here, by assigning to previously non-existent property myDataTable the reference to the new DataTable. Any other code in the same page will be able to access this DataTable by using its full name, YAHOO.example.myDataTable.

We could have used the full name all along instead of dt. There are several advantages to not do so. First, it is shorter to write and I do value my time. Second, it may be faster for the interpreter. The extra time needed to create that extra dt variable compensates the time it would take the interpreter to solve YAHOO then example and finally myDataTable several times. Finally, the YUI Compressor can safely replace the dt or any other possibly longer name with a shorter made-up name since it knows it is local to that function, something it cannot do with the full name.

The YAHOO.example namespace should be used with caution. If you are building libraries, you should define, keep a record of and manage your own namespace as I've shown in the a previous article, but for code that fits into a single page and will never be included inside other pages, it is a handy place to put things.

DataTable Options

The caption and summary attributes will be copied to the corresponding HTML tags. If the data comes already sorted, you can let the DataTable know via the sortedBy attribute so that it will highlight the corresponding column. This attribute does not force a sort on the DataTable, it simply tells it when the data comes already sorted. The selectionMode attribute lets you specify whether the user can select by row, cell range or cell block and whether it can be single or multiple item selection. If the draggableColumns attribute is set, the columns can be reordered by dragging them with the mouse into their new positions.

When we created myDataSource, we cut the URL at the question mark. We don't specify the full request because the DataTable might be paginated or associated to a search box to filter records and each might require different URL arguments. We set the DataSource to the base URL of the server so we can reuse it for all requests. We set the initial arguments for the request through the initialRequest attribute.

Normally the DataTable will draw an HTML table which will adjust itself according to its style, the available space and its contents, as normal HTML tables do. The scrollable attribute will let you specify a width and height for it and will produce a couple of scrollbars so that the table contents will scroll but will leave the headers always visible.

If the data set is long, there are several alternatives to speed up the rendering. The renderLoopSize attribute tells the DataTable to yield its thread to the browser every several rows thus unfreezing the UI. Though in the end the DataTable will take longer to render, due to this yielding and resuming, the user will perceive it as faster since it will start seeing some results almost immediately and the interface will remain responsive.

The DataTable also supports pagination, both on the client and on the server side. You enable it by setting the paginator attribute to an instance of the Paginator object (or one you provide) which takes several attributes, all optional and with suitable defaults, such as page size, initial page, location of the page navigation controls or a template for those controls with placeholders for each element.

Client-side pagination can be used for styling, ie. you might not want your DataTable to stretch forever. Sometimes, the time to render the DataTable, specially with complex formatters can be quite long. Client-side pagination allows you to render a page at a time, which can make the UI more responsive. It might also avoid the warning that some browsers provide when a script is taking too long to finish.

For really long data sets you need server-side pagination. This requires you to change the paginationEventHandler attribute from the default client-side handler to the built-in server-side handler or one you provide. The built-in handler keeps a page cache so that when the user returns to a previously seen page, it will not generate a new request. You can also change the generateRequest method from the default to one of your own which will build the URL search argument (the part after the question mark) to suit your server. This will replace the one given in the initialRequest attribute.

Events

The DataTable provides a large set of custom events which you can subscribe to. These are handled through the YAHOO.util.Event component of YUI. The DataTable uses event delegation extensively so that you can respond to, lets say, click events on all or any cells in a DataTable without setting an event listener for each actual cell, which would take huge amounts of memory. The DataTable listens to clicks at the HTML table level and then, according to the target, it fires its own custom event providing arguments far more suitable for manipulating the DataTable than the raw DOM event could.

Most significant moments in the DataTable trigger events of one kind or another, some of them originating in the DOM (all sorts of clicks at every level, keystrokes or mouse movements) others produced by the DataTable itself, such as when the table is rendered, a column is sorted or a row selected.

The DataTable also has built-in methods which you can use as event listeners, some of them already assigned, such as sorting a column for clicks on column headers, others which you can choose from such as either popping up the in-line cell editor for clicks on cells or do cell selection or even do both. In the examples (1, 2) we have assigned the method that pops up the in-line cell editor (onEventShowCellEditor) to cell clicks (cellClickEvent). Right after doing so, we subscribe to that same event the onEventSelectRow listener. More than one listener can be assigned to the same event and each will be called in turn in the order they were assigned.

Notice, though, that event listeners cannot consume the event. The cell editor listener first checks if the cell clicked is in a column with an editor assigned, if not, it ignores it. However, the select row event is called either way so in example 1, clicking on the cells of the two right-most columns will make both the editor pop up and the row to get selected. This side effect might not be what you want. We fixed that in the example 2. Basically, what we do there is the opposite of what onEventShowCellEditor does internally, first we find the column where the click happened and, if it doesn't have an editor assigned, we call onEventSelectRow. A couple of things to notice. If you listen to an event triggered by the DataTable, the scope of the listener will be set to that of the object that triggered the event, ie, the DataTable. Second, the single argument to that listener is an object which will have properties suitable to manipulate the YUI object which makes it easy to find the column as we do in the example.

You can also capture other interesting moments by overriding some existing basically do-nothing methods.

As a general rule, for the whole of the YUI library, methods starting with the prefix on and more precisely onEvent are event listeners either already set or available for you to assign to events of your choice, such as onEventShowCellEditor. Methods starting with the prefix do (quite often doBefore or doAfter something) are usually overridable methods. Of course, JavaScript will allow you to override any method and YUI can't prevent you from doing so. Why should it?

In-line Cell Editing

There are two ways you can modify the DataTable, you can set the editor property in the columns definition to one of the built-in editors or a custom one you provide. This will make an editor pop up right on top of the cell being edited. You can have single or multi-line text editors, multi-options (several checkboxes or radio buttons for a single cell), dropdowns or full calendars (using YAHOO.widget.Calendar). Each pop up editor has Ok/Cancel buttons on it. You can assign a validator function in the editorOptions property of the columns definition to do an initial check on the entered value.

The other way is by drawing active HTML controls such as checkboxes, radio buttons or dropdown boxes via the built-in or your custom formatters. You respond to these controls through the corresponding DataTable events. Of course you can handle these events by listening to the raw DOM event, but the DataTable event will provide you with far more useful arguments specifically related to the DataTable, such as the cell reference or the Record and Column objects. While the in-line editors directly modify the underlying RecordSet object, where the data is actually stored, none of these do so because some of these controls might not be meant to actually edit data but to trigger other actions so, if you mean them to edit data, you are responsible to change the RecordSet which is easy enough with the arguments provided to the event listener.

The changes to the RecordSet do not get sent to the server. Since there is no standard way to communicate and trigger transactions on a server, the DataTable only provides plenty of events and overridable functions to let the programmer know when a change happens and then send those changes to the server in whichever way the server expects them. Lots of people initially believe that by setting the DataSource, a two way communication is set. The DataSource can only fetch data not send if back to the server. All the options that would be needed to specify how to communicate changes to the server, whether to use HTTP GET, POST or PUT requests, URL, XML or JSON-encoding, data conversion and how to receive confirmation, rejection or error information back and what to do with it, would make such a two way communications component big, cumbersome and tedious to set up. The DataTable provides the developer with the hooks to make it, possibly using the Connection Manager component for asynchronous communication or Get component which allows you to asynchronously fetch data from other domains.

The DataTable provides no means to put it on hold until the server confirmation is received. The changes are immediately reflected on the UI. If the server rejects a change or replies with an error, it is up to the developer to revert the change. Though this is in line with the philosophy of providing a responsive, non-blocking UI, it is somewhat dangerous because the user will see the change in the screen and might focus elsewhere on the page or even navigate away from it trusting the change has been made in the server, failing or unable to see the rejection from the server.

Integration with other Components

Further interaction with the DataTable can be provided by the pop up ContextMenu of the Menu component, often assigned to right clicks on cells or rows. Record filtering can be done via the search box of the AutoComplete component. The Browser History Manager component can be used along the native Paginator so that the paging through the DataTable gets reflected in the browser so you can go back to previously visited pages with the browser back button or history drop down. As mentioned, if the Calendar component is included, the in-line editor for dates will use it automatically. Column resizing and dragging depends on the Drag and Drop component. The same data shown in tabular form with the DataTable can be displayed graphically with the Charts component, both use the DataSource to fetch information.

Read more from Daniel Barreiro. Daniel Barreiro's Atom feed

Comments

19 Comments

Jim said:

thanks for the article, i've been using the DataTable for a few things but i'd like to use it more including the inline-editing features. (I think i'll use smaller buttons for ok and cancel though)
jim

Palani said:

Is it possible to add the additional rows below of each rows while page rendering itself?

Satyam said:

Palani,

The feature is not supported by the DataTable itself.

It is possible to do so and some people have done it, but it is quite involved since many of the dynamic features of the DataTable mean the whole table might be rearranged by sorting columns, paging, column resizing or reordering.

Added rows might get wiped out by any such redrawing and your added rows might interfere with it.

This said, it can be done and it has been done, but much care is required.


Anonymous said:

Thanks for this article.

Patrick said:

is there any quick way to get all of the data from the datatable, after rows have been added/deleted, in JSON?

I've tried datatable.getDataSource().liveData but that does not seem to be updated or what I want.

Satyam said:

Patrick,

No, there is no immediate way to do what you want. Normally, each individual update/addition/deletion is transmitted to the server and it is not often that you would let the user do a bunch of modifications and send to the server all the lot at once, even those records that have not been modified.

The DataSource is only that, a generic utility to read data sources, but once it has fetched, extracted and parsed the values, it passes them on to the component that will consume them, in this case, the DataTable, without holding any reference to them. So, the place to look for them is in the DataTable.

Within the DataTable, method getRecordSet will point you to the DataTable's own storage. You can loop through it. Within RecordSet, method getLength provides you the actual number of records, and method getRecord returns each Record and method getData of the Record object, without arguments, gives you all the values at once or you can fetch the values you want by asking explicitly for each.

Stefan said:

You can hide and show rows in a yui dataTable by manipulating e.g. the tr style.

Example:

function unHideTableRow(searchData) {

if (myDataTable != null) {
var recordSet = myDataTable.getRecordSet();
for (var i = 0; i
record = recordSet.getRecord(i);
if (searchData == record.getData('searchData')) {
var trEl = myDataTable.getTrEl(i);
trEl.style.display = ''; //set 'none' to hide...
break;
}
}
}
}

SlugO said:

Is there a way to add attributes to DataTable's checkboxes? I'm asking cos I need to be able to assign them unique name="" attributes so that my PHP code can go through them.

It works fine in the HTML table that the DataTable parses but I can't really grasp how to handle DataTable's checkboxes, they just get generated.

SlugO said:

Is there a way to add attributes to DataTable's checkboxes? I'm asking cos I need to be able to assign them unique name="" attributes so that my PHP code can go through them.

It works fine in the HTML table that the DataTable parses but I can't really grasp how to handle DataTable's checkboxes, they just get generated.

SlugO said:

Is there a way to add attributes to DataTable's checkboxes? I'm asking cos I need to be able to assign them unique name="" attributes so that my PHP code can go through them.

It works fine in the HTML table that the DataTable parses but I can't really grasp how to handle DataTable's checkboxes, they just get generated.

Satyam said:

Checkboxes don't have names because they are not meant to be directly submitted to a server but processed within the page, with JavaScript and the DOM and then, if needed, assembled into an AJAX request.

If you want to put names on them, you should define your own custom formatter function for the column holding the checkboxes and draw the checkboxes yourself with whatever attributes you want

Michael said:

I'm trying to integrate the JQuery Sparkline inside one of the rows. I created a customer formater that adds a class to a span element. Normally I call a JQuery method as follows:

$('.inlinesparkline').sparkline();

to render the sparkline. But the HTML isn't actually there yet. Is there any support for calling additional javascript like this after the AJAX request and the new html is rendered?

Thanks

Satyam said:

Michael,

The DataTable will fire the initEvent when it is done with the initial rendering and renderEvent every time it has to redraw itself due to sorting, paging or whatever that makes it redraw the full table. With either of them you will be certain the full page is drawn.

Raaj said:

I am trying to use jsp page for calling the datasource into the yui data table. But , the datas are not inserted into the data table.

I get " tbodies.length is null or not an object" and at the same time the data table is in the loading state.


Could anyone help me out

Satyam said:

Raaj,

The YUI forums, at:

http://yuilibrary.com/forum/

will be a better place to get support. It has a section for DataTable/DataSource.

Satyam

charlie said:

Hi,

An interesting introduction. I am a bit of a newbie so would it be possible for you to set out how to load the data from an XML file that looks like this? (It was generated by serialisation in vb .net)

Thanks

Charles

<?xml version="1.0" encoding="utf-8"?>
<CalculationResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TotalLiability>0</TotalLiability>
<CashFlowList>
<CashFlow>
<Year>0</Year>
<Age>60</Age>
<AmountBeforeDiscount>700</AmountBeforeDiscount>
<DiscountFactorCurrent>0.97590007294853309</DiscountFactorCurrent>
<DiscountFactor>0.95238095238095233</DiscountFactor>
<DecrementRateCurrent>0</DecrementRateCurrent>
<DecrementFactor>0</DecrementFactor>
<AmountDiscounted>0</AmountDiscounted>
<Years>0</Years>
</CashFlow>
<CashFlow>
<Year>1</Year>
<Age>60</Age>
<AmountBeforeDiscount>300</AmountBeforeDiscount>
<DiscountFactorCurrent>0.97590007294853309</DiscountFactorCurrent>
<DiscountFactor>0.95238095238095233</DiscountFactor>
<DecrementRateCurrent>0</DecrementRateCurrent>
<DecrementFactor>0</DecrementFactor>
<AmountDiscounted>0</AmountDiscounted>
<Years>0</Years>
</CashFlow>
<CashFlow>
<Year>1</Year>
<Age>61</Age>
<AmountBeforeDiscount>800</AmountBeforeDiscount>
<DiscountFactorCurrent>0.97590007294853309</DiscountFactorCurrent>
<DiscountFactor>0.95238095238095233</DiscountFactor>
<DecrementRateCurrent>0</DecrementRateCurrent>
<DecrementFactor>0</DecrementFactor>
<AmountDiscounted>0</AmountDiscounted>
<Years>0</Years>
</CashFlow>

Satyam said:

As the comment just above yours says, there is a support forum where your question can be properly answered.

Venkatesh said:

HI ,

I have a scenario where i need to bind the values in the yui datable ceel to a form.
Please let me know how to handle this part.

Also please let me know how to update the HTML table with the value of my yu datatable.

Satyam said:

Please use the forum for support issues:

http://yuilibrary.com/forum/

Satyam

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.