Ext3.x版本锁定列和多表头的插件,BUG修复版

Ext3.x版本的锁定列和多表头都是通过插件实现,但是这两个插件不能一起工作,而实际上这样的业务也是存在的,即在多表头的情况下也需要锁定列。

在Ext的论坛上有很多这样的需求,都没有一个解决方案,除了一个需要收费的(150没有),而且联系起来也麻烦。后面决定自己做一个。

效果如下图:

文件:

/*!
 * Ext JS Library 3.3.0
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * 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(
                '<div class="x-grid3" hidefocus="true">',
                '<div class="x-grid3-locked">',
                '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{lstyle}">{lockedHeader}</div></div><div class="x-clear"></div></div>',
                '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{lstyle}">{lockedBody}</div><div class="x-grid3-scroll-spacer"></div></div>',
                '</div>',
                '<div class="x-grid3-viewport x-grid3-unlocked">',
                '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
                '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
                '</div>',
                '<div class="x-grid3-resize-marker">&#160;</div>',
                '<div class="x-grid3-resize-proxy">&#160;</div>',
                '</div>'
            );
        }

        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}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '', '{value}</div></td>');
        }
        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 = '&#160;';
                }
                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() || ['&#160;', '&#160;'];
        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: '&#160;' });

        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 || '&nbsp;'
            });
            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() || ['&#160;', '&#160;'];
        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.

本篇博客将会继续更新.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sword_happy

您的鼓励亦是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值