Yahoo! UI Library

RowExpansionDataTable Widget  1.0

Yahoo! UI Library > rowexpansiondatatable > rowexpansion.js (source view)
Search:
 
Filters
/**********
*
* Row Expansion DataTable for YUI 2
* Author: gelinase@yahoo-inc.com / Eric Gelinas
* <br/>
* Modified by Daniel Barreiro (satyam@satyam.com.ar)
* @module rowexpansiondatatable
* @requires yahoo, dom, event, element, datasource, datatable
* @title RowExpansionDataTable Widget
***********/
(function(){

    var Dom = YAHOO.util.Dom,
		Event = YAHOO.util.Event,
		Lang = YAHOO.lang,
		DT = YAHOO.widget.DataTable,

        ROW_EXPANSION = 'yuiDtRowExpansion',
		
		TEMPLATE = 'rowExpansionTemplate',

        CLASS_EXPANDED = 'yui-dt-expansion-expanded',
        CLASS_COLLAPSED = 'yui-dt-expansion-collapsed',
        CLASS_EXPANSION = 'yui-dt-expansion-expansion',
        CLASS_LINER = 'yui-dt-expansion-liner',
		CLASS_TRIGGER = 'yui-dt-expansion-trigger';


    /**
    * The RowExpansionDataTable class extends the DataTable class to provide
    * functionality for expanding rows to show more contextual data.
    *
    * @namespace YAHOO.widget
    * @class RowExpansionDataTable
    * @extends YAHOO.widget.DataTable
    * @constructor
    * @param elContainer {HTMLElement} Container element for the TABLE.
    * @param aColumnDefs {Object[]} Array of object literal Column definitions.
    * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
    * @param oConfigs {object} (optional) Object literal of configuration values.
    */
    var REDT = function(elContainer,aColumnDefs,oDataSource,oConfigs) {

		// add trigger column
		aColumnDefs.unshift({
			key:ROW_EXPANSION,
			label:'',
			className:CLASS_TRIGGER
		});
        REDT.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 

    };
	
	YAHOO.widget.RowExpansionDataTable = REDT;

	// Copy over DataTable constants and other static members
	Lang.augmentObject(REDT, DT);
		
    Lang.extend( 
        REDT,
        DT, 
        {
			/**
			 * Initialize internal event listeners
			 *
			 * @method _initEvents
			 * @private
			 */
			_initEvents: function () {
				REDT.superclass._initEvents.call(this);

                this.on( 'postRenderEvent', this.restoreExpandedRows );			
				this.on( 'cellClickEvent', this.onEventToggleRowExpansion );

			},
			/**
			 * Loops through all Record instances in the RecordSet
			 * @method forAllRecords
			 * @param fn {Function} Reference to a function that will be called once for ever record.  
			 *                      fn will receive a Record instance and the index for the record as its arguments.
			 *                      fn may return false to break out of the loop.
			 * @param scope {Object} (optional) Scope to execute fn in.  It defaults to the DataTable instance.
			 *
			 */
			forAllRecords: function (fn, scope) {
				if (!Lang.isFunction(fn)) {return;}
				scope = scope || this;
				for (var recs = this._oRecordSet._records, l = recs.length, i = 0; i < l; i++) {
					if (fn.call(scope, recs[i], i) === false) {return;}
				}
			},

            /**
             * Gets expansion state object for a specific record associated with the
             * DataTable.
             *
             * @method getExpansionState
             * @param {Mixed} recordId Record / Row / or Index id
             * @param {String} key  (optional) Key to return within the state object. Default is to
             * return all as a map
             * @return {Object} State data object or value for Key. 
			 * The full state object contains <ul>
			 * <li><b>record</b>: reference to the associated Record instance</li>
			 * <li><b>expanded</b>: true when the row is expanded</li>
			 * <li><b>expTrEl</b>: the TR element for the expansion row</li>
			 * <li><b>expLinerEl</b>: the container for the expansion</li>
			 * <li><i>other</i>: Developers may store extra information in it</li>
			 * </ul>
            **/
            getExpansionState : function( recordId, key ){

                var record = this.getRecord( recordId ),
                    expansionState = record._expansionState;
				if (key) {
					return expansionState?expansionState[key]:null;
				} else {
					expansionState = expansionState || {};
					expansionState.record = record;
					return expansionState;
				}
            },

            /**
             * Sets a value to a expansion state object with a given Key for a record
             * which is associated with the DataTable
             *
             * @method setExpansionState
             * @param {Mixed} recordId Record / Row / or Index id
             * @param {String} key Key to use in map
             * @param {Mixed} value Value to assign to the key
             * @return {Object} State data object
            **/
            setExpansionState : function( recordId, key, value ){

                var record = this.getRecord( recordId ),
                    expansionState = record._expansionState || (record._expansionState = {});

                expansionState[ key ] = value;

                return expansionState;

            },

            /**
             * Over-ridden initAttributes method from DataTable, inherited from Element
             *
             * @method initAttributes
             * @param {Object} configuration attributes taken from the fourth argument to the constructor
            **/
            initAttributes : function( oConfigs ) {

                oConfigs = oConfigs || {};

                REDT.superclass.initAttributes.call( this, oConfigs );

                /**
                 * Template for the expansion row.  
				 * If a String, it will be processed through YAHOO.lang.substitute and will have all the values
				 * in the associated Record available.
				 * If a Function, it will receive the expansion state object, see: <a href="#method_getExpansionState">getExpansionState</a></li>
                 *
                 * @attribute rowExpansionTemplate
                 * @type {String or Function} 
                 * @default null
                **/
                this.setAttributeConfig(TEMPLATE, {
                    value: null,
                    validator: function( template ){
                        return (
                            Lang.isString( template ) ||
                            Lang.isFunction( template )
                        );
                    }
                });

            },


            /**
             * Toggles the expansion state of a row
             *
             * @method toggleRowExpansion
             * @param {Mixed} recordId Record / Row / or Index id
            **/
            toggleRowExpansion : function( recordId ){

                if( this.getExpansionState( recordId,'expanded' ) ){

                    this.collapseRow( recordId );

                } else {

                    this.expandRow( recordId );

                }

            },

            /**
             * Sets the expansion state of a row to expanded
             *
             * @method expandRow
             * @param {Mixed} recordId Record / Row / or Index id
             * @return {Boolean} successful
            **/
            expandRow : function( recordId ){

                var state = this.getExpansionState( recordId );

                if( !state.expanded){
					this.setExpansionState( recordId, 'expanded', true );
					var expTrEl = state.expTrEl,
						record = state.record,
                        trEl = this.getTrEl( record );
					if (expTrEl) {
						Dom.insertAfter(state.expTrEl,this.getTrEl(recordId));
						Dom.setStyle(state.expTrEl,'display','');
					} else {

						expTrEl = document.createElement('tr');
						var expTdEl = document.createElement( 'td' ),
							expLinerEl = document.createElement( 'div' ),
							template = this.get(TEMPLATE);

						Dom.addClass(expTrEl, CLASS_EXPANSION);
						expTdEl.colSpan = this.getFirstTrEl().childNodes.length;
						Dom.addClass(expLinerEl, CLASS_LINER);
						expTrEl.appendChild( expTdEl );
						expTdEl.appendChild( expLinerEl);
						
						this.setExpansionState( recordId, 'expTrEl', expTrEl);
						this.setExpansionState( recordId, 'expLinerEl', expLinerEl);
						
						// refresh the copy of the expansion state
						state = this.getExpansionState(recordId);

						if( Lang.isString( template ) ){

							expLinerEl.innerHTML = Lang.substitute( template, record.getData() );

						} else if( Lang.isFunction( template ) ) {

							template.call(this,  state );

						} else {

							return false;

						}
					}

                    //Insert new row
                    Dom.insertAfter( expTrEl, trEl );




					Dom.replaceClass( trEl, CLASS_COLLAPSED, CLASS_EXPANDED );

					/**
					 * Fires when a row is expanded
					 *
					 * @event rowExpandedEvent
					 * @param state {Object} see <a href="#method_getExpansionState">getExpansionState</a>
					 */
					this.fireEvent( "rowExpandedEvent", state);

					return true;

                }

            },

            /**
             * Sets the expansion state of a row to collapsed
             * @method collapseRow
             * @param {Mixed} recordId Record / Row / or Index id
             * @return {Boolean} successful
            **/
            collapseRow : function( recordId ){

                var state = this.getExpansionState(recordId);

                if(state.expanded ){

                    this.setExpansionState( recordId, 'expanded', false );
					

                    Dom.replaceClass( this.getTrEl(state.record), CLASS_EXPANDED, CLASS_COLLAPSED );
					Dom.setStyle(state.expTrEl,'display','none');

					/**
					 * Fires when a row is collapsed
					 *
					 * @event rowCollapsedEvent
					 * @param state {Object} see <a href="#method_getExpansionState">getExpansionState</a>
					 */
					this.fireEvent("rowCollapsedEvent",this.getExpansionState(recordId));

					return true;

				} else {

					return false;

				}

            },

            /**
             * Collapses all expanded rows. 
             *
             * @method collapseAllRows
            **/
            collapseAllRows : function(){

				this.forAllRecords(this.collapseRow);

            },

            /**
             * Restores rows which have been expanded state but the original markup
			 * has been removed from the page.
             *
             * @method restoreExpandedRows
            **/
            restoreExpandedRows : function(){

				this.forAllRecords(function(record) {
					if (this.getExpansionState(record,'expanded')) {
						var trEl = this.getTrEl(record);
						if (trEl) {
							Dom.replaceClass(trEl,CLASS_COLLAPSED, CLASS_EXPANDED);        
							Dom.insertAfter(this.getExpansionState(record,'expTrEl'),trEl);
						}
					}
				});

            },
			/**
			 * returns the container element for the expansion
			 * @method getExpansionContainer
             * @param {Mixed} recordId Record / Row / or Index id
			 * @return {HTMLElement} container element
			 */
			getExpansionContainer: function (recordId) {
				return this.getExpansionState(recordId,'expLinerEl');
			},


            /**
             * Helper method which toggles row expansion. 
             *
             * @method onEventToggleRowExpansion
             * @param {Object} oArgs context of a subscribed event
            **/
            onEventToggleRowExpansion : function( oArgs ){
				var column = this.getColumn(oArgs.target);
				if (column && column.key == ROW_EXPANSION) {

                    this.toggleRowExpansion( oArgs.target );
					Event.stopPropagation(oArgs.event);
					return false;

                }

            },
			/**
			 * Destroys a expansion row
			 * @method _destroyExpansion
			 * @param recordId <HTMLElement | Number | String>    DOM reference to a TR element (or child of a TR element), RecordSet position index, or Record ID.
			 * @private
			 */
			_destroyExpansion: function (recordId) {
				var state = this.getExpansionState(recordId),
					expTrEl = state.expTrEl;
				if (expTrEl) {
					/**
					 * Fires when a row is about to be deleted
					 *
					 * @event rowExpansionDestroyEvent
					 * @param state {Object} see <a href="#method_getExpansionState">getExpansionState</a>
					 */
					this.fireEvent('rowExpansionDestroyEvent', state);
					expTrEl.parentNode.removeChild(expTrEl);
				}
			},
			/**
			 * Overrides DataTable's destroy method to also destroy expansion rows.
			 * @method destroy
			 */
			destroy: function() {
				this.forAllRecords(this._destroyExpansion);
				REDT.superclass.destroy.apply(this, arguments);
			
			},
			/**
			 * This is an override for DataTable's own <code>initializeTable</code>
			 * which it calls after it destroys all expansion rows
			 *
			 * @method initializeTable
			 */
			initializeTable: function () {
				this.forAllRecords(this._destroyExpansion);
				REDT.superclass.initializeTable.apply(this, arguments);
			},
			/**
			 * This is an override for DataTable's own <code>onDataReturnSetRows</code>
			 * which it calls after it destroys all expansion rows since
			 * with server-side pagination, with every new page it ignores the previous records
			 *
			 * @method onDataReturnSetRows
			 */
			onDataReturnSetRows: function () {
				this.forAllRecords(this._destroyExpansion);
				REDT.superclass.onDataReturnSetRows.apply(this, arguments);
			},
			/**
			 * This is an override for DataTable's own <code>deleteRow</code>
			 * to delete the expansion row before the main row is deleted
			 *
			 * @method deleteRow
			 * @param row {HTMLElement | String | Number} DOM element reference or ID string
			 * to DataTable page element or RecordSet index.
			 */
			deleteRow:function (row) {
				this._destroyExpansion(row);
				REDT.superclass.deleteRow.apply(this,arguments);
			},
			/**
			 * This is an override for DataTable's own <code>deleteRows</code>
			 * to delete the expansion rows before the main rows are deleted
			 *
			 * @method deleteRows
			 * @param row {HTMLElement | String | Number} DOM element reference or ID string
			 * to DataTable page element or RecordSet index.
			 * @param count {Number} (optional) How many rows to delete. A negative value
			 * will delete towards the beginning.
			 */
			deleteRows:function (row, count) {
				if (count > 0) {
					while (count--) {
						this._destroyExpansion(row);
						row = this.getNextTrEl(row);
					}
				} else {
					while (count++) {
						this._destroyExpansion(row);
						row = this.getPreviousTrEl(row);
					}
				}
				REDT.superclass.deleteRows.apply(this,arguments);
			}
		}
	);

})();

Copyright © 2010 Yahoo! Inc. All rights reserved.