You are hereBlogs / rahul's blog / ExtJs - Printing GridPanels with GroupingView and GroupSummary plugin

ExtJs - Printing GridPanels with GroupingView and GroupSummary plugin


rahul's picture

By rahul - Posted on 08 October 2010

I was thinking of writing this blog entry for sometime now. A couple of months earlier, I needed to enable client-side printing of ExtJs GridPanels. And just as I was to begin writing code for the same, I came across the wonderful Ext.ux.Printer class from Ed Spencer, that provided the necessary framework for printing any ExtJs component, and out-of-the-box functionality for printing GridPanels and TreePanels.

And immediately, I was able to use this class (and associated Renderers) to provide printing support for the GridPanels, until I was asked to enable printing of GridPanels with GroupingViews and optionally configured with a GroupSummary plugin, in a way that the grouped data came across to the printed content.

The task required more improvisation and efforts than I thought it would. The Printer class is based upon the use of ExtJs XTemplates to convert and render data as html. I had problems getting the proper XTemplate configuration to print the grouped data, until I found a way to use nested XTemplates to pass through a series of XTemplate.apply calls to generate the html for the grouped data (and in the end I was happy that I chose to stick with the Printer class framework instead of moving to a custom looping mechanism which I gave a serious thought when getting stuck at various points).

So, here's the code for the Renderer to print grouped GridPanels optionally configured with GroupSummary plugin:

 

/**
* @class GetIt.GridPrinter
* @author Ed Spencer (edward@domine.co.uk)
* Grouped Grid Panel printing support by:
*   Rahul singla (http://www.rahulsingla.com)
* Class providing a common way of printing Ext.Components. Ext.ux.Printer.print delegates the printing to a specialised
* renderer class (each of which subclasses Ext.ux.Printer.BaseRenderer), based on the xtype of the component.
* Each renderer is registered with an xtype, and is used if the component to print has that xtype.
* 
* See the files in the renderers directory to customise or to provide your own renderers.
* 
* Usage example:
* 
* var grid = new Ext.grid.GridPanel({
*   colModel: //some column model,
*   store   : //some store
* });
* 
* Ext.ux.Printer.print(grid);
* 
*/
Ext.ux.Printer = function() {

    return {
        /**
        * @property renderers
        * @type Object
        * An object in the form {xtype: RendererClass} which is manages the renderers registered by xtype
        */
        renderers: {},

        /**
        * Registers a renderer function to handle components of a given xtype
        * @param {String} xtype The component xtype the renderer will handle
        * @param {Function} renderer The renderer to invoke for components of this xtype
        */
        registerRenderer: function(xtype, renderer) {
            this.renderers[xtype] = new (renderer)();
        },

        /**
        * Returns the registered renderer for a given xtype
        * @param {String} xtype The component xtype to find a renderer for
        * @return {Object/undefined} The renderer instance for this xtype, or null if not found
        */
        getRenderer: function(xtype) {
            return this.renderers[xtype];
        },

        /**
        * Prints the passed grid. Reflects on the grid's column model to build a table, and fills it using the store
        * @param {Ext.Component} component The component to print
        */
        print: function(component) {
            var xtypes = component.getXTypes().split('/');

            //iterate backwards over the xtypes of this component, dispatching to the most specific renderer
            for (var i = xtypes.length - 1; i >= 0; i--) {
                var xtype = xtypes[i],
            renderer = this.getRenderer(xtype);

                if (renderer != undefined) {
                    renderer.print(component);
                    break;
                }
            }
        }
    };
} ();

/**
* Override how getXTypes works so that it doesn't require that every single class has
* an xtype registered for it.
*/
Ext.override(Ext.Component, {
    getXTypes: function() {
        var tc = this.constructor;
        if (!tc.xtypes) {
            var c = [], sc = this;
            while (sc) { //was: while(sc && sc.constructor.xtype) {
                var xtype = sc.constructor.xtype;
                if (xtype != undefined) c.unshift(xtype);

                sc = sc.constructor.superclass;
            }
            tc.xtypeChain = c;
            tc.xtypes = c.join('/');
        }
        return tc.xtypes;
    }
});

