├── Base64.js ├── Button.js ├── CSVFormatter └── CSVFormatter.js ├── ExcelFormatter ├── Cell.js ├── ExcelFormatter.js ├── Style.js ├── Workbook.js └── Worksheet.js ├── Exporter-all.js ├── Exporter.js ├── Formatter.js ├── LICENSE.txt ├── README.textile ├── build └── example ├── index.html └── style.css /Base64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Base64 encode / decode 4 | * http://www.webtoolkit.info/ 5 | * 6 | **/ 7 | 8 | var Base64 = (function() { 9 | 10 | // private property 11 | var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 12 | 13 | // private method for UTF-8 encoding 14 | function utf8Encode(string) { 15 | string = string.replace(/\r\n/g,"\n"); 16 | var utftext = ""; 17 | for (var n = 0; n < string.length; n++) { 18 | var c = string.charCodeAt(n); 19 | if (c < 128) { 20 | utftext += String.fromCharCode(c); 21 | } 22 | else if((c > 127) && (c < 2048)) { 23 | utftext += String.fromCharCode((c >> 6) | 192); 24 | utftext += String.fromCharCode((c & 63) | 128); 25 | } 26 | else { 27 | utftext += String.fromCharCode((c >> 12) | 224); 28 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 29 | utftext += String.fromCharCode((c & 63) | 128); 30 | } 31 | } 32 | return utftext; 33 | } 34 | 35 | // public method for encoding 36 | return { 37 | //This was the original line, which tries to use Firefox's built in Base64 encoder, but this kept throwing exceptions.... 38 | // encode : (typeof btoa == 'function') ? function(input) { return btoa(input); } : function (input) { 39 | 40 | 41 | encode : function (input) { 42 | var output = ""; 43 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 44 | var i = 0; 45 | input = utf8Encode(input); 46 | while (i < input.length) { 47 | chr1 = input.charCodeAt(i++); 48 | chr2 = input.charCodeAt(i++); 49 | chr3 = input.charCodeAt(i++); 50 | enc1 = chr1 >> 2; 51 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 52 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 53 | enc4 = chr3 & 63; 54 | if (isNaN(chr2)) { 55 | enc3 = enc4 = 64; 56 | } else if (isNaN(chr3)) { 57 | enc4 = 64; 58 | } 59 | output = output + 60 | keyStr.charAt(enc1) + keyStr.charAt(enc2) + 61 | keyStr.charAt(enc3) + keyStr.charAt(enc4); 62 | } 63 | return output; 64 | } 65 | }; 66 | })(); -------------------------------------------------------------------------------- /Button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.Button 3 | * @extends Ext.Button 4 | * @author Nige White, with modifications from Ed Spencer 5 | * Specialised Button class that allows downloading of data via data: urls. 6 | * Internally, this is just a link. 7 | * Pass it either an Ext.Component subclass with a 'store' property, or just a store: 8 | * new Ext.ux.Exporter.Button({component: someGrid}); 9 | * new Ext.ux.Exporter.Button({store: someStore}); 10 | * @cfg {Ext.Component} component The component the store is bound to 11 | * @cfg {Ext.data.Store} store The store to export (alternatively, pass a component with a store property) 12 | */ 13 | Ext.ux.Exporter.Button = Ext.extend(Ext.Button, { 14 | constructor: function(config) { 15 | config = config || {}; 16 | 17 | Ext.applyIf(config, { 18 | exportFunction: 'exportGrid', 19 | disabled : true, 20 | text : 'Download', 21 | cls : 'download' 22 | }); 23 | 24 | if (config.store == undefined && config.component != undefined) { 25 | Ext.applyIf(config, { 26 | store: config.component.store 27 | }); 28 | } else { 29 | Ext.applyIf(config, { 30 | component: { 31 | store: config.store 32 | } 33 | }); 34 | } 35 | 36 | Ext.ux.Exporter.Button.superclass.constructor.call(this, config); 37 | 38 | if (this.store && Ext.isFunction(this.store.on)) { 39 | var setLink = function() { 40 | this.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' + Ext.ux.Exporter[config.exportFunction](this.component, null, config); 41 | 42 | this.enable(); 43 | }; 44 | 45 | if (this.el) { 46 | setLink.call(this); 47 | } else { 48 | this.on('render', setLink, this); 49 | } 50 | 51 | this.store.on('load', setLink, this); 52 | } 53 | }, 54 | 55 | template: new Ext.Template( 56 | '', 57 | '', 58 | "
{0}
"), 59 | 60 | onRender: function(ct, position){ 61 | var btn, targs = [this.text || ' ', this.href, this.target || "_self"]; 62 | if (position){ 63 | btn = this.template.insertBefore(position, targs, true); 64 | }else{ 65 | btn = this.template.append(ct, targs, true); 66 | } 67 | var btnEl = btn.child("a:first"); 68 | this.btnEl = btnEl; 69 | btnEl.on('focus', this.onFocus, this); 70 | btnEl.on('blur', this.onBlur, this); 71 | 72 | this.initButtonEl(btn, btnEl); 73 | Ext.ButtonToggleMgr.register(this); 74 | }, 75 | 76 | onClick : function(e){ 77 | if (e.button != 0) return; 78 | 79 | if (!this.disabled){ 80 | this.fireEvent("click", this, e); 81 | 82 | if (this.handler) this.handler.call(this.scope || this, this, e); 83 | } 84 | } 85 | }); 86 | 87 | Ext.reg('exportbutton', Ext.ux.Exporter.Button); -------------------------------------------------------------------------------- /CSVFormatter/CSVFormatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.CSVFormatter 3 | * @extends Ext.ux.Exporter.Formatter 4 | * Specialised Format class for outputting .csv files 5 | */ 6 | Ext.ux.Exporter.CSVFormatter = Ext.extend(Ext.ux.Exporter.Formatter, { 7 | 8 | }); -------------------------------------------------------------------------------- /ExcelFormatter/Cell.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.ExcelFormatter.Cell 3 | * @extends Object 4 | * Represents a single cell in a worksheet 5 | */ 6 | Ext.ux.Exporter.ExcelFormatter.Cell = Ext.extend(Object, { 7 | 8 | constructor: function(config) { 9 | Ext.applyIf(config, { 10 | type: "String" 11 | }); 12 | 13 | Ext.apply(this, config); 14 | 15 | Ext.ux.Exporter.ExcelFormatter.Cell.superclass.constructor.apply(this, arguments); 16 | }, 17 | 18 | render: function() { 19 | return this.tpl.apply(this); 20 | }, 21 | 22 | tpl: new Ext.XTemplate( 23 | '', 24 | '', 25 | '' 26 | ) 27 | }); -------------------------------------------------------------------------------- /ExcelFormatter/ExcelFormatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.ExcelFormatter 3 | * @extends Ext.ux.Exporter.Formatter 4 | * Specialised Format class for outputting .xls files 5 | */ 6 | Ext.ux.Exporter.ExcelFormatter = Ext.extend(Ext.ux.Exporter.Formatter, { 7 | 8 | format: function(store, config) { 9 | var workbook = new Ext.ux.Exporter.ExcelFormatter.Workbook(config); 10 | workbook.addWorksheet(store, config || {}); 11 | 12 | return workbook.render(); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /ExcelFormatter/Style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.ExcelFormatter.Style 3 | * @extends Object 4 | * Represents a style declaration for a Workbook (this is like defining CSS rules). Example: 5 | * 6 | * new Ext.ux.Exporter.ExcelFormatter.Style({ 7 | * attributes: [ 8 | * { 9 | * name: "Alignment", 10 | * properties: [ 11 | * {name: "Vertical", value: "Top"}, 12 | * {name: "WrapText", value: "1"} 13 | * ] 14 | * }, 15 | * { 16 | * name: "Borders", 17 | * children: [ 18 | * name: "Border", 19 | * properties: [ 20 | * {name: "Color", value: "#e4e4e4"}, 21 | * {name: "Weight", value: "1"} 22 | * ] 23 | * ] 24 | * } 25 | * ] 26 | * }) 27 | * 28 | * @cfg {String} id The ID of this style (required) 29 | * @cfg {Array} attributes The attributes for this style 30 | * @cfg {String} parentStyle The (optional parentStyle ID) 31 | */ 32 | Ext.ux.Exporter.ExcelFormatter.Style = Ext.extend(Object, { 33 | 34 | constructor: function(config) { 35 | config = config || {}; 36 | 37 | Ext.apply(this, config, { 38 | parentStyle: '', 39 | attributes : [] 40 | }); 41 | 42 | Ext.ux.Exporter.ExcelFormatter.Style.superclass.constructor.apply(this, arguments); 43 | 44 | if (this.id == undefined) throw new Error("An ID must be provided to Style"); 45 | 46 | this.preparePropertyStrings(); 47 | }, 48 | 49 | /** 50 | * Iterates over the attributes in this style, and any children they may have, creating property 51 | * strings on each suitable for use in the XTemplate 52 | */ 53 | preparePropertyStrings: function() { 54 | Ext.each(this.attributes, function(attr, index) { 55 | this.attributes[index].propertiesString = this.buildPropertyString(attr); 56 | this.attributes[index].children = attr.children || []; 57 | 58 | Ext.each(attr.children, function(child, childIndex) { 59 | this.attributes[index].children[childIndex].propertiesString = this.buildPropertyString(child); 60 | }, this); 61 | }, this); 62 | }, 63 | 64 | /** 65 | * Builds a concatenated property string for a given attribute, suitable for use in the XTemplate 66 | */ 67 | buildPropertyString: function(attribute) { 68 | var propertiesString = ""; 69 | 70 | Ext.each(attribute.properties || [], function(property) { 71 | propertiesString += String.format('ss:{0}="{1}" ', property.name, property.value); 72 | }, this); 73 | 74 | return propertiesString; 75 | }, 76 | 77 | render: function() { 78 | return this.tpl.apply(this); 79 | }, 80 | 81 | tpl: new Ext.XTemplate( 82 | '', 83 | '', 84 | '', 85 | '', 86 | '', 87 | '', 88 | '', 89 | '', 90 | '', 91 | '', 92 | '', 93 | '', 94 | '', 95 | '', 96 | '', 97 | '', 98 | '', 99 | '', 100 | '' 101 | ) 102 | }); -------------------------------------------------------------------------------- /ExcelFormatter/Workbook.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.ExcelFormatter.Workbook 3 | * @extends Object 4 | * Represents an Excel workbook 5 | */ 6 | Ext.ux.Exporter.ExcelFormatter.Workbook = Ext.extend(Object, { 7 | 8 | constructor: function(config) { 9 | config = config || {}; 10 | 11 | Ext.apply(this, config, { 12 | /** 13 | * @property title 14 | * @type String 15 | * The title of the workbook (defaults to "Workbook") 16 | */ 17 | title: "Workbook", 18 | 19 | /** 20 | * @property worksheets 21 | * @type Array 22 | * The array of worksheets inside this workbook 23 | */ 24 | worksheets: [], 25 | 26 | /** 27 | * @property compileWorksheets 28 | * @type Array 29 | * Array of all rendered Worksheets 30 | */ 31 | compiledWorksheets: [], 32 | 33 | /** 34 | * @property cellBorderColor 35 | * @type String 36 | * The colour of border to use for each Cell 37 | */ 38 | cellBorderColor: "#e4e4e4", 39 | 40 | /** 41 | * @property styles 42 | * @type Array 43 | * The array of Ext.ux.Exporter.ExcelFormatter.Style objects attached to this workbook 44 | */ 45 | styles: [], 46 | 47 | /** 48 | * @property compiledStyles 49 | * @type Array 50 | * Array of all rendered Ext.ux.Exporter.ExcelFormatter.Style objects for this workbook 51 | */ 52 | compiledStyles: [], 53 | 54 | /** 55 | * @property hasDefaultStyle 56 | * @type Boolean 57 | * True to add the default styling options to all cells (defaults to true) 58 | */ 59 | hasDefaultStyle: true, 60 | 61 | /** 62 | * @property hasStripeStyles 63 | * @type Boolean 64 | * True to add the striping styles (defaults to true) 65 | */ 66 | hasStripeStyles: true, 67 | 68 | windowHeight : 9000, 69 | windowWidth : 50000, 70 | protectStructure: false, 71 | protectWindows : false 72 | }); 73 | 74 | if (this.hasDefaultStyle) this.addDefaultStyle(); 75 | if (this.hasStripeStyles) this.addStripedStyles(); 76 | 77 | this.addTitleStyle(); 78 | this.addHeaderStyle(); 79 | }, 80 | 81 | render: function() { 82 | this.compileStyles(); 83 | this.joinedCompiledStyles = this.compiledStyles.join(""); 84 | 85 | this.compileWorksheets(); 86 | this.joinedWorksheets = this.compiledWorksheets.join(""); 87 | 88 | return this.tpl.apply(this); 89 | }, 90 | 91 | /** 92 | * Adds a worksheet to this workbook based on a store and optional config 93 | * @param {Ext.data.Store} store The store to initialize the worksheet with 94 | * @param {Object} config Optional config object 95 | * @return {Ext.ux.Exporter.ExcelFormatter.Worksheet} The worksheet 96 | */ 97 | addWorksheet: function(store, config) { 98 | var worksheet = new Ext.ux.Exporter.ExcelFormatter.Worksheet(store, config); 99 | 100 | this.worksheets.push(worksheet); 101 | 102 | return worksheet; 103 | }, 104 | 105 | /** 106 | * Adds a new Ext.ux.Exporter.ExcelFormatter.Style to this Workbook 107 | * @param {Object} config The style config, passed to the Style constructor (required) 108 | */ 109 | addStyle: function(config) { 110 | var style = new Ext.ux.Exporter.ExcelFormatter.Style(config || {}); 111 | 112 | this.styles.push(style); 113 | 114 | return style; 115 | }, 116 | 117 | /** 118 | * Compiles each Style attached to this Workbook by rendering it 119 | * @return {Array} The compiled styles array 120 | */ 121 | compileStyles: function() { 122 | this.compiledStyles = []; 123 | 124 | Ext.each(this.styles, function(style) { 125 | this.compiledStyles.push(style.render()); 126 | }, this); 127 | 128 | return this.compiledStyles; 129 | }, 130 | 131 | /** 132 | * Compiles each Worksheet attached to this Workbook by rendering it 133 | * @return {Array} The compiled worksheets array 134 | */ 135 | compileWorksheets: function() { 136 | this.compiledWorksheets = []; 137 | 138 | Ext.each(this.worksheets, function(worksheet) { 139 | this.compiledWorksheets.push(worksheet.render()); 140 | }, this); 141 | 142 | return this.compiledWorksheets; 143 | }, 144 | 145 | tpl: new Ext.XTemplate( 146 | '', 147 | '', 148 | '', 149 | '{title}', 150 | '', 151 | '', 152 | '{windowHeight}', 153 | '{windowWidth}', 154 | '{protectStructure}', 155 | '{protectWindows}', 156 | '', 157 | '', 158 | '{joinedCompiledStyles}', 159 | '', 160 | '{joinedWorksheets}', 161 | '' 162 | ), 163 | 164 | /** 165 | * Adds the default Style to this workbook. This sets the default font face and size, as well as cell borders 166 | */ 167 | addDefaultStyle: function() { 168 | var borderProperties = [ 169 | {name: "Color", value: this.cellBorderColor}, 170 | {name: "Weight", value: "1"}, 171 | {name: "LineStyle", value: "Continuous"} 172 | ]; 173 | 174 | this.addStyle({ 175 | id: 'Default', 176 | attributes: [ 177 | { 178 | name: "Alignment", 179 | properties: [ 180 | {name: "Vertical", value: "Top"}, 181 | {name: "WrapText", value: "1"} 182 | ] 183 | }, 184 | { 185 | name: "Font", 186 | properties: [ 187 | {name: "FontName", value: "arial"}, 188 | {name: "Size", value: "10"} 189 | ] 190 | }, 191 | {name: "Interior"}, {name: "NumberFormat"}, {name: "Protection"}, 192 | { 193 | name: "Borders", 194 | children: [ 195 | { 196 | name: "Border", 197 | properties: [{name: "Position", value: "Top"}].concat(borderProperties) 198 | }, 199 | { 200 | name: "Border", 201 | properties: [{name: "Position", value: "Bottom"}].concat(borderProperties) 202 | }, 203 | { 204 | name: "Border", 205 | properties: [{name: "Position", value: "Left"}].concat(borderProperties) 206 | }, 207 | { 208 | name: "Border", 209 | properties: [{name: "Position", value: "Right"}].concat(borderProperties) 210 | } 211 | ] 212 | } 213 | ] 214 | }); 215 | }, 216 | 217 | addTitleStyle: function() { 218 | this.addStyle({ 219 | id: "title", 220 | attributes: [ 221 | {name: "Borders"}, 222 | {name: "Font"}, 223 | { 224 | name: "NumberFormat", 225 | properties: [ 226 | {name: "Format", value: "@"} 227 | ] 228 | }, 229 | { 230 | name: "Alignment", 231 | properties: [ 232 | {name: "WrapText", value: "1"}, 233 | {name: "Horizontal", value: "Center"}, 234 | {name: "Vertical", value: "Center"} 235 | ] 236 | } 237 | ] 238 | }); 239 | }, 240 | 241 | addHeaderStyle: function() { 242 | this.addStyle({ 243 | id: "headercell", 244 | attributes: [ 245 | { 246 | name: "Font", 247 | properties: [ 248 | {name: "Bold", value: "1"}, 249 | {name: "Size", value: "10"} 250 | ] 251 | }, 252 | { 253 | name: "Interior", 254 | properties: [ 255 | {name: "Pattern", value: "Solid"}, 256 | {name: "Color", value: "#A3C9F1"} 257 | ] 258 | }, 259 | { 260 | name: "Alignment", 261 | properties: [ 262 | {name: "WrapText", value: "1"}, 263 | {name: "Horizontal", value: "Center"} 264 | ] 265 | } 266 | ] 267 | }); 268 | }, 269 | 270 | /** 271 | * Adds the default striping styles to this workbook 272 | */ 273 | addStripedStyles: function() { 274 | this.addStyle({ 275 | id: "even", 276 | attributes: [ 277 | { 278 | name: "Interior", 279 | properties: [ 280 | {name: "Pattern", value: "Solid"}, 281 | {name: "Color", value: "#CCFFFF"} 282 | ] 283 | } 284 | ] 285 | }); 286 | 287 | this.addStyle({ 288 | id: "odd", 289 | attributes: [ 290 | { 291 | name: "Interior", 292 | properties: [ 293 | {name: "Pattern", value: "Solid"}, 294 | {name: "Color", value: "#CCCCFF"} 295 | ] 296 | } 297 | ] 298 | }); 299 | 300 | Ext.each(['even', 'odd'], function(parentStyle) { 301 | this.addChildNumberFormatStyle(parentStyle, parentStyle + 'date', "[ENG][$-409]dd\-mmm\-yyyy;@"); 302 | this.addChildNumberFormatStyle(parentStyle, parentStyle + 'int', "0"); 303 | this.addChildNumberFormatStyle(parentStyle, parentStyle + 'float', "0.00"); 304 | }, this); 305 | }, 306 | 307 | /** 308 | * Private convenience function to easily add a NumberFormat style for a given parentStyle 309 | * @param {String} parentStyle The ID of the parentStyle Style 310 | * @param {String} id The ID of the new style 311 | * @param {String} value The value of the NumberFormat's Format property 312 | */ 313 | addChildNumberFormatStyle: function(parentStyle, id, value) { 314 | this.addStyle({ 315 | id: id, 316 | parentStyle: "even", 317 | attributes: [ 318 | { 319 | name: "NumberFormat", 320 | properties: [{name: "Format", value: value}] 321 | } 322 | ] 323 | }); 324 | } 325 | }); -------------------------------------------------------------------------------- /ExcelFormatter/Worksheet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.ExcelFormatter.Worksheet 3 | * @extends Object 4 | * Represents an Excel worksheet 5 | * @cfg {Ext.data.Store} store The store to use (required) 6 | */ 7 | Ext.ux.Exporter.ExcelFormatter.Worksheet = Ext.extend(Object, { 8 | 9 | constructor: function(store, config) { 10 | config = config || {}; 11 | 12 | this.store = store; 13 | 14 | Ext.applyIf(config, { 15 | hasTitle : true, 16 | hasHeadings: true, 17 | stripeRows : true, 18 | 19 | title : "Workbook", 20 | columns : store.fields == undefined ? {} : store.fields.items 21 | }); 22 | 23 | Ext.apply(this, config); 24 | 25 | Ext.ux.Exporter.ExcelFormatter.Worksheet.superclass.constructor.apply(this, arguments); 26 | }, 27 | 28 | /** 29 | * @property dateFormatString 30 | * @type String 31 | * String used to format dates (defaults to "Y-m-d"). All other data types are left unmolested 32 | */ 33 | dateFormatString: "Y-m-d", 34 | 35 | worksheetTpl: new Ext.XTemplate( 36 | '', 37 | '', 38 | '', 39 | '', 40 | '', 41 | '{columns}', 42 | '', 43 | '', 44 | '', 45 | '{title}', 46 | '', 47 | '', 48 | '', 49 | '', 50 | '{header}', 51 | '', 52 | '{rows}', 53 | '', 54 | '', 55 | '', 56 | '', 57 | '', 58 | '', 59 | '', 60 | '', 61 | '', 62 | 'Blank', 63 | '1', 64 | '32767', 65 | '', 66 | '600', 67 | '', 68 | '', 69 | '', 70 | 'False', 71 | 'False', 72 | '', 73 | '' 74 | ), 75 | 76 | /** 77 | * Builds the Worksheet XML 78 | * @param {Ext.data.Store} store The store to build from 79 | */ 80 | render: function(store) { 81 | return this.worksheetTpl.apply({ 82 | header : this.buildHeader(), 83 | columns : this.buildColumns().join(""), 84 | rows : this.buildRows().join(""), 85 | colCount: this.columns.length, 86 | rowCount: this.store.getCount() + 2, 87 | title : this.title 88 | }); 89 | }, 90 | 91 | buildColumns: function() { 92 | var cols = []; 93 | 94 | Ext.each(this.columns, function(column) { 95 | cols.push(this.buildColumn()); 96 | }, this); 97 | 98 | return cols; 99 | }, 100 | 101 | buildColumn: function(width) { 102 | return String.format('', width || 164); 103 | }, 104 | 105 | buildRows: function() { 106 | var rows = []; 107 | 108 | this.store.each(function(record, index) { 109 | rows.push(this.buildRow(record, index)); 110 | }, this); 111 | 112 | return rows; 113 | }, 114 | 115 | buildHeader: function() { 116 | var cells = []; 117 | 118 | Ext.each(this.columns, function(col) { 119 | var title; 120 | 121 | if (col.header != undefined) { 122 | title = col.header; 123 | } else { 124 | //make columns taken from Record fields (e.g. with a col.name) human-readable 125 | title = col.name.replace(/_/g, " "); 126 | title = title.charAt(0).toUpperCase() + title.substr(1).toLowerCase(); 127 | } 128 | 129 | cells.push(String.format('{0}', title)); 130 | }, this); 131 | 132 | return cells.join(""); 133 | }, 134 | 135 | buildRow: function(record, index) { 136 | var style, 137 | cells = []; 138 | if (this.stripeRows === true) style = index % 2 == 0 ? 'even' : 'odd'; 139 | 140 | Ext.each(this.columns, function(col) { 141 | var name = col.name || col.dataIndex; 142 | 143 | //if given a renderer via a ColumnModel, use it and ensure data type is set to String 144 | if (Ext.isFunction(col.renderer)) { 145 | var value = col.renderer(record.get(name), null, record), 146 | type = "String"; 147 | } else { 148 | var value = record.get(name), 149 | type = this.typeMappings[col.type || record.fields.item(name).type]; 150 | } 151 | 152 | cells.push(this.buildCell(value, type, style).render()); 153 | }, this); 154 | 155 | return String.format("{0}", cells.join("")); 156 | }, 157 | 158 | buildCell: function(value, type, style) { 159 | if (type == "DateTime" && Ext.isFunction(value.format)) value = value.format(this.dateFormatString); 160 | 161 | return new Ext.ux.Exporter.ExcelFormatter.Cell({ 162 | value: value, 163 | type : type, 164 | style: style 165 | }); 166 | }, 167 | 168 | /** 169 | * @property typeMappings 170 | * @type Object 171 | * Mappings from Ext.data.Record types to Excel types 172 | */ 173 | typeMappings: { 174 | 'int' : "Number", 175 | 'string': "String", 176 | 'float' : "Number", 177 | 'date' : "DateTime" 178 | } 179 | }); -------------------------------------------------------------------------------- /Exporter-all.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Base64 encode / decode 4 | * http://www.webtoolkit.info/ 5 | * 6 | **/ 7 | 8 | var Base64 = (function() { 9 | 10 | // private property 11 | var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 12 | 13 | // private method for UTF-8 encoding 14 | function utf8Encode(string) { 15 | string = string.replace(/\r\n/g,"\n"); 16 | var utftext = ""; 17 | for (var n = 0; n < string.length; n++) { 18 | var c = string.charCodeAt(n); 19 | if (c < 128) { 20 | utftext += String.fromCharCode(c); 21 | } 22 | else if((c > 127) && (c < 2048)) { 23 | utftext += String.fromCharCode((c >> 6) | 192); 24 | utftext += String.fromCharCode((c & 63) | 128); 25 | } 26 | else { 27 | utftext += String.fromCharCode((c >> 12) | 224); 28 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 29 | utftext += String.fromCharCode((c & 63) | 128); 30 | } 31 | } 32 | return utftext; 33 | } 34 | 35 | // public method for encoding 36 | return { 37 | //This was the original line, which tries to use Firefox's built in Base64 encoder, but this kept throwing exceptions.... 38 | // encode : (typeof btoa == 'function') ? function(input) { return btoa(input); } : function (input) { 39 | 40 | 41 | encode : function (input) { 42 | var output = ""; 43 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 44 | var i = 0; 45 | input = utf8Encode(input); 46 | while (i < input.length) { 47 | chr1 = input.charCodeAt(i++); 48 | chr2 = input.charCodeAt(i++); 49 | chr3 = input.charCodeAt(i++); 50 | enc1 = chr1 >> 2; 51 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 52 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 53 | enc4 = chr3 & 63; 54 | if (isNaN(chr2)) { 55 | enc3 = enc4 = 64; 56 | } else if (isNaN(chr3)) { 57 | enc4 = 64; 58 | } 59 | output = output + 60 | keyStr.charAt(enc1) + keyStr.charAt(enc2) + 61 | keyStr.charAt(enc3) + keyStr.charAt(enc4); 62 | } 63 | return output; 64 | } 65 | }; 66 | })(); 67 | 68 | /** 69 | * @class Ext.ux.Exporter 70 | * @author Ed Spencer (http://edspencer.net) 71 | * Class providing a common way of downloading data in .xls or .csv format 72 | */ 73 | Ext.ux.Exporter = function() { 74 | return { 75 | /** 76 | * Exports a grid, using the .xls formatter by default 77 | * @param {Ext.grid.GridPanel} grid The grid to export from 78 | * @param {Object} config Optional config settings for the formatter 79 | */ 80 | exportGrid: function(grid, formatter, config) { 81 | config = config || {}; 82 | formatter = formatter || new Ext.ux.Exporter.ExcelFormatter(); 83 | 84 | Ext.applyIf(config, { 85 | title : grid.title, 86 | columns: grid.getColumnModel().config 87 | }); 88 | 89 | return Base64.encode(formatter.format(grid.store, config)); 90 | }, 91 | 92 | exportStore: function(store, formatter, config) { 93 | config = config || {}; 94 | formatter = formatter || new Ext.ux.Exporter.ExcelFormatter(); 95 | 96 | Ext.applyIf(config, { 97 | columns: config.store.fields.items 98 | }); 99 | 100 | return Base64.encode(formatter.format(store, config)); 101 | }, 102 | 103 | exportTree: function(tree, formatter, config) { 104 | config = config || {}; 105 | formatter = formatter || new Ext.ux.Exporter.ExcelFormatter(); 106 | 107 | var store = tree.store || config.store; 108 | 109 | Ext.applyIf(config, { 110 | title: tree.title 111 | }); 112 | 113 | return Base64.encode(formatter.format(store, config)); 114 | } 115 | }; 116 | }(); 117 | 118 | /** 119 | * @class Ext.ux.Exporter.Button 120 | * @extends Ext.Button 121 | * @author Nige White, with modifications from Ed Spencer 122 | * Specialised Button class that allows downloading of data via data: urls. 123 | * Internally, this is just a link. 124 | * Pass it either an Ext.Component subclass with a 'store' property, or just a store: 125 | * new Ext.ux.Exporter.Button({component: someGrid}); 126 | * new Ext.ux.Exporter.Button({store: someStore}); 127 | * @cfg {Ext.Component} component The component the store is bound to 128 | * @cfg {Ext.data.Store} store The store to export (alternatively, pass a component with a store property) 129 | */ 130 | Ext.ux.Exporter.Button = Ext.extend(Ext.Button, { 131 | constructor: function(config) { 132 | config = config || {}; 133 | 134 | Ext.applyIf(config, { 135 | exportFunction: 'exportGrid', 136 | disabled : true, 137 | text : 'Download', 138 | cls : 'download' 139 | }); 140 | 141 | if (config.store == undefined && config.component != undefined) { 142 | Ext.applyIf(config, { 143 | store: config.component.store 144 | }); 145 | } else { 146 | Ext.applyIf(config, { 147 | component: { 148 | store: config.store 149 | } 150 | }); 151 | } 152 | 153 | Ext.ux.Exporter.Button.superclass.constructor.call(this, config); 154 | 155 | if (this.store && Ext.isFunction(this.store.on)) { 156 | var setLink = function() { 157 | this.getEl().child('a', true).href = 'data:application/vnd.ms-excel;base64,' + Ext.ux.Exporter[config.exportFunction](this.component, null, config); 158 | 159 | this.enable(); 160 | }; 161 | 162 | if (this.el) { 163 | setLink.call(this); 164 | } else { 165 | this.on('render', setLink, this); 166 | } 167 | 168 | this.store.on('load', setLink, this); 169 | } 170 | }, 171 | 172 | template: new Ext.Template( 173 | '', 174 | '', 175 | "
{0}
"), 176 | 177 | onRender: function(ct, position){ 178 | var btn, targs = [this.text || ' ', this.href, this.target || "_self"]; 179 | if (position){ 180 | btn = this.template.insertBefore(position, targs, true); 181 | }else{ 182 | btn = this.template.append(ct, targs, true); 183 | } 184 | var btnEl = btn.child("a:first"); 185 | this.btnEl = btnEl; 186 | btnEl.on('focus', this.onFocus, this); 187 | btnEl.on('blur', this.onBlur, this); 188 | 189 | this.initButtonEl(btn, btnEl); 190 | Ext.ButtonToggleMgr.register(this); 191 | }, 192 | 193 | onClick : function(e){ 194 | if (e.button != 0) return; 195 | 196 | if (!this.disabled){ 197 | this.fireEvent("click", this, e); 198 | 199 | if (this.handler) this.handler.call(this.scope || this, this, e); 200 | } 201 | } 202 | }); 203 | 204 | Ext.reg('exportbutton', Ext.ux.Exporter.Button); 205 | 206 | /** 207 | * @class Ext.ux.Exporter.Formatter 208 | * @author Ed Spencer (http://edspencer.net) 209 | * @cfg {Ext.data.Store} store The store to export 210 | */ 211 | Ext.ux.Exporter.Formatter = function(config) { 212 | config = config || {}; 213 | 214 | Ext.applyIf(config, { 215 | 216 | }); 217 | }; 218 | 219 | Ext.ux.Exporter.Formatter.prototype = { 220 | /** 221 | * Performs the actual formatting. This must be overridden by a subclass 222 | */ 223 | format: Ext.emptyFn 224 | }; 225 | 226 | /** 227 | * @class Ext.ux.Exporter.ExcelFormatter 228 | * @extends Ext.ux.Exporter.Formatter 229 | * Specialised Format class for outputting .xls files 230 | */ 231 | Ext.ux.Exporter.ExcelFormatter = Ext.extend(Ext.ux.Exporter.Formatter, { 232 | 233 | format: function(store, config) { 234 | var workbook = new Ext.ux.Exporter.ExcelFormatter.Workbook(config); 235 | workbook.addWorksheet(store, config || {}); 236 | 237 | return workbook.render(); 238 | } 239 | }); 240 | 241 | /** 242 | * @class Ext.ux.Exporter.ExcelFormatter.Workbook 243 | * @extends Object 244 | * Represents an Excel workbook 245 | */ 246 | Ext.ux.Exporter.ExcelFormatter.Workbook = Ext.extend(Object, { 247 | 248 | constructor: function(config) { 249 | config = config || {}; 250 | 251 | Ext.apply(this, config, { 252 | /** 253 | * @property title 254 | * @type String 255 | * The title of the workbook (defaults to "Workbook") 256 | */ 257 | title: "Workbook", 258 | 259 | /** 260 | * @property worksheets 261 | * @type Array 262 | * The array of worksheets inside this workbook 263 | */ 264 | worksheets: [], 265 | 266 | /** 267 | * @property compileWorksheets 268 | * @type Array 269 | * Array of all rendered Worksheets 270 | */ 271 | compiledWorksheets: [], 272 | 273 | /** 274 | * @property cellBorderColor 275 | * @type String 276 | * The colour of border to use for each Cell 277 | */ 278 | cellBorderColor: "#e4e4e4", 279 | 280 | /** 281 | * @property styles 282 | * @type Array 283 | * The array of Ext.ux.Exporter.ExcelFormatter.Style objects attached to this workbook 284 | */ 285 | styles: [], 286 | 287 | /** 288 | * @property compiledStyles 289 | * @type Array 290 | * Array of all rendered Ext.ux.Exporter.ExcelFormatter.Style objects for this workbook 291 | */ 292 | compiledStyles: [], 293 | 294 | /** 295 | * @property hasDefaultStyle 296 | * @type Boolean 297 | * True to add the default styling options to all cells (defaults to true) 298 | */ 299 | hasDefaultStyle: true, 300 | 301 | /** 302 | * @property hasStripeStyles 303 | * @type Boolean 304 | * True to add the striping styles (defaults to true) 305 | */ 306 | hasStripeStyles: true, 307 | 308 | windowHeight : 9000, 309 | windowWidth : 50000, 310 | protectStructure: false, 311 | protectWindows : false 312 | }); 313 | 314 | if (this.hasDefaultStyle) this.addDefaultStyle(); 315 | if (this.hasStripeStyles) this.addStripedStyles(); 316 | 317 | this.addTitleStyle(); 318 | this.addHeaderStyle(); 319 | }, 320 | 321 | render: function() { 322 | this.compileStyles(); 323 | this.joinedCompiledStyles = this.compiledStyles.join(""); 324 | 325 | this.compileWorksheets(); 326 | this.joinedWorksheets = this.compiledWorksheets.join(""); 327 | 328 | return this.tpl.apply(this); 329 | }, 330 | 331 | /** 332 | * Adds a worksheet to this workbook based on a store and optional config 333 | * @param {Ext.data.Store} store The store to initialize the worksheet with 334 | * @param {Object} config Optional config object 335 | * @return {Ext.ux.Exporter.ExcelFormatter.Worksheet} The worksheet 336 | */ 337 | addWorksheet: function(store, config) { 338 | var worksheet = new Ext.ux.Exporter.ExcelFormatter.Worksheet(store, config); 339 | 340 | this.worksheets.push(worksheet); 341 | 342 | return worksheet; 343 | }, 344 | 345 | /** 346 | * Adds a new Ext.ux.Exporter.ExcelFormatter.Style to this Workbook 347 | * @param {Object} config The style config, passed to the Style constructor (required) 348 | */ 349 | addStyle: function(config) { 350 | var style = new Ext.ux.Exporter.ExcelFormatter.Style(config || {}); 351 | 352 | this.styles.push(style); 353 | 354 | return style; 355 | }, 356 | 357 | /** 358 | * Compiles each Style attached to this Workbook by rendering it 359 | * @return {Array} The compiled styles array 360 | */ 361 | compileStyles: function() { 362 | this.compiledStyles = []; 363 | 364 | Ext.each(this.styles, function(style) { 365 | this.compiledStyles.push(style.render()); 366 | }, this); 367 | 368 | return this.compiledStyles; 369 | }, 370 | 371 | /** 372 | * Compiles each Worksheet attached to this Workbook by rendering it 373 | * @return {Array} The compiled worksheets array 374 | */ 375 | compileWorksheets: function() { 376 | this.compiledWorksheets = []; 377 | 378 | Ext.each(this.worksheets, function(worksheet) { 379 | this.compiledWorksheets.push(worksheet.render()); 380 | }, this); 381 | 382 | return this.compiledWorksheets; 383 | }, 384 | 385 | tpl: new Ext.XTemplate( 386 | '', 387 | '', 388 | '', 389 | '{title}', 390 | '', 391 | '', 392 | '{windowHeight}', 393 | '{windowWidth}', 394 | '{protectStructure}', 395 | '{protectWindows}', 396 | '', 397 | '', 398 | '{joinedCompiledStyles}', 399 | '', 400 | '{joinedWorksheets}', 401 | '' 402 | ), 403 | 404 | /** 405 | * Adds the default Style to this workbook. This sets the default font face and size, as well as cell borders 406 | */ 407 | addDefaultStyle: function() { 408 | var borderProperties = [ 409 | {name: "Color", value: this.cellBorderColor}, 410 | {name: "Weight", value: "1"}, 411 | {name: "LineStyle", value: "Continuous"} 412 | ]; 413 | 414 | this.addStyle({ 415 | id: 'Default', 416 | attributes: [ 417 | { 418 | name: "Alignment", 419 | properties: [ 420 | {name: "Vertical", value: "Top"}, 421 | {name: "WrapText", value: "1"} 422 | ] 423 | }, 424 | { 425 | name: "Font", 426 | properties: [ 427 | {name: "FontName", value: "arial"}, 428 | {name: "Size", value: "10"} 429 | ] 430 | }, 431 | {name: "Interior"}, {name: "NumberFormat"}, {name: "Protection"}, 432 | { 433 | name: "Borders", 434 | children: [ 435 | { 436 | name: "Border", 437 | properties: [{name: "Position", value: "Top"}].concat(borderProperties) 438 | }, 439 | { 440 | name: "Border", 441 | properties: [{name: "Position", value: "Bottom"}].concat(borderProperties) 442 | }, 443 | { 444 | name: "Border", 445 | properties: [{name: "Position", value: "Left"}].concat(borderProperties) 446 | }, 447 | { 448 | name: "Border", 449 | properties: [{name: "Position", value: "Right"}].concat(borderProperties) 450 | } 451 | ] 452 | } 453 | ] 454 | }); 455 | }, 456 | 457 | addTitleStyle: function() { 458 | this.addStyle({ 459 | id: "title", 460 | attributes: [ 461 | {name: "Borders"}, 462 | {name: "Font"}, 463 | { 464 | name: "NumberFormat", 465 | properties: [ 466 | {name: "Format", value: "@"} 467 | ] 468 | }, 469 | { 470 | name: "Alignment", 471 | properties: [ 472 | {name: "WrapText", value: "1"}, 473 | {name: "Horizontal", value: "Center"}, 474 | {name: "Vertical", value: "Center"} 475 | ] 476 | } 477 | ] 478 | }); 479 | }, 480 | 481 | addHeaderStyle: function() { 482 | this.addStyle({ 483 | id: "headercell", 484 | attributes: [ 485 | { 486 | name: "Font", 487 | properties: [ 488 | {name: "Bold", value: "1"}, 489 | {name: "Size", value: "10"} 490 | ] 491 | }, 492 | { 493 | name: "Interior", 494 | properties: [ 495 | {name: "Pattern", value: "Solid"}, 496 | {name: "Color", value: "#A3C9F1"} 497 | ] 498 | }, 499 | { 500 | name: "Alignment", 501 | properties: [ 502 | {name: "WrapText", value: "1"}, 503 | {name: "Horizontal", value: "Center"} 504 | ] 505 | } 506 | ] 507 | }); 508 | }, 509 | 510 | /** 511 | * Adds the default striping styles to this workbook 512 | */ 513 | addStripedStyles: function() { 514 | this.addStyle({ 515 | id: "even", 516 | attributes: [ 517 | { 518 | name: "Interior", 519 | properties: [ 520 | {name: "Pattern", value: "Solid"}, 521 | {name: "Color", value: "#CCFFFF"} 522 | ] 523 | } 524 | ] 525 | }); 526 | 527 | this.addStyle({ 528 | id: "odd", 529 | attributes: [ 530 | { 531 | name: "Interior", 532 | properties: [ 533 | {name: "Pattern", value: "Solid"}, 534 | {name: "Color", value: "#CCCCFF"} 535 | ] 536 | } 537 | ] 538 | }); 539 | 540 | Ext.each(['even', 'odd'], function(parentStyle) { 541 | this.addChildNumberFormatStyle(parentStyle, parentStyle + 'date', "[ENG][$-409]dd\-mmm\-yyyy;@"); 542 | this.addChildNumberFormatStyle(parentStyle, parentStyle + 'int', "0"); 543 | this.addChildNumberFormatStyle(parentStyle, parentStyle + 'float', "0.00"); 544 | }, this); 545 | }, 546 | 547 | /** 548 | * Private convenience function to easily add a NumberFormat style for a given parentStyle 549 | * @param {String} parentStyle The ID of the parentStyle Style 550 | * @param {String} id The ID of the new style 551 | * @param {String} value The value of the NumberFormat's Format property 552 | */ 553 | addChildNumberFormatStyle: function(parentStyle, id, value) { 554 | this.addStyle({ 555 | id: id, 556 | parentStyle: "even", 557 | attributes: [ 558 | { 559 | name: "NumberFormat", 560 | properties: [{name: "Format", value: value}] 561 | } 562 | ] 563 | }); 564 | } 565 | }); 566 | 567 | /** 568 | * @class Ext.ux.Exporter.ExcelFormatter.Worksheet 569 | * @extends Object 570 | * Represents an Excel worksheet 571 | * @cfg {Ext.data.Store} store The store to use (required) 572 | */ 573 | Ext.ux.Exporter.ExcelFormatter.Worksheet = Ext.extend(Object, { 574 | 575 | constructor: function(store, config) { 576 | config = config || {}; 577 | 578 | this.store = store; 579 | 580 | Ext.applyIf(config, { 581 | hasTitle : true, 582 | hasHeadings: true, 583 | stripeRows : true, 584 | 585 | title : "Workbook", 586 | columns : store.fields == undefined ? {} : store.fields.items 587 | }); 588 | 589 | Ext.apply(this, config); 590 | 591 | Ext.ux.Exporter.ExcelFormatter.Worksheet.superclass.constructor.apply(this, arguments); 592 | }, 593 | 594 | /** 595 | * @property dateFormatString 596 | * @type String 597 | * String used to format dates (defaults to "Y-m-d"). All other data types are left unmolested 598 | */ 599 | dateFormatString: "Y-m-d", 600 | 601 | worksheetTpl: new Ext.XTemplate( 602 | '', 603 | '', 604 | '', 605 | '', 606 | '', 607 | '{columns}', 608 | '', 609 | '', 610 | '', 611 | '{title}', 612 | '', 613 | '', 614 | '', 615 | '', 616 | '{header}', 617 | '', 618 | '{rows}', 619 | '', 620 | '', 621 | '', 622 | '', 623 | '', 624 | '', 625 | '', 626 | '', 627 | '', 628 | 'Blank', 629 | '1', 630 | '32767', 631 | '', 632 | '600', 633 | '', 634 | '', 635 | '', 636 | 'False', 637 | 'False', 638 | '', 639 | '' 640 | ), 641 | 642 | /** 643 | * Builds the Worksheet XML 644 | * @param {Ext.data.Store} store The store to build from 645 | */ 646 | render: function(store) { 647 | return this.worksheetTpl.apply({ 648 | header : this.buildHeader(), 649 | columns : this.buildColumns().join(""), 650 | rows : this.buildRows().join(""), 651 | colCount: this.columns.length, 652 | rowCount: this.store.getCount() + 2, 653 | title : this.title 654 | }); 655 | }, 656 | 657 | buildColumns: function() { 658 | var cols = []; 659 | 660 | Ext.each(this.columns, function(column) { 661 | cols.push(this.buildColumn()); 662 | }, this); 663 | 664 | return cols; 665 | }, 666 | 667 | buildColumn: function(width) { 668 | return String.format('', width || 164); 669 | }, 670 | 671 | buildRows: function() { 672 | var rows = []; 673 | 674 | this.store.each(function(record, index) { 675 | rows.push(this.buildRow(record, index)); 676 | }, this); 677 | 678 | return rows; 679 | }, 680 | 681 | buildHeader: function() { 682 | var cells = []; 683 | 684 | Ext.each(this.columns, function(col) { 685 | var title; 686 | 687 | if (col.header != undefined) { 688 | title = col.header; 689 | } else { 690 | //make columns taken from Record fields (e.g. with a col.name) human-readable 691 | title = col.name.replace(/_/g, " "); 692 | title = title.charAt(0).toUpperCase() + title.substr(1).toLowerCase(); 693 | } 694 | 695 | cells.push(String.format('{0}', title)); 696 | }, this); 697 | 698 | return cells.join(""); 699 | }, 700 | 701 | buildRow: function(record, index) { 702 | var style, 703 | cells = []; 704 | if (this.stripeRows === true) style = index % 2 == 0 ? 'even' : 'odd'; 705 | 706 | Ext.each(this.columns, function(col) { 707 | var name = col.name || col.dataIndex; 708 | 709 | //if given a renderer via a ColumnModel, use it and ensure data type is set to String 710 | if (Ext.isFunction(col.renderer)) { 711 | var value = col.renderer(record.get(name), null, record), 712 | type = "String"; 713 | } else { 714 | var value = record.get(name), 715 | type = this.typeMappings[col.type || record.fields.item(name).type]; 716 | } 717 | 718 | cells.push(this.buildCell(value, type, style).render()); 719 | }, this); 720 | 721 | return String.format("{0}", cells.join("")); 722 | }, 723 | 724 | buildCell: function(value, type, style) { 725 | if (type == "DateTime" && Ext.isFunction(value.format)) value = value.format(this.dateFormatString); 726 | 727 | return new Ext.ux.Exporter.ExcelFormatter.Cell({ 728 | value: value, 729 | type : type, 730 | style: style 731 | }); 732 | }, 733 | 734 | /** 735 | * @property typeMappings 736 | * @type Object 737 | * Mappings from Ext.data.Record types to Excel types 738 | */ 739 | typeMappings: { 740 | 'int' : "Number", 741 | 'string': "String", 742 | 'float' : "Number", 743 | 'date' : "DateTime" 744 | } 745 | }); 746 | 747 | /** 748 | * @class Ext.ux.Exporter.ExcelFormatter.Cell 749 | * @extends Object 750 | * Represents a single cell in a worksheet 751 | */ 752 | Ext.ux.Exporter.ExcelFormatter.Cell = Ext.extend(Object, { 753 | 754 | constructor: function(config) { 755 | Ext.applyIf(config, { 756 | type: "String" 757 | }); 758 | 759 | Ext.apply(this, config); 760 | 761 | Ext.ux.Exporter.ExcelFormatter.Cell.superclass.constructor.apply(this, arguments); 762 | }, 763 | 764 | render: function() { 765 | return this.tpl.apply(this); 766 | }, 767 | 768 | tpl: new Ext.XTemplate( 769 | '', 770 | '', 771 | '' 772 | ) 773 | }); 774 | 775 | /** 776 | * @class Ext.ux.Exporter.ExcelFormatter.Style 777 | * @extends Object 778 | * Represents a style declaration for a Workbook (this is like defining CSS rules). Example: 779 | * 780 | * new Ext.ux.Exporter.ExcelFormatter.Style({ 781 | * attributes: [ 782 | * { 783 | * name: "Alignment", 784 | * properties: [ 785 | * {name: "Vertical", value: "Top"}, 786 | * {name: "WrapText", value: "1"} 787 | * ] 788 | * }, 789 | * { 790 | * name: "Borders", 791 | * children: [ 792 | * name: "Border", 793 | * properties: [ 794 | * {name: "Color", value: "#e4e4e4"}, 795 | * {name: "Weight", value: "1"} 796 | * ] 797 | * ] 798 | * } 799 | * ] 800 | * }) 801 | * 802 | * @cfg {String} id The ID of this style (required) 803 | * @cfg {Array} attributes The attributes for this style 804 | * @cfg {String} parentStyle The (optional parentStyle ID) 805 | */ 806 | Ext.ux.Exporter.ExcelFormatter.Style = Ext.extend(Object, { 807 | 808 | constructor: function(config) { 809 | config = config || {}; 810 | 811 | Ext.apply(this, config, { 812 | parentStyle: '', 813 | attributes : [] 814 | }); 815 | 816 | Ext.ux.Exporter.ExcelFormatter.Style.superclass.constructor.apply(this, arguments); 817 | 818 | if (this.id == undefined) throw new Error("An ID must be provided to Style"); 819 | 820 | this.preparePropertyStrings(); 821 | }, 822 | 823 | /** 824 | * Iterates over the attributes in this style, and any children they may have, creating property 825 | * strings on each suitable for use in the XTemplate 826 | */ 827 | preparePropertyStrings: function() { 828 | Ext.each(this.attributes, function(attr, index) { 829 | this.attributes[index].propertiesString = this.buildPropertyString(attr); 830 | this.attributes[index].children = attr.children || []; 831 | 832 | Ext.each(attr.children, function(child, childIndex) { 833 | this.attributes[index].children[childIndex].propertiesString = this.buildPropertyString(child); 834 | }, this); 835 | }, this); 836 | }, 837 | 838 | /** 839 | * Builds a concatenated property string for a given attribute, suitable for use in the XTemplate 840 | */ 841 | buildPropertyString: function(attribute) { 842 | var propertiesString = ""; 843 | 844 | Ext.each(attribute.properties || [], function(property) { 845 | propertiesString += String.format('ss:{0}="{1}" ', property.name, property.value); 846 | }, this); 847 | 848 | return propertiesString; 849 | }, 850 | 851 | render: function() { 852 | return this.tpl.apply(this); 853 | }, 854 | 855 | tpl: new Ext.XTemplate( 856 | '', 857 | '', 858 | '', 859 | '', 860 | '', 861 | '', 862 | '', 863 | '', 864 | '', 865 | '', 866 | '', 867 | '', 868 | '', 869 | '', 870 | '', 871 | '', 872 | '', 873 | '', 874 | '' 875 | ) 876 | }); 877 | 878 | -------------------------------------------------------------------------------- /Exporter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter 3 | * @author Ed Spencer (http://edspencer.net) 4 | * Class providing a common way of downloading data in .xls or .csv format 5 | */ 6 | Ext.ux.Exporter = function() { 7 | return { 8 | /** 9 | * Exports a grid, using the .xls formatter by default 10 | * @param {Ext.grid.GridPanel} grid The grid to export from 11 | * @param {Object} config Optional config settings for the formatter 12 | */ 13 | exportGrid: function(grid, formatter, config) { 14 | config = config || {}; 15 | formatter = formatter || new Ext.ux.Exporter.ExcelFormatter(); 16 | 17 | Ext.applyIf(config, { 18 | title : grid.title, 19 | columns: grid.getColumnModel().config 20 | }); 21 | 22 | return Base64.encode(formatter.format(grid.store, config)); 23 | }, 24 | 25 | exportStore: function(store, formatter, config) { 26 | config = config || {}; 27 | formatter = formatter || new Ext.ux.Exporter.ExcelFormatter(); 28 | 29 | Ext.applyIf(config, { 30 | columns: config.store.fields.items 31 | }); 32 | 33 | return Base64.encode(formatter.format(store, config)); 34 | }, 35 | 36 | exportTree: function(tree, formatter, config) { 37 | config = config || {}; 38 | formatter = formatter || new Ext.ux.Exporter.ExcelFormatter(); 39 | 40 | var store = tree.store || config.store; 41 | 42 | Ext.applyIf(config, { 43 | title: tree.title 44 | }); 45 | 46 | return Base64.encode(formatter.format(store, config)); 47 | } 48 | }; 49 | }(); -------------------------------------------------------------------------------- /Formatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Ext.ux.Exporter.Formatter 3 | * @author Ed Spencer (http://edspencer.net) 4 | * @cfg {Ext.data.Store} store The store to export 5 | */ 6 | Ext.ux.Exporter.Formatter = function(config) { 7 | config = config || {}; 8 | 9 | Ext.applyIf(config, { 10 | 11 | }); 12 | }; 13 | 14 | Ext.ux.Exporter.Formatter.prototype = { 15 | /** 16 | * Performs the actual formatting. This must be overridden by a subclass 17 | */ 18 | format: Ext.emptyFn 19 | }; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ed Spencer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Ext.ux.Exporter 2 | 3 | h2. About 4 | 5 | p. Exporter is a generic export class which takes any Ext.data.Store-based component (e.g. grids and similar) and exports the data in any format. 6 | 7 | p. Exporter works completely client-side. It uses a Formatter class to generate a document (.xls, .csv etc) and then redirects the user's browser to a data url so that they can view or download it. 8 | 9 | h2. Installation 10 | 11 | p. Download the latest version in any of the following ways: 12 | 13 | * git clone git://github.com/edspencer/Ext.ux.Exporter.git 14 | * Download from http://github.com/edspencer/Ext.ux.Exporter/zipball/master 15 | * Copy/paste Exporter-all.js from http://github.com/edspencer/Ext.ux.Exporter/blob/master/Exporter-all.js 16 | 17 | p. To install Ext.ux.Exporter, just include Exporter-all.js somewhere on your page: 18 | 19 | bc. 20 | 21 | h2. Usage 22 | 23 | p. The most common use case for the Exporter is exporting a grid to Excel, which is as simple as doing the following: 24 | 25 | bc.. var grid = new Ext.grid.GridPanel({ 26 | store: someStore, 27 | tbar : [ 28 | { 29 | xtype: 'exportbutton', 30 | store: someStore 31 | } 32 | ], 33 | //your normal grid config goes here 34 | }); 35 | 36 | p. The provided 'exportbutton' is just a specialised Ext.Button subclass which redirects the user to the generated data url. If you want to provide your own implementation here, just check out the Button.js code (it's ridiculously simple). 37 | 38 | h2. Example 39 | 40 | Download or clone the code (see Installation section for details) and then drag the example/index.html file into any browser to see an example of the Exporter at work. -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | # This file defines the order in which the plugin files must be loaded. 2 | # It is used by the Ext MVC's ruby plugin builder, which which can be invoked from your application's base directory like this: 3 | 4 | # ruby script/plugin build Exporter 5 | 6 | # This will generate a file named Exporter-all.js in this directory, which is just a concatenation of all the files you list here 7 | # NOTE: This is only used by the plugin builder, not the app itself. You must always ensure that your plugin is compiled to 8 | # Exporter-all.js by some other means if you do not use the plugin builder, as it is that file that will be loaded by your app 9 | 10 | Base64.js 11 | 12 | Exporter.js 13 | Button.js 14 | 15 | Formatter.js 16 | 17 | ExcelFormatter/ExcelFormatter.js 18 | ExcelFormatter/Workbook.js 19 | ExcelFormatter/Worksheet.js 20 | ExcelFormatter/Cell.js 21 | ExcelFormatter/Style.js -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Ext.ux.Exporter Example 9 | 10 | 11 | 12 |
13 |
14 |
15 | Loading... 16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 112 | 113 | -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | #loading-mask { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 100%; 6 | height: 100%; 7 | z-index: 21000; /* Normal loading masks are 20001 */ 8 | background-color: white; 9 | } 10 | 11 | #loading { 12 | position: absolute; 13 | left: 50%; 14 | top: 50%; 15 | padding: 2px; 16 | z-index: 21001; 17 | height: auto; 18 | margin: -35px 0 0 -30px; 19 | } 20 | 21 | #loading .loading-indicator { 22 | background: url(http://extjs.com/deploy/dev/docs/resources/extanim32.gif) no-repeat; 23 | color: #555; 24 | font: bold 13px tahoma,arial,helvetica; 25 | padding: 8px 42px; 26 | margin: 0; 27 | text-align: center; 28 | height: auto; 29 | } --------------------------------------------------------------------------------