﻿/*
 * Ext Scheduler v1.5 beta
 * Copyright(c) 2009-2010 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 */

Ext.ns('Sch');

/**
 * @class Sch.SchedulerView
 * @extends Ext.grid.GridView
 * <p>This class encapsulates the user interface of an {@link Sch.SchedulerPanel}.
 * Methods of this class may be used to access user interface elements to enable
 * special display effects. Do not change the DOM structure of the user interface.</p>
 * <p>This class does not provide ways to manipulate the underlying data. The data
 * model of a Scheduler is managed by {@link Ext.data.Store}.</p>
 * @constructor
 * @param {Object} config
 */
Sch.SchedulerViewConfig = {
    // Increase depth for getTarget functionality
    cellSelectorDepth : 6,
    rowSelectorDepth : 11,
    
    onEventAdd : function(s, recs) {
        for (var i = 0; i < recs.length; i++) {
            this.refreshRow(this.grid.getResourceByEventRecord(recs[i]));
        }
    },
    
    onEventRemove : function(s, rec) {
        var el = this.grid.getElementFromEventRecord(rec);
        
        if (el) {
            el.fadeOut({
                remove : true,
                callback : this.refreshRow.createDelegate(this, [this.grid.getResourceByEventRecord(rec)])
            });
        }
    },
    
    onEventUpdate : function(s, rec, operation, hashPrevious) {
        if (hashPrevious && hashPrevious.ResourceId) {
            // If an event has been moved to a new row, refresh old row first
            this.refreshRow(this.ds.getAt(this.ds.findExact('Id', hashPrevious.ResourceId)));
        }
        this.refreshRow(this.grid.getResourceByEventRecord(rec));
    },
    
    // Overridden to attach listeners to eventstore
    initData : function(){
        Sch.SchedulerView.superclass.initData.apply(this, arguments);
        
        if(!this.es){
            this.es = this.grid.eventStore;
            this.es.on({
                scope: this,
                load: this.onLoad,
                datachanged: this.onDataChange,
                add: this.onEventAdd,
                update: this.onEventUpdate,
                remove: this.onEventRemove,
                clear: this.onClear
            });
        }
    },
    
    constructor : function(config){
        Ext.apply(this, config);
        
        if (config && config.forceFit) {
            this.refresh = this.refresh.createInterceptor(function(){this.fitColumns(true);});
        }
	    Sch.SchedulerView.superclass.constructor.call(this);    
    },
    
    // Override to add start/end dates to each column data object
    getColumnData : function(){
        // build a map for all the columns
        var cs = [], cm = this.cm, colCount = cm.getColumnCount();
        for(var i = 0; i < colCount; i++){
            var name = cm.getDataIndex(i);
            cs[i] = {
                name : name || '', 
                id : cm.getColumnId(i),
                style : this.getColumnStyle(i),
                renderer : cm.getRenderer(i),
                
                start : cm.config[i].start,
                end : cm.config[i].end
            };
        }
        return cs;
    },
    
    // This function fits respects the original width of the static columns and
    // fits the time columns into the space that's left
    fitColumns : function(preventRefresh, onlyExpand, omitColumn){
        var cm = this.cm, i;
        var tw = cm.getTotalWidth(false);
        var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();

        if(aw < 20){ // not initialized, so don't screw up the default widths
            return;
        }
        var extra = aw - tw;

        if(extra === 0){
            return;
        }

        var vc = cm.getColumnCount(true);
        
        if(vc <= this.grid.nbrStaticColumns){ // not showing any time columns yet
            return;
        }
        
        var ac = vc-(typeof omitColumn == 'number' ? 1 : 0);
        if(ac === 0){
            ac = 1;
            omitColumn = undefined;
        }
         
        if(omitColumn ){
             cm.setColumnWidth(omitColumn, Math.max(1, cm.getColumnWidth(omitColumn)), true);
        }
        
        var colCount = cm.getColumnCount(),
            cols = [],
            extraCol = 0,
            width = 0,
            w,
            staticColumnsWidth = 0;
        
        // Calculate static columns total width
        for (i = 0; i < this.grid.nbrStaticColumns; i++){
            if(!cm.isHidden(i)){
                extraCol = i;
                w = i === omitColumn ? cm.getColumnWidth(i) : cm.config[i].width;
                cols.push(i);
                cols.push(w);
                staticColumnsWidth += w;
            }
        }
        
        var timeColumnWidth = (aw - staticColumnsWidth)/ (colCount - this.grid.nbrStaticColumns);
        
        for (i = this.grid.nbrStaticColumns; i < colCount; i++){
            cols.push(i);
            cols.push(timeColumnWidth);
        }
        
        while (cols.length){
            w = cols.pop();
            i = cols.pop();
            cm.setColumnWidth(i, Math.max(this.grid.minColumnWidth, Math.floor(w)), true);
        }
       
        if(preventRefresh !== true){
            this.updateAllColumnWidths();
        }
    },


    // private
    doRender : function(cs, rs, ds, startRow, colCount, stripe){
        var g = this.grid, ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1;
        var tstyle = 'width:'+this.getTotalWidth()+';';
        // buffers
        var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r, events;
        for(var j = 0, len = rs.length; j < len; j++){
            r = rs[j]; cb = [];
            var rowIndex = (j+startRow),
                resId = r.get('Id');
            
            // @Modification: Events for current row
            events = this.es.data.filterBy(function(brec) {
                return brec.data.ResourceId == resId;
            });
            
            for(var i = 0; i < colCount; i++){
                c = cs[i];
                p.id = c.id;
                p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
                p.attr = p.cellAttr = "";
                p.cellCtCls = '';
                
                // @Modification: Added events and g to param list
                p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds, events, g);
                p.style = c.style;
                
                cb[cb.length] = ct.apply(p);
            }
            var alt = [];
            if(stripe && ((rowIndex+1) % 2 === 0)){
                alt[0] = "x-grid3-row-alt";
            }
            if(r.dirty){
                alt[1] = " x-grid3-dirty-row";
            }
            rp.cols = colCount;
            if(this.getRowClass){
                alt[2] = this.getRowClass(r, rowIndex, rp, ds);
            }
            rp.alt = alt.join(" ");
            rp.cells = cb.join("");
            buf[buf.length] =  rt.apply(rp);
        }
        return buf.join("");
    },
    
    // @Modification: Added cellCtCls
    initTemplates : function(){
        this.templates = this.templates || {};
        var ts = this.templates;
        
        if (!ts.gcell) {
		    ts.gcell = new Ext.XTemplate(
				'<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} {cls}" style="{style}">',
				'<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">',
				'{value}</div></td>'
			);
		}
			
        if(!ts.cell){
            ts.cell = new Ext.XTemplate(
            '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
                '<div class="x-grid3-cell-inner {cellCtCls} x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
            '</td>'
            );
        }
        Sch.SchedulerView.superclass.initTemplates.call(this);
    },
    
    getTimeColumnWidth : function(widthFraction, totalWidthOfTimeColumns){
        var width = totalWidthOfTimeColumns * widthFraction;
        return (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? width : (width - this.borderWidth > 0 ? width - this.borderWidth : 0));
    },
    
    getColumnHeaderCls : function(i, last) {
        var css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
        return css + (this.cm.config[i].headerCls || '');
    },
    
    // Grouping header related functionality, based on Condors GroupHeaderGrid plugin
	renderHeaders : function() {

		var ts = this.templates, 
		         headers = [], 
		         cm = this.cm, 
		         rows = cm.rows, 
		         width, 
		         id, 
		         group, 
		         firstGroupWidth, 
		         tw = this.cm.getTotalWidth(), 
		         totalWidthOfTimeColumns,
		         i,
		         len,
		         gcol;
		         
		if (rows.length > 0) {
		    firstGroupWidth = this.getFirstGroupWidth.call(this, rows[0][0], 0);
		    totalWidthOfTimeColumns = tw - firstGroupWidth;
		    if (firstGroupWidth > 0 && !(Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2))) {
		       totalWidthOfTimeColumns -= this.borderWidth;
		    }
		}
		
		for (var row = 0, rlen = rows.length; row < rlen; row++) {
			 var r = rows[row], cells = [], cls;
			
			for (i = 0, gcol = 0, len = r.length; i < len; i++) {
				group = r[i];
				id = this.getColumnId(group.dataIndex ? cm.findColumnIndex(group.dataIndex) : gcol);
				
				if (i === 0 && group.colspan) {
				    width = firstGroupWidth;
				    cls = '';
				} else {
				    width = this.getTimeColumnWidth(group.width, totalWidthOfTimeColumns);
				    cls = 'sch-timeheader ';
				}
				group.colspan = group.colspan || 1;
				
				cells[i] = ts.gcell.apply({
					cls: cls + (group.header ? 'ux-grid-hd-group-cell' : 'ux-grid-hd-nogroup-cell'),
					id: id,
					row: row,
					style: 'width:' + width + 'px;' + (group.align ? 'text-align:' + group.align + ';' : ''),
					tooltip: group.tooltip ? (Ext.QuickTips.isEnabled() ? 'ext:qtip' : 'title') + '="' + group.tooltip + '"' : '',
					istyle: group.align == 'right' ? 'padding-right:16px' : '',
					btn: this.grid.enableHdMenu && group.header,
					value: (width > 10) ? (group.header || '&nbsp;') : '&nbsp;'
				});
				gcol += group.colspan;
			}
			headers[row] = ts.header.apply({
				tstyle: 'width:' + this.getTotalWidth(),
				cells: cells.join('')
			});
		}
		
		
		// The default column header rendering, overriden to be able to assign a css class to each header cell
        len = cm.getColumnCount();
        
        var ct = ts.hcell,
            cb = [], 
            p = {},
            last = len - 1;
            
        for(i = 0; i < len; i++){
            p.id = cm.getColumnId(i);
            p.value = cm.getColumnHeader(i) || '';
            p.style = this.getColumnStyle(i, true);
            p.tooltip = this.getColumnTooltip(i);
            p.css = this.getColumnHeaderCls(i, last);
            if(cm.config[i].align == 'right'){
                p.istyle = 'padding-right:16px';
            } else {
                delete p.istyle;
            }
            cb[cb.length] = ct.apply(p);
        }
		headers.push(ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'}));
		return headers.join('');
	},

	onColumnWidthUpdated : function(){
		Sch.SchedulerView.superclass.onColumnWidthUpdated.apply(this, arguments);
		this.updateGroupStyles.call(this);
	},

	onAllColumnWidthsUpdated : function(){
		Sch.SchedulerView.superclass.onAllColumnWidthsUpdated.apply(this, arguments);
		this.updateGroupStyles.call(this);
	},

	onColumnHiddenUpdated : function(){
		Sch.SchedulerView.superclass.onColumnHiddenUpdated.apply(this, arguments);
		this.updateGroupStyles.call(this);
	},

	getHeaderCell : function(index){
		return this.mainHd.query(this.cellSelector)[index];
	},

	findHeaderCell : function(el){
		return el ? this.fly(el).findParent('td.x-grid3-hd', this.cellSelectorDepth) : false;
	},

	findHeaderIndex : function(el){
		var cell = this.findHeaderCell(el);
		return cell ? this.getCellIndex(cell) : false;
	},

	updateSortIcon : function(col, dir){
		var sc = this.sortClasses;
		var hds = this.mainHd.select(this.cellSelector).removeClass(sc);
		hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
	},

	getFirstGroupWidth: function(group, gcol) {
		var width = 0, hidden = true, visCols = 0;
		for (var i = gcol, len = gcol + group.colspan; i < len; i++) {
			if (!this.cm.isHidden(i)) {
				var cw = this.cm.getColumnWidth(i);
				if(typeof cw == 'number'){
					width += cw;
				}
				hidden = false;
			}
		}
		return (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2)) ? width : Math.max(width - this.borderWidth, 0);
	},
	
	updateGroupStyles: function(col) {
		var tables = this.mainHd.query('.x-grid3-header-offset > table'), 
		             rows = this.cm.rows, 
		             firstGroupWidth, 
		             tw = this.cm.getTotalWidth();
		
		if (rows.length > 0) {
		    firstGroupWidth = this.getFirstGroupWidth.call(this, rows[0][0], 0);
		    totalWidthOfTimeColumns = tw - firstGroupWidth;
		    if (firstGroupWidth > 0 && !(Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2))) {
		       totalWidthOfTimeColumns -= this.borderWidth;
		    }
		}
		
		for (var row = 0; row < tables.length; row++) {
			tables[row].style.width = tw + 'px';
			
			if (row < rows.length) {
				var cells = tables[row].firstChild.firstChild.childNodes;
				
				for (var i = 0, gcol = 0; i < cells.length; i++) {
					var group = rows[row][i];
					if ((typeof col != 'number') || (col >= gcol && col < gcol + (group.colspan || 0))) {
					    if (i === 0) {
						    cells[i].style.width = firstGroupWidth + 'px';
					    } else {
				            width = this.getTimeColumnWidth(group.width, totalWidthOfTimeColumns);
					    	cells[i].style.width = width + 'px';
					    }
					}
				}
			}
		}
	},
	
	getAccColumnWidth : function(endCol) {
	    var w = 0,
            cm = this.cm,
            i;
             
        for (i = 0; i < endCol; i++) {
            if (!cm.isHidden(i)) {
                w += cm.getColumnWidth(i);
            }
        }
        
        return w;
	},
	
	updateTimeColumnWidths : function(width, suppressRefresh) {
        var cm = this.cm;
        for (var i = this.grid.nbrStaticColumns, l = cm.getColumnCount(); i < l; i++) {
	        cm.setColumnWidth(i, width, true);
	    }
	    
	    if (!suppressRefresh) {
	        this.refresh(true);
	    }
    },
    
    getHeaderGroupRow : function(cell) {
        var cls = cell.className, 
            retVal = -1;
        
        if (cls) {
            var m = cls.match(/ux-grid-hd-group-row-(\d+)/);
            if (m && m.length === 2) {
                retVal = parseInt(m[1], 10);
            }
        }
        return retVal;
    },
    
    onHeaderClick : Ext.grid.GridView.prototype.onHeaderClick.createInterceptor(function(g, index, e){
        // Block the default sort mechanism when clicking on a grouping header row
        return !e.getTarget('.ux-grid-hd-group-cell');
    })
};

Sch.SchedulerView = Ext.extend(Ext.grid.GridView, Sch.SchedulerViewConfig);