/**
* @class Ext.ux.Printer.BaseRenderer
* @extends Object
* @author Ed Spencer
* Abstract base renderer class. Don't use this directly, use a subclass instead
*/
Ext.ux.Printer.BaseRenderer = Ext.extend(Object, {
    /**
    * Prints the component
    * @param {Ext.Component} component The component to print
    */
    print: function(component) {
        var name = component && component.getXType
             ? String.format("print_{0}_{1}", component.getXType(), component.id.replace(/-/g, '_'))
             : "print";

        var win = window.open('', name);

        win.document.write(this.generateHTML(component));
        
        //Print after a timeout to be cross-browser compatible.
        setTimeout(function() {
            win.document.close();

            win.print();
            win.close();
        }, 1000);
    },

    /**
    * Generates the HTML Markup which wraps whatever this.generateBody produces
    * @param {Ext.Component} component The component to generate HTML for
    * @return {String} An HTML fragment to be placed inside the print window
    */
    generateHTML: function(component) {
        return new Ext.XTemplate(
      '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
      '<html>',
        '<head>',
          '<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />',
          '<link href="' + this.stylesheetPath + '" rel="stylesheet" type="text/css" media="screen,print" />',
          '<title>' + this.getTitle(component) + '</title>',
        '</head>',
        '<body>',
          this.generateBody(component),
        '</body>',
      '</html>'
    ).apply(this.prepareData(component));
    },

    /**
    * Returns the HTML that will be placed into the print window. This should produce HTML to go inside the
    * <body> element only, as <head> is generated in the print function
    * @param {Ext.Component} component The component to render
    * @return {String} The HTML fragment to place inside the print window's <body> element
    */
    generateBody: Ext.emptyFn,

    /**
    * Prepares data suitable for use in an XTemplate from the component 
    * @param {Ext.Component} component The component to acquire data from
    * @return {Array} An empty array (override this to prepare your own data)
    */
    prepareData: function(component) {
        return component;
    },

    /**
    * Returns the title to give to the print window
    * @param {Ext.Component} component The component to be printed
    * @return {String} The window title
    */
    getTitle: function(component) {
        return typeof component.getTitle == 'function' ? component.getTitle() : (component.title || "Printing");
    },

    /**
    * @property stylesheetPath
    * @type String
    * The path at which the print stylesheet can be found (defaults to 'stylesheets/print.css')
    */
    stylesheetPath: 'stylesheets/print.css'
});

/**
* @class Ext.ux.Printer.ColumnTreeRenderer
* @extends Ext.ux.Printer.BaseRenderer
* @author Ed Spencer
* Helper class to easily print the contents of a column tree
*/
Ext.ux.Printer.ColumnTreeRenderer = Ext.extend(Ext.ux.Printer.BaseRenderer, {

    /**
    * Generates the body HTML for the tree
    * @param {Ext.tree.ColumnTree} tree The tree to print
    */
    generateBody: function(tree) {
        var columns = this.getColumns(tree);

        //use the headerTpl and bodyTpl XTemplates to create the main XTemplate below
        var headings = this.headerTpl.apply(columns);
        var body = this.bodyTpl.apply(columns);

        return String.format('<table>{0}<tpl for=".">{1}</tpl></table>', headings, body);
    },

    /**
    * Returns the array of columns from a tree
    * @param {Ext.tree.ColumnTree} tree The tree to get columns from
    * @return {Array} The array of tree columns
    */
    getColumns: function(tree) {
        return tree.columns;
    },

    /**
    * Descends down the tree from the root, creating an array of data suitable for use in an XTemplate
    * @param {Ext.tree.ColumnTree} tree The column tree
    * @return {Array} Data suitable for use in the body XTemplate
    */
    prepareData: function(tree) {
        var root = tree.root,
        data = [],
        cols = this.getColumns(tree),
        padding = this.indentPadding;

        var f = function(node) {
            if (node.hidden === true || node.isHiddenRoot() === true) return;

            var row = Ext.apply({ depth: node.getDepth() * padding }, node.attributes);

            Ext.iterate(row, function(key, value) {
                Ext.each(cols, function(column) {
                    if (column.dataIndex == key) {
                        row[key] = column.renderer ? column.renderer(value) : value;
                    }
                }, this);
            });

            //the property used in the first column is renamed to 'text' in node.attributes, so reassign it here
            row[this.getColumns(tree)[0].dataIndex] = node.attributes.text;

            data.push(row);
        };

        root.cascade(f, this);

        return data;
    },

    /**
    * @property indentPadding
    * @type Number
    * Number of pixels to indent node by. This is multiplied by the node depth, so a node with node.getDepth() == 3 will
    * be padded by 45 (or 3x your custom indentPadding)
    */
    indentPadding: 15,

    /**
    * @property headerTpl
    * @type Ext.XTemplate
    * The XTemplate used to create the headings row. By default this just uses <th> elements, override to provide your own
    */
    headerTpl: new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<th width="{width}">{header}</th>',
      '</tpl>',
    '</tr>'
  ),

    /**
    * @property bodyTpl
    * @type Ext.XTemplate
    * The XTemplate used to create each row. This is used inside the 'print' function to build another XTemplate, to which the data
    * are then applied (see the escaped dataIndex attribute here - this ends up as "{dataIndex}")
    */
    bodyTpl: new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<td style="padding-left: {[xindex == 1 ? "\\{depth\\}" : "0"]}px">\{{dataIndex}\}</td>',
      '</tpl>',
    '</tr>'
  )
});

