Ext3.x版本的锁定列和多表头都是通过插件实现,但是这两个插件不能一起工作,而实际上这样的业务也是存在的,即在多表头的情况下也需要锁定列。
在Ext的论坛上有很多这样的需求,都没有一个解决方案,除了一个需要收费的(150没有),而且联系起来也麻烦。后面决定自己做一个。
效果如下图:
文件:
/*!
* Ext JS Library 3.3.0
* Copyright(c) 2006-2010 Ext JS, Inc.
* [email protected]
* http://www.extjs.com/license
*/
Ext.ns('Ext.ux.grid');
Ext.ux.grid.LockingHeaderGroupView = Ext.extend(Ext.grid.GridView, {
lockText: '锁定',
unlockText: '解锁',
rowBorderWidth: 1,
lockedBorderWidth: 1,
//先支持只有两层的多表头情况
//从外部传入
//grows : [[{},{},{},{"align":"center","colspan":2,"header":"合并后名称"},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]],
/*
* This option ensures that height between the rows is synchronized
* between the locked and unlocked sides. This option only needs to be used
* when the row heights aren't predictable.
*/
syncHeights: false,
initTemplates: function () {
var ts = this.templates || {};
if (!ts.masterTpl) {
ts.masterTpl = new Ext.Template(
'',
'',
'{lockedHeader}',
'{lockedBody}',
'',
' ',
' ',
' ',
''
);
}
if (!ts.gcell) {
ts.gcell = new Ext.XTemplate('', ' ');
}
this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");
this.templates = ts;
Ext.ux.grid.LockingHeaderGroupView.superclass.initTemplates.call(this);
},
getEditorParent: function (ed) {
return this.el.dom;
},
initElements: function () {
var el = Ext.get(this.grid.getGridEl().dom.firstChild),
lockedWrap = el.child('div.x-grid3-locked'),
lockedHd = lockedWrap.child('div.x-grid3-header'),
lockedScroller = lockedWrap.child('div.x-grid3-scroller'),
mainWrap = el.child('div.x-grid3-viewport'),
mainHd = mainWrap.child('div.x-grid3-header'),
scroller = mainWrap.child('div.x-grid3-scroller');
if (this.grid.hideHeaders) {
lockedHd.setDisplayed(false);
mainHd.setDisplayed(false);
}
if (this.forceFit) {
scroller.setStyle('overflow-x', 'hidden');
}
Ext.apply(this, {
el: el,
mainWrap: mainWrap,
mainHd: mainHd,
innerHd: mainHd.dom.firstChild,
scroller: scroller,
mainBody: scroller.child('div.x-grid3-body'),
focusEl: scroller.child('a'),
resizeMarker: el.child('div.x-grid3-resize-marker'),
resizeProxy: el.child('div.x-grid3-resize-proxy'),
lockedWrap: lockedWrap,
lockedHd: lockedHd,
lockedScroller: lockedScroller,
lockedBody: lockedScroller.child('div.x-grid3-body'),
lockedInnerHd: lockedHd.child('div.x-grid3-header-inner', true)
});
this.focusEl.swallowEvent('click', true);
},
getLockedRows: function () {
return this.hasRows() ? this.lockedBody.dom.childNodes : [];
},
getLockedRow: function (row) {
return this.getLockedRows()[row];
},
getCell: function (row, col) {
var lockedLen = this.cm.getLockedCount();
if (col < lockedLen) {
return this.getLockedRow(row).getElementsByTagName('td')[col];
}
return Ext.ux.grid.LockingHeaderGroupView.superclass.getCell.call(this, row, col - lockedLen);
},
getHeaderCell: function (index) {
var lockedLen = this.cm.getLockedCount();
if (index < lockedLen) {
return this.lockedHd.dom.getElementsByTagName('td')[index];
}
return Ext.ux.grid.LockingHeaderGroupView.superclass.getHeaderCell.call(this, index - lockedLen);
},
addRowClass: function (row, cls) {
var lockedRow = this.getLockedRow(row);
if (lockedRow) {
this.fly(lockedRow).addClass(cls);
}
Ext.ux.grid.LockingHeaderGroupView.superclass.addRowClass.call(this, row, cls);
},
removeRowClass: function (row, cls) {
var lockedRow = this.getLockedRow(row);
if (lockedRow) {
this.fly(lockedRow).removeClass(cls);
}
Ext.ux.grid.LockingHeaderGroupView.superclass.removeRowClass.call(this, row, cls);
},
removeRow: function (row) {
Ext.removeNode(this.getLockedRow(row));
Ext.ux.grid.LockingHeaderGroupView.superclass.removeRow.call(this, row);
},
removeRows: function (firstRow, lastRow) {
var lockedBody = this.lockedBody.dom,
rowIndex = firstRow;
for (; rowIndex <= lastRow; rowIndex++) {
Ext.removeNode(lockedBody.childNodes[firstRow]);
}
Ext.ux.grid.LockingHeaderGroupView.superclass.removeRows.call(this, firstRow, lastRow);
},
syncScroll: function (e) {
this.lockedScroller.dom.scrollTop = this.scroller.dom.scrollTop;
Ext.ux.grid.LockingHeaderGroupView.superclass.syncScroll.call(this, e);
},
updateSortIcon: function (col, dir) {
var sortClasses = this.sortClasses,
lockedHeaders = this.lockedHd.select('td').removeClass(sortClasses),
headers = this.mainHd.select('td').removeClass(sortClasses),
lockedLen = this.cm.getLockedCount(),
cls = sortClasses[dir == 'DESC' ? 1 : 0];
if (col < lockedLen) {
lockedHeaders.item(col).addClass(cls);
} else {
headers.item(col - lockedLen).addClass(cls);
}
},
updateAllColumnWidths: function () {
var tw = this.getTotalWidth(),
clen = this.cm.getColumnCount(),
lw = this.getLockedWidth(),
llen = this.cm.getLockedCount(),
ws = [], len, i;
this.updateLockedWidth();
for (i = 0; i < clen; i++) {
ws[i] = this.getColumnWidth(i);
var hd = this.getHeaderCell(i);
hd.style.width = ws[i];
}
var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j;
for (i = 0, len = ns.length; i < len; i++) {
row = lns[i];
row.style.width = lw;
if (row.firstChild) {
row.firstChild.style.width = lw;
trow = row.firstChild.rows[0];
for (j = 0; j < llen; j++) {
trow.childNodes[j].style.width = ws[j];
}
}
row = ns[i];
row.style.width = tw;
if (row.firstChild) {
row.firstChild.style.width = tw;
trow = row.firstChild.rows[0];
for (j = llen; j < clen; j++) {
trow.childNodes[j - llen].style.width = ws[j];
}
}
}
this.onAllColumnWidthsUpdated(ws, tw);
this.syncHeaderHeight();
},
updateColumnWidth: function (col, width) {
var w = this.getColumnWidth(col),
llen = this.cm.getLockedCount(),
ns, rw, c, row;
this.updateLockedWidth();
if (col < llen) {
ns = this.getLockedRows();
rw = this.getLockedWidth();
c = col;
} else {
ns = this.getRows();
rw = this.getTotalWidth();
c = col - llen;
}
var hd = this.getHeaderCell(col);
hd.style.width = w;
for (var i = 0, len = ns.length; i < len; i++) {
row = ns[i];
row.style.width = rw;
if (row.firstChild) {
row.firstChild.style.width = rw;
row.firstChild.rows[0].childNodes[c].style.width = w;
}
}
this.onColumnWidthUpdated(col, w, this.getTotalWidth());
this.syncHeaderHeight();
},
updateColumnHidden: function (col, hidden) {
var llen = this.cm.getLockedCount(),
ns, rw, c, row,
display = hidden ? 'none' : '';
this.updateLockedWidth();
if (col < llen) {
ns = this.getLockedRows();
rw = this.getLockedWidth();
c = col;
} else {
ns = this.getRows();
rw = this.getTotalWidth();
c = col - llen;
}
var hd = this.getHeaderCell(col);
hd.style.display = display;
for (var i = 0, len = ns.length; i < len; i++) {
row = ns[i];
row.style.width = rw;
if (row.firstChild) {
row.firstChild.style.width = rw;
row.firstChild.rows[0].childNodes[c].style.display = display;
}
}
this.onColumnHiddenUpdated(col, hidden, this.getTotalWidth());
delete this.lastViewWidth;
this.layout();
},
doRender: function (cs, rs, ds, startRow, colCount, stripe) {
var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount - 1,
tstyle = 'width:' + this.getTotalWidth() + ';',
lstyle = 'width:' + this.getLockedWidth() + ';',
buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r;
for (var j = 0, len = rs.length; j < len; j++) {
r = rs[j]; cb = []; lcb = [];
var rowIndex = (j + startRow);
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 ' : '')) +
(this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : '');
p.attr = p.cellAttr = '';
p.style = c.style;
//根据配置加入渲染前事件
if (c.scope && c.scope.beforeRenderer) {
c.scope.beforeRenderer.call(c.scope, r.data[c.name], p, r, j, i);
}
p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
if (Ext.isEmpty(p.value)) {
p.value = ' ';
}
if (this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])) {
p.css += ' x-grid3-dirty-cell';
}
if (c.locked) {
lcb[lcb.length] = ct.apply(p);
} else {
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('');
rp.tstyle = tstyle;
buf[buf.length] = rt.apply(rp);
rp.cells = lcb.join('');
rp.tstyle = lstyle;
lbuf[lbuf.length] = rt.apply(rp);
}
return [buf.join(''), lbuf.join('')];
},
processRows: function (startRow, skipStripe) {
if (!this.ds || this.ds.getCount() < 1) {
return;
}
var rows = this.getRows(),
lrows = this.getLockedRows(),
row, lrow;
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
for (var i = 0, len = rows.length; i < len; ++i) {
row = rows[i];
lrow = lrows[i];
row.rowIndex = i;
lrow.rowIndex = i;
if (!skipStripe) {
row.className = row.className.replace(this.rowClsRe, ' ');
lrow.className = lrow.className.replace(this.rowClsRe, ' ');
if ((i + 1) % 2 === 0) {
row.className += ' x-grid3-row-alt';
lrow.className += ' x-grid3-row-alt';
}
}
this.syncRowHeights(row, lrow);
}
if (startRow === 0) {
Ext.fly(rows[0]).addClass(this.firstRowCls);
Ext.fly(lrows[0]).addClass(this.firstRowCls);
}
Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls);
},
syncRowHeights: function (row1, row2) {
if (this.syncHeights) {
var el1 = Ext.get(row1),
el2 = Ext.get(row2),
h1 = el1.getHeight(),
h2 = el2.getHeight();
if (h1 > h2) {
el2.setHeight(h1);
} else if (h2 > h1) {
el1.setHeight(h2);
}
}
},
afterRender: function () {
if (!this.ds || !this.cm) {
return;
}
var bd = this.renderRows() || [' ', ' '];
this.mainBody.dom.innerHTML = bd[0];
this.lockedBody.dom.innerHTML = bd[1];
this.processRows(0, true);
if (this.deferEmptyText !== true) {
this.applyEmptyText();
}
this.grid.fireEvent('viewready', this.grid);
},
renderUI: function () {
var templates = this.templates,
header = this.renderHeaders(),
body = templates.body.apply({ rows: ' ' });
return templates.masterTpl.apply({
body: body,
header: header[0],
ostyle: 'width:' + this.getOffsetWidth() + ';',
bstyle: 'width:' + this.getTotalWidth() + ';',
lockedBody: body,
lockedHeader: header[1],
lstyle: 'width:' + this.getLockedWidth() + ';'
});
},
afterRenderUI: function () {
var g = this.grid;
this.initElements();
Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
Ext.fly(this.lockedInnerHd).on('click', this.handleHdDown, this);
this.mainHd.on({
scope: this,
mouseover: this.handleHdOver,
mouseout: this.handleHdOut,
mousemove: this.handleHdMove
});
this.lockedHd.on({
scope: this,
mouseover: this.handleHdOver,
mouseout: this.handleHdOut,
mousemove: this.handleHdMove
});
this.scroller.on('scroll', this.syncScroll, this);
if (g.enableColumnResize !== false) {
this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
this.splitZone.setOuterHandleElId(Ext.id(this.lockedHd.dom));
this.splitZone.setOuterHandleElId(Ext.id(this.mainHd.dom));
}
if (g.enableColumnMove) {
this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
this.columnDrag.setOuterHandleElId(Ext.id(this.lockedInnerHd));
this.columnDrag.setOuterHandleElId(Ext.id(this.innerHd));
this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
}
if (g.enableHdMenu !== false) {
this.hmenu = new Ext.menu.Menu({ id: g.id + '-hctx' });
this.hmenu.add(
{ itemId: 'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc' },
{ itemId: 'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc' }
);
if (this.grid.enableColLock !== false) {
this.hmenu.add('-',
{ itemId: 'lock', text: this.lockText, cls: 'xg-hmenu-lock' },
{ itemId: 'unlock', text: this.unlockText, cls: 'xg-hmenu-unlock' }
);
}
if (g.enableColumnHide !== false) {
this.colMenu = new Ext.menu.Menu({ id: g.id + '-hcols-menu' });
this.colMenu.on({
scope: this,
beforeshow: this.beforeColMenuShow,
itemclick: this.handleHdMenuClick
});
this.hmenu.add('-', {
itemId: 'columns',
hideOnClick: false,
text: this.columnsText,
menu: this.colMenu,
iconCls: 'x-cols-icon'
});
}
this.hmenu.on('itemclick', this.handleHdMenuClick, this);
}
if (g.trackMouseOver) {
this.mainBody.on({
scope: this,
mouseover: this.onRowOver,
mouseout: this.onRowOut
});
this.lockedBody.on({
scope: this,
mouseover: this.onRowOver,
mouseout: this.onRowOut
});
}
if (g.enableDragDrop || g.enableDrag) {
this.dragZone = new Ext.grid.GridDragZone(g, {
ddGroup: g.ddGroup || 'GridDD'
});
}
this.updateHeaderSortState();
},
layout: function () {
if (!this.mainBody) {
return;
}
var g = this.grid;
var c = g.getGridEl();
var csize = c.getSize(true);
var vw = csize.width;
if (!g.hideHeaders && (vw < 20 || csize.height < 20)) {
return;
}
this.syncHeaderHeight();
if (g.autoHeight) {
this.scroller.dom.style.overflow = 'visible';
this.lockedScroller.dom.style.overflow = 'visible';
if (Ext.isWebKit) {
this.scroller.dom.style.position = 'static';
this.lockedScroller.dom.style.position = 'static';
}
} else {
this.el.setSize(csize.width, csize.height);
var hdHeight = this.mainHd.getHeight();
var vh = csize.height - (hdHeight);
}
this.updateLockedWidth();
if (this.forceFit) {
if (this.lastViewWidth != vw) {
this.fitColumns(false, false);
this.lastViewWidth = vw;
}
} else {
this.autoExpand();
this.syncHeaderScroll();
}
this.onLayout(vw, vh);
},
getOffsetWidth: function () {
return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth() + this.getScrollOffset()) + 'px';
},
//GROUP 方法
getGroupStyle: function (group, gcol) {
var width = 0, hidden = true;
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;
}
}
if (group.colspan > 1) {
if (Ext.isWebKit) {
//Chrome浏览器下,取消注释会出现表头对不齐的问题
//width += (group.colspan - 1);
} else if (Ext.isIE7) {
width = width - 2;
} else {
width--;
}
} else {
if (Ext.isGecko) {
if (gcol == 1 || gcol == 3) {
width++;
}
}
}
return {
width: (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? width : Math.max(width - this.borderWidth, 0)) + 'px',
hidden: hidden
};
},
updateGroupStyles: function (col) {
var tables = this.mainHd.query('.x-grid3-header-offset > table'), tw = this.getTotalWidth(), grows = this.grows;
for (var row = 0; row < tables.length; row++) {
tables[row].style.width = tw;
if (row < grows.length) {
var cells = tables[row].firstChild.firstChild.childNodes;
for (var i = 0, gcol = 0; i < cells.length; i++) {
var group = grows[row][i];
if ((typeof col != 'number') || (col >= gcol && col < gcol + group.colspan)) {
var gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);
cells[i].style.width = gs.width;
cells[i].style.display = gs.hidden ? 'none' : '';
}
gcol += group.colspan;
}
}
}
},
getGroupRowIndex: function (el) {
if (el) {
var m = el.className.match(this.hrowRe);
if (m && m[1]) {
return parseInt(m[1], 10);
}
}
return this.grows.length;
},
getGroupSpan: function (row, col) {
if (row < 0) {
return {
col: 0,
colspan: this.cm.getColumnCount()
};
}
var r = this.grows[row];
if (r) {
for (var i = 0, gcol = 0, len = r.length; i < len; i++) {
var group = r[i];
if (col >= gcol && col < gcol + group.colspan) {
return {
col: gcol,
colspan: group.colspan
};
}
gcol += group.colspan;
}
return {
col: gcol,
colspan: 0
};
}
return {
col: col,
colspan: 1
};
},
renderHeaders: function () {
var ts = this.templates, headers = [], cm = this.cm, grows = this.grows;
var len = cm.getColumnCount();
if (!grows || grows.length == 0) {
//没有多表头,
return this.getLockingHeaders();
}
var lockIndex = -1, lockGrows = [], unlockGrows = [];
for (var i = 0; i < len; i++) {
if (cm.isLocked(i)) {
//这个字段是锁定字段,根据这个字段所处的位置,将多表头定义的rows分割成两部分
//注意不能break,需要找到最后的一个locked字段
lockIndex = i;
}
}
grows = grows[0];//FIXME 定义分组情况的数组,只支持两级分组
//lockIndex = 5;
//[{},{},{},{"align":"center","colspan":2,"header":"合并后名称"},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]
if (lockIndex >= 0) {
var index = 0; var colspan = 0;
//原判断错误,修复了右边unLockedGrid只有一列的时候,不显示表头的问题
for (var i = 0; i < grows.length; i++) {
var group = grows[i];
colspan += group.colspan || 1;
index = colspan - 1;
if (lockIndex >= index) {
lockGrows.push(group);
} else {
unlockGrows.push(group);
}
}
}
var lastLockCol = 0;
for (var i = 0; i < lockGrows.length; i++) {
var g = lockGrows[i];
lastLockCol += (g.colspan || 1);
}
//分组表头,包括锁定的分组和未锁定的分组
var lockGroupHeader = this.getGroupHeader(lockGrows, true, 0);
var unlockGroupHeader = this.getGroupHeader(unlockGrows, false, lastLockCol);
//实际的表头,包括锁定和未锁定
var lockingHeaders = this.getLockingHeaders();
var s1 = [unlockGroupHeader, lockingHeaders[0]].join('');
var s2 = [lockGroupHeader, lockingHeaders[1]].join('');
return [s1, s2];
},
/**
* 返回多表头的header部分
* @param {} grows 列的集合,可能是锁定部分和未锁定部分
* @param {} lockflag 是否锁定的标识
* @param {} firstIndex 比如对于未锁定部分,表示的就是:lockGrows的第一个元素在整个cm的位置
* @return {}
*/
getGroupHeader: function (grows, lockflag, lastLockCol) {
var ts = this.templates, cm = this.cm, cells = [];
for (var i = 0, len = grows.length; i < len; i++) {
var group = grows[i];
group.colspan = group.colspan || 1;
//FIXME 如果没有分组的列,那么需要加入一个style,参见GridView.getColumnStyle
var colIndex = group.dataIndex ? cm.findColumnIndex(group.dataIndex) : lastLockCol;
//最底下的一层分组(不计算表格的header这一组)
if (group.colspan == 1) {
cm.config[colIndex].marginTop = true;
}
var id = this.getColumnId(colIndex), gs = this.getGroupStyle.call(this, group, lastLockCol);
cells[i] = ts.gcell.apply({
cls: (group.header && group.header != '') ? 'ux-grid-hd-group-cell' : 'ux-grid-hd-nogroup-cell',
id: id,
row: 0,
style: 'width:' + gs.width + ';' + (gs.hidden ? 'display:none;' : '') + (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: group.header || ' '
});
lastLockCol += group.colspan;
}
var tstyle = 'width:' + this.getLockedWidth() + ';';
if (!lockflag) {
tstyle = 'width:' + this.getTotalWidth() + ';';
}
//加入合并的表头
return ts.header.apply({
cells: cells.join(''),
tstyle: tstyle
});
},
/**
* 返回锁定列的表头部分
* @return {}
*/
getLockingHeaders: function () {
var cm = this.cm,
ts = this.templates,
ct = ts.hcell,
cb = [], lcb = [],
p = {},
len = cm.getColumnCount(),
last = len - 1;
for (var 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 = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
(cm.config[i].headerCls ? ' ' + cm.config[i].headerCls : '');
if (cm.config[i].align == 'right') {
p.istyle = 'padding-right:16px';
} else {
delete p.istyle;
}
//FIXME 如果是多表头的情况下,需要设置文字上下居中,这里只针对适合2组表头的情况.marginTop在ColumnHeaderGroup.js中设置了
if (!p.istyle) {
p.istyle = '';
}
if (cm.config[i].marginTop && !cm.config[i].hidden) {
p.istyle += ';margin-top:-20px;height:40px;line-height:40px;';
}
if (cm.isLocked(i)) {
lcb[lcb.length] = ct.apply(p);
} else {
cb[cb.length] = ct.apply(p);
}
}
return [ts.header.apply({ cells: cb.join(''), tstyle: 'width:' + this.getTotalWidth() + ';' }),
ts.header.apply({ cells: lcb.join(''), tstyle: 'width:' + this.getLockedWidth() + ';' })];
},
updateHeaders: function () {
var hd = this.renderHeaders();
this.innerHd.firstChild.innerHTML = hd[0];
this.innerHd.firstChild.style.width = this.getOffsetWidth();
this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
this.lockedInnerHd.firstChild.innerHTML = hd[1];
var lw = this.getLockedWidth();
this.lockedInnerHd.firstChild.style.width = lw;
this.lockedInnerHd.firstChild.firstChild.style.width = lw;
},
getResolvedXY: function (resolved) {
if (!resolved) {
return null;
}
var c = resolved.cell, r = resolved.row;
return c ? Ext.fly(c).getXY() : [this.scroller.getX(), Ext.fly(r).getY()];
},
syncFocusEl: function (row, col, hscroll) {
Ext.ux.grid.LockingHeaderGroupView.superclass.syncFocusEl.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);
},
ensureVisible: function (row, col, hscroll) {
return Ext.ux.grid.LockingHeaderGroupView.superclass.ensureVisible.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);
},
insertRows: function (dm, firstRow, lastRow, isUpdate) {
var last = dm.getCount() - 1;
if (!isUpdate && firstRow === 0 && lastRow >= last) {
this.refresh();
} else {
if (!isUpdate) {
this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
}
var html = this.renderRows(firstRow, lastRow),
before = this.getRow(firstRow);
if (before) {
if (firstRow === 0) {
this.removeRowClass(0, this.firstRowCls);
}
Ext.DomHelper.insertHtml('beforeBegin', before, html[0]);
before = this.getLockedRow(firstRow);
Ext.DomHelper.insertHtml('beforeBegin', before, html[1]);
} else {
this.removeRowClass(last - 1, this.lastRowCls);
Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]);
Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]);
}
if (!isUpdate) {
this.fireEvent('rowsinserted', this, firstRow, lastRow);
this.processRows(firstRow);
} else if (firstRow === 0 || firstRow >= last) {
this.addRowClass(firstRow, firstRow === 0 ? this.firstRowCls : this.lastRowCls);
}
}
this.syncFocusEl(firstRow);
},
getColumnStyle: function (col, isHeader) {
var style = !isHeader ? this.cm.config[col].cellStyle || this.cm.config[col].css || '' : this.cm.config[col].headerStyle || '';
style += 'width:' + this.getColumnWidth(col) + ';';
if (this.cm.isHidden(col)) {
style += 'display:none;';
}
var align = this.cm.config[col].align;
if (align) {
style += 'text-align:' + align + ';';
}
return style;
},
getLockedWidth: function () {
return (this.cm.getTotalLockedWidth() + 1) + 'px';
},
getTotalWidth: function () {
return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth()) + 'px';
},
getColumnData: function () {
var cs = [], cm = this.cm, colCount = cm.getColumnCount();
for (var i = 0; i < colCount; i++) {
var name = cm.getDataIndex(i);
cs[i] = {
name: (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),
renderer: cm.getRenderer(i),
scope: cm.getRendererScope(i),
id: cm.getColumnId(i),
style: this.getColumnStyle(i),
locked: cm.isLocked(i)
};
}
return cs;
},
renderBody: function () {
var markup = this.renderRows() || [' ', ' '];
return [this.templates.body.apply({ rows: markup[0] }), this.templates.body.apply({ rows: markup[1] })];
},
refreshRow: function (record) {
var store = this.ds,
colCount = this.cm.getColumnCount(),
columns = this.getColumnData(),
last = colCount - 1,
cls = ['x-grid3-row'],
rowParams = {
tstyle: String.format("width: {0};", this.getTotalWidth())
},
lockedRowParams = {
tstyle: String.format("width: {0};", this.getLockedWidth())
},
colBuffer = [],
lockedColBuffer = [],
cellTpl = this.templates.cell,
rowIndex,
row,
lockedRow,
column,
meta,
css,
i;
if (Ext.isNumber(record)) {
rowIndex = record;
record = store.getAt(rowIndex);
} else {
rowIndex = store.indexOf(record);
}
if (!record || rowIndex < 0) {
return;
}
for (i = 0; i < colCount; i++) {
column = columns[i];
if (i == 0) {
css = 'x-grid3-cell-first';
} else {
css = (i == last) ? 'x-grid3-cell-last ' : '';
}
meta = {
id: column.id,
style: column.style,
css: css,
attr: "",
cellAttr: ""
};
if (column.scope && column.scope.beforeRenderer) {
column.scope.beforeRenderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
}
meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
if (Ext.isEmpty(meta.value)) {
meta.value = ' ';
}
if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
meta.css += ' x-grid3-dirty-cell';
}
if (column.locked) {
lockedColBuffer[i] = cellTpl.apply(meta);
} else {
colBuffer[i] = cellTpl.apply(meta);
}
}
row = this.getRow(rowIndex);
row.className = '';
lockedRow = this.getLockedRow(rowIndex);
lockedRow.className = '';
if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
cls.push('x-grid3-row-alt');
}
if (this.getRowClass) {
rowParams.cols = colCount;
cls.push(this.getRowClass(record, rowIndex, rowParams, store));
}
// Unlocked rows
this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
rowParams.cells = colBuffer.join("");
row.innerHTML = this.templates.rowInner.apply(rowParams);
// Locked rows
this.fly(lockedRow).addClass(cls).setStyle(lockedRowParams.tstyle);
lockedRowParams.cells = lockedColBuffer.join("");
lockedRow.innerHTML = this.templates.rowInner.apply(lockedRowParams);
lockedRow.rowIndex = rowIndex;
this.syncRowHeights(row, lockedRow);
this.fireEvent('rowupdated', this, rowIndex, record);
},
refresh: function (headersToo) {
this.fireEvent('beforerefresh', this);
this.grid.stopEditing(true);
var result = this.renderBody();
this.mainBody.update(result[0]).setWidth(this.getTotalWidth());
this.lockedBody.update(result[1]).setWidth(this.getLockedWidth());
if (headersToo === true) {
this.updateHeaders();
this.updateHeaderSortState();
}
this.processRows(0, true);
this.layout();
this.applyEmptyText();
this.fireEvent('refresh', this);
//修复了左右两边lockedGrid|unLockedGrid行高不一致的问题
$(".x-grid3-row").height(20);
},
onDenyColumnLock: function () {
},
initData: function (ds, cm) {
if (this.cm) {
this.cm.un('columnlockchange', this.onColumnLock, this);
}
Ext.ux.grid.LockingHeaderGroupView.superclass.initData.call(this, ds, cm);
if (this.cm) {
this.cm.on('columnlockchange', this.onColumnLock, this);
}
},
onColumnLock: function () {
this.refresh(true);
},
handleHdMenuClick: function (item) {
var index = this.hdCtxIndex,
cm = this.cm,
id = item.getItemId(),
llen = cm.getLockedCount();
switch (id) {
case 'lock':
if (cm.getColumnCount(true) <= llen + 1) {
this.onDenyColumnLock();
return undefined;
}
cm.setLocked(index, true);
if (llen != index) {
cm.moveColumn(index, llen);
this.grid.fireEvent('columnmove', index, llen);
}
break;
case 'unlock':
if (llen - 1 != index) {
cm.setLocked(index, false, true);
cm.moveColumn(index, llen - 1);
this.grid.fireEvent('columnmove', index, llen - 1);
} else {
cm.setLocked(index, false);
}
break;
default:
return Ext.ux.grid.LockingHeaderGroupView.superclass.handleHdMenuClick.call(this, item);
}
return true;
},
handleHdDown: function (e, t) {
Ext.ux.grid.LockingHeaderGroupView.superclass.handleHdDown.call(this, e, t);
if (this.grid.enableColLock !== false) {
if (Ext.fly(t).hasClass('x-grid3-hd-btn')) {
var hd = this.findHeaderCell(t),
index = this.getCellIndex(hd),
ms = this.hmenu.items, cm = this.cm;
ms.get('lock').setDisabled(cm.isLocked(index));
ms.get('unlock').setDisabled(!cm.isLocked(index));
}
}
},
syncHeaderHeight: function () {
var hrow = Ext.fly(this.innerHd).child('tr', true),
lhrow = Ext.fly(this.lockedInnerHd).child('tr', true);
if (!hrow || !lhrow) {
return;
}
hrow.style.height = 'auto';
lhrow.style.height = 'auto';
var hd = hrow.offsetHeight,
lhd = lhrow.offsetHeight,
height = Math.max(lhd, hd) + 'px';
hrow.style.height = height;
lhrow.style.height = height;
},
updateLockedWidth: function () {
var lw = this.cm.getTotalLockedWidth(),
tw = this.cm.getTotalWidth() - lw,
csize = this.grid.getGridEl().getSize(true),
lp = Ext.isBorderBox ? 0 : this.lockedBorderWidth,
rp = Ext.isBorderBox ? 0 : this.rowBorderWidth,
vw = (csize.width - lw - lp - rp) + 'px',
so = this.getScrollOffset();
if (!this.grid.autoHeight) {
//多页签时,隐藏的页签高度不能为0,否则会导致表体不见
if (csize.height - this.mainHd.getHeight() == 0) {
var vh = (this.grid.getGridEl().dom.style.height - this.mainHd.getHeight()) + 'px';
this.lockedScroller.dom.style.height = vh;
this.scroller.dom.style.height = vh;
} else {
var vh = (csize.height - this.mainHd.getHeight()) + 'px';
this.lockedScroller.dom.style.height = vh;
this.scroller.dom.style.height = vh;
}
}
this.lockedWrap.dom.style.width = (lw + rp) + 'px';
this.scroller.dom.style.width = vw;
this.mainWrap.dom.style.left = (lw + lp + rp) + 'px';
if (this.innerHd) {
this.lockedInnerHd.firstChild.style.width = lw + 'px';
this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px';
this.innerHd.style.width = vw;
this.innerHd.firstChild.style.width = (tw + rp + so) + 'px';
this.innerHd.firstChild.firstChild.style.width = tw + 'px';
}
if (this.mainBody) {
this.lockedBody.dom.style.width = (lw + rp) + 'px';
this.mainBody.dom.style.width = (tw + rp) + 'px';
}
}
});
这个文件现在在项目(Ext3.4)上使用,需要注意,这里只提供js文件,需要导入ux.css样式表;
下面给出示例:
//同步获取节点数据
var ssnAry = eval("(" + $.ajax({
url: "PdmP029.csx?tag=GetSsnJsonList",
async: false,
data: {
ssnId: '41'
}
}).responseText + ")");
//使用RowSelectionModel
var sm = new Ext.grid.RowSelectionModel();
//定义ColumnModel
var columns = [{
header: "内码",
dataIndex: "MSI_IDS",
hidden: true,
locked: true
}, {
header: "船号",
dataIndex: "PROJ_NO",
width: sm_width,
renderer: projStore.toValue,
locked: true
}, {
header: "作业对象类型",
dataIndex: "OBJ_TYPE",
width: md_width,
locked: true
}, {
header: "作业对象编码",
dataIndex: "OBJ_CODE",
width: md_width,
locked: true
}, {
header: "搭载开始时间",
dataIndex: "C_BEGIN_DATE",
width: md_width,
xtype: 'datecolumn',
format: Date.patterns.ISO8601Short,
locked: true
}, {
header: "差距",
dataIndex: "C_CYC",
width: md_width,
renderer: function (v) {
if (v == '-0') {
return '0';
}
return v;
},
locked: true
}
];
//定义fields
var fields = ['PROJ_NO', 'OBJ_TYPE', 'OBJ_CODE', 'MSI_IDS', 'C_BEGIN_DATE', 'C_CYC', 'FLAG'];
//定义多表头
var hgroup = [[{
header: '基本信息',
align: 'center',
rowspan: 1,
colspan: 6
}
]];
//节点循环,动态生成字段fields,列模型columns
for (var i = 0; i < ssnAry.data.length; i++) {
columns.push({
header: '内码',
dataIndex: "MSI_ID_" + ssnAry.data[i].id,
hidden: true
});
columns.push({
header: '中日程计划开始',
dataIndex: "OBEGIN_DATE_" + ssnAry.data[i].id,
xtype: 'datecolumn',
format: Date.patterns.ISO8601Short,
width: xs_width,
hidden: true
});
columns.push({
header: '中日程计划结束',
dataIndex: "OEND_DATE_" + ssnAry.data[i].id,
xtype: 'datecolumn',
format: Date.patterns.ISO8601Short,
width: xs_width,
hidden: true
});
columns.push({
header: '计划开始',
dataIndex: "BEGIN_DATE_" + ssnAry.data[i].id,
xtype: 'datecolumn',
format: Date.patterns.ISO8601Short,
width: md_width,
editor: new ef.DateField({
format: Date.patterns.ISO8601Short
})
});
columns.push({
header: '计划结束',
dataIndex: "END_DATE_" + ssnAry.data[i].id,
xtype: 'datecolumn',
format: Date.patterns.ISO8601Short,
width: md_width,
editor: new ef.DateField({
format: Date.patterns.ISO8601Short
})
});
fields.push("MSI_ID_" + ssnAry.data[i].id);
fields.push("CYC_" + ssnAry.data[i].id);
fields.push("STATE_" + ssnAry.data[i].id);
fields.push("FLOAT_CYC_" + ssnAry.data[i].id);
fields.push("OBEGIN_DATE_" + ssnAry.data[i].id);
fields.push("OEND_DATE_" + ssnAry.data[i].id);
fields.push({
name: "ACTUAL_BEGIN_" + ssnAry.data[i].id,
type: "date",
dateFormat: Date.patterns.ISO8601Short
});
fields.push("ACTUAL_END_" + ssnAry.data[i].id);
fields.push({
name: "BEGIN_DATE_" + ssnAry.data[i].id,
type: "date",
dateFormat: Date.patterns.ISO8601Short
});
fields.push({
name: "END_DATE_" + ssnAry.data[i].id,
type: "date",
dateFormat: Date.patterns.ISO8601Short
});
hgroup[0].push({
header: ssnAry.data[i].cvalue,
align: 'center',
rowspan: 2,
colspan: 5
});
}
//定义数据集
var store = new Ext.data.JsonStore({
url: "MON15.csx?tag=GetJsonList",
fields: fields,
pruneModifiedRecords: true,
root: "data",
id: '',
totalProperty: "totalCount",
listeners: {
beforeload: function (v) {
if (versionid_cmb.getValue() == '') {
prityTip("提示", '请先选择计划!');
return false;
}
var p = {
proj_no: proj_cmb.getValue(),
obj_code: obj_code_txt.getValue(),
versionid: versionid_cmb.getValue(),
limit: pagebar.pageSize,
page: "1",
show_act: show_act.getValue()
};
Ext.apply(Ext.version.startsWith('3') ? v.baseParams : v.proxy.extraParams, p);
},
load: function (store, records) {
SetCellBg();
}
}
});
//定义LockingColumnModel
var cm = new Ext.ux.grid.LockingColumnModel({
columns: columns,
isCellEditable: function (colIndex, rowIndex) {
//如果本记录字段FLAG==实际,那么本行记录不允许编辑
var record = store.getAt(rowIndex);
if (record.get('FLAG') == '实际') {
return false;
}
return Ext.grid.ColumnModel.prototype.isCellEditable.call(this, colIndex, rowIndex);
},
listeners: {
hiddenchange: function () {
grid.getView().updateHeaders();
}
}
});
//分页工具栏
var pagebar = new prj.util.PagingToolbar(store, 500, true);
//grid定义
var grid = new Ext.RSEGrid({
id: 'MON15_grid',
loadMask: true,
store: store,
bbar: pagebar,
sm: sm,
cm: cm,
region: 'center',
margins: '3 3 3 3',
border: 1,
split: true,
tbar: tbar,
listeners: {
afteredit: function (e) {},
sortchange: function () {}
},
view: new Ext.ux.grid.LockingHeaderGroupView({
grows: hgroup,
getRowClass: function (record, rowIndex, rowParams, store) {
if (record.data.FLAG == '实际') {
return 'row-bg-lemonchiffon';
}
}
})
});
效果图:
写在后面的话: 左右对不齐的BUG修复方法,使用jQuery暴力修改,因此需要引入jQuery.js.
从CSDN找到了多表头锁定控件,到修改BUG,再到生产中实际运用.关键还是源码源码源码.
这个控件还剩下一个小尾巴BUG,就是暂时还不支持checkboxSelectionModel.
本篇博客将会继续更新.