/*
 * Ext Scheduler v1.0 beta
 * Copyright(c) 2009-2010 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 * This file is partially based on Maxim TreeGrid, BSD license:
 *   Copyright (c) 2009, Maxim G. Bazhenov
 *
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *
 *    Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright notice, this 
 *    list of conditions and the following disclaimer in the documentation and/or 
 *    other materials provided with the distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 *   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 *   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 *   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
Ext.ns('Sch');

/**
 * @class Sch.TreeGanttPanel
 * @extends Sch.EditorSchedulerPanel
 * <p>The Gantt panel, encapsulating the Gantt specific functionality. Use this together with either a GanttView or a GroupingGanttView as shown in the various examples.</p>
 * <pre><code>
 *
 * var g = new Sch.TreeGanttPanel({
 *            height : 600,
 *            width: 1000,
 *            enableGrouping : true,
 *            renderTo : Ext.getBody(),
 *            leftLabelField : 'Title',
 *            rightLabelField : 'Responsible',
 *            
 *            viewModel : {
 *                start : start, 
 *                end : end, 
 *                columnType : 'weekAndDays',
 *                viewBehaviour : Sch.ViewBehaviour.WeekView
 *            });
 * </code></pre>
 * <p>The columnType property maps to constructor functions used to create the actual grid columns (defined in Sch.ColumnFactory). Possible values:</p>
 *<ul>
 *   <li>quarterMinutes</li>
 *   <li>hour</li>
 *   <li>hourAndDay</li>     
 *   <li>dayAndHours</li>    
 *   <li>day</li>
 *   <li>dayAndWeeks</li>    
 *   <li>dayAndMonths</li>   
 *   <li>dayWeekAndMonths</li>  
 *   <li>week</li>   
 *   <li>weekAndMonths</li>  
 *   <li>weekAndDays</li>    
 *   <li>month</li>
 *   <li>monthAndQuarters</li>   
 *   <li>year</li>   
 *</ul>
 * @xtype gantt
 */