Ext.ux.Printer.registerRenderer('columntree', Ext.ux.Printer.ColumnTreeRenderer);

/**
* @class Ext.ux.Printer.GridPanelRenderer
* @extends Ext.ux.Printer.BaseRenderer
* @author Ed Spencer
* Helper class to easily print the contents of a grid. Will open a new window with a table where the first row
* contains the headings from your column model, and with a row for each item in your grid's store. When formatted
* with appropriate CSS it should look very similar to a default grid. If renderers are specified in your column
* model, they will be used in creating the table. Override headerTpl and bodyTpl to change how the markup is generated
*/
Ext.ux.Printer.GridPanelRenderer = Ext.extend(Ext.ux.Printer.BaseRenderer, {

    /**
    * Generates the body HTML for the grid
    * @param {Ext.grid.GridPanel} grid The grid to print
    */
    generateBody: function(grid) {
        var columns = this.getColumns(grid);

        //use the headerTpl and bodyTpl XTemplates to create the main XTemplate below
        var headings = this.headerTpl.apply(columns);
        var body = this.bodyTpl.apply(columns);

        return String.format('<table>{0}<tpl for=".">{1}</tpl></table>', headings, body);
    },

    /**
    * Prepares data from the grid for use in the XTemplate
    * @param {Ext.grid.GridPanel} grid The grid panel
    * @return {Array} Data suitable for use in the XTemplate
    */
    prepareData: function(grid) {
        //We generate an XTemplate here by using 2 intermediary XTemplates - one to create the header,
        //the other to create the body (see the escaped {} below)
        var columns = this.getColumns(grid);

        //build a useable array of store data for the XTemplate
        var data = [];
        grid.store.data.each(function(item) {
            var convertedData = {};

            //apply renderers from column model
            Ext.iterate(item.data, function(key, value) {
                Ext.each(columns, function(column) {
                    if (column.dataIndex == key) {
                        convertedData[key] = column.renderer ? column.renderer(value, null, item) : value;
                        return false;
                    }
                }, this);
            });

            data.push(convertedData);
        });

        return data;
    },

    /**
    * Returns the array of columns from a grid
    * @param {Ext.grid.GridPanel} grid The grid to get columns from
    * @return {Array} The array of grid columns
    */
    getColumns: function(grid) {
        var columns = [];

        Ext.each(grid.getColumnModel().config, function(col) {
            if (col.hidden != true) columns.push(col);
        }, this);

        return columns;
    },

    /**
    * @property headerTpl
    * @type Ext.XTemplate
    * The XTemplate used to create the headings row. By default this just uses <th> elements, override to provide your own
    */
    headerTpl: new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<th>{header}</th>',
      '</tpl>',
    '</tr>'
  ),

    /**
    * @property bodyTpl
    * @type Ext.XTemplate
    * The XTemplate used to create each row. This is used inside the 'print' function to build another XTemplate, to which the data
    * are then applied (see the escaped dataIndex attribute here - this ends up as "{dataIndex}")
    */
    bodyTpl: new Ext.XTemplate(
    '<tr>',
      '<tpl for=".">',
        '<td>\{{dataIndex}\}</td>',
      '</tpl>',
    '</tr>'
  )
});

