PDA

View Full Version : Scope issue assigning link to button in grid



spor
22 Jun 2010, 1:06 AM
I'm trying to apply the excel export plugin to my code, but can't get it right with the scope.

I have a button in a toolbar that will get the content in a grid and export it to Excel.

I want to assign the following to the button:


this.button.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' +
Base64.encode(this.getExcelXml());

But, where in the code can it be done?

The button is defined in the initComponent of the grid:

this.button = new Ext.ux.LinkButton({})

Then the button is assigned to the toolbar:

tbar: [
this.button
]

Here's the more complete structure of my code:

Arkiv.grids.Somelist = Ext.extend(Arkiv.grids.Grid, {


initComponent: function(){
this.button = new Ext.ux.LinkButton({})

Ext.apply(this, {
viewConfig: {
forceFit: true
},
store: myStore,


columns: [
{
header: 'ID',
dataIndex: recId
},

{
header: 'Firstname',
dataIndex: 'firstname'
}, {
header: 'Lastname',
dataIndex: 'lastname'
}],

tbar: [
// {
this.button

// }
],

})

Arkiv.grids.Somelist.superclass.initComponent.apply(this, arguments);

this.button.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' +
Base64.encode(this.getExcelXml());
})
Ext.reg('somelist', Arkiv.grids.Somelist)

Animal
22 Jun 2010, 1:11 AM
??

You set up the href at initialization time? There IS NO XML at that time.

That needs to be done in a load handler.

spor
22 Jun 2010, 1:31 AM
Thanks for replying.

Alright, so I've added a handler to the button.


this.button = new Ext.ux.LinkButton({

handler: this.setLink
})

.....

setLink: function(){
this.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' +
Base64.encode(this.findParentByType('somelist').getExcelXml());
}

I don't get any error now, but when I click on the link/button it just reloads the page instead of opening Excel with exported data. Am I missing something?

Animal
22 Jun 2010, 1:53 AM
I suspect that the href must be set BEFORE the <a> is clicked.

As I say. In a load listener.

spor
22 Jun 2010, 2:08 AM
Do you mean like this:


this.button = new Ext.ux.LinkButton({

listeners: {
load: this.setLink
}

})

That didn't work either, the page still only reloads when I click on the export link.

Animal
22 Jun 2010, 2:20 AM
So find out why. See what the href is by examining the DOM.

spor
22 Jun 2010, 2:52 AM
The href is an empty string...

Is the following a load listener that you were suggesting and is it properly set up?


this.button = new Ext.ux.LinkButton({

listeners: {
load: this.setLink
}

})

....................

setLink: function(){
this.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' +
Base64.encode(this.findParentByType('somelist').getExcelXml());
}

When initially loading it first stops at the setLink function but it doesnt go inside it even though there are break points inside it. Then it goes to the listener of the button.

Animal
22 Jun 2010, 3:32 AM
Does a Button have a load event?

What class does?

spor
22 Jun 2010, 3:47 AM
To be frank, I don't know really know what you meant with using a load listener. That's why I tried that out after googling "load listener". Looking at the api for Button, it doesn't seem to have a load event, neither does GridPanel.

Could you explain/show me a bit more about what a "load listener" is? I am sure it can't be very difficult to assign a href value to a button with data from a store in a GridPanel. I just don't know the approach for that :/

Edit: Store has a load event. Do I have to set a listener inside the store?

manube
22 Jun 2010, 4:24 AM
Hello,
Ithink I'm using the same 'tools' than you to get an MS Excel from a grid, so just use it as following:

buttons: [{
iconCls: 'x-button-excel',
text: 'Excel',
handler: function(){
document.location='data:application/vnd.ms-excel;base64,' + Base64.encode(Ext.getCmp('your-grid-id').getExcelXml());
}
}]
Manu

spor
22 Jun 2010, 4:41 AM
Hi manube, thanks for replying.

I inserted your code and got this:


Arkiv.grids.Somelist = Ext.extend(Arkiv.grids.Grid, {
id : 'somelist',
initComponent: function(){

this.button = new Ext.ux.LinkButton({
id: 'linkbutton',
handler: function(){
document.location='data:application/vnd.ms-excel;base64,' + Base64.encode(Ext.getCmp('somelist').getExcelXml());
}


})I didn't work either, the page reloads and Excel doesn't open.

I debugged with firebug and after the loading I do get data with Ext.getCmp('somelist') on the console. However, Ext.getCmp('somelist').getExcelXml() results with this error; TypeError: fld is undefined.

Btw, here's the code of the tool/plugin I'm using:

/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/

Ext.ns('Arkiv.ux')

var Base64 = (function() {

// private property
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// private method for UTF-8 encoding
function utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}

// public method for encoding
return {
encode : (typeof btoa == 'function') ? function(input) { return btoa(input); } : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = utf8Encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
}
return output;
}
};
})();

Ext.ux.LinkButton = Ext.extend(Ext.Button, {
initComponent: function(){
Ext.apply(this, {

text : 'Download',
cls : 'download'
});

Ext.ux.LinkButton.superclass.initComponent.apply(this, arguments);

},

template: new Ext.Template(
'<table border="0" cellpadding="0" cellspacing="0" class="x-btn-wrap"><tbody><tr>',
'<td class="x-btn-left"><i> </i></td><td class="x-btn-center"><a class="x-btn-text" href="{1}" target="{2}">{0}</a></td><td class="x-btn-right"><i> </i></td>',
"</tr></tbody></table>"),

onRender: function(ct, position){
var btn, targs = [this.text || ' ', this.href, this.target || "_self"];
if(position){
btn = this.template.insertBefore(position, targs, true);
}else{
btn = this.template.append(ct, targs, true);
}
var btnEl = btn.child("a:first");
this.btnEl = btnEl;
btnEl.on('focus', this.onFocus, this);
btnEl.on('blur', this.onBlur, this);

this.initButtonEl(btn, btnEl);
Ext.ButtonToggleMgr.register(this);
},

onClick : function(e){
if(e.button != 0){
return;
}
if(!this.disabled){
this.fireEvent("click", this, e);
if(this.handler){
this.handler.call(this.scope || this, this, e);
}
}
}

});

Ext.reg('linkbutton', Ext.ux.LinkButton);

Ext.override(Ext.grid.GridPanel, {
getExcelXml: function(includeHidden) {
var worksheet = this.createWorksheet(includeHidden);
var totalWidth = this.getColumnModel().getTotalWidth(includeHidden);
return '<?xml version="1.0" encoding="utf-8"?>' +
'<ss:Workbook xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:o="urn:schemas-microsoft-com:office:office">' +
'<o:DocumentProperties><o:Title>' + this.title + '</o:Title></o:DocumentProperties>' +
'<ss:ExcelWorkbook>' +
'<ss:WindowHeight>' + worksheet.height + '</ss:WindowHeight>' +
'<ss:WindowWidth>' + worksheet.width + '</ss:WindowWidth>' +
'<ss:ProtectStructure>False</ss:ProtectStructure>' +
'<ss:ProtectWindows>False</ss:ProtectWindows>' +
'</ss:ExcelWorkbook>' +
'<ss:Styles>' +
'<ss:Style ss:ID="Default">' +
'<ss:Alignment ss:Vertical="Top" ss:WrapText="1" />' +
'<ss:Font ss:FontName="arial" ss:Size="10" />' +
'<ss:Borders>' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Top" />' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Bottom" />' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Left" />' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Right" />' +
'</ss:Borders>' +
'<ss:Interior />' +
'<ss:NumberFormat />' +
'<ss:Protection />' +
'</ss:Style>' +
'<ss:Style ss:ID="title">' +
'<ss:Borders />' +
'<ss:Font />' +
'<ss:Alignment ss:WrapText="1" ss:Vertical="Center" ss:Horizontal="Center" />' +
'<ss:NumberFormat ss:Format="@" />' +
'</ss:Style>' +
'<ss:Style ss:ID="headercell">' +
'<ss:Font ss:Bold="1" ss:Size="10" />' +
'<ss:Alignment ss:WrapText="1" ss:Horizontal="Center" />' +
'<ss:Interior ss:Pattern="Solid" ss:Color="#A3C9F1" />' +
'</ss:Style>' +
'<ss:Style ss:ID="even">' +
'<ss:Interior ss:Pattern="Solid" ss:Color="#CCFFFF" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="even" ss:ID="evendate">' +
'<ss:NumberFormat ss:Format="[ENG][$-409]dd\-mmm\-yyyy;@" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="even" ss:ID="evenint">' +
'<ss:NumberFormat ss:Format="0" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="even" ss:ID="evenfloat">' +
'<ss:NumberFormat ss:Format="0.00" />' +
'</ss:Style>' +
'<ss:Style ss:ID="odd">' +
'<ss:Interior ss:Pattern="Solid" ss:Color="#CCCCFF" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="odd" ss:ID="odddate">' +
'<ss:NumberFormat ss:Format="[ENG][$-409]dd\-mmm\-yyyy;@" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="odd" ss:ID="oddint">' +
'<ss:NumberFormat ss:Format="0" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="odd" ss:ID="oddfloat">' +
'<ss:NumberFormat ss:Format="0.00" />' +
'</ss:Style>' +
'</ss:Styles>' +
worksheet.xml +
'</ss:Workbook>';
},

createWorksheet: function(includeHidden) {

// Calculate cell data types and extra class names which affect formatting
var cellType = [];
var cellTypeClass = [];
var cm = this.getColumnModel();
var totalWidthInPixels = 0;
var colXml = '';
var headerXml = '';
for (var i = 0; i < cm.getColumnCount(); i++) {
if (includeHidden || !cm.isHidden(i)) {
var w = cm.getColumnWidth(i)
totalWidthInPixels += w;
colXml += '<ss:Column ss:AutoFitWidth="1" ss:Width="' + w + '" />';
headerXml += '<ss:Cell ss:StyleID="headercell">' +
'<ss:Data ss:Type="String">' + cm.getColumnHeader(i) + '</ss:Data>' +
'<ss:NamedCell ss:Name="Print_Titles" /></ss:Cell>';
var fld = this.store.recordType.prototype.fields.get(cm.getDataIndex(i));
switch(fld.type) {
case "int":
cellType.push("Number");
cellTypeClass.push("int");
break;
case "float":
cellType.push("Number");
cellTypeClass.push("float");
break;
case "bool":
case "boolean":
cellType.push("String");
cellTypeClass.push("");
break;
case "date":
cellType.push("DateTime");
cellTypeClass.push("date");
break;
default:
cellType.push("String");
cellTypeClass.push("");
break;
}
}
}
var visibleColumnCount = cellType.length;

var result = {
height: 9000,
width: Math.floor(totalWidthInPixels * 30) + 50
};

// Generate worksheet header details.
var t = '<ss:Worksheet ss:Name="' + this.title + '">' +
'<ss:Names>' +
'<ss:NamedRange ss:Name="Print_Titles" ss:RefersTo="=\'' + this.title + '\'!R1:R2" />' +
'</ss:Names>' +
'<ss:Table x:FullRows="1" x:FullColumns="1"' +
' ss:ExpandedColumnCount="' + visibleColumnCount +
'" ss:ExpandedRowCount="' + (this.store.getCount() + 2) + '">' +
colXml +
'<ss:Row ss:Height="38">' +
'<ss:Cell ss:StyleID="title" ss:MergeAcross="' + (visibleColumnCount - 1) + '">' +
'<ss:Data xmlns:html="http://www.w3.org/TR/REC-html40" ss:Type="String">' +
'<html:B><html:U><html:Font html:Size="15">' + this.title +
'</html:Font></html:U></html:B></ss:Data><ss:NamedCell ss:Name="Print_Titles" />' +
'</ss:Cell>' +
'</ss:Row>' +
'<ss:Row ss:AutoFitHeight="1">' +
headerXml +
'</ss:Row>';

// Generate the data rows from the data in the Store
for (var i = 0, it = this.store.data.items, l = it.length; i < l; i++) {
t += '<ss:Row>';
var cellClass = (i & 1) ? 'odd' : 'even';
r = it[i].data;
var k = 0;
for (var j = 0; j < cm.getColumnCount(); j++) {
if (includeHidden || !cm.isHidden(j)) {
var v = r[cm.getDataIndex(j)];
t += '<ss:Cell ss:StyleID="' + cellClass + cellTypeClass[k] + '"><ss:Data ss:Type="' + cellType[k] + '">';
if (cellType[k] == 'DateTime') {
t += v.format('Y-m-d');
} else {
t += v;
}
t +='</ss:Data></ss:Cell>';
k++;
}
}
t += '</ss:Row>';
}

result.xml = t + '</ss:Table>' +
'<x:WorksheetOptions>' +
'<x:PageSetup>' +
'<x:Layout x:CenterHorizontal="1" x:Orientation="Landscape" />' +
'<x:Footer x:Data="Page &amp;P of &amp;N" x:Margin="0.5" />' +
'<x:PageMargins x:Top="0.5" x:Right="0.5" x:Left="0.5" x:Bottom="0.8" />' +
'</x:PageSetup>' +
'<x:FitToPage />' +
'<x:Print>' +
'<x:PrintErrors>Blank</x:PrintErrors>' +
'<x:FitWidth>1</x:FitWidth>' +
'<x:FitHeight>32767</x:FitHeight>' +
'<x:ValidPrinterInfo />' +
'<x:VerticalResolution>600</x:VerticalResolution>' +
'</x:Print>' +
'<x:Selected />' +
'<x:DoNotDisplayGridlines />' +
'<x:ProtectObjects>False</x:ProtectObjects>' +
'<x:ProtectScenarios>False</x:ProtectScenarios>' +
'</x:WorksheetOptions>' +
'</ss:Worksheet>';
return result;
}
});

manube
22 Jun 2010, 4:51 AM
Hi,
Try to load the code (button/button handler) in your rendering page and not in the initComponent method.
The fld var references the store record type which is undefined during the grid initialization.
Tell me if it works.
Bye

Animal
22 Jun 2010, 5:05 AM
Set the href in a load handler.

Set it in a load handler.

spor
22 Jun 2010, 5:50 AM
manube: I tried the following, adding the button from a class that instantiate the grid, it didn't work either.

Some example code would be much appreciated.



createGrid: function(){

this.linkButton = new Ext.LinkButton({
id: 'grid-excel-button',
text: 'Export to Excel',
handler: function(){
document.location = 'data:application/vnd.ms-excel;base64,' + Base64.encode(Ext.getCmp('somelist').getExcelXml());
}
});

/*
buttons: [{
iconCls: 'x-button-excel',
text: 'Excel',
handler: function(){
document.location = 'data:application/vnd.ms-excel;base64,' + Base64.encode(Ext.getCmp('your-grid-id').getExcelXml());
}
}]
*/
this.grid = new Arkiv.grids.Somelist({
id: 'somelist',
region: 'east',
split: true,
hideBorders: true,
width: 100,
bbar: new Ext.Toolbar({
buttons: [this.linkButton]
})

})
}


Animal: I'm a bit confused by different terms that are being used. Could you show me an example of how to set the href in a load handler?

Animal
22 Jun 2010, 6:05 AM
listeners: {
load: functionRef

manube
22 Jun 2010, 7:27 AM
Hi,
Simply create your grid and add the button in the bottom bar for instance :


var grid = new Ext.grid.GridPanel({
id: 'my-grid-id',
store: myStore,
...
bbar:
new Ext.Toolbar({
buttons: [{
iconCls: 'x-button-excel',
text: 'Excel',
handler: function(){
document.location='data:application/vnd.ms-excel;base64,' + Base64.encode(Ext.getCmp('my-grid-id').getExcelXml());
}
}]
})
});

Animal
22 Jun 2010, 7:43 AM
Stop now.

spor
23 Jun 2010, 2:30 AM
manube, your code doesn't work in my situation, tried it and still got TypeError: fld is undefined.

Animal, so I've tried your approach but it doesn't look like the store is even loaded before the method for the load event is called.

Error from firebug:
Ext.getCmp("somelist").linkButton.getEl() is undefined

According to the API:
Important: loading is asynchronous! This call will return before the new data has been loaded. To perform any post-processing where information from the load call is required, specify the callback function to be called, or use a a 'load' event handler (http://www.sencha.com/../deploy/dev/docs/output/Ext.util.Observable.html#Ext.util.Observable-listeners).

Here is my code snippet:

Arkiv.grids.Somelist = Ext.extend(Arkiv.grids.Grid, {

initComponent: function(){
this.linkButton = new Ext.LinkButton({
id: 'grid-excel-button',
text: 'Export to Excel'
})
Ext.apply(this, {
viewConfig: {
forceFit: true
},
id: 'somelist',
store: new Ext.data.ArrayStore({

fields: ['firstname', 'lastname', 'city', 'unid', 'id'],
autoLoad: true,
data: Arkiv.grids.dummyDataCont3,
listeners: {
load: this.setLink
}
}),
...........................

setLink: function(){
Ext.getCmp('somelist').linkButton.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' +
Base64.encode(Ext.getCmp('somelist').getExcelXml());
}


As I have mentioned, when debugging i observed that the setLink method is run before the store is populated.

Perhaps it should have worked if Store had an event called afterLoad or something, but that doesn't exist.

Anyone else who knows how to solve this issue? Any help would be much appreciated!

Animal
23 Jun 2010, 2:50 AM
Before it's loaded. No. You are loading locally from data.

How are you debugging this? Have you set a breakpoint in the setLink method and had a look what's what?

You cannot set the id of a Component in initComponent.

If setLink is a member function of your grid, then use "this".

BUT BE SURE TO SET THE SCOPE IN YOUR LISTENER.

And that IS. IS. IS. IS documented. Clearly, and fulsomely.

spor
23 Jun 2010, 3:29 AM
Yes, I have set breakpoints on all the relevant parts of the code and when the setLink method is invoked, Ext.getCmp('somelist').linkButton.getEl() will run and return nothing.

Ext.getCmp('somelist').linkButton will return the correct object, so that is not the issue.

I have
Ext.reg('somelist', Arkiv.grids.Somelist) at the end of my code.

Yes, setLink is a member function of the grid, so the scope is correct in my listener, isn't it?

So the million dollar question is, what else needs to be done? This might be easy and obvious for ExtJS gurus, but I am not in that category, so please bare with me :) So, any explicit code examples would have been useful for me. I hope it is understandable.

Edit: Alright, the store isn't null after all. Since it is already in the scope of the store when setLink is invoked. But why will Ext.getCmp('somelist').linkButton.getEl() return null?

spor
24 Jun 2010, 12:22 AM
Using manube's approach I finally got the export to work.

Solution:

In exporter.js


var fld = this.store.recordType.prototype.fields.get(cm.getDataIndex(i));

Had to be changed to this:


var fld = this.store.recordType.prototype.fields.get(i);


Not sure why the original code of the exporter tool used double indexes to retrieve the fields. However, I'm happy that it now works!

Thanks for help, guys.

Animal
24 Jun 2010, 12:23 AM
What if the user moved the columns?

Then i would not be correct.

spor
24 Jun 2010, 2:37 AM
Not sure what you mean by moving the columns. In which way?

Do I have this problem because I'm using ExtJS 3.2.0 as opposed to earlier versions?

This post is also interesting regarding newer versions:
http://www.sencha.com/forum/showthread.php?32400-GridPanel-directly-to-Excel.&p=466672#post466672

Animal
24 Jun 2010, 3:06 AM
Drag and drop column reordering!

spor
24 Jun 2010, 11:44 PM
I see... I am not using that functionality on my grid then. Do you know why the original code doesn't work in my situation?

Animal
25 Jun 2010, 12:49 AM
I just can't see what the fuss is about. JUst debug it.

I just updated it to work with the new types in 3.2.

Drop this unchanged into examples/<anywhere> and run it



<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Grid export to Excel Example</title>

<!-- ** CSS ** -->
<!-- base library -->
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="../ux/css/RowEditor.css" />

<!-- overrides to base library -->

<!-- page specific -->
<link rel="stylesheet" type="text/css" href="../shared/examples.css" />
<link rel="stylesheet" type="text/css" href="grid-examples.css" />

<style type=text/css>
/* style rows on mouseover */
.x-grid3-row-over .x-grid3-cell-inner {
font-weight: bold;
}
.x-grid3-cell-inner table {
table-layout: auto!important;
}

/* Style LinkButtons */
table.x-btn .x-btn-small td.x-btn-mc em a {
color: inherit;
text-decoration: none;
display: block;
height: 12px;
padding-bottom: 2px;
padding-top: 2px;
}

body.ext-opera table.x-btn .x-btn-small td.x-btn-mc em a,
body.ext-chrome table.x-btn .x-btn-small td.x-btn-mc em a {
padding-top: 1px;
}

body.ext-chrome table.x-btn .x-btn-small td.x-btn-mc em a {
padding-bottom: 3px;
}
</style>

<!-- ** Javascript ** -->
<!-- ExtJS library: base/adapter -->
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>

<!-- ExtJS library: all widgets -->
<script type="text/javascript" src="../../ext-all.js"></script>
<script type="text/javascript" src="../ux/RowEditor.js"></script>

<!-- page specific -->
<script type="text/javascript">
/**
* @class Ext.LinkButton
* @extends Ext.Button
* A Button which encapsulates an &lt;a> element to enable navigation, or downloading of files.
* @constructor
* Creates a new LinkButton
*/
Ext.LinkButton = Ext.extend(Ext.Button, {
template: new Ext.Template(
'<table id="{4}" cellspacing="0" class="x-btn {3}">',
'<tbody class="{1}">',
'<tr><td class="x-btn-tl"><i>&#160;</i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i>&#160;</i></td></tr>',
'<tr>',
'<td class="x-btn-ml"><i>&#160;</i></td>',
'<td class="x-btn-mc">',
'<em class="{2}" unselectable="on">',
'<a href="{5}" style="display:block" target="{6}" class="x-btn-text">{0}</a>',
'</em>',
'</td>',
'<td class="x-btn-mr"><i>&#160;</i></td>',
'</tr>',
'<tr><td class="x-btn-bl"><i>&#160;</i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i>&#160;</i></td></tr>',
'</tbody>',
'</table>').compile(),

buttonSelector : 'a:first',

/**
* @cfg String href
* The URL to create a link for.
*/
/**
* @cfg String target
* The target for the &lt;a> element.
*/
/**
* @cfg Object
* A set of parameters which are always passed to the URL specified in the href
*/
baseParams: {},

// private
params: {},

getTemplateArgs: function() {
return Ext.Button.prototype.getTemplateArgs.apply(this).concat([this.getHref(), this.target]);
},

onClick : function(e){
if(e.button != 0){
return;
}
if(this.disabled){
e.stopEvent();
} else {
if (this.fireEvent("click", this, e) !== false) {
if(this.handler){
this.handler.call(this.scope || this, this, e);
}
}
}
},

setHref: function(href) {
this.href = href
if (this.rendered) {
this.el.child(this.buttonSelector, true).href = this.href;
}
},

// private
getHref: function() {
var result = this.href;
var p = Ext.urlEncode(Ext.apply(Ext.apply({}, this.baseParams), this.params));
if (p.length) {
result += ((this.href.indexOf('?') == -1) ? '?' : '&') + p;
}
return result;
},

/**
* Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
* @param {Object} Parameters to use in the href URL.
*/
setParams: function(p) {
this.params = p;
this.el.child(this.buttonSelector, true).href = this.getHref();
}
});
Ext.reg('linkbutton', Ext.LinkButton);

/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/

var Base64 = (function() {

// private property
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// private method for UTF-8 encoding
function utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}

// public method for encoding
return {
encode : (typeof btoa == 'function') ? function(input) { return btoa(input); } : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = utf8Encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
}
return output;
}
};
})();

Ext.override(Ext.grid.GridPanel, {

getExcelXml: function(includeHidden) {
var worksheet = this.createWorksheet(includeHidden);
var totalWidth = this.getColumnModel().getTotalWidth(includeHidden);
return '<?xml version="1.0" encoding="utf-8"?>' +
'<ss:Workbook xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:o="urn:schemas-microsoft-com:office:office">' +
'<o:DocumentProperties><o:Title>' + this.title + '</o:Title></o:DocumentProperties>' +
'<ss:ExcelWorkbook>' +
'<ss:WindowHeight>' + worksheet.height + '</ss:WindowHeight>' +
'<ss:WindowWidth>' + worksheet.width + '</ss:WindowWidth>' +
'<ss:ProtectStructure>False</ss:ProtectStructure>' +
'<ss:ProtectWindows>False</ss:ProtectWindows>' +
'</ss:ExcelWorkbook>' +
'<ss:Styles>' +
'<ss:Style ss:ID="Default">' +
'<ss:Alignment ss:Vertical="Top" ss:WrapText="1" />' +
'<ss:Font ss:FontName="arial" ss:Size="10" />' +
'<ss:Borders>' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Top" />' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Bottom" />' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Left" />' +
'<ss:Border ss:Color="#e4e4e4" ss:Weight="1" ss:LineStyle="Continuous" ss:Position="Right" />' +
'</ss:Borders>' +
'<ss:Interior />' +
'<ss:NumberFormat />' +
'<ss:Protection />' +
'</ss:Style>' +
'<ss:Style ss:ID="title">' +
'<ss:Borders />' +
'<ss:Font />' +
'<ss:Alignment ss:WrapText="1" ss:Vertical="Center" ss:Horizontal="Center" />' +
'<ss:NumberFormat ss:Format="@" />' +
'</ss:Style>' +
'<ss:Style ss:ID="headercell">' +
'<ss:Font ss:Bold="1" ss:Size="10" />' +
'<ss:Alignment ss:WrapText="1" ss:Horizontal="Center" />' +
'<ss:Interior ss:Pattern="Solid" ss:Color="#A3C9F1" />' +
'</ss:Style>' +
'<ss:Style ss:ID="even">' +
'<ss:Interior ss:Pattern="Solid" ss:Color="#CCFFFF" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="even" ss:ID="evendate">' +
'<ss:NumberFormat ss:Format="[ENG][$-409]dd\-mmm\-yyyy;@" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="even" ss:ID="evenint">' +
'<ss:NumberFormat ss:Format="0" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="even" ss:ID="evenfloat">' +
'<ss:NumberFormat ss:Format="0.00" />' +
'</ss:Style>' +
'<ss:Style ss:ID="odd">' +
'<ss:Interior ss:Pattern="Solid" ss:Color="#CCCCFF" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="odd" ss:ID="odddate">' +
'<ss:NumberFormat ss:Format="[ENG][$-409]dd\-mmm\-yyyy;@" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="odd" ss:ID="oddint">' +
'<ss:NumberFormat ss:Format="0" />' +
'</ss:Style>' +
'<ss:Style ss:Parent="odd" ss:ID="oddfloat">' +
'<ss:NumberFormat ss:Format="0.00" />' +
'</ss:Style>' +
'</ss:Styles>' +
worksheet.xml +
'</ss:Workbook>';
},

createWorksheet: function(includeHidden) {

// Calculate cell data types and extra class names which affect formatting
var cellType = [];
var cellTypeClass = [];
var cm = this.getColumnModel();
var totalWidthInPixels = 0;
var colXml = '';
var headerXml = '';
for (var i = 0; i < cm.getColumnCount(); i++) {
if (includeHidden || !cm.isHidden(i)) {
var w = cm.getColumnWidth(i)
totalWidthInPixels += w;
colXml += '<ss:Column ss:AutoFitWidth="1" ss:Width="' + w + '" />';
headerXml += '<ss:Cell ss:StyleID="headercell">' +
'<ss:Data ss:Type="String">' + cm.getColumnHeader(i) + '</ss:Data>' +
'<ss:NamedCell ss:Name="Print_Titles" /></ss:Cell>';
var fld = this.store.recordType.prototype.fields.get(cm.getDataIndex(i));
switch(fld.type) {
case Ext.data.Types.INT:
cellType.push("Number");
cellTypeClass.push("int");
break;
case Ext.data.Types.FLOAT:
cellType.push("Number");
cellTypeClass.push("float");
break;
case Ext.data.Types.BOOLEAN:
cellType.push("String");
cellTypeClass.push("");
break;
case Ext.data.Types.DATE:
cellType.push("DateTime");
cellTypeClass.push("date");
break;
default:
cellType.push("String");
cellTypeClass.push("");
break;
}
}
}
var visibleColumnCount = cellType.length;

var result = {
height: 9000,
width: Math.floor(totalWidthInPixels * 30) + 50
};

// Generate worksheet header details.
var t = '<ss:Worksheet ss:Name="' + this.title + '">' +
'<ss:Names>' +
'<ss:NamedRange ss:Name="Print_Titles" ss:RefersTo="=\'' + this.title + '\'!R1:R2" />' +
'</ss:Names>' +
'<ss:Table x:FullRows="1" x:FullColumns="1"' +
' ss:ExpandedColumnCount="' + visibleColumnCount +
'" ss:ExpandedRowCount="' + (this.store.getCount() + 2) + '">' +
colXml +
'<ss:Row ss:Height="38">' +
'<ss:Cell ss:StyleID="title" ss:MergeAcross="' + (visibleColumnCount - 1) + '">' +
'<ss:Data xmlns:html="http://www.w3.org/TR/REC-html40" ss:Type="String">' +
'<html:B><html:U><html:Font html:Size="15">' + this.title +
'</html:Font></html:U></html:B> Generated by ExtJs</ss:Data><ss:NamedCell ss:Name="Print_Titles" />' +
'</ss:Cell>' +
'</ss:Row>' +
'<ss:Row ss:AutoFitHeight="1">' +
headerXml +
'</ss:Row>';

// Generate the data rows from the data in the Store
for (var i = 0, it = this.store.data.items, l = it.length; i < l; i++) {
t += '<ss:Row>';
var cellClass = (i & 1) ? 'odd' : 'even';
r = it[i].data;
var k = 0;
for (var j = 0; j < cm.getColumnCount(); j++) {
if (includeHidden || !cm.isHidden(j)) {
var v = r[cm.getDataIndex(j)];
t += '<ss:Cell ss:StyleID="' + cellClass + cellTypeClass[k] + '"><ss:Data ss:Type="' + cellType[j] + '">';
if (cellType[k] == 'DateTime') {
t += v.format('Y-m-d');
} else {
t += v;
}
t +='</ss:Data></ss:Cell>';
k++;
}
}
t += '</ss:Row>';
}

result.xml = t + '</ss:Table>' +
'<x:WorksheetOptions>' +
'<x:PageSetup>' +
'<x:Layout x:CenterHorizontal="1" x:Orientation="Landscape" />' +
'<x:Footer x:Data="Page &amp;P of &amp;N" x:Margin="0.5" />' +
'<x:PageMargins x:Top="0.5" x:Right="0.5" x:Left="0.5" x:Bottom="0.8" />' +
'</x:PageSetup>' +
'<x:FitToPage />' +
'<x:Print>' +
'<x:PrintErrors>Blank</x:PrintErrors>' +
'<x:FitWidth>1</x:FitWidth>' +
'<x:FitHeight>32767</x:FitHeight>' +
'<x:ValidPrinterInfo />' +
'<x:VerticalResolution>600</x:VerticalResolution>' +
'</x:Print>' +
'<x:Selected />' +
'<x:DoNotDisplayGridlines />' +
'<x:ProtectObjects>False</x:ProtectObjects>' +
'<x:ProtectScenarios>False</x:ProtectScenarios>' +
'</x:WorksheetOptions>' +
'</ss:Worksheet>';
return result;
}
});

Ext.onReady(function(){

// sample static data for the store
var myData = [
['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
['Altria Group Inc',83.81,0.28,0.34,'9/1 12:00am'],
['American Express Company',52.55,0.01,0.02,'9/1 12:00am'],
['American International Group, Inc.',64.13,0.31,0.49,'9/1 12:00am'],
['AT&T Inc.',31.61,-0.48,-1.54,'9/1 12:00am'],
['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
['Caterpillar Inc.',67.27,0.92,1.39,'9/1 12:00am'],
['Citigroup, Inc.',49.37,0.02,0.04,'9/1 12:00am'],
['E.I. du Pont de Nemours and Company',40.48,0.51,1.28,'9/1 12:00am'],
['Exxon Mobil Corp',68.1,-0.43,-0.64,'9/1 12:00am'],
['General Electric Company',34.14,-0.08,-0.23,'9/1 12:00am'],
['General Motors Corporation',30.27,1.09,3.74,'9/1 12:00am'],
['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
['Honeywell Intl Inc',38.77,0.05,0.13,'9/1 12:00am'],
['Intel Corporation',19.88,0.31,1.58,'9/1 12:00am'],
['International Business Machines',81.41,0.44,0.54,'9/1 12:00am'],
['Johnson & Johnson',64.72,0.06,0.09,'9/1 12:00am'],
['JP Morgan & Chase & Co',45.73,0.07,0.15,'9/1 12:00am'],
['McDonald\'s Corporation',36.76,0.86,2.40,'9/1 12:00am'],
['Merck & Co., Inc.',40.96,0.41,1.01,'9/1 12:00am'],
['Microsoft Corporation',25.84,0.14,0.54,'9/1 12:00am'],
['Pfizer Inc',27.96,0.4,1.45,'9/1 12:00am'],
['The Coca-Cola Company',45.07,0.26,0.58,'9/1 12:00am'],
['The Home Depot, Inc.',34.64,0.35,1.02,'9/1 12:00am'],
['The Procter & Gamble Company',61.91,0.01,0.02,'9/1 12:00am'],
['United Technologies Corporation',63.26,0.55,0.88,'9/1 12:00am'],
['Verizon Communications',35.57,0.39,1.11,'9/1 12:00am'],
['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
];

/**
* Custom function used for column renderer
* @param {Object} val
*/
function change(val){
if(val > 0){
return '<span style="color:green;">' + val + '</span>';
}else if(val < 0){
return '<span style="color:red;">' + val + '</span>';
}
return val;
}

/**
* Custom function used for column renderer
* @param {Object} val
*/
function pctChange(val){
if(val > 0){
return '<span style="color:green;">' + val + '%</span>';
}else if(val < 0){
return '<span style="color:red;">' + val + '%</span>';
}
return val;
}

// create the data store
var store = new Ext.data.ArrayStore({
fields: [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
],
listeners: {
load: function() {
grid.getBottomToolbar().excelButton.setHref('data:application/vnd.ms-excel;base64,' + Base64.encode(grid.getExcelXml()));
}
}
});

// create the Grid
var grid = new Ext.grid.GridPanel({
title: 'Array Grid',
store: store,
columns: [
{id:'company',header: 'Company', width: 160, sortable: true, dataIndex: 'company'},
{header: 'Price', width: 75, sortable: true, renderer: 'usMoney', dataIndex: 'price'},
{header: 'Change', width: 75, sortable: true, renderer: change, dataIndex: 'change'},
{header: '% Change', width: 75, sortable: true, renderer: pctChange, dataIndex: 'pctChange'},
{header: 'Last Updated', width: 85, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
],
stripeRows: true,
autoExpandColumn: 'company',
height: 350,
width: 600,
bbar: new Ext.PagingToolbar({
store: store,
pageSize: 12,
items: [{
xtype: 'linkbutton',
ref: 'excelButton',
text: 'Excel'
}]
})
});

// manually load local data. This will cause the LinkButton's href to be updated
store.loadData(myData);

// render the grid to the specified div in the page
grid.render(document.body);
});
</script>
</head>
<body></body>
</html>