Sch.TreeGanttPanel = Ext.extend(Sch.EditorSchedulerPanel, {
    
    clicksToEdit : 1,
    
    /*
     * @cfg {string} overCls
     * CSS class to add to the event element on hover
     */
    overClass : 'sch-event-hover',
    
    /**
     * @cfg {String} resizeHandles Defines which resize handles to show. Accepted values are 'none', 'left', 'right', 'both', defaults to 'both'.
     */
    resizeHandles : 'both',
    
    /**
     * @cfg {Boolean} highlightWeekends
     * True to highlight weekends
     */
    highlightWeekends : true,
    
    /**
     * @cfg {Boolean} enableTaskDragDrop
     * True to allow drag drop of tasks
     */
    enableTaskDragDrop : true,
    
    /**
     * @cfg {Boolean} enableDependencyDragDrop
     * True to allow creation of dependencies by using drag and drop between task terminals
     */
    enableDependencyDragDrop : true,
     
    /**
     * @cfg {Boolean} enableTaskContextMenu
     * True to enable task context menu
     */
    enableTaskContextMenu: true,
    
    /**
     * @cfg {Boolean} enableLabelEdit
     * True to allow inline editing of labels
     */
    enableLabelEdit : true,
    
    /**
     * @cfg {Boolean} enableLockedFieldsEdit
     * True to allow inline editing of labels
     */
    enableLockedFieldsEdit : true,
    
    /**
     * @cfg {Boolean} recalculateParentsAfterEdit
     * True to update parent start/end dates after a task has been updated
     */
    recalculateParentsAfterEdit: true,
    
    /**
     * @cfg {Boolean} cascadeChanges
     * True to cascade date changes to tasks that depend on the modified task
     */
    cascadeChanges: false,
     
    /**
    * @cfg {Boolean} showTreeLines
    * True to show lines outlining the tree structure (not yet supported)
    */
    showTreeLines : false,
     
    /**
     * @cfg {Boolean} highlightAffectedTasks
     * True to highlight affected tasks while editing (only applicatble when cascadeChanges is set to true), whether it be inline editing, drag and drop or resize operations
     */
    highlightAffectedTasks: true,

    /**
     * Wrapper function returning the dependency manager instance
     * @return {Sch.DependencyManager} dependencyManager The dependency manager instance
     */
    getDependencyManager : function() {
        return this.dependencyManager;
    },
    
    /**
     * Toggle weekend highlighting
     * @param {Boolean} disabled 
     */
    disableWeekendHighlighting : function(disabled) {
        this.weekendZonesPlugin.setDisabled(disabled);
    },
    
    /**
     * This function updates only the column header width, could be used with a slider to give a preview of the new column width before making the change (which is a heavy operation)
     * @param {Int} width The new header width 
     */
    updateTimeColumnHeaderWidths : function(width) {
        var cm = this.getColumnModel();
        for (var i = this.nbrStaticColumns, l = cm.getColumnCount(); i < l; i++) {
	        cm.setColumnWidth(i, width, true);
	    }
	    
	    this.getView().updateHeaders();
    },
    
    /**
     * This function updates the time column widths of the gantt panel
     * @param {Int} width The new column width 
     */
    updateTimeColumnWidths : function(width) {
        this.getView().updateTimeColumnWidths(width);
    },
    
    /**
     * <p>Returns the event record for a DOM id </p>
     * @param {Mixed} el The DOM node or Ext Element to lookup
     * @return {Record} The event record
     */
    getEventRecordFromElement : function(el) {
        var element = Ext.get(el);
        if (!element.is(this.eventWrapSelector)) {
            element = element.up(this.eventWrapSelector);
        }
        return this.getEventRecordFromDomId(element.child(this.eventSelector).id);
    },
    
    /**
     * <p>Tries to fit the time columns to the available view width</p>
     */
    fitTimeColumns : function() {
        this.getView().fitTimeColumns();
    },
    
    //Returns the duration in days between two dates, which is different depending if you include weekends or not.
    getDuration : function(start, end) {
        // TODO check for weekends boolean
        return Math.round(Date.getDurationInDays(start, end)*10)/10;
    },
    
    // private
    eventWrapSelector : '.sch-event-wrap',
    
    columnLines : false,
    
    // The amount of time to wait before processing parents after a record add/modify/delete. Aims to speed up the UI, set to 0 to process right away
    recalculateParentsDelay : 10,
    
    // private
    constructor : function(config) {
        this.addEvents(
            /**
             * @event labeledit_beforecomplete
             * Fires after a change has been made to a label field, but before the change is reflected in the underlying field.
             * @param {TreeGanttPanel} g The gantt panel object
             * @param {Mixed} value The current field value
             * @param {Mixed} startValue The original field value
             */
            'labeledit_beforecomplete', 
            
            /**
             * @event labeledit_complete
             * Fires after editing is complete and any changed value has been written to the underlying field.
             * @param {TreeGanttPanel} g The gantt panel object
             * @param {Mixed} value The current field value
             * @param {Mixed} startValue The original field value
             */
            'labeledit_complete'
        );
        config = config || {};
        Ext.applyIf(config, {
            plugins : []
        });
        
        Ext.apply(this, config);
        
        if (!config.eventTemplate) {
            config.eventTemplate = new Ext.Template(
                '<div class="sch-event-wrap sch-gantt-task" style="left:{leftOffset}px;width:{width}px">',
                    // Left label 
                    '<div class="sch-gantt-labelct-left"><label class="sch-gantt-label sch-gantt-label-left">{leftLabel}</label></div>',
                    
                    // Task bar
                    '<div id="{id}" class="sch-event sch-gantt-item sch-gantt-task-bar {cls}" style="width:{width}px">',
                        // Left terminal
                        config.enableDependencyDragDrop !== false ? '<div class="sch-gantt-terminal sch-gantt-terminal-start"></div>' : '',
                        (this.resizeHandles === 'both' || this.resizeHandles === 'left') ? String.format(this.resizeHandleHtml, 'west') : '',
                    
                        '<div class="sch-gantt-progress-bar" style="width:{percentDone}%">&#160;</div>',
                        (this.resizeHandles === 'both' || this.resizeHandles === 'right') ? String.format(this.resizeHandleHtml, 'east') : '',
                        // Right terminal
                        config.enableDependencyDragDrop !== false ? '<div class="sch-gantt-terminal sch-gantt-terminal-end"></div>' : '',
                    '</div>',
                    
                    // Right label 
                    '<div class="sch-gantt-labelct-right" style="left:{width}px"><label class="sch-gantt-label sch-gantt-label-right">{rightLabel}</label></div>',
                '</div>'
            ).compile();
        }
        
        if (!config.parentEventTemplate) {
            config.parentEventTemplate = new Ext.Template(
                '<div class="sch-event-wrap sch-gantt-parent-task" style="left:{leftOffset}px;width:{width}px">',
                    // Left label 
                    '<div class="sch-gantt-labelct-left"><label class="sch-gantt-label sch-gantt-label-left">{leftLabel}</label></div>',
                    
                    // Task bar
                    '<div id="{id}" class="sch-event sch-gantt-item sch-gantt-parenttask-bar {cls}" style="width:{width}px">',
                        '<div class="sch-gantt-parenttask-leftarrow"></div>'+
                        '<div class="sch-gantt-progress-bar" style="width:{percentDone}%">&#160;</div>',
                        '<div class="sch-gantt-parenttask-rightarrow"></div>'+
                    '</div>',
                    
                    // Right label 
                    '<div class="sch-gantt-labelct-right" style="left:{width}px"><label class="sch-gantt-label sch-gantt-label-right">{rightLabel}</label></div>',
                '</div>'
            ).compile();
        }
    
        if (!config.milestoneTemplate) {
            config.milestoneTemplate = new Ext.Template(
                '<div class="sch-event-wrap sch-gantt-milestone" style="left:{leftOffset}px">',
                    // Left label 
                    '<div class="sch-gantt-labelct-left"><label class="sch-gantt-label sch-gantt-label-left">{leftLabel}</label></div>',
                    
                    // Milestone indicator
                    '<div id="{id}" class="sch-event sch-gantt-item sch-gantt-milestone-diamond {cls}">',
                        // Left terminal
                        config.enableDependencyDragDrop !== false ? '<div ext:qtip="' + this.dependencyTerminalTooltip + '" class="sch-gantt-terminal sch-gantt-terminal-start"></div>' : '',
                        
                        // Right terminal
                        config.enableDependencyDragDrop !== false ? '<div ext:qtip="' + this.dependencyTerminalTooltip + '" class="sch-gantt-terminal sch-gantt-terminal-end"></div>' : '',
                    '</div>',
                    
                    // Right label 
                    '<div class="sch-gantt-labelct-right"><label class="sch-gantt-label sch-gantt-label-right">{rightLabel}</label></div>',
                '</div>'
            ).compile();
        }
    
        Sch.TreeGanttPanel.superclass.constructor.call(this, config);
    },
    
    // private
    configureFunctionality : function() {
        var plugs = this.plugins;
        
        if (this.enableLabelEdit !== false) {
            this.labelEditor = new Sch.gantt.plugins.LabelEditor();
            plugs.push(this.labelEditor);
        }
        
        if (this.enableTaskContextMenu !== false) {
            this.taskContextMenu = new Sch.gantt.plugins.TaskContextMenu();
            plugs.push(this.taskContextMenu);
        }
        
        if (this.resizeHandles !== 'none' && !this.resizePlug) {
            this.resizePlug = new Sch.gantt.plugins.Resize({
                validatorFn : this.resizeValidatorFn || Ext.emptyFn,
                validatorFnScope : this.validatorFnScope || this
            });
            plugs.push(this.resizePlug);
        }
        
        if (this.enableTaskDragDrop && !this.dragdropPlug) {
            this.dragdropPlug = new Sch.gantt.plugins.DragDrop({
                validatorFn : this.dndValidatorFn  || Ext.emptyFn,
                validatorFnScope : this.validatorFnScope || this,
                getItemDepth : 8
            });
            
            this.on('beforednd', function(p, record, e) {
                // Stop task drag and drop when a resize handle, a terminal or a parent item is clicked
                var t = e.getTarget();
                return !t.className.match('x-resizable-handle') && !t.className.match('sch-gantt-terminal') && !e.getTarget('.sch-gantt-parenttask-bar');
            });
            plugs.push(this.dragdropPlug);
        }
        
        if (this.highlightWeekends) {
            this.weekendZonesPlugin = new Sch.plugins.Zones({
                store : new Ext.data.JsonStore({
                    fields : ['StartDate', 'EndDate']
                })
            });
            plugs.push(this.weekendZonesPlugin);
        }
        
        if (this.showTodayLine) {
            var lineStore = new Ext.data.JsonStore({
                    fields : ['Date', 'Cls', 'Text'],
                    data : [{Date : new Date(), Cls : 'sch-todayLine', Text : 'Current time'}]
                });
                
            this.todayLinePlugin = new Sch.plugins.Lines({ store : lineStore });
            
            var runner = new Ext.util.TaskRunner();
            runner.start({
                run: function() {
                    lineStore.getAt(0).set('Date', new Date());
                },
                interval: 60000 // Update line store every minute
            });
            
            plugs.push(this.todayLinePlugin);
        }
        
        if (this.highlightAffectedTasks) {
            
            this.on('beforeedit', function(o) {
                if (o.field === 'StartDate' || o.field === 'EndDate' || o.field === 'Duration') {
                    this.doHighlightAffectedTasks(o.record.id);
                }
            }, this);
            
            this.on('afterrender', function() {
                var colConfig = this.getColumnModel().config;
                
                // Wire up 'hide' listeners for start date and end date editors to be able to unhighlight
                // dom nodes after user cancelled an edit
                for (var i = 0; i < colConfig.length; i++) {
                    if ((colConfig[i].dataIndex === 'StartDate' || colConfig[i].dataIndex === 'EndDate' || colConfig[i].dataIndex === 'Duration') && colConfig[i].editor) {
                        colConfig[i].editor.on('hide', this.clearSelectedTasksAndDependencies, this);
                    }
                }
            }, this);
            
            if (this.resizePlug) {
                this.on('resizestart', function(grid, record) {
                    this.doHighlightAffectedTasks(record.id);
                }, this);
                
                this.on('afterresize', function(grid, record) {
                    this.clearSelectedTasksAndDependencies();
                }, this);
            }
            
            if (this.dragdropPlug) {
                this.on('dndstart', function(grid, record) {
                    this.doHighlightAffectedTasks(record.id);
                }, this);
                
                this.on('afterdnd', function() {
                    this.clearSelectedTasksAndDependencies();
                }, this);
            }
        }
    },
    
    // private, override base class version to provide the gantt specific view
    internalRenderer : function(v, m, event, row, col, ds, grid) {
        var cellResult = '',
            viewStart = grid.getStart(),
            viewEnd = grid.getEnd(),
            colWidth = grid.getColumnModel().getColumnWidth(col),
            c = this;
            
        // Call timeCellRenderer to be able to set css/style properties on individual grid cells
        grid.timeCellRenderer.call(this, event, m, row, col, ds, c.start, c.end, grid);
            
        var start = event.get('StartDate'),
            end = event.get('EndDate'),
            startsInsideView = start.betweenLesser(c.start, c.end);
        
        // Determine if the event should be rendered or not
        if (startsInsideView || (col == grid.nbrStaticColumns && start < c.start && end > c.start)) {
            
            var availableTimeInColumn = Date.getDurationInMinutes(c.start, c.end),
                leftOffset = Math.floor((Date.getDurationInMinutes(c.start, startsInsideView ? start : c.start) / availableTimeInColumn) * colWidth),
                // Get data from user "renderer" 
                eventData = grid.eventRenderer.call(this, event, row, col, ds) || {};
                
            if (end - start > 0) {
                // Regular task
                var itemWidth = Math.floor(grid.getXFromDate(Date.min(end, viewEnd)) - grid.getXFromDate(startsInsideView ? start : viewStart)),
                    endsOutsideView = end > viewEnd,
                    isLeaf = ds.isLeafNode(event);
                
                if (!isLeaf) {
                    itemWidth += 12; // Add the down arrow width
                    leftOffset -= 6; // Remove half of the down arrow width
                }
                
                // Apply scheduler specific properties
                Ext.apply(eventData, {
                    // Core properties
                    id : grid.eventPrefix + event.id,
                    cls : (eventData.cls || '') + (event.dirty ? ' sch-dirty' : '') + (endsOutsideView ? ' sch-event-endsoutside ' : '') + (startsInsideView ? '' : ' sch-event-startsoutside'),
                    width : Math.max(1, itemWidth - grid.eventBorderWidth),
                    leftOffset : leftOffset,
                    
                    // Gantt specific
                    percentDone : event.get('PercentDone'),
                    leftLabel : event.get(grid.leftLabelField) || '',
                    rightLabel : event.get(grid.rightLabelField) || ''
                });
                
                eventData.text = eventData.text || '&#160;';
                cellResult += grid[isLeaf ? "eventTemplate" : "parentEventTemplate"].apply(eventData);
            } else {
                // Milestone
                Ext.apply(eventData, {
                    // Core properties
                    id : grid.eventPrefix + event.id,
                    cls : (eventData.cls || '') + (event.dirty ? ' sch-dirty' : ''),
                    leftOffset : leftOffset - 8, // Remove half of the diamond width
                    
                    // Gantt specific
                    leftLabel : event.get(grid.leftLabelField) || '',
                    rightLabel : event.get(grid.rightLabelField) || ''
                });
                
                cellResult += grid.milestoneTemplate.apply(eventData);
            }
        }
        
        m.css += ' sch-timetd';
        
        // Z-index is trouble in IE, thanks Condor for this fix
        if (Ext.isIE) {
            m.attr = 'style="z-index:' + (grid.getColumnModel().getColumnCount() - col) + '"';
        }
        return cellResult;
    },
    
    // private
    populateWeekendZonesPlugin : function() {
        var data = [],
            gStart = this.getStart(),
            gEnd = this.getEnd(),
            c = gStart.clone();
        
        while(c.getDay() !== 6) {
            c = c.add(Date.DAY, 1);
        }
        
        while (c < gEnd) {
            data.push({
                StartDate : c,
                EndDate : c.add(Date.DAY, 2)
            });
            c = c.add(Date.WEEK, 1);
        }
        
        this.weekendZonesPlugin.store.loadData(data);
    },
    
    // private
    getView : function(){
        if(!this.view){
            this.viewConfig = this.viewConfig || {};
            Ext.applyIf(this.viewConfig, {
                cellSelectorDepth : 22,
                rowSelectorDepth : 12
            });
            this.view = new Sch.TreeGanttView(this.viewConfig);
        }
        return this.view;
    },
    
    // private
    initComponent : function() {
        // Make sure all side components relying on the eventStore property will still work
        this.eventStore = this.store;
        if (this.highlightWeekends) {
            this.on('viewchange', this.populateWeekendZonesPlugin, this);
        }
        Sch.TreeGanttPanel.superclass.initComponent.call(this);
    },
    
    initEvents : function() {
        this.on('afterrender', this.onGanttRender, this);
        this.on('mouseenter', this.onTaskOver, this);
        this.on('mouseleave', this.onTaskOut, this);
        
        if (this.recalculateParentsAfterEdit) {
            this.store.on({
                'update' : this.onStoreUpdate, 
                'add' : this.onStoreAddRemove, 
                'remove' : this.onStoreAddRemove, 
                scope : this
            });
        }
        this.on('mouseleave', this.onTaskOut, this);
        Sch.TreeGanttPanel.superclass.initEvents.call(this);
    },
    
    // private, Override to only count the time columns since the view is a locking view meaning a separate container element
    getXFromDate : function(date) {
       var retVal = -1,
           cm = this.getColumnModel(),
           count = cm.getColumnCount();
           
       for (var i = this.nbrStaticColumns; i < count; i++) {
            if (date <= this.getColumnEnd(i)) {
                var diff = date - this.getColumnStart(i),
                    timeInColumn = this.getColumnEnd(i) - this.getColumnStart(i),
                    cw = cm.getColumnWidth(i);
                    
                return (cw * ((i - this.nbrStaticColumns) + (diff / timeInColumn)));
            } 
       }
       
       return null;
    },
    
    // private
    onGanttRender : function() {    
        var v = this.getView();
        this.el.addClass('sch-ganttpanel' + (this.highlightWeekends ? ' sch-ganttpanel-highlightweekends' : ''));
        
        if (this.showTreeLines) {
            this.el.addClass('sch-ganttpanel-with-treelines');
        }
        
        this.dependencyManager = new Sch.DependencyManager(this, {
            containerEl : v.scroller,
            checkVisible : true,
            enableDependencyDragDrop : this.enableDependencyDragDrop,
            store : this.dependencyStore
        });
        
        v.on('refresh', this.dependencyManager.renderDependencies, this.dependencyManager);
        v.on('togglerow', this.dependencyManager.renderDependencies, this.dependencyManager);
    },
    
    
    editLeftLabel : function(record) {
        this.labelEditor.editLeft(record);
    },
    
    editRightLabel : function(record) {
        this.labelEditor.editRight(record);
    },
    
    getDependenciesForTask : function(record) {
        return this.dependencyManager.getDependenciesForTask(record);
    },
    
    highlightDependency : function(depId) {
        this.dependencyManager.highlightDependency(depId);
    },
    
    unhighlightDependency : function(depId) {
        this.dependencyManager.unhighlightDependency(depId);
    },
    
    getTaskById : function(id) {
        return this.store.getById(id);
    },
    
    doHighlightAffectedTasks : function(taskId) {
        if (!this.cascadeChanges) return;
        
        var el = this.getElementFromEventId(taskId);
        
        // El might not exist in DOM
        if (el) {
            el.addClass('sch-event-selected');
        }
        
        this.dependencyStore.queryBy(function(dep) {
            if (dep.get('From') == taskId) {
                this.highlightDependency(dep.id);
                this.doHighlightAffectedTasks(dep.get('To'));
            }
        }, this);
    },
    
    clearSelectedTasksAndDependencies : function() {
        this.getView().el.select('.sch-event-selected').removeClass('sch-event-selected');
        this.getView().el.select('.sch-dependency-selected').removeClass('sch-dependency-selected');
    },
    
    /* Maxim TreeGrid */
    /**
     * @cfg {String|Integer} master_column_id Master column id. Master column cells are nested.
     * Master column cell values are used to build breadcrumbs.
     */
    master_column_id : 0,

    /**
     * @access private
     */
    onClick : function(e)
    {
        var target = e.getTarget('.ux-maximgb-tg-elbow-active', 1);
        
        // Row click
        if (target) {
            var view = this.getView(),
                row = view.findRowIndex(target),
                store = this.getStore(),
                record = store.getAt(row);
                
            if (store.isExpandedNode(record)) {
                store.collapseNode(record);
            }
            else {
                store.expandNode(record);
            }
        } else {
            Sch.TreeGanttPanel.superclass.onClick.call(this, e);
        }
    },

    /**
     * @access private
     */
    onMouseDown : function(e)
    {
        if (!e.getTarget('.ux-maximgb-tg-elbow-active', 1)) {
            Sch.TreeGanttPanel.superclass.onMouseDown.call(this, e);
        }
    },
    
    onStoreAddRemove : function() {
        this.recalculateParents.defer(this.recalculateParentsDelay, this, arguments);
    },
    
    onStoreUpdate : function(s, rec, operation, prev) {
        // No need to recalculate for commits
        if (operation !== Ext.data.Record.COMMIT && (!prev || prev.StartDate || prev.EndDate)) {
            this.recalculateParents.defer(this.recalculateParentsDelay, this, arguments);
        }
    },
    
    recalculateParents : function(store, rec) {
        rec = Ext.isArray(rec) ? rec[0] : rec;
        var earliest = new Date(9999,0,0), 
            latest = new Date(0),
            parent = this.store.getNodeParent(rec);
        
        if (parent) {
            var children = this.store.getNodeChildren(parent);
            if (children.length > 0) {
                Ext.each(children, function(r) {
                    earliest = Date.min(earliest, r.get('StartDate'));
                    latest = Date.max(latest, r.get('EndDate'));
                });
                
                parent.beginEdit();
                parent.set('StartDate', earliest);
                parent.set('EndDate', latest);
                parent.endEdit();
            }
        }
    }
}); 

Ext.reg('treegantt', Sch.TreeGanttPanel);