<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example: Master and detail tables</title>
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic,700italic">
<link rel="stylesheet" href="../../build/cssgrids/cssgrids-min.css">
<link rel="stylesheet" href="../assets/css/main.css">
<link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css">
<link rel="shortcut icon" type="image/png" href="../assets/favicon.png">
<script src="../../build/yui/yui-min.js"></script>
</head>
<body>
<!--
<a href="https://github.com/yui/yui3"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
-->
<div id="doc">
<div id="hd">
<h1><img src="http://yuilibrary.com/img/yui-logo.png"></h1>
</div>
<h1>Example: Master and detail tables</h1>
<div class="yui3-g">
<div class="yui3-u-3-4">
<div id="main">
<div class="content"><style scoped>
.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 {
cursor: pointer;
}
</style>
<div class="intro">
<p>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.
</p>
</div>
<div id="template" class="example yui3-skin-sam dt-example yui3-g">
<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>
<script>
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 <code>delegate</code> 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:'
});
});
});
</script>
<h2>Sample Data</h2>
<p>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 (<em>except for the lowly
amoeba, we couldn't think of any ...</em>).</p>
<pre class="code prettyprint">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' ] }
];</pre>
<h2>The DataTables</h2>
<p>Two DataTables are utilized for this example and for convenience they operate using the same <code>animal_data</code> JavaScript array. In most practical applications
the data would probably be received from a remote source via DataSource or using the Model <code>sync</code> capability.
</p>
<h3>The "Master" table</h3>
<p>Our primary DataTable consists of two columns, <code>aname</code> 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 <code>nchars</code> simply returns the length of the <code>chars</code> array
associated with the record, or zero if none are defined.</p>
<pre class="code prettyprint">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");</pre>
<p>Since we will need a click handler to track TR clicks on the Master DataTable, we will define a new
attribute <code>selectedRow</code> and setup a TR click handler that assigns this attribute on a click.</p>
<pre class="code prettyprint">//
// 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);</pre>
<h3>The "Detail" table</h3>
<p>We can proceed with defining the linked child table and rendering it initially because we have hidden
this DataTable within a DIV with style <code>display:none;</code> (the DIV becomes visible on the first row click). This child DataTable consisits of
another calculated (i.e. unbound) column <code>aname</code> (which just fills with the parent category name) and
a column <code>char_name</code>. The data for this table is initially empty, but will be populated by the click handler.</p>
<pre class="code prettyprint">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");</pre>
<h2>The selectedRow Listener</h2>
<p>
The "glue" between the master and detail DataTables is the delegated click handler on
the Master DataTable's rows -- or more specifically, the <code>selectedRowChange</code> event handler. When a row is clicked and the
<code>selectedRow</code> is changed, the underlying record from the Master table is
determined and the Detail DataTable is populated with the corresponding <code>chars</code> data from the clicked record.
We also handle TR highlighting for the clicked row by toggling a background color within this delegate handler.
</p>
<pre class="code prettyprint">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:'
});
});</pre>
<p>
Note: In the case of the use of remote data via DataSource, the
<code>selectedRowChange</code> handler could be modified to generate a <code>sendRequest</code>
or similar remote call for the Detail data and the <code>on:success</code> handler
could be setup to set the <code>data</code> attribute.
</p>
<h2>Full Source Code</h2>
<h3>CSS</h3>
<pre class="code prettyprint">.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;
}</pre>
<h3>HTML Markup</h3>
<p>
<strong>Note:</strong> be sure to add the <code>yui3-skin-sam</code> classname to the
page's <code><body></code> element or to a parent element of the widget in order to apply
the default CSS skin. See <a href="http://yuilibrary.com/yui/docs/tutorials/skins/">Understanding Skinning</a>.
</p>
<pre class="code prettyprint"><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></pre>
<h3>Javascript</h3>
<pre class="code prettyprint">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:'
});
});
});</pre>
</div>
</div>
</div>
<div class="yui3-u-1-4">
<div class="sidebar">
<div class="sidebox">
<div class="hd">
<h2 class="no-toc">Examples</h2>
</div>
<div class="bd">
<ul class="examples">
<li data-description="This example illustrates simple DataTable use cases.">
<a href="datatable-basic.html">Basic DataTable</a>
</li>
<li data-description="DataTable loaded with JSON data from a remote webservice via DataSource.Get">
<a href="datatable-dsget.html">DataTable + DataSource.Get + JSON Data</a>
</li>
<li data-description="DataTable loaded with XML data from a remote webservice via DataSource.IO.">
<a href="datatable-dsio.html">DataTable + DataSource.IO + XML Data</a>
</li>
<li data-description="Custom format data for display.">
<a href="datatable-formatting.html">Formatting Row Data for Display</a>
</li>
<li data-description="DataTable with nested column headers.">
<a href="datatable-nestedcols.html">Nested Column Headers</a>
</li>
<li data-description="DataTable with column sorting.">
<a href="datatable-sort.html">Column Sorting</a>
</li>
<li data-description="DataTable with vertical and/or horizontal scrolling rows.">
<a href="datatable-scroll.html">Scrolling DataTable</a>
</li>
<li data-description="Using DataTable's recordType attribute to create calculated, sortable columns.">
<a href="datatable-recordtype.html">Sortable generated columns</a>
</li>
<li data-description="Populating one DataTable from details in the data of another.">
<a href="datatable-masterdetail.html">Master and detail tables</a>
</li>
<li data-description="Checkbox column that retains checked state when sorting.">
<a href="datatable-chkboxselect.html">Checkbox select column</a>
</li>
</ul>
</div>
</div>
<div class="sidebox">
<div class="hd">
<h2 class="no-toc">Examples That Use This Component</h2>
</div>
<div class="bd">
<ul class="examples">
<li data-description="Shows how to instantiate multiple Panel instances, and use nested modality to interact with a Datatable.">
<a href="../panel/panel-form.html">Creating a Modal Form</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="../assets/vendor/prettify/prettify-min.js"></script>
<script>prettyPrint();</script>
<script>
YUI.Env.Tests = {
examples: [],
project: '../assets',
assets: '../assets/datatable',
name: 'datatable-masterdetail',
title: 'Master and detail tables',
newWindow: '',
auto: false
};
YUI.Env.Tests.examples.push('datatable-basic');
YUI.Env.Tests.examples.push('datatable-dsget');
YUI.Env.Tests.examples.push('datatable-dsio');
YUI.Env.Tests.examples.push('datatable-formatting');
YUI.Env.Tests.examples.push('datatable-nestedcols');
YUI.Env.Tests.examples.push('datatable-sort');
YUI.Env.Tests.examples.push('datatable-scroll');
YUI.Env.Tests.examples.push('datatable-recordtype');
YUI.Env.Tests.examples.push('datatable-masterdetail');
YUI.Env.Tests.examples.push('datatable-chkboxselect');
YUI.Env.Tests.examples.push('panel-form');
</script>
<script src="../assets/yui/test-runner.js"></script>
</body>
</html>