Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / ux / LockingGridView.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /*!
8  * Ext JS Library 3.3.0
9  * Copyright(c) 2006-2010 Ext JS, Inc.
10  * licensing@extjs.com
11  * http://www.extjs.com/license
12  */
13 Ext.ns('Ext.ux.grid');
14
15 Ext.ux.grid.LockingGridView = Ext.extend(Ext.grid.GridView, {
16     lockText : 'Lock',
17     unlockText : 'Unlock',
18     rowBorderWidth : 1,
19     lockedBorderWidth : 1,
20
21     /*
22      * This option ensures that height between the rows is synchronized
23      * between the locked and unlocked sides. This option only needs to be used
24      * when the row heights aren't predictable.
25      */
26     syncHeights: false,
27
28     initTemplates : function(){
29         var ts = this.templates || {};
30
31         if (!ts.masterTpl) {
32             ts.masterTpl = new Ext.Template(
33                 '<div class="x-grid3" hidefocus="true">',
34                     '<div class="x-grid3-locked">',
35                         '<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>',
36                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{lstyle}">{lockedBody}</div><div class="x-grid3-scroll-spacer"></div></div>',
37                     '</div>',
38                     '<div class="x-grid3-viewport x-grid3-unlocked">',
39                         '<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>',
40                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
41                     '</div>',
42                     '<div class="x-grid3-resize-marker">&#160;</div>',
43                     '<div class="x-grid3-resize-proxy">&#160;</div>',
44                 '</div>'
45             );
46         }
47
48         this.templates = ts;
49
50         Ext.ux.grid.LockingGridView.superclass.initTemplates.call(this);
51     },
52
53     getEditorParent : function(ed){
54         return this.el.dom;
55     },
56
57     initElements : function(){
58         var el             = Ext.get(this.grid.getGridEl().dom.firstChild),
59             lockedWrap     = el.child('div.x-grid3-locked'),
60             lockedHd       = lockedWrap.child('div.x-grid3-header'),
61             lockedScroller = lockedWrap.child('div.x-grid3-scroller'),
62             mainWrap       = el.child('div.x-grid3-viewport'),
63             mainHd         = mainWrap.child('div.x-grid3-header'),
64             scroller       = mainWrap.child('div.x-grid3-scroller');
65             
66         if (this.grid.hideHeaders) {
67             lockedHd.setDisplayed(false);
68             mainHd.setDisplayed(false);
69         }
70         
71         if(this.forceFit){
72             scroller.setStyle('overflow-x', 'hidden');
73         }
74         
75         Ext.apply(this, {
76             el      : el,
77             mainWrap: mainWrap,
78             mainHd  : mainHd,
79             innerHd : mainHd.dom.firstChild,
80             scroller: scroller,
81             mainBody: scroller.child('div.x-grid3-body'),
82             focusEl : scroller.child('a'),
83             resizeMarker: el.child('div.x-grid3-resize-marker'),
84             resizeProxy : el.child('div.x-grid3-resize-proxy'),
85             lockedWrap: lockedWrap,
86             lockedHd: lockedHd,
87             lockedScroller: lockedScroller,
88             lockedBody: lockedScroller.child('div.x-grid3-body'),
89             lockedInnerHd: lockedHd.child('div.x-grid3-header-inner', true)
90         });
91         
92         this.focusEl.swallowEvent('click', true);
93     },
94
95     getLockedRows : function(){
96         return this.hasRows() ? this.lockedBody.dom.childNodes : [];
97     },
98
99     getLockedRow : function(row){
100         return this.getLockedRows()[row];
101     },
102
103     getCell : function(row, col){
104         var lockedLen = this.cm.getLockedCount();
105         if(col < lockedLen){
106             return this.getLockedRow(row).getElementsByTagName('td')[col];
107         }
108         return Ext.ux.grid.LockingGridView.superclass.getCell.call(this, row, col - lockedLen);
109     },
110
111     getHeaderCell : function(index){
112         var lockedLen = this.cm.getLockedCount();
113         if(index < lockedLen){
114             return this.lockedHd.dom.getElementsByTagName('td')[index];
115         }
116         return Ext.ux.grid.LockingGridView.superclass.getHeaderCell.call(this, index - lockedLen);
117     },
118
119     addRowClass : function(row, cls){
120         var lockedRow = this.getLockedRow(row);
121         if(lockedRow){
122             this.fly(lockedRow).addClass(cls);
123         }
124         Ext.ux.grid.LockingGridView.superclass.addRowClass.call(this, row, cls);
125     },
126
127     removeRowClass : function(row, cls){
128         var lockedRow = this.getLockedRow(row);
129         if(lockedRow){
130             this.fly(lockedRow).removeClass(cls);
131         }
132         Ext.ux.grid.LockingGridView.superclass.removeRowClass.call(this, row, cls);
133     },
134
135     removeRow : function(row) {
136         Ext.removeNode(this.getLockedRow(row));
137         Ext.ux.grid.LockingGridView.superclass.removeRow.call(this, row);
138     },
139
140     removeRows : function(firstRow, lastRow){
141         var lockedBody = this.lockedBody.dom,
142             rowIndex = firstRow;
143         for(; rowIndex <= lastRow; rowIndex++){
144             Ext.removeNode(lockedBody.childNodes[firstRow]);
145         }
146         Ext.ux.grid.LockingGridView.superclass.removeRows.call(this, firstRow, lastRow);
147     },
148
149     syncScroll : function(e){
150         this.lockedScroller.dom.scrollTop = this.scroller.dom.scrollTop;
151         Ext.ux.grid.LockingGridView.superclass.syncScroll.call(this, e);
152     },
153
154     updateSortIcon : function(col, dir){
155         var sortClasses = this.sortClasses,
156             lockedHeaders = this.lockedHd.select('td').removeClass(sortClasses),
157             headers = this.mainHd.select('td').removeClass(sortClasses),
158             lockedLen = this.cm.getLockedCount(),
159             cls = sortClasses[dir == 'DESC' ? 1 : 0];
160             
161         if(col < lockedLen){
162             lockedHeaders.item(col).addClass(cls);
163         }else{
164             headers.item(col - lockedLen).addClass(cls);
165         }
166     },
167
168     updateAllColumnWidths : function(){
169         var tw = this.getTotalWidth(),
170             clen = this.cm.getColumnCount(),
171             lw = this.getLockedWidth(),
172             llen = this.cm.getLockedCount(),
173             ws = [], len, i;
174         this.updateLockedWidth();
175         for(i = 0; i < clen; i++){
176             ws[i] = this.getColumnWidth(i);
177             var hd = this.getHeaderCell(i);
178             hd.style.width = ws[i];
179         }
180         var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j;
181         for(i = 0, len = ns.length; i < len; i++){
182             row = lns[i];
183             row.style.width = lw;
184             if(row.firstChild){
185                 row.firstChild.style.width = lw;
186                 trow = row.firstChild.rows[0];
187                 for (j = 0; j < llen; j++) {
188                    trow.childNodes[j].style.width = ws[j];
189                 }
190             }
191             row = ns[i];
192             row.style.width = tw;
193             if(row.firstChild){
194                 row.firstChild.style.width = tw;
195                 trow = row.firstChild.rows[0];
196                 for (j = llen; j < clen; j++) {
197                    trow.childNodes[j - llen].style.width = ws[j];
198                 }
199             }
200         }
201         this.onAllColumnWidthsUpdated(ws, tw);
202         this.syncHeaderHeight();
203     },
204
205     updateColumnWidth : function(col, width){
206         var w = this.getColumnWidth(col),
207             llen = this.cm.getLockedCount(),
208             ns, rw, c, row;
209         this.updateLockedWidth();
210         if(col < llen){
211             ns = this.getLockedRows();
212             rw = this.getLockedWidth();
213             c = col;
214         }else{
215             ns = this.getRows();
216             rw = this.getTotalWidth();
217             c = col - llen;
218         }
219         var hd = this.getHeaderCell(col);
220         hd.style.width = w;
221         for(var i = 0, len = ns.length; i < len; i++){
222             row = ns[i];
223             row.style.width = rw;
224             if(row.firstChild){
225                 row.firstChild.style.width = rw;
226                 row.firstChild.rows[0].childNodes[c].style.width = w;
227             }
228         }
229         this.onColumnWidthUpdated(col, w, this.getTotalWidth());
230         this.syncHeaderHeight();
231     },
232
233     updateColumnHidden : function(col, hidden){
234         var llen = this.cm.getLockedCount(),
235             ns, rw, c, row,
236             display = hidden ? 'none' : '';
237         this.updateLockedWidth();
238         if(col < llen){
239             ns = this.getLockedRows();
240             rw = this.getLockedWidth();
241             c = col;
242         }else{
243             ns = this.getRows();
244             rw = this.getTotalWidth();
245             c = col - llen;
246         }
247         var hd = this.getHeaderCell(col);
248         hd.style.display = display;
249         for(var i = 0, len = ns.length; i < len; i++){
250             row = ns[i];
251             row.style.width = rw;
252             if(row.firstChild){
253                 row.firstChild.style.width = rw;
254                 row.firstChild.rows[0].childNodes[c].style.display = display;
255             }
256         }
257         this.onColumnHiddenUpdated(col, hidden, this.getTotalWidth());
258         delete this.lastViewWidth;
259         this.layout();
260     },
261
262     doRender : function(cs, rs, ds, startRow, colCount, stripe){
263         var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1,
264             tstyle = 'width:'+this.getTotalWidth()+';',
265             lstyle = 'width:'+this.getLockedWidth()+';',
266             buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r;
267         for(var j = 0, len = rs.length; j < len; j++){
268             r = rs[j]; cb = []; lcb = [];
269             var rowIndex = (j+startRow);
270             for(var i = 0; i < colCount; i++){
271                 c = cs[i];
272                 p.id = c.id;
273                 p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
274                     (this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : '');
275                 p.attr = p.cellAttr = '';
276                 p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
277                 p.style = c.style;
278                 if(Ext.isEmpty(p.value)){
279                     p.value = '&#160;';
280                 }
281                 if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){
282                     p.css += ' x-grid3-dirty-cell';
283                 }
284                 if(c.locked){
285                     lcb[lcb.length] = ct.apply(p);
286                 }else{
287                     cb[cb.length] = ct.apply(p);
288                 }
289             }
290             var alt = [];
291             if(stripe && ((rowIndex+1) % 2 === 0)){
292                 alt[0] = 'x-grid3-row-alt';
293             }
294             if(r.dirty){
295                 alt[1] = ' x-grid3-dirty-row';
296             }
297             rp.cols = colCount;
298             if(this.getRowClass){
299                 alt[2] = this.getRowClass(r, rowIndex, rp, ds);
300             }
301             rp.alt = alt.join(' ');
302             rp.cells = cb.join('');
303             rp.tstyle = tstyle;
304             buf[buf.length] = rt.apply(rp);
305             rp.cells = lcb.join('');
306             rp.tstyle = lstyle;
307             lbuf[lbuf.length] = rt.apply(rp);
308         }
309         return [buf.join(''), lbuf.join('')];
310     },
311     processRows : function(startRow, skipStripe){
312         if(!this.ds || this.ds.getCount() < 1){
313             return;
314         }
315         var rows = this.getRows(),
316             lrows = this.getLockedRows(),
317             row, lrow;
318         skipStripe = skipStripe || !this.grid.stripeRows;
319         startRow = startRow || 0;
320         for(var i = 0, len = rows.length; i < len; ++i){
321             row = rows[i];
322             lrow = lrows[i];
323             row.rowIndex = i;
324             lrow.rowIndex = i;
325             if(!skipStripe){
326                 row.className = row.className.replace(this.rowClsRe, ' ');
327                 lrow.className = lrow.className.replace(this.rowClsRe, ' ');
328                 if ((i + 1) % 2 === 0){
329                     row.className += ' x-grid3-row-alt';
330                     lrow.className += ' x-grid3-row-alt';
331                 }
332             }
333             this.syncRowHeights(row, lrow);
334         }
335         if(startRow === 0){
336             Ext.fly(rows[0]).addClass(this.firstRowCls);
337             Ext.fly(lrows[0]).addClass(this.firstRowCls);
338         }
339         Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
340         Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls);
341     },
342     
343     syncRowHeights: function(row1, row2){
344         if(this.syncHeights){
345             var el1 = Ext.get(row1),
346                 el2 = Ext.get(row2),
347                 h1 = el1.getHeight(),
348                 h2 = el2.getHeight();
349
350             if(h1 > h2){
351                 el2.setHeight(h1);
352             }else if(h2 > h1){
353                 el1.setHeight(h2);
354             }
355         }
356     },
357
358     afterRender : function(){
359         if(!this.ds || !this.cm){
360             return;
361         }
362         var bd = this.renderRows() || ['&#160;', '&#160;'];
363         this.mainBody.dom.innerHTML = bd[0];
364         this.lockedBody.dom.innerHTML = bd[1];
365         this.processRows(0, true);
366         if(this.deferEmptyText !== true){
367             this.applyEmptyText();
368         }
369         this.grid.fireEvent('viewready', this.grid);
370     },
371
372     renderUI : function(){        
373         var templates = this.templates,
374             header = this.renderHeaders(),
375             body = templates.body.apply({rows:'&#160;'});
376
377         return templates.masterTpl.apply({
378             body  : body,
379             header: header[0],
380             ostyle: 'width:' + this.getOffsetWidth() + ';',
381             bstyle: 'width:' + this.getTotalWidth()  + ';',
382             lockedBody: body,
383             lockedHeader: header[1],
384             lstyle: 'width:'+this.getLockedWidth()+';'
385         });
386     },
387     
388     afterRenderUI: function(){
389         var g = this.grid;
390         this.initElements();
391         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
392         Ext.fly(this.lockedInnerHd).on('click', this.handleHdDown, this);
393         this.mainHd.on({
394             scope: this,
395             mouseover: this.handleHdOver,
396             mouseout: this.handleHdOut,
397             mousemove: this.handleHdMove
398         });
399         this.lockedHd.on({
400             scope: this,
401             mouseover: this.handleHdOver,
402             mouseout: this.handleHdOut,
403             mousemove: this.handleHdMove
404         });
405         this.scroller.on('scroll', this.syncScroll,  this);
406         if(g.enableColumnResize !== false){
407             this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
408             this.splitZone.setOuterHandleElId(Ext.id(this.lockedHd.dom));
409             this.splitZone.setOuterHandleElId(Ext.id(this.mainHd.dom));
410         }
411         if(g.enableColumnMove){
412             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
413             this.columnDrag.setOuterHandleElId(Ext.id(this.lockedInnerHd));
414             this.columnDrag.setOuterHandleElId(Ext.id(this.innerHd));
415             this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
416         }
417         if(g.enableHdMenu !== false){
418             this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});
419             this.hmenu.add(
420                 {itemId: 'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
421                 {itemId: 'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
422             );
423             if(this.grid.enableColLock !== false){
424                 this.hmenu.add('-',
425                     {itemId: 'lock', text: this.lockText, cls: 'xg-hmenu-lock'},
426                     {itemId: 'unlock', text: this.unlockText, cls: 'xg-hmenu-unlock'}
427                 );
428             }
429             if(g.enableColumnHide !== false){
430                 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});
431                 this.colMenu.on({
432                     scope: this,
433                     beforeshow: this.beforeColMenuShow,
434                     itemclick: this.handleHdMenuClick
435                 });
436                 this.hmenu.add('-', {
437                     itemId:'columns',
438                     hideOnClick: false,
439                     text: this.columnsText,
440                     menu: this.colMenu,
441                     iconCls: 'x-cols-icon'
442                 });
443             }
444             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
445         }
446         if(g.trackMouseOver){
447             this.mainBody.on({
448                 scope: this,
449                 mouseover: this.onRowOver,
450                 mouseout: this.onRowOut
451             });
452             this.lockedBody.on({
453                 scope: this,
454                 mouseover: this.onRowOver,
455                 mouseout: this.onRowOut
456             });
457         }
458
459         if(g.enableDragDrop || g.enableDrag){
460             this.dragZone = new Ext.grid.GridDragZone(g, {
461                 ddGroup : g.ddGroup || 'GridDD'
462             });
463         }
464         this.updateHeaderSortState();    
465     },
466
467     layout : function(){
468         if(!this.mainBody){
469             return;
470         }
471         var g = this.grid;
472         var c = g.getGridEl();
473         var csize = c.getSize(true);
474         var vw = csize.width;
475         if(!g.hideHeaders && (vw < 20 || csize.height < 20)){
476             return;
477         }
478         this.syncHeaderHeight();
479         if(g.autoHeight){
480             this.scroller.dom.style.overflow = 'visible';
481             this.lockedScroller.dom.style.overflow = 'visible';
482             if(Ext.isWebKit){
483                 this.scroller.dom.style.position = 'static';
484                 this.lockedScroller.dom.style.position = 'static';
485             }
486         }else{
487             this.el.setSize(csize.width, csize.height);
488             var hdHeight = this.mainHd.getHeight();
489             var vh = csize.height - (hdHeight);
490         }
491         this.updateLockedWidth();
492         if(this.forceFit){
493             if(this.lastViewWidth != vw){
494                 this.fitColumns(false, false);
495                 this.lastViewWidth = vw;
496             }
497         }else {
498             this.autoExpand();
499             this.syncHeaderScroll();
500         }
501         this.onLayout(vw, vh);
502     },
503
504     getOffsetWidth : function() {
505         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth() + this.getScrollOffset()) + 'px';
506     },
507
508     renderHeaders : function(){
509         var cm = this.cm,
510             ts = this.templates,
511             ct = ts.hcell,
512             cb = [], lcb = [],
513             p = {},
514             len = cm.getColumnCount(),
515             last = len - 1;
516         for(var i = 0; i < len; i++){
517             p.id = cm.getColumnId(i);
518             p.value = cm.getColumnHeader(i) || '';
519             p.style = this.getColumnStyle(i, true);
520             p.tooltip = this.getColumnTooltip(i);
521             p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
522                 (cm.config[i].headerCls ? ' ' + cm.config[i].headerCls : '');
523             if(cm.config[i].align == 'right'){
524                 p.istyle = 'padding-right:16px';
525             } else {
526                 delete p.istyle;
527             }
528             if(cm.isLocked(i)){
529                 lcb[lcb.length] = ct.apply(p);
530             }else{
531                 cb[cb.length] = ct.apply(p);
532             }
533         }
534         return [ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'}),
535                 ts.header.apply({cells: lcb.join(''), tstyle:'width:'+this.getLockedWidth()+';'})];
536     },
537
538     updateHeaders : function(){
539         var hd = this.renderHeaders();
540         this.innerHd.firstChild.innerHTML = hd[0];
541         this.innerHd.firstChild.style.width = this.getOffsetWidth();
542         this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
543         this.lockedInnerHd.firstChild.innerHTML = hd[1];
544         var lw = this.getLockedWidth();
545         this.lockedInnerHd.firstChild.style.width = lw;
546         this.lockedInnerHd.firstChild.firstChild.style.width = lw;
547     },
548
549     getResolvedXY : function(resolved){
550         if(!resolved){
551             return null;
552         }
553         var c = resolved.cell, r = resolved.row;
554         return c ? Ext.fly(c).getXY() : [this.scroller.getX(), Ext.fly(r).getY()];
555     },
556
557     syncFocusEl : function(row, col, hscroll){
558         Ext.ux.grid.LockingGridView.superclass.syncFocusEl.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);
559     },
560
561     ensureVisible : function(row, col, hscroll){
562         return Ext.ux.grid.LockingGridView.superclass.ensureVisible.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);
563     },
564
565     insertRows : function(dm, firstRow, lastRow, isUpdate){
566         var last = dm.getCount() - 1;
567         if(!isUpdate && firstRow === 0 && lastRow >= last){
568             this.refresh();
569         }else{
570             if(!isUpdate){
571                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
572             }
573             var html = this.renderRows(firstRow, lastRow),
574                 before = this.getRow(firstRow);
575             if(before){
576                 if(firstRow === 0){
577                     this.removeRowClass(0, this.firstRowCls);
578                 }
579                 Ext.DomHelper.insertHtml('beforeBegin', before, html[0]);
580                 before = this.getLockedRow(firstRow);
581                 Ext.DomHelper.insertHtml('beforeBegin', before, html[1]);
582             }else{
583                 this.removeRowClass(last - 1, this.lastRowCls);
584                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]);
585                 Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]);
586             }
587             if(!isUpdate){
588                 this.fireEvent('rowsinserted', this, firstRow, lastRow);
589                 this.processRows(firstRow);
590             }else if(firstRow === 0 || firstRow >= last){
591                 this.addRowClass(firstRow, firstRow === 0 ? this.firstRowCls : this.lastRowCls);
592             }
593         }
594         this.syncFocusEl(firstRow);
595     },
596
597     getColumnStyle : function(col, isHeader){
598         var style = !isHeader ? this.cm.config[col].cellStyle || this.cm.config[col].css || '' : this.cm.config[col].headerStyle || '';
599         style += 'width:'+this.getColumnWidth(col)+';';
600         if(this.cm.isHidden(col)){
601             style += 'display:none;';
602         }
603         var align = this.cm.config[col].align;
604         if(align){
605             style += 'text-align:'+align+';';
606         }
607         return style;
608     },
609
610     getLockedWidth : function() {
611         return this.cm.getTotalLockedWidth() + 'px';
612     },
613
614     getTotalWidth : function() {
615         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth()) + 'px';
616     },
617
618     getColumnData : function(){
619         var cs = [], cm = this.cm, colCount = cm.getColumnCount();
620         for(var i = 0; i < colCount; i++){
621             var name = cm.getDataIndex(i);
622             cs[i] = {
623                 name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),
624                 renderer : cm.getRenderer(i),
625                 id : cm.getColumnId(i),
626                 style : this.getColumnStyle(i),
627                 locked : cm.isLocked(i)
628             };
629         }
630         return cs;
631     },
632
633     renderBody : function(){
634         var markup = this.renderRows() || ['&#160;', '&#160;'];
635         return [this.templates.body.apply({rows: markup[0]}), this.templates.body.apply({rows: markup[1]})];
636     },
637     
638     refreshRow: function(record){
639         var store = this.ds, 
640             colCount = this.cm.getColumnCount(), 
641             columns = this.getColumnData(), 
642             last = colCount - 1, 
643             cls = ['x-grid3-row'], 
644             rowParams = {
645                 tstyle: String.format("width: {0};", this.getTotalWidth())
646             }, 
647             lockedRowParams = {
648                 tstyle: String.format("width: {0};", this.getLockedWidth())
649             }, 
650             colBuffer = [], 
651             lockedColBuffer = [], 
652             cellTpl = this.templates.cell, 
653             rowIndex, 
654             row, 
655             lockedRow, 
656             column, 
657             meta, 
658             css, 
659             i;
660         
661         if (Ext.isNumber(record)) {
662             rowIndex = record;
663             record = store.getAt(rowIndex);
664         } else {
665             rowIndex = store.indexOf(record);
666         }
667         
668         if (!record || rowIndex < 0) {
669             return;
670         }
671         
672         for (i = 0; i < colCount; i++) {
673             column = columns[i];
674             
675             if (i == 0) {
676                 css = 'x-grid3-cell-first';
677             } else {
678                 css = (i == last) ? 'x-grid3-cell-last ' : '';
679             }
680             
681             meta = {
682                 id: column.id,
683                 style: column.style,
684                 css: css,
685                 attr: "",
686                 cellAttr: ""
687             };
688             
689             meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
690             
691             if (Ext.isEmpty(meta.value)) {
692                 meta.value = ' ';
693             }
694             
695             if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
696                 meta.css += ' x-grid3-dirty-cell';
697             }
698             
699             if (column.locked) {
700                 lockedColBuffer[i] = cellTpl.apply(meta);
701             } else {
702                 colBuffer[i] = cellTpl.apply(meta);
703             }
704         }
705         
706         row = this.getRow(rowIndex);
707         row.className = '';
708         lockedRow = this.getLockedRow(rowIndex);
709         lockedRow.className = '';
710         
711         if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
712             cls.push('x-grid3-row-alt');
713         }
714         
715         if (this.getRowClass) {
716             rowParams.cols = colCount;
717             cls.push(this.getRowClass(record, rowIndex, rowParams, store));
718         }
719         
720         // Unlocked rows
721         this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
722         rowParams.cells = colBuffer.join("");
723         row.innerHTML = this.templates.rowInner.apply(rowParams);
724         
725         // Locked rows
726         this.fly(lockedRow).addClass(cls).setStyle(lockedRowParams.tstyle);
727         lockedRowParams.cells = lockedColBuffer.join("");
728         lockedRow.innerHTML = this.templates.rowInner.apply(lockedRowParams);
729         lockedRow.rowIndex = rowIndex;
730         this.syncRowHeights(row, lockedRow);  
731         this.fireEvent('rowupdated', this, rowIndex, record);
732     },
733
734     refresh : function(headersToo){
735         this.fireEvent('beforerefresh', this);
736         this.grid.stopEditing(true);
737         var result = this.renderBody();
738         this.mainBody.update(result[0]).setWidth(this.getTotalWidth());
739         this.lockedBody.update(result[1]).setWidth(this.getLockedWidth());
740         if(headersToo === true){
741             this.updateHeaders();
742             this.updateHeaderSortState();
743         }
744         this.processRows(0, true);
745         this.layout();
746         this.applyEmptyText();
747         this.fireEvent('refresh', this);
748     },
749
750     onDenyColumnLock : function(){
751
752     },
753
754     initData : function(ds, cm){
755         if(this.cm){
756             this.cm.un('columnlockchange', this.onColumnLock, this);
757         }
758         Ext.ux.grid.LockingGridView.superclass.initData.call(this, ds, cm);
759         if(this.cm){
760             this.cm.on('columnlockchange', this.onColumnLock, this);
761         }
762     },
763
764     onColumnLock : function(){
765         this.refresh(true);
766     },
767
768     handleHdMenuClick : function(item){
769         var index = this.hdCtxIndex,
770             cm = this.cm,
771             id = item.getItemId(),
772             llen = cm.getLockedCount();
773         switch(id){
774             case 'lock':
775                 if(cm.getColumnCount(true) <= llen + 1){
776                     this.onDenyColumnLock();
777                     return undefined;
778                 }
779                 cm.setLocked(index, true);
780                 if(llen != index){
781                     cm.moveColumn(index, llen);
782                     this.grid.fireEvent('columnmove', index, llen);
783                 }
784             break;
785             case 'unlock':
786                 if(llen - 1 != index){
787                     cm.setLocked(index, false, true);
788                     cm.moveColumn(index, llen - 1);
789                     this.grid.fireEvent('columnmove', index, llen - 1);
790                 }else{
791                     cm.setLocked(index, false);
792                 }
793             break;
794             default:
795                 return Ext.ux.grid.LockingGridView.superclass.handleHdMenuClick.call(this, item);
796         }
797         return true;
798     },
799
800     handleHdDown : function(e, t){
801         Ext.ux.grid.LockingGridView.superclass.handleHdDown.call(this, e, t);
802         if(this.grid.enableColLock !== false){
803             if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
804                 var hd = this.findHeaderCell(t),
805                     index = this.getCellIndex(hd),
806                     ms = this.hmenu.items, cm = this.cm;
807                 ms.get('lock').setDisabled(cm.isLocked(index));
808                 ms.get('unlock').setDisabled(!cm.isLocked(index));
809             }
810         }
811     },
812
813     syncHeaderHeight: function(){
814         var hrow = Ext.fly(this.innerHd).child('tr', true),
815             lhrow = Ext.fly(this.lockedInnerHd).child('tr', true);
816             
817         hrow.style.height = 'auto';
818         lhrow.style.height = 'auto';
819         var hd = hrow.offsetHeight,
820             lhd = lhrow.offsetHeight,
821             height = Math.max(lhd, hd) + 'px';
822             
823         hrow.style.height = height;
824         lhrow.style.height = height;
825
826     },
827
828     updateLockedWidth: function(){
829         var lw = this.cm.getTotalLockedWidth(),
830             tw = this.cm.getTotalWidth() - lw,
831             csize = this.grid.getGridEl().getSize(true),
832             lp = Ext.isBorderBox ? 0 : this.lockedBorderWidth,
833             rp = Ext.isBorderBox ? 0 : this.rowBorderWidth,
834             vw = (csize.width - lw - lp - rp) + 'px',
835             so = this.getScrollOffset();
836         if(!this.grid.autoHeight){
837             var vh = (csize.height - this.mainHd.getHeight()) + 'px';
838             this.lockedScroller.dom.style.height = vh;
839             this.scroller.dom.style.height = vh;
840         }
841         this.lockedWrap.dom.style.width = (lw + rp) + 'px';
842         this.scroller.dom.style.width = vw;
843         this.mainWrap.dom.style.left = (lw + lp + rp) + 'px';
844         if(this.innerHd){
845             this.lockedInnerHd.firstChild.style.width = lw + 'px';
846             this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px';
847             this.innerHd.style.width = vw;
848             this.innerHd.firstChild.style.width = (tw + rp + so) + 'px';
849             this.innerHd.firstChild.firstChild.style.width = tw + 'px';
850         }
851         if(this.mainBody){
852             this.lockedBody.dom.style.width = (lw + rp) + 'px';
853             this.mainBody.dom.style.width = (tw + rp) + 'px';
854         }
855     }
856 });
857
858 Ext.ux.grid.LockingColumnModel = Ext.extend(Ext.grid.ColumnModel, {
859     /**
860      * Returns true if the given column index is currently locked
861      * @param {Number} colIndex The column index
862      * @return {Boolean} True if the column is locked
863      */
864     isLocked : function(colIndex){
865         return this.config[colIndex].locked === true;
866     },
867
868     /**
869      * Locks or unlocks a given column
870      * @param {Number} colIndex The column index
871      * @param {Boolean} value True to lock, false to unlock
872      * @param {Boolean} suppressEvent Pass false to cause the columnlockchange event not to fire
873      */
874     setLocked : function(colIndex, value, suppressEvent){
875         if (this.isLocked(colIndex) == value) {
876             return;
877         }
878         this.config[colIndex].locked = value;
879         if (!suppressEvent) {
880             this.fireEvent('columnlockchange', this, colIndex, value);
881         }
882     },
883
884     /**
885      * Returns the total width of all locked columns
886      * @return {Number} The width of all locked columns
887      */
888     getTotalLockedWidth : function(){
889         var totalWidth = 0;
890         for (var i = 0, len = this.config.length; i < len; i++) {
891             if (this.isLocked(i) && !this.isHidden(i)) {
892                 totalWidth += this.getColumnWidth(i);
893             }
894         }
895
896         return totalWidth;
897     },
898
899     /**
900      * Returns the total number of locked columns
901      * @return {Number} The number of locked columns
902      */
903     getLockedCount : function() {
904         var len = this.config.length;
905
906         for (var i = 0; i < len; i++) {
907             if (!this.isLocked(i)) {
908                 return i;
909             }
910         }
911
912         //if we get to this point all of the columns are locked so we return the total
913         return len;
914     },
915
916     /**
917      * Moves a column from one position to another
918      * @param {Number} oldIndex The current column index
919      * @param {Number} newIndex The destination column index
920      */
921     moveColumn : function(oldIndex, newIndex){
922         var oldLocked = this.isLocked(oldIndex),
923             newLocked = this.isLocked(newIndex);
924
925         if (oldIndex < newIndex && oldLocked && !newLocked) {
926             this.setLocked(oldIndex, false, true);
927         } else if (oldIndex > newIndex && !oldLocked && newLocked) {
928             this.setLocked(oldIndex, true, true);
929         }
930
931         Ext.ux.grid.LockingColumnModel.superclass.moveColumn.apply(this, arguments);
932     }
933 });