YAHOO.ext.grid.Gridにtableタグを使う。

YAHOO.ext.grid.Gridはかなり便利に使える。だがタグを全部spanで作るのでtableでマークアップしたときのように高さが足らなければ勝手に増えてくれたりはしない。
全ての行の高さを一律に変更するのならスタイルのygrid-colにheightプロパティを書くだけだと思うが要件に合わない。しょうがないのでマークアップをtableで行うようにしてみた。
(jsファイル)

/*
	<<CJS widget library>>
	SimpleGrid
	extends : YAHOO.ext.grid.Grid
*/

/*
	setting namespaces
*/
if(typeof(CJS) == "undefined") CJS = {};
if(typeof(CJS.widget) == "undefined") CJS.widget = {};

/*
	SimpleGrid
	
*/
CJS.widget.SimpleGrid = function(){
    CJS.widget.SimpleGrid.superclass.constructor.apply(this, arguments);
    this.view = new CJS.widget.SimpleGridView();
};

YAHOO.extendX(CJS.widget.SimpleGrid, YAHOO.ext.grid.Grid,
{
	beforeRenderRow : function(){}
}
);


CJS.widget.SimpleGridView = function(){
    	CJS.widget.SimpleGridView.superclass.constructor.apply(this, arguments);
};

YAHOO.extendX(CJS.widget.SimpleGridView, YAHOO.ext.grid.GridView,{
	renderRows : function(dataModel){
	        this.grid.stopEditing();
	        if(this.grid.selModel){
	            this.grid.selModel.clearSelections();
	        }
	    	var bt = this.getBodyTable();
	    	//bt.innerHTML = '';
	    	this.rowHeight = this.getRowHeight();
	    	this.insertRows(dataModel, 0, dataModel.getRowCount()-1);
	    }
		,
	render : function(){
        var grid = this.grid;
        var container = grid.container.dom;
        var dataModel = grid.dataModel;
        this.plugDataModel(dataModel);
    
        var colModel = grid.colModel;
        colModel.onWidthChange.subscribe(this.updateColumns, this, true);
        colModel.onHeaderChange.subscribe(this.updateHeaders, this, true);
        colModel.onHiddenChange.subscribe(this.handleHiddenChange, this, true);
        
        if(grid.monitorWindowResize === true){
            YAHOO.ext.EventManager.onWindowResize(this.onWindowResize, this, true);
        }
        var autoSizeDelegate = this.autoSizeColumn.createDelegate(this);
        
        var colCount = colModel.getColumnCount();
        
        var dh = YAHOO.ext.DomHelper;
        this.pwrap = dh.append(container, 
            {tag: 'div', cls: 'ygrid-positioner', 
            style: 'position:relative;width:100%;height:100%;left:0;top:0;overflow:hidden;'}, true);
        var pos = this.pwrap.dom;
        
        //create wrapper elements that handle offsets and scrolling
        var wrap = dh.append(pos, {tag: 'div', cls: 'ygrid-wrap'});
        this.wrap = wrap;
        this.wrapEl = getEl(wrap, true);
        YAHOO.ext.EventManager.on(wrap, 'scroll', this.handleScroll, this, true);
        
        var hwrap = dh.append(pos, {tag: 'div', cls: 'ygrid-wrap-headers'});
        this.hwrap = getEl(hwrap, true);
     
        var tbl = dh.append(wrap, {tag: 'table', cls: 'ygrid-wrap-body', id: container.id + '-body'});
        if(YAHOO.ext.util.Browser.isGecko){
        	var tbody = dh.append(tbl, {tag: 'tbody', cls: 'ygrid-wrap-body', id: container.id + '-tbody'});
        }
        var bwrap = tbl;
        tbl.cellPadding = 0;
        tbl.cellSpacing = 0;
        tbl.style.tableLayout="fixed";
        this.bwrap = getEl(bwrap, true);
        this.bwrap.setWidth(colModel.getTotalWidth());
        //bwrap.rows = bwrap.childNodes;
        
        this.footerHeight = 0;
        var foot = this.appendFooter(this.pwrap.dom);
        if(foot){
            this.footer = getEl(foot, true);
            this.footerHeight = this.footer.getHeight();
        }
        this.updateWrapHeight();
        
        var hrow = dh.append(hwrap, {tag: 'span', cls: 'ygrid-hrow'});
        this.hrow = hrow;
        
        if(!YAHOO.ext.util.Browser.isGecko){
            // IE doesn't like iframes, we will leave this alone
            var iframe = document.createElement('iframe');
            iframe.className = 'ygrid-hrow-frame';
            iframe.frameBorder = 0;
            iframe.src = YAHOO.ext.SSL_SECURE_URL;
            hwrap.appendChild(iframe);
        }
        this.headerCtrl = new YAHOO.ext.grid.HeaderController(this.grid);
        this.headers = [];
        this.cols = [];
        
        var htemplate = dh.createTemplate({
           tag: 'span', cls: 'ygrid-hd ygrid-header-{0}', children: [{
                tag: 'span',
                cls: 'ygrid-hd-body',
                html: '<table border="0" cellpadding="0" cellspacing="0" title="{2}">' +
                      '<tbody><tr><td><span>{1}</span></td>' +
                      '<td><span class="sort-desc"></span><span class="sort-asc"></span></td>' +
                      '</tr></tbody></table>'
           }]
        });
        htemplate.compile();
        for(var i = 0; i < colCount; i++){
            var hd = htemplate.append(hrow, [i, colModel.getColumnHeader(i), colModel.getColumnTooltip(i) || '']);
            var spans = hd.getElementsByTagName('span');
            hd.textNode = spans[1];
            hd.sortDesc = spans[2];
            hd.sortAsc = spans[3];
    	    hd.columnIndex = i;
            this.headers.push(hd);
            if(colModel.isSortable(i)){
                this.headerCtrl.register(hd);
            }
            var split = dh.append(hrow, {tag: 'span', cls: 'ygrid-hd-split'});
            hd.split = split;
        	
        	if(colModel.isResizable(i) && !colModel.isFixed(i)){
            	YAHOO.util.Event.on(split, 'dblclick', autoSizeDelegate.createCallback(i+0, true));
            	var sb = new YAHOO.ext.SplitBar(split, hd, null, YAHOO.ext.SplitBar.LEFT);
            	sb.columnIndex = i;
            	sb.minSize = grid.minColumnWidth;
            	sb.onMoved.subscribe(this.onColumnSplitterMoved, this, true);
            	YAHOO.util.Dom.addClass(sb.proxy, 'ygrid-column-sizer');
            	YAHOO.util.Dom.setStyle(sb.proxy, 'background-color', '');
            	sb.dd._resizeProxy = function(){
            	    var el = this.getDragEl();
            	    YAHOO.util.Dom.setStyle(el, 'height', (hwrap.clientHeight+wrap.clientHeight-2) +'px');
            	};
        	}else{
        	    split.style.cursor = 'default';
        	}
        }
       if(grid.autoSizeColumns){
            this.renderRows(dataModel);
            this.autoSizeColumns();
        }else{
            this.updateColumns();
            this.renderRows(dataModel);
        }
        
        for(var i = 0; i < colCount; i++){
            if(colModel.isHidden(i)){
                this.hideColumn(i);
            }
        }
        this.updateHeaderSortState();
        return this.bwrap;
    },

    insertRows : function(dataModel, firstRow, lastRow){
        this.updateBodyHeight();
        this.adjustForScroll(true);
        var renderers = this.getColumnRenderers();
        var dindexes = this.getDataIndexes();
        var colCount = this.grid.colModel.getColumnCount();
        var beforeRow = null;
        var bt = this.getBodyTable();
        var tbody = bt.firstChild;
        
        if(firstRow < bt.childNodes.length){
            beforeRow = tbody.childNodes[firstRow];
        }
        for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
            var row = document.createElement('tr');
            row.className = 'ygrid-row';
            //row.style.top = (rowIndex * this.rowHeight) + 'px';
            row.style.height = "auto";
            this.renderRow(dataModel, row, rowIndex, colCount, renderers, dindexes);
            if(beforeRow){
            	tbody.insertBefore(row, beforeRow);
            }else{
                tbody.appendChild(row);
            }
        }
        this.updateRowIndexes(firstRow);
        this.adjustForScroll(true);
    },

    
    renderRow : function(dataModel, row, rowIndex, colCount, renderers, dindexes){
    	this.grid.beforeRenderRow(dataModel, row, rowIndex, colCount, renderers, dindexes);
        for(var colIndex = 0; colIndex < colCount; colIndex++){
            var td = document.createElement('td');
            td.className = 'ygrid-col ygrid-col-' + colIndex + (colIndex == colCount-1 ? ' ygrid-col-last' : '');
            td.style.vertivalAlign = "top";
            td.columnIndex = colIndex;
            td.tabIndex = 0;
            td.style.height = "auto";
            var span = document.createElement('span');
            span.className = 'ygrid-cell-text';
            td.appendChild(span);
            var val = renderers[colIndex](dataModel.getValueAt(rowIndex, dindexes[colIndex]), rowIndex, colIndex, td);
            if(typeof val == 'undefined' || val === '') val = '&#160;';
            span.innerHTML = val;
            row.appendChild(td);
        }
    },
    updateRowIndexes : function(firstRow, lastRow){
        var stripeRows = this.grid.stripeRows;
        var bt = this.getBodyTable();
        var tbody = bt.firstChild;
        var nodes = bt.childNodes;
        firstRow = firstRow || 0;
        lastRow = lastRow || nodes.length-1;
        var re = /^(?:ygrid-row ygrid-row-alt|ygrid-row)/;
        for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
            var node = nodes[rowIndex];
            if(stripeRows && (rowIndex+1) % 2 == 0){
        		node.className = node.className.replace(re, 'ygrid-row ygrid-row-alt');
        	}else{
        		node.className = node.className.replace(re, 'ygrid-row');
        	}
     //       node.rowIndex = rowIndex;
     //       nodes[rowIndex].style.top = (rowIndex * this.rowHeight) + 'px';
        }
    }
	,    /** @private */
    _adjustForScroll : function(){
        this.forceScrollUpdate();
        if(this.scrollbarMode == YAHOO.ext.grid.GridView.SCROLLBARS_OVERLAP){
            var adjustment = 0;
            if(this.wrap.clientWidth && this.wrap.clientWidth !== 0){
                adjustment = this.wrap.offsetWidth - this.wrap.clientWidth;
            }
            this.hwrap.setWidth(this.wrap.offsetWidth-adjustment);
        }else{
            this.hwrap.setWidth(this.wrap.offsetWidth);
        }
     //   this.bwrap.setWidth(Math.max(this.grid.colModel.getTotalWidth(), this.wrap.clientWidth));
        this.bwrap.setWidth(this.grid.colModel.getTotalWidth());
    }
    ,updateColumns : function(){
        this.grid.stopEditing();
        var colModel = this.grid.colModel;
        var hcols = this.headers;
        var colCount = colModel.getColumnCount();
        var pos = 0;
        var totalWidth = colModel.getTotalWidth();
        for(var i = 0; i < colCount; i++){
            if(colModel.isHidden(i)) continue;
            var width = colModel.getColumnWidth(i);
            hcols[i].style.width = width + 'px';
            hcols[i].style.left = pos + 'px';
            hcols[i].split.style.left = (pos+width-3) + 'px';
            this.setCSSWidth(i, width - 1, pos);
            pos += width;
        }
        this.lastWidth = totalWidth;
        if(this.grid.autoWidth){
            this.grid.container.setWidth(totalWidth+this.grid.container.getBorderWidth('lr'));
            this.grid.autoSize();
        }
        this.bwrap.setWidth(totalWidth);
        if(!YAHOO.ext.util.Browser.isIE){ // fix scrolling prob in gecko and opera
        	this.wrap.scrollLeft = this.hwrap.dom.scrollLeft;
        }
        this.syncScroll();
        this.forceScrollUpdate();
        if(this.grid.autoHeight){
            this.autoHeight();
            this.updateWrapHeight();
        }
    },
    updateCell : function(dataModel, rowIndex, dataIndex){
        var colIndex = this.getColumnIndexByDataIndex(dataIndex);
        if(typeof colIndex == 'undefined'){ // not present in grid
            return;
        }
        var bt = this.getBodyTable();
        var row = bt.firstChild.childNodes[rowIndex];
        var cell = row.childNodes[colIndex];
        var renderer = this.grid.colModel.getRenderer(colIndex);
        var val = renderer(dataModel.getValueAt(rowIndex, dataIndex), rowIndex, colIndex, cell);
        if(typeof val == 'undefined' || val === '') val = '&#160;';
        cell.firstChild.innerHTML = val;
    }

	});

