Demonstrates a method of linking two DataTables together. In this case we link a row selection within a Master (or "parent") table to creation of a separate Detail (or "child") table. This is a common usage case for datasets that may have related rows within different datasets or as a result of typical database one-to-many key relationships.
Let's assume we have an array of data that includes parent elements and children elements. The example we'll use defines several animal categories and for each category it provides the names of some common characters from literature or pop culture of each type (except for the lowly amoeba, we couldn't think of any ...).
var animal_data = [ { aname: 'Lions', chars:[ 'Leo', 'Simba', 'Elsa', 'Cowardly Lion' ] }, { aname: 'Amoebas' }, { aname: 'Tigers', chars:[ 'Shere Kahn', 'Tigger', 'Tony' ] }, { aname: 'Mules', chars:[ 'Francis' ] }, { aname: 'Bears', chars:[ 'Smokey', 'Reginald', 'Winnie-the-Pooh', 'Baloo', 'Yogi' ] }, { aname: 'Snakes', chars:[ 'Kaa', 'The Serpent', 'Nagini' ] } ];
Two DataTables are utilized for this example and for convenience they operate using the same animal_data
JavaScript array. In most practical applications
the data would probably be received from a remote source via DataSource or using the Model sync
capability.
Our primary DataTable consists of two columns, aname
which is the category of the animals and the other column is
a calculated (or "unbound") column that is populated by a custom formatter. The custom formatter for nchars
simply returns the length of the chars
array
associated with the record, or zero if none are defined.
var dt_master = new Y.DataTable({ columns : [ { key:'aname', label:'Type' }, { name:'nchars', label:'No. of Chars', formatter: function(o){ return ( o.data.chars ) ? o.data.chars.length : 0; } } ], data : animal_data, width: 200, caption: 'Select an animal category below:' }).render("#mtable");
Since we will need a click handler to track TR clicks on the Master DataTable, we will define a new
attribute selectedRow
and setup a TR click handler that assigns this attribute on a click.
// // Add a new attribute to track the last TR clicked, // this is used in the details DT formatter below and later // in the row click handler `delegate` for row highlighting // // also setup a click listener to update the "selectedRow" attribute on TR clicks // dt_master.addAttr("selectedRow", { value: null }); dt_master.delegate('click', function (e) { this.set('selectedRow', e.currentTarget); }, '.yui3-datatable-data tr', dt_master);
We can proceed with defining the linked child table and rendering it initially because we have hidden
this DataTable within a DIV with style display:none;
(the DIV becomes visible on the first row click). This child DataTable consisits of
another calculated (i.e. unbound) column aname
(which just fills with the parent category name) and
a column char_name
. The data for this table is initially empty, but will be populated by the click handler.
var dt_detail = new Y.DataTable({ columns : [ { name:'aname', label:'Animal Category', formatter: function(o){ // just retrieve the selected Master record and return the "aname" column var parent_rec = dt_master.getRecord( dt_master.get('selectedRow') ); return parent_rec.get('aname'); } }, { key:'char_name', label:'Character' } ], data : [], strings : { emptyMessage : "No critter characters were found!" }, width: 350, caption: 'Characters of the category include:' }).render("#dtable");
The "glue" between the master and detail DataTables is the delegated click handler on
the Master DataTable's rows -- or more specifically, the selectedRowChange
event handler. When a row is clicked and the
selectedRow
is changed, the underlying record from the Master table is
determined and the Detail DataTable is populated with the corresponding chars
data from the clicked record.
We also handle TR highlighting for the clicked row by toggling a background color within this delegate handler.
dt_master.after('selectedRowChange', function (e) { var tr = e.newVal, // the Node for the TR clicked ... last_tr = e.prevVal, // " " " the last TR clicked ... rec = this.getRecord(tr); // the current Record for the clicked TR // // This if-block does double duty, // (a) it tracks the first click to toggle the "details" DIV to visible // (b) it un-hightlights the last TR clicked // if ( !last_tr ) { // first time thru ... display the Detail DT DIV that was hidden Y.one("#chars").show(); } else { last_tr.removeClass("myhilite"); } // // After unhighlighting, now highlight the current TR // tr.addClass("myhilite"); // // Collect the "chars" member of the parent record into an array of // objects with property name "aname" // var detail_data = []; if ( rec.get('chars') ) { Y.Array.each( rec.get('chars'), function(item){ detail_data.push( {char_name:item}); }); } // // Set the "detail_data" to the dt_detail DataTable // also update the heading in "acategory" // ( it automatically refreshes ) // dt_detail.setAttrs({ data: detail_data, caption: 'Characters of the <strong>' + rec.get('aname') + '</strong> category include:' }); });
Note: In the case of the use of remote data via DataSource, the
selectedRowChange
handler could be modified to generate a sendRequest
or similar remote call for the Detail data and the on:success
handler
could be setup to set the data
attribute.
.yui3-skin-sam .yui3-datatable-caption { font-size: 13px; font-style: normal; text-align: left; } .yui3-datatable-col-nchars { text-align: center; } .yui3-skin-sam .yui3-datatable tr.myhilite td { background-color: #C0ffc0; } #mtable tbody tr { /* Turn on cursor to show TR's are selectable on Master DataTable only */ cursor: pointer; }
Note: be sure to add the yui3-skin-sam
classname to the
page's <body>
element or to a parent element of the widget in order to apply
the default CSS skin. See Understanding Skinning.
<div id="template" class="yui3-skin-sam dt-example yui3-g"> <!-- You need this skin class --> <div class="yui3-u-1-3" id="mtable"></div> <!-- This is the HTML section for the "Details" markup ... NOTE: it is hidden initially !! --> <div class="yui3-u-2-3" id="chars" style="display:none;"> <div id="dtable"></div> </div> </div>
YUI().use( "datatable", function (Y) { var animal_data = [ { aname: 'Lions', chars:[ 'Leo', 'Simba', 'Elsa', 'Cowardly Lion' ] }, { aname: 'Amoebas' }, { aname: 'Tigers', chars:[ 'Shere Kahn', 'Tigger', 'Tony' ] }, { aname: 'Mules', chars:[ 'Francis' ] }, { aname: 'Bears', chars:[ 'Smokey', 'Reginald', 'Winnie-the-Pooh', 'Baloo', 'Yogi' ] }, { aname: 'Snakes', chars:[ 'Kaa', 'The Serpent', 'Nagini' ] } ]; // // Create the "parent" DataTable // var dt_master = new Y.DataTable({ columns : [ { key:'aname', label:'Type' }, { name:'nchars', label:'No. of Chars', formatter: function(o){ return ( o.data.chars ) ? o.data.chars.length : 0; } } ], data : animal_data, width: 200, caption: 'Select an animal category below:' }).render("#mtable"); // // Add a new attribute to track the last TR clicked, // this is used in the details DT formatter below and later // in the row click handler `delegate` for row highlighting // // also setup a click listener to update the "selectedRow" attribute on TR // clicks // dt_master.addAttr("selectedRow", { value: null }); dt_master.delegate('click', function (e) { this.set('selectedRow', e.currentTarget); }, '.yui3-datatable-data tr', dt_master); // // Create the characters DataTable and render it (it is hidden initially) // var dt_detail = new Y.DataTable({ columns : [ { name:'aname', label:'Animal Category', formatter: function(o){ // just retrieve the selected Master record and return the // "aname" column var parent_rec = dt_master.getRecord( dt_master.get('selectedRow') ); return parent_rec.get('aname'); } }, { key:'char_name', label:'Character' } ], data : [], strings : { emptyMessage : "No critter characters were found!" }, width: 350, caption: 'Characters of the category include:' }).render("#dtable"); // // Setup a listener to the Master "selectedRowChange" event (i.e. after a // row click) // dt_master.after('selectedRowChange', function (e) { var tr = e.newVal, // the Node for the TR clicked ... last_tr = e.prevVal, // " " " the last TR clicked ... rec = this.getRecord(tr); // the current Record for the clicked TR // // This if-block does double duty, // (a) it tracks the first click to toggle the "details" DIV to visible // (b) it un-hightlights the last TR clicked // if ( !last_tr ) { // first time thru ... display the Detail DT DIV that was hidden Y.one("#chars").show(); } else { last_tr.removeClass("myhilite"); } // // After unhighlighting, now highlight the current TR // tr.addClass("myhilite"); // // Collect the "chars" member of the parent record into an array of // objects with property name "aname" // var detail_data = []; if ( rec.get('chars') ) { Y.Array.each( rec.get('chars'), function(item){ detail_data.push( {char_name:item}); }); } // // Set the "detail_data" to the dt_detail DataTable // also update the heading in "acategory" // ( it automatically refreshes ) // dt_detail.setAttrs({ data: detail_data, caption: 'Characters of the <strong>' + rec.get('aname') + '</strong> category include:' }); }); });