This example shows one method for managing a selection checkbox column in a DataTable.
This example will use a local Javascript array of data that includes some common Internet port socket numbers and protocol names:
var ports = [
{ port:20, pname:'FTP_data',ptitle:'File Transfer Process Data' },
{ port:21, pname:'FTP', ptitle:'File Transfer Process' },
{ port:22, pname:'SSH', ptitle:'Secure Shell' },
{ port:23, pname:'TELNET', ptitle:'Telnet remote communications' },
... // data continues
];
Our DataTable for this example will utilize a custom formatter as the first column, to display a standard HTML INPUT[type=checkbox] element as an indication that the record is desired to be "selected" for additional processing. But that checkbox won't work on its own, because if a "sort" action happens after the checkbox is clicked the "check" status is lost!
A way to get around this is to create a binding of the checkbox to an
attribute of each record which will remain with the record even upon
sorts, edits, or other modifications to the record. This is accomplished
by defining a custom recordType for the DataTable that incorporates all
of our standard data for our table but also defines a new Attribute
(called select here) that is a boolean value to track whether the record
is selected.
The implementation of these methods is shown below, where we have defined a
custom formatter and emptyCellValue for the "select" column that
creates a checked or unchecked checkbox depending on the state of the
attribute, and defines a custom recordType with our new attribute added.
Additionally, we incorporate (a) scrolling and (b) sorting to demonstrate
that this technique works.
var table = new Y.DataTable({
columns : [
{ key: 'select',
allowHTML: true, // to avoid HTML escaping
label: '<input type="checkbox" class="protocol-select-all" title="Toggle ALL records"/>',
formatter: '<input type="checkbox" checked/>',
emptyCellValue: '<input type="checkbox"/>'
},
{ key: 'port', label: 'Port No.' },
{ key: 'pname', label: 'Protocol' },
{ key: 'ptitle', label: 'Common Name' }
],
data : ports,
scrollable: 'y',
height : '250px',
sortable : ['port','pname'],
sortBy : 'port',
recordType: ['select', 'port', 'pname', 'ptitle']
}).render("#dtable");
Having a DataTable with a bunch of checkboxes in it may look cool (or not!), but we also need to define what to do when they are checked. Since the column formatter for the first column creates the checkboxes, we delegate "click" listeners from the DataTable for the two types of checkboxes—the "check all" checkbox in the header and the individual checkboxes on each data row.
Be sure to avoid subscribing to events directly on elements in each row of a DataTable.
// Define a listener on the DT first column for each record's checkbox,
// to set the value of `select` to the checkbox setting
table.delegate("click", function(e){
// undefined to trigger the emptyCellValue
var checked = e.target.get('checked') || undefined;
// Don't pass `{silent:true}` if there are other objects in your app
// that need to be notified of the checkbox change.
this.getRecord(e.target).set('select', checked, { silent: true });
// Uncheck the header checkbox
this.get('contentBox')
.one('.protocol-select-all').set('checked', false);
}, ".yui3-datatable-data .yui3-datatable-col-select input", table);
// Also define a listener on the single TH checkbox to
// toggle all of the checkboxes
table.delegate('click', function (e) {
// undefined to trigger the emptyCellValue
var checked = e.target.get('checked') || undefined;
// Set the selected attribute in all records in the ModelList silently
// to avoid each update triggering a table update
this.data.invoke('set', 'select', checked, { silent: true });
// Update the table now that all records have been updated
this.syncUI();
}, '.protocol-select-all', table);
The bulk of the nitty-gritty is done now. We'll just wire up a button to process the checked records and another to clear the selection.
function process() {
var ml = table.data,
msg = '',
template = '<li>Record index = {index} Data = {port} : {pname}</li>';
ml.each(function (item, i) {
var data = item.getAttrs(['select', 'port', 'pname']);
if (data.select) {
data.index = i;
msg += Y.Lang.sub(template, data);
}
});
Y.one("#processed").setHTML(msg || '<li>(None)</li>');
}
Y.one("#btnSelected").on("click", process);
Y.one("#btnClearSelected").on("click",function () {
table.data.invoke('set', 'select', undefined);
// Uncheck the header checkbox
table.get('contentBox')
.one('.protocol-select-all').set('checked', false);
process();
});
Note that another option for capturing all the checked checkboxes would be
table.get('contentBox').all('.yui3-datatable-col-select input:checked').
To make sure that was supported across all browsers, we'd then need to
include the selector-css3 module in our use() statement.
Another improvement that could be made for HTML5 compliant clients would be
to add in localStorage access to save the checked record data to the
browser environment. You can see how to do this in the
App Framework's Todo List example.
.yui3-skin-sam .yui3-datatable-col-select {
text-align: center;
}
#processed {
margin-top: 2em;
border: 2px inset;
border-radius: 5px;
padding: 1em;
list-style: none;
}
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 class="example yui3-skin-sam"> <!-- You need this skin class -->
<div id="dtable"></div>
<button id="btnSelected" class="yui3-button">Process Selections</button>
<button id="btnClearSelected" class="yui3-button">Clear Selections</button>
<h4>Processed:</h4>
<ul id="processed">
<li>(None)</li>
</ul>
</div>
YUI({ filter: 'raw' }).use( "datatable-sort", "datatable-scroll", "cssbutton", function (Y) {
var ports = [
{ port:20, pname:'FTP_data',ptitle:'File Transfer Process Data' },
{ port:21, pname:'FTP', ptitle:'File Transfer Process' },
{ port:22, pname:'SSH', ptitle:'Secure Shell' },
{ port:23, pname:'TELNET', ptitle:'Telnet remote communications' },
{ port:25, pname:'SMTP', ptitle:'Simple Mail Transfer Protocol' },
{ port:43, pname:'WHOIS', ptitle:'whois Identification' },
{ port:53, pname:'DNS', ptitle:'Domain Name Service' },
{ port:68, pname:'DHCP', ptitle:'Dynamic Host Control Protocol' },
{ port:79, pname:'FINGER', ptitle:'Finger Identification' },
{ port:80, pname:'HTTP', ptitle:'HyperText Transfer Protocol' },
{ port:110, pname:'POP3', ptitle:'Post Office Protocol v3' },
{ port:115, pname:'SFTP', ptitle:'Secure File Transfer Protocol' },
{ port:119, pname:'NNTP', ptitle:'Network New Transfer Protocol' },
{ port:123, pname:'NTP', ptitle:'Network Time Protocol' },
{ port:139, pname:'NetBIOS',ptitle:'NetBIOS Communication' },
{ port:143, pname:'IMAP', ptitle:'Internet Message Access Protocol' },
{ port:161, pname:'SNMP', ptitle:'Simple Network Management Protocol' },
{ port:194, pname:'IRC', ptitle:'Internet Relay Chat' },
{ port:220, pname:'IMAP3', ptitle:'Internet Message Access Protocol v3' },
{ port:389, pname:'LDAP', ptitle:'Lightweight Directory Access Protocol' },
{ port:443, pname:'SSL', ptitle:'Secure Socket Layer' },
{ port:445, pname:'SMB', ptitle:'NetBIOS over TCP' },
{ port:993, pname:'SIMAP', ptitle:'Secure IMAP Mail' },
{ port:995, pname:'SPOP', ptitle:'Secure POP Mail' }
];
var table = new Y.DataTable({
columns : [
{ key: 'select',
allowHTML: true, // to avoid HTML escaping
label: '<input type="checkbox" class="protocol-select-all" title="Toggle ALL records"/>',
formatter: '<input type="checkbox" checked/>',
emptyCellValue: '<input type="checkbox"/>'
},
{ key: 'port', label: 'Port No.' },
{ key: 'pname', label: 'Protocol' },
{ key: 'ptitle', label: 'Common Name' }
],
data : ports,
scrollable: 'y',
height : '250px',
sortable : ['port','pname'],
sortBy : 'port',
recordType: ['select', 'port', 'pname', 'ptitle']
}).render("#dtable");
// To avoid checkbox click causing harmless error in sorting
// Workaround for bug #2532244
table.detach('*:change');
//----------------
// "checkbox" Click listeners ...
//----------------
// Define a listener on the DT first column for each record's "checkbox",
// to set the value of `select` to the checkbox setting
table.delegate("click", function(e){
// undefined to trigger the emptyCellValue
var checked = e.target.get('checked') || undefined;
// Don't pass `{silent:true}` if there are other objects in your app
// that need to be notified of the checkbox change.
this.getRecord(e.target).set('select', checked, { silent: true });
// Uncheck the header checkbox
this.get('contentBox')
.one('.protocol-select-all').set('checked', false);
}, ".yui3-datatable-data .yui3-datatable-col-select input", table);
// Also define a listener on the single TH "checkbox" to
// toggle all of the checkboxes
table.delegate('click', function (e) {
// undefined to trigger the emptyCellValue
var checked = e.target.get('checked') || undefined;
// Set the selected attribute in all records in the ModelList silently
// to avoid each update triggering a table update
this.data.invoke('set', 'select', checked, { silent: true });
// Update the table now that all records have been updated
this.syncUI();
}, '.protocol-select-all', table);
//----------------
// CSS-Button click handlers ....
//----------------
function process() {
var ml = table.data,
msg = '',
template = '<li>Record index = {index} Data = {port} : {pname}</li>';
ml.each(function (item, i) {
var data = item.getAttrs(['select', 'port', 'pname']);
if (data.select) {
data.index = i;
msg += Y.Lang.sub(template, data);
}
});
Y.one("#processed").setHTML(msg || '<li>(None)</li>');
}
Y.one("#btnSelected").on("click", process);
Y.one("#btnClearSelected").on("click",function () {
table.data.invoke('set', 'select', undefined);
// Uncheck the header checkbox
table.get('contentBox')
.one('.protocol-select-all').set('checked', false);
process();
});
});