Ext.ux.Printer.registerRenderer('grid', Ext.ux.Printer.GridPanelRenderer);

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Customizations.
//http://github.com/edspencer/Ext.ux.Printer/

Ext.ux.Printer.BaseRenderer.prototype.stylesheetPath = '/sites/default/files/content/blog/extjs-printer/Ext.ux.Printer.css';

Ext.override(Ext.ux.Printer.GridPanelRenderer, {
    bodyTpl: new Ext.XTemplate(
        '<tr>',
          '<tpl for=".">',
            '<tpl if="dataIndex"><td>\{{dataIndex}\}</td></tpl>',
            '<tpl if="!dataIndex"><td>&nbsp;</td></tpl>',
          '</tpl>',
        '</tr>'
  )
});

Ext.ux.Printer.GroupedGridPanelRenderer = Ext.extend(Ext.ux.Printer.GridPanelRenderer, {

    generateBody: function(grid) {
        var view = grid.view;

        if (view instanceof Ext.grid.GroupingView && view.canGroup()) {
            this.overrideForGrouping(grid);

            var columns = this.getColumns(grid);

            this.bodyTpl.numColumns = this.getColumns(grid).length;
            var cells = this.bodyTpl.cellTpl.apply(columns);
            this.bodyTpl.innerTemplate = String.format('<tpl for="groupRecords"><tr>{0}</tr></tpl>', cells);

            if (grid.hasPlugin(Ext.grid.GroupSummary)) {
                var summaryCells = this.bodyTpl.groupSummaryCellTemplate.apply(columns);
                this.bodyTpl.groupSummaryTemplate = String.format('<table class=\'group-summary\'><tpl for="summaries"><tr>{0}</tr></tpl></table>', summaryCells);
            } else {
                this.bodyTpl.groupSummaryTemplate = '';
            }

            var headings = this.headerTpl.apply(columns);
            var body = this.bodyTpl.apply({});

            return (String.format('<table class=\'table-parent\'>{0}<tpl for=".">{1}</tpl>{2}</table>', headings, body, this.generateGridTotals(grid)));

        } else {
            //No grouping, use base class logic.
            return (Ext.ux.Printer.GroupedGridPanelRenderer.superclass.generateBody.call(this, grid));
        }
    },

    gridTotalsTpl: new Ext.XTemplate(
                '<tr>',
                  '<td colspan=\'{[values.length]}\'>',
                    '<table class=\'grid-totals\'>',
                        '<tr>',
                          '<tpl for=".">',
                            '<td style=\'{style}\'>{total}</td>',
                          '</tpl>',
                        '</tr>',
                    '</table>',
                  '</td>',
                '</tr>'
              ),

    generateGridTotals: function(grid) {
        var plugin = grid.getPluginByType(Ext.ux.GridTotals);
        if (Ext.isEmpty(plugin)) {
            return ('');
        } else {
            var totals = plugin.getRenderedTotals();
            var columns = this.getColumns(grid);

            var rendered = new Array(columns.length);
            for (var i = 0; i < columns.length; i++) {
                var col = columns[i];
                rendered[i] = { total: totals[col.actualIndex], style: columns[i].style };
            }
            return (this.gridTotalsTpl.apply(rendered));
        }
    },

    overrideForGrouping: function(grid) {
        var me = this;

        Ext.apply(this, {
            columns: null,

            headerTpl: new Ext.XTemplate(
                '<tr>',
                  '<tpl for=".">',
                    '<th style=\'{style}\'>{header}</th>',
                  '</tpl>',
                '</tr>'
              ),

            bodyTpl: new Ext.XTemplate(
                '<tr>',
                  '<td colspan=\'{[this.getColumnCount()]}\'>',
                    '<div class=\'group-header\'>{[this.getGroupTextTemplate()]}</div>',
                    '<table class=\'group-body\'>',
                      '{[this.getInnerTemplate()]}',
                    '</table>',
                    '{[this.getGroupSummaryTemplate()]}',
                  '</td>',
                '</tr>',

                {
                    numColumns: 0,
                    cellTpl: new Ext.XTemplate('<tpl for="."><td style=\'{style}\'>\{{dataIndex}\}</td></tpl>'),
                    groupSummaryCellTemplate: new Ext.XTemplate('<tpl for="."><td style=\'{style}\'>\{{dataIndex}\}</td></tpl>'),
                    innerTemplate: null,
                    groupSummaryTemplate: null,

                    getColumnCount: function() {
                        return (this.numColumns);
                    },

                    getGroupTextTemplate: function() {
                        return ('{groupText}');
                    },

                    getInnerTemplate: function() {
                        return (this.innerTemplate);
                    },

                    getGroupSummaryTemplate: function() {
                        return (this.groupSummaryTemplate);
                    }
                }),

            prepareData: function(grid) {
                var view = grid.view;

                var columns = this.getColumns(grid);
                var groups = this.getGroupedData(grid);
                var groupTextTpl = new Ext.XTemplate(view.groupTextTpl);

                Ext.each(groups, function(group) {
                    var groupRecords = [];
                    group.groupText = groupTextTpl.apply(group);

                    Ext.each(group.rs, function(item) {
                        var convertedData = {};

                        //apply renderers from column model
                        Ext.iterate(item.data, function(key, value) {
                            Ext.each(columns, function(column) {
                                if (column.dataIndex == key) {
                                    convertedData[key] = column.renderer ? column.renderer(value, null, item) : value;
                                    return false;
                                }
                            }, this);
                        });

                        groupRecords.push(convertedData);
                    });

                    group.groupRecords = groupRecords;

                    var summaryRenderer = grid.getPluginByType(Ext.grid.GroupSummary);
                    if (!Ext.isEmpty(summaryRenderer)) {
                        //Summary calculation for column in each group.
                        var cs = view.getColumnData();
                        group.summaries = {};
                        var data = summaryRenderer.calculate(group.rs, cs);

                        Ext.each(columns, function(col) {
                            var rendered = '';
                            if (col.summaryType || col.summaryRenderer) {
                                rendered = (col.summaryRenderer || col.renderer)(data[col.name], {}, { data: data }, 0, col.actualIndex, grid.store);
                            }
                            if (rendered == undefined || rendered === "") rendered = "&#160;";

                            group.summaries[col.dataIndex] = rendered;
                        });
                    }

                    delete group.rs;
                });

                return (groups);
            },

            getColumns: function(grid) {
                if (this.columns == null) {
                    var columns = [];
                    var columnData = grid.view.getColumnData();

                    Ext.each(grid.getColumnModel().config, function(col, index) {
                        if (col.hidden != true) {
                            columns.push(
                                {
                                    dataIndex: col.dataIndex,
                                    header: col.header,
                                    renderer: col.renderer,
                                    summaryType: col.summaryType,
                                    summaryRenderer: col.summaryRenderer,
                                    style: columnData[index].style,
                                    name: columnData[index].name,
                                    actualIndex: index
                                });
                        }
                    }, this);

                    this.columns = columns;
                }
                return this.columns;
            },

            getGroupedData: function(grid) {
                var rs = grid.store.getRange();
                var ds = grid.store;
                var view = grid.view;

                var groupField = ds.getGroupState(),
                    colIndex = grid.colModel.findColumnIndex(groupField),
                    cfg = grid.colModel.config[colIndex],
                    groupRenderer = cfg.groupRenderer || cfg.renderer,
                    prefix = view.showGroupName ? (cfg.groupName || cfg.header) + ': ' : '',
                    groups = [],
                    curGroup, i, len, gid;

                for (i = 0, len = rs.length; i < len; i++) {
                    var rowIndex = i,
                        r = rs[i],
                        gvalue = r.data[groupField];

                    g = view.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
                    if (!curGroup || curGroup.group != g) {
                        gid = view.constructId(gvalue, groupField, colIndex);
                        curGroup = {
                            group: g,
                            gvalue: gvalue,
                            text: prefix + g,
                            groupId: gid,
                            startRow: rowIndex,
                            rs: [r]
                        };
                        groups.push(curGroup);
                    } else {
                        curGroup.rs.push(r);
                    }
                    r._groupId = gid;
                }

                return (groups);
            }
        });
    }
});
Ext.ux.Printer.registerRenderer('grid', Ext.ux.Printer.GroupedGridPanelRenderer);

 

 