css

/* ヘッダー文字色変更 */
.ygrid-hd-body span {
	color: #B07635 ;
}
/* TableGridViewのための設定(行) */
.ygrid-row{
	position:static;
	display:table-row;
}
/* TableGridViewのための設定(列) */
.ygrid-col{
	position:static;
	border-right:1px solid #CCCCCC;
	border-bottom:1px solid #CCCCCC;
	font-family: 'MS Pゴシック';
	white-space:normal;
	display:table-cell;
	height:auto  !important; 
	margin:0px;
	padding:0px;
}
/* TableGridViewのための設定 セルの文字の指定 */
.ygrid-cell-text {
	display: block;
	overflow: hidden;
	white-space: normal; /* 折り返す */
}

javascriptのほうに
tbl.style.tableLayout="fixed";
としているが、これにより列幅を固定にできる。cssに書くことも考えたが、そうするとfixedではない指定をしたときのことも考えなければならなくなる。このコードのままだとヘッダとボディの列がずれる。
ここに書いた以外のコードは変更していないのでYAHOO.ext.grid.*の持つ機能がそのまま使える。データモデルもJSONが使えるので、サーバー側で何かのクラスのインスタンスJSON化して吐き出せばサーバーとクライアントのデータモデルを統一できる。
オブジェクトのJSON化(C#版)は別途。