ExtJS: Printing Panels and Grids

One of the features i had to implement in one of the ExtJS application I’ve worked on  is printing. I was surprised to find out that there was no official print stylesheet for ExtJS. Overall, my strategy was to bring up the print layout within a popup window and call the window.print() command once rendering was done. My application being composed of a mixture of form fields, text and grids within the same screen, the number one issue here for me was how to show that same information in a printable format. No magic from ExtJS here, i referenced a PDF version of the application to redesign the UI for printing purposes. As far as the data itself, i could simply grab everything i needed by referencing the Ext object in the parent window:


var parentExt = window.opener.Ext;

My first issue was dealing with Grids. For that, i used the excellent Ext.ux.Printer extension by Ed Spencer that i had to tweak it a bit to get it to work with that i was trying to do since it is designed to print a grid within a popup window. The grid being part of my print page, i just modified its GridRenderer object to render the grid as an HTML table to the DOM. Note that, because of dimensions requirements being different for a print layout, and also display columns requirement being different between the application grid and its printed version, the more convenient resolution was to create a second hidden gridpanel within the application, with its dimensions and columns optimized for printing. The Ext.us.Printer also comes with a print stylesheet that i used as a base for my own.

The second issue i ran into once the layout was completed and rendered fine was that only the first of my two print pages would actually print content. The second page would just be blank. This was in both Firefox and IE6. It took me a while to figure out that this was due to the overflow:hidden CSS property set by the ExtJS stylesheet on most DIVs. Coupled with the way ExtJS renders content by nesting DIV even setting overflow to visible on the body tag of my popup window’s HTML did not yield the necessary results. I created a couple of CSS class for printing and used the bodyCfg property of the Panel object to finally get the necessary CSS to apply and the pages to print correctly.

Here is a summary of the code:


/* Added to my print style sheet */

.x-print-body

{
 overflow: visible;
}
.x-print-bwrap
{
 left: 0;
 overflow: visible;
 top: 0;
}

/* My Panel definition for printing*/

var printPanel = new Ext.Panel(
 {
 id:'printPanel',
 border:false,
 width:650,
 autoHeight:true,
 style:'padding:5px; overflow:visible',
 renderTo:'printContent',
 bodyStyle:'overflow:visible',
 bodyCfg:
 {
 tag: 'div',
 cls: 'x-print-body'  // Default class not applied if Custom element specified
 },
 bwrapCfg:
 {
 tag: 'div',
 cls: 'x-print-bwrap'  // Default class not applied if Custom element specified
 },
 items:
 []

}

 

 

Until such time!

 

Advertisements

Android Development: PhoneGap’s “No such file or directory” Error

I was following the tutorial on the Phonegap wiki to set up the example project within the Eclipse SDK and ran into this issue where PhoneGap could not find the phonegap.jar under the framework directory:

`stat’: no such file or directory android-sdk-path.bat
D:/Development/Android/example_android/framework/phonegap.jar (Errno::ENOENT)

I had followed the previous steps and was puzzled by this error. It turns out the issue was the version of Ruby I was using was the issue. I was using 1.9.2 and scaling back to 1.9.1 got rid of the error and i am now able to build fine. I was getting also previous errors about the android-sdk-path.bat missing and those were due to the PhoneGap bin directory missing from my PATH variable so make sure to update your path correctly and you will be good to go!

Cross Domain Form Post (XDM) in ExtJS with returned data

I’ve helped a colleague of mine work on a cross domain widget we are building for our applications that allows a user from an application to add information to another application through a pop up widget. Implementation wise, we used a  a simple form implemented using ExtJS FormPanel class. Some of the form fields like State and County dropdowns are  dynamically loaded after rendering, again using ExtJS’s ScriptTagProxy for cross-domain retrieval, no issue here and for more information on cross-domain data retrieval you can check out this post here.

The issue rose when it came to posting the data back to the server on another domain for processing. I did a bit of googling around and stumbled upon this post which explains how it can be done using a combination of server side programming and a little bit of Javascript and browser knowledge. To make a long story short, basically you can post data to a remote domain, and get data back by using the window.name property of an hidden iframe within your form page. Simply ingenious if you ask me,  I knew GWT was using the technique but did not know the the inner workings. Check out this post here for the actual implementation.

By the way if you a looking for a good XDM library, check out EasyXDM.

Addendum: As noted by Oyvind Sean Kinsey in the comments below, what i was trying to accomplish here was XDM, Cross Domain Messaging and not XSS Cross Site Scripting which is actually a security exploit. Thanks again Sean for the clarification, i understand now why the EasyXDM library was renamed from being initially called EasyXSS.

Invalid Argument Error using ScriptTagProxy and IE6

As explained in my earlier post, i used Ext.data.ScriptTag proxy for some cross-domain AJAX data retrieval. Testing in IE6, I kept getting an “Invalid Argument” error in IE traced to this line in the code. A search on the Sencha forum turned up only this post, with no good solution posted. I traced the problem to the this.head.removeChild lines, for some reason, IE6 chokes on it. The fix is quite simple, i overrode the function to use parentNode.removeChild:

Ext.override(Ext.data.ScriptTagProxy, {
	destroyTrans : function(trans, isLoaded){
	try
	{
    	this.head.removeChild(document.getElementById(trans.scriptId));
	}
	catch(e)
	{
		//IE6 does not like removeChild() to be called directly from the parent element
		  document.getElementById(trans.scriptId).parentNode.removeChild(document.getElementById(trans.scriptId))
	}
    clearTimeout(trans.timeoutId);
    if(isLoaded){
        window[trans.cb] = undefined;
        try{
            delete window[trans.cb];
        }catch(e){}
    }else{
        // if hasn't been loaded, wait for load to remove it to prevent script error
        window[trans.cb] = function(){
            window[trans.cb] = undefined;
            try{
                delete window[trans.cb];
            }catch(e){}
        };
    }
}
	});

Of ExtJS’s ScriptTagProxy, Spring Actions, TreePanels and JsonStores

I recently had to implement an Organization lookup (backed by a LDAP repository) widget that will be used by some of the web applications I developed and this in a cross-domain environment.  The data displayed within the widget is broken down in Organization->Group->Person hierarchy and on selection of a Person and click of a button, a callback would sent some information about the selected person to a function in the calling application.

The  organizational hierarchy was implemented logically with a tree using an ExtJS TreePanel component. The challenge then became to propertly load this TreePanel. The ExtJS documentation specifies that a  “TreePanel must have a root node before it is rendered“,  which can be done by specifying the ‘root’ property, or using the setRootNode method. For the purpose of my widget, i needed the tree structure to be completely rendered with parent nodes already expanded to reveal children. What needed was to create this hierarchy in my Spring controller and return it to be loaded in my TreePanel. The Ext.data.ScriptTagProxy component was the key in allowing me to make the cross domain call the widget needs to retrieve the Tree data (Check this article if you are using IE6 and getting a Javascript error when using ScriptTagProxy).

Let’s get in the code, first starting with the Store definition:

var companyStore = new Ext.data.Store({

 autoLoad: true,
 //JSONP Proxy Setup
 proxy: new Ext.data.ScriptTagProxy({
 url: getCompanyTreeURL
 }),
 reader: new Ext.data.JsonReader(
 {
 //I named my json object root's 'node', just for the sake of it
 root: 'node',
 totalProperty: 'totalCount',
 idProperty: 'id',
 fields:
 [
 //Notice that these are TreeNode regular properties
 'id',
 'text',
 'children',
 'expanded',
 'leaf'
 ]
 }),
 listeners:
 {
 load: function(store, recs)
 {
 //Once the data is loaded from the backend, i can draw my tree, with the root node
 //already created by the backend
 var companyTree = new Ext.tree.TreePanel(
 {
 useArrows: true,
 autoScroll: true,
 animate: true,
 renderTo:'tree-div',
 enableDD: true,
 containerScroll: true,
 border: false,
 rootVisible: false,
 root: new Ext.tree.AsyncTreeNode(store.reader.jsonData.node[0])

 });
 }
 }
 });

This is an example of the JSON data that will be created manually in the backend and sent to the company store, and
then finally loaded in the TreePanel root node. Notice the stcCallback1001 function that the data is wrapped in? This is required when you are using a ScriptTagProxy, and you will need to manually do that wrapping in your backend code.

stcCallback1001(
{
 'node':
 [
 {
 'id':1,
 'children':
 [
 {
 'text':'Big Bad Company',
 'id':'222',
 'expanded':true,
 'children':
 [
 {
 'text':'Little Bad Company 1',
 'id':'1005',
 'leaf':true
 },
 {
 'text':'Little Bad Company 2',
 'id':'1010',
 'leaf':true
 }
 ]
 }
 ,
 {
 'text':'Bigger Bad Company',
 'id':'123',
 'expanded':true,
 'children':
 [
 {
 'text':'Little Bigger Bad Company',
 'id':'23423',
 'leaf':true
 }

 ]
 }
 ]
 }
 ]
})

Now switching to setting things up on the server side. If you want to ScriptTagProxy paradigm to work, your response content type needs to be set to “text/javascript” as it will be interpreted as such on the client side.


 @RequestMapping("getCompanyTree.json")
 public void getCompanyTreeList(@RequestParam("callback") String callback, HttpServletResponse response)
 {
 Company root = directoryService.getCompanyTree();
 response.setContentType("text/javascript");
 try
 {
 OutputStreamWriter os = new OutputStreamWriter(response.getOutputStream());
 String companyData = getCompanyNodes(root);
 //The callback parameter value was sent by the ScriptTagProxy object in the UI, we use it to wrap the data in the function call
 os.write(callback + "(");
 os.write("{'node':[{'id':1, " + companyData + "}]}");
 os.write( ")");
 os.flush();
 os.close();
 }catch (IOException e)
 {
 e.printStackTrace();
 }

 }

Still in the controller, this function will actually build out the tree structure in a string format using recursion:


public String getCompanyNodes(Company rootCompany)
 {
 String returnValue = new String();
 boolean firstNode = true;
 returnValue += "'children':[\n";
 for(Company comp : rootCompany.getChildren())
 {
 if(!firstNode)
 {
 returnValue += ",\n";
 }
 else
 {
 firstNode = false;
 }
 returnValue += "{\n";
 returnValue +="'text':'" + comp.getName() + "',\n" +
 "'id':'" + org.getId() + "',\n";
 if(comp.getChildren().size() > 0)
 {
 returnValue += "'expanded':true,\n";
 //A little recursion saves us a few lines of code
 String childrenNodeReturnValue = getCompanyNodes(org);
 returnValue += childrenNodeReturnValue;

 }
 else
 {
 returnValue += "'leaf':true\n";
 }
 returnValue += "}\n";;
 }
 returnValue += "]";
 return returnValue;
 }

There you have it. Hope it helps!