And here goes a quick discussion of the code (you would need to have a look at the Printer class, if you haven't already to understand what's being said below):

  1. The code basically adds a new Renderer (called GroupedGridPanelRenderer) to print GridPanel data and registering itself with the Printer class, replacing the default GridPanelRenderer.
  2. At the beginning of the generateBody() method call (invoked by the Printer class), the new renderer (GroupedGridPanelRenderer) checks to see if the GridPanel has actually been configured with a GroupingView and whether the grouping is enabled. If not, the new Renderer is happy to pass the method call to the default GridPanelRenderer to generate the print content.
  3. However, if the Grouping is enabled, the GroupedGridPanelRenderer starts with generating column config objects, one for each column with properties like the header text, data index, renderer and summary renderer to be used later for generating the print body.
    Hidden columns in the GridPanel are not printed. 
  4. Its important to understand the various print portions generated by the renderer:
    Column Headers, Group Header, Group Content, and Group summary.
  5. And here's a sample of html generated for a group by the renderer:
    <body>
    	<table class='table-parent'>
    		<!--Column headers-->
    		<tr>
    			<th style='width: 100px;'>Create Date</th>
    			<th style='width: 100px;'>Destination</th>
    			<th style='width: 100px;'>Travel Date</th>
    			<th style='width: 100px;'>Status</th>
    			<th style='width: 100px;'>Total Amount</th>
    			<th style='width: 100px;'>Invoiced</th>
    			<th style='width: 100px;'>Balance</th>
    		</tr>
    		<tr>
    			<td colspan='7'>
    				<!--Group Header-->
    				<div class='group-header'>My Group name ()</div>
    				<!--Group data-->
    				<table class='group-body'>
    					<tr>
    						<td style='width: 100px;'>09/27/2010</td>
    						<td style='width: 100px;'></td>
    						<td style='width: 100px;'>03/26/2009</td>
    						<td style='width: 100px;'>Active</td>
    						<td style='width: 100px;'>2100</td>
    						<td style='width: 100px;'>2100</td>
    						<td style='width: 100px;'>0</td>
    					</tr>
    					<tr>
    						<td style='width: 100px;'>09/27/2010</td>
    						<td style='width: 100px;'></td>
    						<td style='width: 100px;'>01/03/2009</td>
    						<td style='width: 100px;'>Active</td>
    						<td style='width: 100px;'>2253</td>
    						<td style='width: 100px;'>2253</td>
    						<td style='width: 100px;'>0</td>
    					</tr>
    				</table>
    				<!--Group summary-->
    				<table class='group-summary'>
    					<tr>
    						<td style='width: 100px;'>15 Trip(s)</td>
    						<td style='width: 100px;'>&#160;</td>
    						<td style='width: 100px;'>&#160;</td>
    						<td style='width: 100px;'>&#160;</td>
    						<td style='width: 100px;'>75531.56</td>
    						<td style='width: 100px;'>75531.56</td>
    						<td style='width: 100px;'>0</td>
    					</tr>
    				</table>
    			</td>
    		</tr>
    	</table>
    </body>


  6. The code first generates cell templates for data of each group with a call to XTemplate.apply.
  7. Then, the code checks to see if the GridPanel has been configured with a GroupSummary plugin. If it is, one more call to XTemplate.apply generates the templates for summary cells of each group.
  8. Then another XTemplate.apply call generates the template for the column headers.
  9. Finally one last XTemplate.apply call aggregates all the templates generated in 6, 7 and 8 and returns them as another XTemplate to the Printer class which applies this template on the grouped data (generated through a prepareData() method call) and renders the html generated in a new window ready to be printed.

Below is a simple demonstration of the Printer class for printing grouped GridPanels:

Here's a screenshot demonstrating the printed content:

ExtJs Grouped GridPanel Print

 

If you have been following the discussion above closely, you would have noticed I have been referring to the technique of returning XTemplates from a call to XTemplate.apply itself, which are used later in more XTemplate.apply invocations (I call this pass-through nested XTemplates and would be discussing the technique in another blog post separately).

Well, if nothing of the above interests you, then surely what will be on interest is to see how to use the above code to actually print the content. Well, that is as simple as including the attached js and css files on your page, and invoking the following for a GridPanel:

 

Ext.ux.Printer.print(myComponent);

Yes, that is it.

The Printer class would automatically take care to delegate the print responsibility to a registered renderer based on the xtype of myComponent. If it's a GridPanel, GroupedGridPanelRenderer further takes care of whether the GridPanel is grouped or not to generate appropriate printed content.

As I said above, it posed hiccups during the implementation, but once ready, I was really happy I hanged on with the Printer class, as this implementation gave me enough understanding and opened flood-gates to enable printing of virtually any ExtJs component in any desired output layout you want.

You can find the complete code for the above sample together with associated resource files attached below.

UPDATE:

  • May 27, 2011- Added support for Ext.ux.GridTotals plugin.
    Added a demonstration of the plugin to the blog post.

 

AttachmentSize
printing-grouped-gridpanels.htm8.13 KB
Ext.ux.Printer.js22.98 KB
Ext.ux.Printer.css827 bytes

You probably seem to be one of the most knowledgable person who has used and developed extensions and plugins for extnet/coolite. It would be all the more helpful if you can write on how to integrate this printing grid extension in a common extnet grid example. Cant seem to find those details anywhere even as a passing mention in the coolite documentation or its forums!


Thanks! Great job.

rahul's picture

Hi Victor, this printing grid extension is not part of either ExtJs or Ext.Net/Coolite. So, there is no reason why you would expect to see its information in official documentation of either of these libraries/toolkits.

And if you read the blog post carefully, all information required for "integrating this printing grid extension in a common extnet grid" is already there. Still for your reference, please find it below again:

  1. Download the 2 attached files with this blog entry and include them on the page.
  2. Then if you want to print an Ext.Net grid client-side in javascript, use the following:
    Ext.ux.Printer.print(Ext.getCmp('myGridPanel'));
  3. If you want to print a grid in server-side code (let's say in a button click event), use the following:
    <ext:Button runat="server" Text="Print grid" OnClientClick="Ext.ux.Printer.print(#{myGridPanel});" />

Please let me know if you have trouble implementing the above steps.

Thanks Rahul, I did just what you suggested, unfortunately the browser get hung up on errors like "Object doesnt support this property or method" constantly at


              if (view instanceof Ext.grid.GroupingView && view.canGroup()) {
                    this.overrideForGrouping(grid);


I am not big JS guy, but hard to figure out from these cryptic messages. Any suggestions? I tried it on the example groupingsummary implementation and still ended up erroring on that line. Any suggestions or similar experience/ thoughts on what I need to be lookin at?

AttachmentSize
Default.aspx_.txt 35.12 KB
rahul's picture

Hi Victor, I have developed and tested this code only on Ext.Net 1.0. Although I strongly believe that it should work with Coolite 0.8.x, but some minor changes might be required. Coolite 0.8 comes with a very old ExtJs version, so maybe some incompatibility with the ExtJs version.

I am really short on time for testing (and would seriously recommend upgrading to Ext.Net 1.0), but if you want to stick around with your Coolite version, then try removing this part from the above code and see if it works:

&& view.canGroup()

Hi rahul

You have good article,

I have tried your ext, work moderate in firefox. report shown but NOT the group total!. but NOT work at all in Chrome and Safari. 

Sorry for this, I'm matured in programming (client/server) but javasript newbie. should I want to investigate the problem. where do i start? 

anyway thanks for the Great Job!!.

rahul's picture

Hi norizam, I am using this plugin for large enterprise apps successfully and it has been tested on all major browsers. First thing to check would be your ExtJs version (plugin was developed on ExtJs 3.3.0, but should work on all 3.x versions).

Regarding investigating the problem, if FireFox's your primary development browser, then use Firebug, and see what (if any) javascript errors are raised. If Chrome's the dev browser, use Chrome's Developer Tools for investigating Script issues. I would not recommend IE for javascript development, but you have Developer Tools available for IE too (integrated in IE 8 and downloaded separately for previous versions).

Thank again for reply...

I'll try to download both tools. hopefully can solve, I'll be back to you for any finding.

Anyway thanks again for the great ext.

rahul's picture

Developer Tools is a part of Chrome, and need not be downloaded separately.

Sorry for taking your time...

Try simulating ... I found the Chrome error ...

Uncaught TypeError: Object [object Object] has no method 'getPluginByType' (GridPanel.js line 310)

It that because of GroupSummary version?. The firefox produce some result but still absent of the group totals.

Cant solve until now.

Anyway thank you

rahul's picture

Hi Norizam, well getPluginByType method was missing from the javascript file. I have updated the file and included that function. Can you please download the file again (you might need to clear your browser cache to ensure you use the latest file) and try again??

Thank You for kind reply....

But I'm facing same problem., unfortunately i run out of time to keep trying, may be need to find other ways...

but, anyway thanks for your good effort.

 

rahul's picture

You might just want to confirm that there's a getPluginByType method defined in the script you downloaded. If not, you have a stale copy of the script.

I'm wondering where i can find the latest copy ...

I also have the grid.getPluginByType error ans also grid.hasPlugin error (function does not exist)

Hope you can help
Paul

rahul's picture

Hi Paul, sorry for the delay in response. I have updated the Plugin code to also support the Ext.ux.GridTotals plugin, and I have also added a sample to the blog post demonstrating using the Printer class with GroupingSummary and Ext.ux.GridTotals plugins applied to the GridPanel.

i have one more question: i want to use the plugin for normal as well as groupinggrids. Now when i use a normal grid i get the error 'ds.getGroupState is not a function' ...

with regards,
Paul

I made a workaround by changing the code in Ext.ux.Printer.print() a little bit:

if(component.store instanceof Ext.data.GroupingStore ) {
      renderer = new Ext.ux.Printer.GroupedGridPanelRenderer;
} else {
      renderer = new Ext.ux.Printer.GridPanelRenderer;
            }
//renderer = this.getRenderer(xtype);

now it works fine for me

Thanks for the great plugin

I'm trying your code and I'm getting the following error at line 591:

Error: invalid 'instanceof' operand constructorFn
Source File: http://127.0.0.1/ext321/ux/Printer/Printer.js
Line: 591

When I look at the constructorFn it is undefined. I'm using extjs 3.2.1

Thank you for your help

Here is the error code I get in FF. I get teh same erro in IE8

Error: invalid instanceof operand constructorFn
Line: 591
constructorFn is undefined when I view in Firebug.. I'm running extjs 321

Thanks for the effort on this

rahul's picture

Hi Richard, can you please check and confirm if you have Ext.grid.GroupSummary plugin on the page on which you are trying to use the Printer?? If not, you might need to add isDefined checks for the same.

I don't as I'm not using the summary. I'm just using a grouping grid. I'll add it and see if it works...

Hi, When i try the priter class as the instractions i have got the following error

invalid 'instanceof' operand constructorFn

 

help please how to solve it
Thanks in advance
rahul's picture

Hi emma, can you please see if you are including Ext.grid.GroupingView on the page??

Hi, Have you tried using this with adobe air?

Also, this works only for extjs 3.3 and above .. right?

rahul's picture

Hi Shorabh, No I have not used it with Adobe AIR. I think I have used it with both ExtJs 3.2.x and ExtJs 3.3.x (the demo above is with ExtJs 3.3.0) but I believe it should work with ExtJs 3.x in general.

Hi, I tried to print a graph of ExtJS in the browser Mozilla
is that it is browser issue, which can print Flash
but my question is, Can the Library Ext.ux.Printer?

Thank you very much!

rahul's picture

Hi Lilux, sorry for the delayed reply. I am afraid I could not understand your question. Did you mean you want to print a Graph generated by ExtJs 4.0??

Ext.ux.Printer does not currently have a renderer for printing graphs but I think one can be added which would need to manipulate dom smartly to print Graphs.

Hi Rahul, plugin works great!!.

However i need to display hidden column as well during the printing.

I have commented the code which does it, however it still does display hidden column.

Its because of this <td style='width:150px;display:none;'>

Please could you let me know how to display hidden columns during printing.

Thanks & Regards,
Ajay

rahul's picture

Hi Ajay, I do not have time to look into the code currently, but from what I recall, the column styles for printing are picked from the column styles for the column in the grid. There are couple of things you can do about this:

  1. Before calling Ext.ux.Printer.print, unhide all the columns in the grid and hide them back after print dialog is dismissed (you would need to maintain in a variable what all columns were hidden before you invoke print).
  2. Add this to Ext.ux.Printer.css:
    table td {
      display: inline !important;
    }

Awesome! Has anyone upgraded or attempted to upgrade this to work on ExtJS4?

rahul's picture

Not me atleast as yet...

Post new comment

The content of this field is kept private and will not be shown publicly.
Type the characters you see in this picture. (verify using audio)
Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive.

Recent comments