Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Export to PDF Canvas containing custom HTML

    Hi,

    I am investigating the export PDF feature in SmartClient using Canvas exportContent() . We are still on version 8.2 (v8.2p_2013-03-07/Pro Development Only) but I have also tried version 8.3 (v8.3p_2013-02-14/Pro Development Only).

    What we essentially want to be able to do is export a PDF with a mixture of SmartClient components and our custom HTML components such as our header and footer with any custom CSS styles for the header and footer. I have been able to (sort of) achieve this in a simple example by doing the following:

    1. Defining a new class that extends Canvas and overriding getInnerHTML() to return the page header HTML and the HTML for a Canvas (contentLayout) that contains a TreeGrid and a ListGrid by calling getPrintHTML() on the Canvas:

    Code:
    isc.defineClass("MyPrintCanvas", "Canvas").addProperties({
    	getInnerHTML: function() {
    		return "<div id='pageHeader'>My header</div>" + 
    		contentLayout.getPrintHTML();
    	}
    });
    2. Creating an instance of MyPrintCanvas and setting autoDraw to false so that it isn't drawn on the screen since it is added just so I can export to PDF.

    Code:
    isc.MyPrintCanvas.create({
    	ID: "printCanvas",
    	autoDraw: false
    });
    3. Calling RPCManager.exportContent on the printCanvas to export to PDF:
    Code:
    isc.Button.create({
    	ID: "pdfPrint",
    	title: "PDF Print",
        click:function () {
        	var settings = {
        		skinName: "Enterprise", 
    			pdfName: "export"// without .pdf
      	    };
    	isc.RPCManager.exportContent(printCanvas, settings);
        }
    });
    Questions:

    1. Am I on the right track with this approach for exporting a mixture of HTML and SmartClient components to PDF?

    2. How do I make MyPrintCanvas recognize custom CSS? I have CSS on the page for the pageHeader element, for example. However, the PDF doesn't recognize this CSS and just displays the text 'My Header'.
    Code:
    	<style>
    		#pageHeader {
        		height: 20px;
        		width: 100%;
     			border-style:solid;
    			border-width:5px;
    		}
    	</style>
    3. In this example, it doesn't seem to make much of a difference, but when would it be better to use PrintCanvas versus Canvas?

    4. Is there a better way to get a reference to the html content instead of copying the html string in getInnerHTML() manually? The HTML content could be more complicated than a simple div.

    Thanks,
    Mehak

    #2
    1. yes, this is generally fine

    2. you can define an extraStyleSheet used during export - see the docs for exportContent() and also the server-side PDFExport docs

    3. PrintCanvas is really for nuances of browser printing. Nothing in it applies to export to PDF

    4. getInnerHTML(), or better, getPrintHTML(), is pretty much the way to get the content. If you have a specific drawback in mind, explain what issue you're seeing.

    Comment


      #3
      Thanks for your prompt answers! I changed to using getPrintHTML() instead of getInnerHTML() as recommended.

      Regarding PDFExport, I read the docs and see how we can add a custom operation to generate a PDF on a custom DataSource. However, since I am calling isc.RPCManager.exportContent() in JS for a whole Canvas, I am not sure how I would do this without a DataSource and invoke a custom generate PDF method through a click of a button on my page.

      I am assuming I would just have to create an AJAX request to a custom Java method in the backend that uses PDFExport instead of using isc.RPCManager.exportContent(printCanvas, settings)?

      Comment


        #4
        You don't seem to be doing anything that would cause you to write custom Java code on the server. You just need to put an extraStyleSheet in the expected location and add a setting to server.properties.

        If you did need to write some custom Java on the server, any way of getting the HTML String over to the server is fine; the typical ways are a custom operation on a DataSource, or an RPCRequest using an .app.xml file (aka RPC DMI).

        Comment


          #5
          Ah, right. I got the header styles to show up by just adding extraStyleSheet pointing to my custom CSS location in server.properties. Looks like this is only available since 8.3 and not 8.2.

          I was just curious...are there any other DSRequest Properties that could potentially be useful to pass as settings to exportContent() other than skinName and pdfName?

          Comment


            #6
            Yes, just look through the dsRequest docs and look at the properties that start with "export". Several, such as exportFileName, apply to exportContent.

            Comment


              #7
              Thanks! Is there a list somewhere specifying which properties would work with exportContent to export to PDF? I tried a couple of these properties but they didn't seem to work for me. The ones I tried are exportDisplay (set to "window") and exportHeader and exportFooter. Maybe these don't work with exportContent.

              Comment


                #8
                Exporting to PDF after paging a ListGrid returns null - potential bug?

                I am using the approach discussed below in my original post to export to PDF and I am noticing a potential bug. I created a custom canvas with autoDraw set to false that calls getPrintHTML() on another Canvas on my page. The Canvas on the page has a TreeGrid and a ListGrid populated based on what is selected in the TreeGrid. I am using the two data sources supplyCategory and supplyItem that come with the SmartClient samples.

                The simple reproducible test code is below. I have a Canvas called contentLayout that has the TreeGrid and ListGrid components. The 'PDF - PrintCanvas' button exports 'MyPrintCanvas,' the custom canvas I created just for exporting to PDF purposes where I am overriding getPrintHTML() to return some header HTML combined with the HTML for the SmartClient canvas displayed on the page. The 'PDF - Content' button just exports contentLayout instead of the custom print canvas and hence it doesn't include the HTML header.

                The problem is when I click an item in the TreeGrid such as 'Canteen and Washroom Products' to populate the ListGrid with supplyItems for that category and scroll all the way to the bottom so that multiple pages are loaded, clicking the 'PDF - PrintCanvas' button returns a PDF with null under the header. For some reason, contentLayout.getPrintHTML() returns null after paging. Also, after exporting, the ListGrid doesn't render the data in the correct columns as can be seen in the attached snapshot.

                Clicking 'PDF - Content' for exporting the contentLayout Canvas exports fine even after scrolling but exporting to PDF on the custom Print Canvas only works for the initially loaded and buffered data (75 records for the first page). Is there another way I should make export to PDF for my custom print canvas work for paged content in a list grid or is this a potential bug?

                Code:
                <%@ taglib uri="/WEB-INF/iscTaglib.xml" prefix="isomorphic" %>
                <HTML><HEAD>
                	<isomorphic:loadISC skin="Enterprise"/>
                </HEAD>
                
                <BODY>
                
                <div id="pageHeader">My header</div>
                
                <SCRIPT>
                
                <isomorphic:loadDS ID="supplyCategory"/>
                <isomorphic:loadDS ID="supplyItem"/>
                
                isc.TreeGrid.create({
                    ID: "categoryTree",
                    width: "30%",
                    showResizeBar: true,
                    dataSource: "supplyCategory",
                    nodeClick: "itemList.fetchData({category: node.categoryName})",
                    selectionType: "single",
                    autoFetchData: true
                });
                
                isc.ListGrid.create({
                    ID: "itemList",
                    dataSource: "supplyItem",
                    fields : [
                        { name:"itemName" },
                        { name:"SKU" },
                        { name:"unitCost", width:50 },
                        { name:"units", width:40 }
                    ],
                    selectionType: "single"
                });
                
                isc.Button.create({
                	ID: "pdfContent",
                	title: "PDF - Content",
                    click:function () {
                    	var settings = {
                    		skinName: "Enterprise", 
                			exportFilename: "export"
                  	    };
                        isc.RPCManager.exportContent(contentLayout, settings);
                    }
                });
                
                isc.Button.create({
                	ID: "pdfPrintCanvas",
                	title: "PDF - PrintCanvas",
                    click:function () {
                    	var settings = {
                    		skinName: "Enterprise", 
                			exportFilename: "export"
                  	    };
                		isc.RPCManager.exportContent(printCanvas, settings);
                    }
                });
                
                isc.defineClass("MyPrintCanvas", "PrintCanvas").addProperties({
                	getPrintHTML: function() {
                		return "<div id='pageHeader'>My header</div>" + 
                		contentLayout.getPrintHTML();
                	}
                });
                
                isc.VLayout.create({
                	ID: "mainLayout",
                	width: "100%",
                    height: "100%",
                    layoutMargin: 10,
                    position: "relative",
                    members: [
                    	isc.HLayout.create({
                    		ID: "buttonLayout",
                    		width: "100%",
                    		membersMargin: 5,
                    		layoutTopMargin: 10,
                    		members: [pdfContent, pdfPrintCanvas]
                    	}),
                		isc.HLayout.create({
                			ID: "contentLayout",
                			width: "100%",
                			height: "100%",
                			layoutTopMargin: 10,
                			members: [categoryTree, itemList]
                		})
                	]
                });
                
                isc.MyPrintCanvas.create({
                	ID: "printCanvas",
                	autoDraw: false
                });
                
                </SCRIPT>
                </BODY></HTML>
                Attached Files

                Comment


                  #9
                  getPrintHTML() is potentially asynchronous, that's why it supports a callback argument. You need to use this.

                  Printing in general prints only loaded data. You can use settings like dataFetchMode if you want to print all data and it's small enough to be reasonable to load and print. A 200 page .pdf is useless for most purposes, so consider Excel export instead if the data volume is potentially that large.

                  Comment


                    #10
                    Thanks for pointing that out! I got it working by modifying my code slightly. Instead of calling isc.RPCManager.exportContent(printCanvas, settings), I now have a HTMLFlow instance on which I set the contents once I get the HTML response back from getPrintHTML on the displayed Canvas, as shown below. When I scroll in the ListGrid now and click the button to export a PDF, I see the ListGrid populated fine with the loaded content.

                    Code:
                    contentLayout.getPrintHTML(null, function(HTML) {
                    	printHTMLCanvas.setContents("<div id='pageHeader'>My header</div>" + HTML);
                    	isc.RPCManager.exportContent(printHTMLCanvas, settings);
                    });
                    Code:
                    isc.HTMLFlow.create({
                    	ID: "printHTMLCanvas",
                    	autoDraw: false
                    });
                    1. Do you recommend another approach other than this?
                    2. I am still seeing the issue where the data is shown under one column instead of in their respective columns when I export the PDF after scrolling down (as depicted in the attached screenshot from my previous post). Is this a bug?
                    3. This maybe a topic for another thread but all works good with this approach except when I modify my ListGrid to have expansion components. Exporting to PDF doesn't return anything for the ListGrid after scrolling in the ListGrid, even if none of the rows are expanded. If needed, I can submit a very small reproducible test case of this if needed by modifying my sample in my previous post.

                    Comment


                      #11
                      1. This approach means you are copying HTML over from some components that you may be able to just print directly. Consider that you can pass an array of specific components to print, or use printProperties to specify a list of components to exclude from printing. This is probably simpler and faster.

                      2. Can't tell, we would need exact version, browser and OS to see if it can be reproduced.

                      3. We'll check on this.

                      Comment


                        #12
                        1. This approach means you are copying HTML over from some components that you may be able to just print directly. Consider that you can pass an array of specific components to print, or use printProperties to specify a list of components to exclude from printing. This is probably simpler and faster.
                        True, it seemed to be a little roundabout the way I got it to work but I wasn't sure how else I would use the callback parameter on getPrintHTML() (as you suggested in your previous post) to return paged content for ListGrids combined with custom HTML.
                        I originally called exportContent() on a custom canvas (MyPrintCanvas) instance just so I could export custom content to PDF. I overrode its getPrintHTML() function to return some custom HTML appended to contentCanvas.getPrintHTML() (contentCanvas is a Canvas displayed on the page) but contentCanvas.getPrintHTML() returned null after scrolling in a ListGrid contained within it.
                        Since getPrintHTML() is asynchronous in this case, I then tried calling contentLayout.getPrintHTML(null, callback) to export to PDF but wasn't sure what to do in the callback. Using HTMLFlow and setting its HTML in the callback function seemed to work.
                        The complete simple sample is below. I am not sure how I would use printProperties as you suggested to do the same. Could you point me on what I need to modify in my sample to achieve this the right way?

                        2. Can't tell, we would need exact version, browser and OS to see if it can be reproduced.
                        I am using SmartClient v8.3p_2013-02-14/Pro Development Only and Chrome on Mac OSX.

                        3. We'll check on this.
                        Thanks! In the code sample below, if you uncomment out the expansion properties for the itemList ListGrid, you can reproduce this issue. It happens either if any row is expanded or none are.

                        Code:
                        <%@ taglib uri="/WEB-INF/iscTaglib.xml" prefix="isomorphic" %>
                        <HTML><HEAD>
                        	<isomorphic:loadISC skin="Enterprise"/>
                        </HEAD>
                        
                        <BODY><SCRIPT>
                        
                        <isomorphic:loadDS ID="supplyCategory"/>
                        <isomorphic:loadDS ID="supplyItem"/>
                        
                        isc.TreeGrid.create({
                            ID: "categoryTree",
                            width: "30%",
                            showResizeBar: true,
                            dataSource: "supplyCategory",
                            nodeClick: "itemList.fetchData({category: node.categoryName})",
                            selectionType: "single",
                            autoFetchData: true
                        });
                        
                        isc.ListGrid.create({
                            ID: "itemList",
                            dataSource: "supplyItem",
                            fields : [
                                { name:"itemName" },
                                { name:"SKU" },
                                { name:"unitCost", width:50 },
                                { name:"units", width:40 }
                            ],
                            selectionType: "single"
                            // [ISSUE 3] Uncomment to get issue with expansion components.
                        	/*,canExpandMultipleRecords: true,
                        	canExpandRecords: true,
                        	getExpansionComponent: function (record) {
                        		return isc.DetailViewer.create({
                        			data: record,
                        			fields: [
                        				{name: "itemName", title: "Name"},
                        				{name: "description", title: "Description"},
                        				{name: "category", title: "Category"}
                        			]	
                        		});		
                        	}*/
                        });
                        
                        isc.Button.create({
                        	ID: "pdfContent",
                        	title: "Export PDF (Only Tree and List grids)",
                        	width: 400,
                            click:function () {
                            	var settings = {
                            		skinName: "Enterprise", 
                        			exportFilename: "export"
                          	    };
                          	    // [ISSUE 3] Exporting to PDF after scrolling in a ListGrid with expansion components doesn't work.
                                isc.RPCManager.exportContent(contentLayout, settings);
                            }
                        });
                        
                        isc.Button.create({
                        	ID: "pdfPrintCanvas",
                        	title: "Export PDF (Custom HTML with Tree and List grid)",
                        	width: 400,
                            click:function () {
                            	var settings = {
                            		skinName: "Enterprise", 
                        			exportFilename: "export"
                          	    };
                          	    // [ISSUE 1] Calling exportContent directly on printCanvas returns null for paged content.
                          	    //isc.RPCManager.exportContent(printCanvas, settings);
                          	    // [ISSUE 1] I could only get this to work with the method that is uncommented 
                          	    // below that uses the HTML returned in the callback and setting it on HTMLFlow.
                          	    contentLayout.getPrintHTML(null, function(HTML) {
                          	    	printHTMLCanvas.setContents("<div id='pageHeader'>My header</div>" + HTML);
                          	    	isc.RPCManager.exportContent(printHTMLCanvas, settings);
                          	    });
                            }
                        });
                        
                        isc.defineClass("MyPrintCanvas", "PrintCanvas").addProperties({
                        	getPrintHTML: function(printProps, callback) {
                        		return "<div id='pageHeader'>My header</div>" + 
                        		contentLayout.getPrintHTML();
                        	}
                        });
                        
                        isc.VLayout.create({
                        	ID: "mainLayout",
                        	width: "100%",
                            height: "100%",
                            layoutMargin: 10,
                            position: "relative",
                            members: [
                            	isc.HLayout.create({
                            		ID: "buttonLayout",
                            		width: "100%",
                            		membersMargin: 5,
                            		layoutTopMargin: 10,
                            		members: [pdfPrintCanvas, pdfContent]
                            	}),
                        		isc.HLayout.create({
                        			ID: "contentLayout",
                        			width: "100%",
                        			height: "100%",
                        			layoutTopMargin: 10,
                        			members: [categoryTree, itemList]
                        		})
                        	]
                        });
                        
                        isc.MyPrintCanvas.create({
                        	ID: "printCanvas",
                        	autoDraw: false
                        });
                        
                        isc.HTMLFlow.create({
                        	ID: "printHTMLCanvas",
                        	autoDraw: false
                        });
                        
                        </SCRIPT>
                        </BODY></HTML>

                        Comment


                          #13
                          To export specific components, pass an Array of Canvas as the first argument of exportContent(). In this case you never call getPrintHTML() at all so clearly don't need a callback.

                          To get your custom HTML into the output, just create an HTMLFlow and provide that HTML as htmlFlow.contents, and include it in the array passed to exportContent().

                          Comment


                            #14
                            Thanks, Isomorphic. That would definitely be a better approach. However, exportContent() doesn't seem to take an array but only takes a Canvas object as argument. I tried passing an array thinking maybe this is just not documented but get the error: Object [object Array] has no method 'getPrintHTML' on exportContent(). Is this going to be added as a feature in the future?
                            For now, do I just do it the way I have with setting html on a getPrintHTML() callback on HTMLFlow and calling exportContent() on that?

                            Comment


                              #15
                              Hmm, it's an implemented feature but appears to have been broken. We'll check on this.

                              Comment

                              Working...
                              X