Created
February 23, 2010 00:00
-
-
Save mattparker/311684 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>Extending DataTable - showing and hiding columns</title> | |
<link type="text/css" rel="stylesheet" href="http://developer.yahoo.com/yui/3/assets/yui.css"> | |
<link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/3.0.0/build/cssfonts/fonts-min.css"> | |
<style type="text/css"> | |
/* Any special style you need beyond the usual skinning */ | |
</style> | |
<!-- Combo-handled YUI CSS files: --> | |
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.8.0r4/build/datatable/assets/skins/sam/datatable.css&2.8.0r4/build/menu/assets/skins/sam/menu.css"> | |
<!-- Combo-handled YUI JS files: --> | |
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.8.0r4/build/yahoo/yahoo.js&2.8.0r4/build/dom/dom.js&2.8.0r4/build/event/event.js&2.8.0r4/build/element/element.js&2.8.0r4/build/datasource/datasource.js&2.8.0r4/build/datatable/datatable.js&2.8.0r4/build/container/container_core.js&2.8.0r4/build/menu/menu.js"></script> | |
<script type="text/javascript" src="http://github.com/mattparker/Yui-DataTable-extension---column-chooser/raw/master/lplt-datatable.js"></script> | |
</head> | |
<body id="yahoo-com" class="yui-skin-sam"> | |
<div id="doc3" class="yui-t2"> | |
<div id="bd"> | |
<div id="yui-main"> | |
<div class="yui-b"> | |
<div class="yui-ge"> | |
<div id="main" class="yui-u first example"> | |
<h2>DataTable: Column visibility context menu</h2> | |
<div id="example" class="promo"> | |
<div class="example-intro"> | |
<p> DataTables and their datasources can hold more data than you might want | |
to display - the example Showing, Hiding and Reordering Columns | |
<a href="http://developer.yahoo.com/yui/examples/datatable/dt_colshowhide.html" | |
title="Datatable column show and hide example"> | |
http://developer.yahoo.com/yui/examples/datatable/dt_colshowhide.html</a> | |
shows one way to add column showing/hiding to a datatable. | |
</p> | |
<p> | |
Sometimes you want features like this on all your DataTables (or any widget): | |
then it might be time to extend the widget. Satyam's written a couple of | |
articles on getting started with exending the YUI library: | |
<a href="http://yuiblog.com/blog/2008/06/24/buildingwidgets" | |
title="Satyams article on builing your own library and extending from Element"> | |
http://yuiblog.com/blog/2008/06/24/buildingwidgets</a>. | |
</p> | |
<p> | |
This example shows how to take an example and build it in. We're going to use | |
a ContextMenu that's triggered by a right-click on the table header. The | |
items on the ContextMenu will be the column labels, with some checks showing | |
which columns are currently visible. The ID column is initially hidden. | |
</p> | |
</div> | |
<div class="module example-container"> | |
<div id="example-canvas" class="bd"> | |
<p> <!-- Container for the table --> | |
<div id="basic"></div> | |
</p> | |
<script> | |
(function(){ | |
var Data = { | |
bookorders: [ | |
{id:"po-0167", date:new Date(1980, 2, 24), quantity:1, amount:4, title:"A Book About Nothing"}, | |
{id:"po-0783", date:new Date("January 3, 1983"), quantity:null, amount:12.12345, title:"The Meaning of Life"}, | |
{id:"po-0297", date:new Date(1978, 11, 12), quantity:12, amount:1.25, title:"This Book Was Meant to Be Read Aloud"}, | |
{id:"po-1482", date:new Date("March 11, 1985"), quantity:6, amount:3.5, title:"Read Me Twice"} | |
] | |
}; | |
var myColumnDefs = [ | |
{key:"id",label:"ID", hidden:true, sortable:true, resizeable:true}, | |
{key:"date", formatter:YAHOO.widget.DataTable.formatDate, sortable:true, sortOptions:{defaultDir:YAHOO.widget.DataTable.CLASS_DESC},resizeable:true}, | |
{key:"quantity", formatter:YAHOO.widget.DataTable.formatNumber, sortable:true, resizeable:true}, | |
{key:"amount", label: "Cost" ,formatter:YAHOO.widget.DataTable.formatCurrency, sortable:true, resizeable:true}, | |
{key:"title", sortable:true, resizeable:true} | |
]; | |
var myDataSource = new YAHOO.util.DataSource( Data.bookorders); | |
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY; | |
myDataSource.responseSchema = { | |
fields: ["id","date","quantity","amount","title"] | |
}; | |
myDataTable = new YAHOO.LPLT.DataTable("basic", | |
myColumnDefs, myDataSource, {showColumnChooser: true, | |
// And just because I'm British... | |
dateOptions: { format: "%d/%m/%Y", locale: "en" }, | |
currencyOptions :{ prefix: "£" }}); | |
})(); | |
</script> | |
</div> | |
</div> | |
</div> | |
<h3 class="first">Extending the DataTable widget</h3> | |
<p>We start by creating the constructor for the new widget, all wrapped in an | |
anonymous function with some shortcuts:</p> | |
<textarea> | |
(function(){ | |
var Lang = YAHOO.lang, | |
Dom = YAHOO.util.Dom; | |
/** | |
* Constructor: parent does the work, and we add a listener to set up | |
* the columnchooser | |
*/ | |
var lpltTable = function( el , oColDef, oDS, oCfg ){ | |
lpltTable.superclass.constructor.call( this, el , oColDef, oDS, oCfg ); | |
// Events when we need to build/rebuild the menu | |
this.on( "initEvent" , this.initColumnChooser, this, true ); | |
this.on( "columnInsertEvent" , this.refreshColumnChooserMenu, this, true ); | |
this.on( "columnRemoveEvent" , this.refreshColumnChooserMenu, this, true ); | |
this.on( "columnReorderEvent" , this.refreshColumnChooserMenu, this, true ); | |
}; | |
</textarea> | |
<p> | |
The call to the superclass constructor will get us a nice new DataTable. Once | |
we've done that we listen to some events that we'll be interested in. The first, | |
initEvent, will set up the context menu; the other three will refresh it if | |
we later decide to add columns, remove them, or re-order them. These events | |
call methods that don't exist yet, initColumnChooser and refreshColumnChooserMenu | |
- they'll come shortly. | |
</p> | |
<h4 id="step2">Extending - adding config options</h4> | |
<p> | |
Next we need to add the code to make the context menu and so on. We do this | |
by extending YAHOO.widget.DataTable to our new constructor. The third argument | |
is an object with the properties and methods we want to add to the base | |
DataTable.</p> | |
<textarea>YAHOO.extend( lpltTable, YAHOO.widget.DataTable, { | |
/** | |
* @description Reference to the column chooser context menu | |
* @type Object | |
*/ | |
_columnChooserMenu:null, | |
initAttributes: function( oCfg ){ | |
lpltTable.superclass.initAttributes.call( this, oCfg ); | |
/** | |
* @attribute showColumnChooser | |
* @description Should the column chooser menu should be used | |
* @type Bool | |
* @default false | |
*/ | |
this.setAttributeConfig( 'showColumnChooser' , { | |
validator: function(v){ return Lang.isBoolean( v ); }, | |
value: false | |
} | |
); | |
}, | |
</textarea> | |
<p> The first things to do are to add a property to store the ContextMenu in, | |
and then add an initAttributes method. This allows us to add config options | |
really easily. As before we call the 'parent' class initAttributes first, | |
and then add a simple config option, showColumnChooser. There's a fair bit | |
more you can do with these - read Satyam's article if you haven't already. | |
</p> | |
<h4 id="step3">Build the context menu</h4> | |
<p> | |
Now we start doing some real work: the initColumnChooser method | |
sets up the ContextMenu. The trigger: this.getTheadEl() will link it | |
to the table header. Of course, because we've extended the DataTable we | |
have access to all its methods, and our scope means we can use 'this' | |
to refer to our new DataTable object. </p> | |
<textarea> | |
/** | |
* Adds a context menu that takes columns and allows them to be shown | |
* or hidden | |
* | |
*/ | |
initColumnChooser: function(){ | |
// check if column chooser should be used: | |
if( !this.get( "showColumnChooser" ) ){ return; } | |
// create a context menu and add items to it, one for each column | |
var cMenu = new YAHOO.widget.ContextMenu( Dom.generateId( ), | |
{ trigger: this.getTheadEl() } ); | |
this._addColumnChooserMenuItems( cMenu ); | |
// render the menu | |
cMenu.render( this.get("element") ); | |
// menu click handler | |
cMenu.subscribe( "click", this.doColumnChooser, this, true ); | |
// And a column show/hide handler that updates the menu check states. | |
// Doing it this way means that if columns are shown/hidden in other | |
// ways the menu will be up to date | |
this.subscribe( "columnHideEvent" , this.updateColumnChooser, this, true ); | |
this.subscribe( "columnShowEvent" , this.updateColumnChooser, this, true ); | |
// finally, make sure the context menu is destroyed when the table is: | |
this.subscribe( "beforeDestroyEvent" , this.destroyColumnChooser , this, true); | |
// Keep a reference to it: | |
this._columnChooserMenu = cMenu; | |
}, | |
</textarea> | |
<h4 id="step4">Adding items</h4> | |
<p> | |
So this method creates a ContextMenu, renders it, and listens for important | |
events. But we're going to want to add items to the menu more than | |
once, so we outsource them to _addColumnChooserMenuItems. | |
</p> | |
<textarea> /** | |
* Adds columns to the column chooser context menu | |
* @param {YAHOO.widget.ContextMenu} Context menu to add items to | |
*/ | |
_addColumnChooserMenuItems: function( cMenu ){ | |
// loop through the columns: | |
var allColumns = this.getColumnSet().keys; | |
for( var i = 0, l = allColumns.length; i < l; i++ ){ | |
cMenu.addItem( { text: allColumns[ i ].label || allColumns[ i ].getKey(), | |
value: allColumns[ i ].getKey() , | |
checked: !allColumns[i].hidden } ); | |
} | |
}, | |
</textarea> | |
<p> | |
We loop through the ColumnSet and add items to the context menu. | |
We'll use the key for the value of the menu item so we can easily | |
find our column later when someone clicks on it. | |
</p> | |
<h4 id="step5">Keeping up-to-date</h4> | |
<p> The next two methods are make sure things are up to date if columns get | |
hidden, moved etc by other means. They loop through, either re-adding | |
the items to the menu, or updating the checks. | |
</p> | |
<textarea> /** | |
* Clears the column chooser context menu and re-reads columns | |
*/ | |
refreshColumnChooserMenu: function(){ | |
var cMenu = this._columnChooserMenu; | |
if( cMenu === undefined || cMenu === null ){ | |
return false; | |
} | |
cMenu.clearContent(); | |
this._addColumnChooserMenuItems( cMenu ); | |
this._columnChooserMenu = cMenu; | |
}, | |
/** | |
* Updates the 'checked' state of the menu based on visibility of cols. | |
*/ | |
updateColumnChooser: function( ){ | |
// loop through the columns: | |
var allColumns = this.getColumnSet().keys, | |
cMenu = this._columnChooserMenu; | |
for( var i = 0, l = allColumns.length; i < l; i++ ){ | |
cMenu.getItem( i ).cfg.setProperty( "checked", !allColumns[i].hidden ); | |
} | |
}, | |
</textarea> | |
<h4 id="step6">Handling a context menu click</h4> | |
<p> | |
Up to now, we'll have a context menu OK, but clicking won't do anything | |
(except throw an error): we need to write the handler for the context menu | |
click.</p> | |
<textarea> | |
/** | |
* Handler for column chooser context menu | |
* @param String will be "click" | |
* @param Array Arguments passed: first is Event Object, second is the | |
* MenuItem that was clicked | |
* @param Object The menu | |
*/ | |
doColumnChooser: function( strEv, obEv, menu ){ | |
// This is the menuitem | |
var tar = obEv[1], | |
// This is the column: | |
col = this.getColumn( tar.value ); | |
// If column is hidden, show it: | |
if( col.hidden ){ | |
this.showColumn( col ); | |
} | |
else{ | |
this.hideColumn( col ); | |
} | |
}, | |
</textarea> | |
<p> | |
The second argument gives us access to the menuitem that was clicked, | |
and the value of the menuitem is the key for the column, so it's | |
easy to grab the column and show or hide it. | |
</p> | |
<h4 id="step7">Destroying it all </h4> | |
<p> | |
Finally, we keep things tidy. If you've got a lot going on on your | |
app, you'll likely need to do some tidying up after yourself | |
to stop the DOM getting too bloated and filling up with listeners until | |
it all grinds to a halt. One of the keys to this is destroying widgets | |
when you're done with them. | |
</p> | |
<p> | |
By default there's no destroy event in DataTable, but there is a | |
destroy method. But we need to know | |
when the table's being destroyed so we can destroy the ContextMenu. Fortunately, | |
it's straightforward to add an event: </p> | |
<textarea> | |
/** | |
* Over-ride destroy to fire an event first and then do the destruction | |
*/ | |
destroy: function(){ | |
this.fireEvent( "beforeDestroyEvent" ); | |
lpltTable.superclass.destroy.call( this ); | |
} , | |
</textarea> | |
<p> | |
Right back when we were setting up the ContextMenu we subscribed to the | |
beforeDestroyEvent (that we've just created) | |
to call destroyColumnChooser: so we'd better destroy | |
the ContextMenu: | |
</p> | |
<textarea> | |
destroyColumnChooser: function(){ | |
if( this._columnChooserMenu !== undefined ){ | |
this._columnChooserMenu.destroy(); | |
} | |
} | |
</textarea> | |
<h4 id="step8">Adding it to our namespace.</h4> | |
<p> | |
Everything we've done up to now is in an anonymous function, so we'll | |
give ourselves a namespace to make it accessible. Of course you can | |
use any namespace you like. | |
</p> | |
<textarea> | |
YAHOO.namespace("LPLT"); | |
YAHOO.LPLT.DataTable = lpltTable; | |
</textarea> | |
<h3 id="allcode">Full Code listing</h3> | |
<textarea> | |
(function(){ | |
var Lang = YAHOO.lang, | |
Dom = YAHOO.util.Dom; | |
/** | |
* Constructor: parent does the work, and we add a listener to set up | |
* the columnchooser | |
*/ | |
var lpltTable = function( el , oColDef, oDS, oCfg ){ | |
lpltTable.superclass.constructor.call( this, el , oColDef, oDS, oCfg ); | |
// Events when we need to build/rebuild the menu | |
this.on( "initEvent" , this.initColumnChooser, this, true ); | |
this.on( "columnInsertEvent" , this.refreshColumnChooserMenu, this, true ); | |
this.on( "columnRemoveEvent" , this.refreshColumnChooserMenu, this, true ); | |
this.on( "columnReorderEvent" , this.refreshColumnChooserMenu, this, true ); | |
}; | |
YAHOO.extend( lpltTable, YAHOO.widget.DataTable, { | |
/** | |
* @description Reference to the column chooser context menu | |
* @type Object | |
*/ | |
_columnChooserMenu:null, | |
initAttributes: function( oCfg ){ | |
lpltTable.superclass.initAttributes.call( this, oCfg ); | |
/** | |
* @attribute showColumnChooser | |
* @description Should the column chooser menu should be used | |
* @type Bool | |
* @default false | |
*/ | |
this.setAttributeConfig( 'showColumnChooser' , { | |
validator: function(v){ return Lang.isBoolean( v ); }, | |
value: false | |
} | |
); | |
/** | |
* @attribute multiCellEditNav | |
* @description Turns on navigation around the table with inline editing | |
* @type Bool | |
* @default false | |
*/ | |
this.setAttributeConfig( 'multiCellEditNav' , { | |
validator: function(v){ return Lang.isBoolean( v ); }, | |
value: false, | |
method: function( v ){ | |
if ( v === true ){ | |
this.on( "editorKeydownEvent", this.moveCellsWhileEditing , this , true ); | |
this.on( "editorShowEvent", this.keepEditorVisible, this, true ); | |
} | |
} | |
} | |
); | |
}, | |
/** | |
* Adds a context menu that takes columns and allows them to be shown | |
* or hidden | |
* | |
*/ | |
initColumnChooser: function(){ | |
// check if column chooser should be used: | |
if( !this.get( "showColumnChooser" ) ){ return; } | |
// create a context menu and add items to it, one for each column | |
var cMenu = new YAHOO.widget.ContextMenu( Dom.generateId( ), | |
{ trigger: this.getTheadEl() } ); | |
this._addColumnChooserMenuItems( cMenu ); | |
// render the menu | |
cMenu.render( this.get("element") ); | |
// menu click handler | |
cMenu.subscribe( "click", this.doColumnChooser, this, true ); | |
// And a column show/hide handler that updates the menu check states. | |
// Doing it this way means that if columns are shown/hidden in other | |
// ways the menu will be up to date | |
this.subscribe( "columnHideEvent" , this.updateColumnChooser, this, true ); | |
this.subscribe( "columnShowEvent" , this.updateColumnChooser, this, true ); | |
// finally, make sure the context menu is destroyed when the table is: | |
this.subscribe( "beforeDestroyEvent" , this.destroyColumnChooser , this, true); | |
// Keep a reference to it: | |
this._columnChooserMenu = cMenu; | |
}, | |
/** | |
* Clears the column chooser context menu and re-reads columns | |
*/ | |
refreshColumnChooserMenu: function(){ | |
var cMenu = this._columnChooserMenu; | |
if( cMenu === undefined || cMenu === null ){ | |
return false; | |
} | |
cMenu.clearContent(); | |
this._addColumnChooserMenuItems( cMenu ); | |
this._columnChooserMenu = cMenu; | |
}, | |
/** | |
* Updates the 'checked' state of the menu based on visibility of cols. | |
*/ | |
updateColumnChooser: function( ){ | |
// loop through the columns: | |
var allColumns = this.getColumnSet().keys, | |
cMenu = this._columnChooserMenu; | |
for( var i = 0, l = allColumns.length; i < l; i++ ){ | |
cMenu.getItem( i ).cfg.setProperty( "checked", !allColumns[i].hidden ); | |
} | |
}, | |
/** | |
* Adds columns to the column chooser context menu | |
* @param {YAHOO.widget.ContextMenu} Context menu to add items to | |
*/ | |
_addColumnChooserMenuItems: function( cMenu ){ | |
// loop through the columns: | |
var allColumns = this.getColumnSet().keys; | |
for( var i = 0, l = allColumns.length; i < l; i++ ){ | |
cMenu.addItem( { text: allColumns[ i ].label || allColumns[ i ].getKey(), | |
value: allColumns[ i ].getKey() , | |
checked: !allColumns[i].hidden } ); | |
} | |
}, | |
/** | |
* Handler for column chooser context menu | |
* @param String will be "click" | |
* @param Array Arguments passed: first is Event Object, second is the | |
* MenuItem that was clicked | |
* @param Object The menu | |
*/ | |
doColumnChooser: function( strEv, obEv, menu ){ | |
// This is the menuitem | |
var tar = obEv[1], | |
// This is the column: | |
col = this.getColumn( tar.value ); | |
// If column is hidden, show it: | |
if( col.hidden ){ | |
this.showColumn( col ); | |
} | |
else{ | |
//tar.cfg.setProperty( "checked" , true ); | |
this.hideColumn( col ); | |
} | |
}, | |
/** | |
* Destroys the column chooser context menu. | |
*/ | |
destroyColumnChooser: function(){ | |
if( this._columnChooserMenu !== undefined ){ | |
this._columnChooserMenu.destroy(); | |
} | |
}, | |
/** | |
* Over-ride destroy to fire an event first and then do the destruction | |
*/ | |
destroy: function(){ | |
this.fireEvent( "beforeDestroyEvent" ); | |
lpltTable.superclass.destroy.call( this ); | |
} | |
} | |
); | |
YAHOO.namespace( "LPLT" ); | |
YAHOO.LPLT.DataTable = lpltTable; | |
})(); | |
</textarea> | |
<h3>About...</h3> | |
<p style="margin-top: 2em;">Matt Parker is the technical director | |
at <a href="http://www.lamplightdb.co.uk" title="Lamplight Database Systems Limited"> | |
Lamplight Database Systems,</a> a small company based in London, UK | |
that supplies a comprehensive management system to charities | |
and non-profits. | |
</p> | |
</div> | |
<div class="yui-u sidebar"> | |
<div class="mod box4" style="height: 400px; line-height: 200px; text-align: center;">(sidebar)</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="yui-b toc3" style="height: 600px; line-height: 200px; text-align: center;">(left nav)</div> | |
</div> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment