/**********
*
* 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);
            }
        }
    );

})();