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){}
        };
    }
}
	});
Advertisements

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!

ExtJS: Reloading a Combobox’s Store using OnTriggerClick

I am working on an application where i use a grid and have a combobox outside the grid whose selections are based on the selected rows within the grid. Basically the content of the combobox is dynamically updated based on the selection of rows. I’ve tried many ways of going about solving this problem since as usual i started getting Javascript errors in IE6. I first thought and tried to override the onTriggerClick function of the combobox, but i kept getting this bug where the first click on the trigger did not display the return list of values, but instead displayed a very thin blue box, and any subsequent click would then actually load the store and display the values correctly. Unable to find a solution i gave up on this approach and then tried to use the expand event to reload the combobox’s store. There i also encountered a bug where i would get an “Object is undefined” error in IE while clicking to trigger the combobox. I then did some research on the Sencha forum and then tried moving my code inside the beforequery event listener of the combobox. This worked like a charm.

For code example and a more detailed explanation, check the thread on the Sencha forum:

http://www.sencha.com/forum/showthread.php?119029-Reloading-a-Combobox-using-onTriggerClick&p=552445#post552445

Popup Window in IE6: Window appears behind or below the main or parent window

I’ve been trying to open a popup window on selecting an item from a ComboBox in ExtJS and on opening the window, using

window.open,

the popup would start loading and then quickly move below the current page. The behavior i observed only happened in IE6, Firefox loaded the popup fine and it stayed on top. With a bit of research and debugging on my own, i came to find out that something in the ExtJS code was causing the focus to return the <body> element of the main page. The solution to the problem?

//Function to open the Window
openDelegateWindow= function()
{
var popup = window.open( "qantume.htm", "Selector", "alwaysRaised=yes, status=0, height=300, width=650, resizable=0" );
 if (window.focus)
 {
 popup.focus()
 }
 return false;
}

....
//Function  calls for opening the window in your code
...
setTimeout(openDelegateWindow()', 100);
....

By delaying the start of the openDelegateWindow code, i am figuring the code execution has enough time to return the focus to the <body> tag, and then the open popup window can start uninterrupted and keep the focus on the popup window.

For your reference also i have found this script, which is helpful in making your popup quasi modal in that it checks the focus to make sure that it is not away from the current popup window, unless the focus is on one of the controls within the popup window. If the focus is anywhere else, it is rendered back to the popup window, making it basically stay on top until the user closes it. This is not foolproof, there are ways around, but they are really for the “I really need to break your script” type of user, as explained by the author. You can find it here:

http://getyourwebsitehere.com/jswb/modwin/modalwinparent.html#code

 

ExtJS JSON Date Serializer: TimeZones and Format

Through development, we finally ran into the Javascript date issue where the dates sent between the front and the backend were not in sync. The front end was sending the dates to the backend in “YYYY-MM_DD” format, and the Jackson’s deserializer would append the application’s server timezone to the date, and this would result in inconsistent dates in the right conditions since it assumed that the date sent was in GMT and would try to convert it to the local time which ended up shaving a day of the date. To solve this problem, we decided the frontend would send the date with the local timezone which would be saved on the server. It took me a little while to figure it out, but i finally used this piece of code to set the serializer’s date format:

Ext.util.JSON.encodeDate = function(o)
{
 return '"' + o.format("Y-m-d'T'H:i:s.uZ") + '"';
}

To set the correct format for your particular server, check the Date format patterns accepted by ExtJS Date object:

http://dev.sencha.com/deploy/dev/docs/?class=Ext.util.JSON&member=encodeDate

IE6: Invalid Argument Error

If you are using ExtJS and getting an “Invalid Argument” Javascript error in IE6, make sure to check your CSS for example, i get them when dynamically setting “Width” on an element. If the value is not conventionally expressed in pixels. IE will choke on it while Firefox will still accept it. Make sure then to check your style for mispelled words, or incorrectly specified widths or heights

Disappearing Ext JS RadioGroup and CheckboxGroup inputs in IE6

I ran into an issue in IE6 with a bunch of my controls not showing up when the page loaded. It was specially with the RadioGroup control in ExtJS. The radio buttons would display fine in Firefox but not appear at all in IE6 even though the HTML was present. I thought it was a width or height issue as it is often the case with ExtJS and IE6 but nothing i did could take care of the problem. I then looked at the container panels of the RadioGroups i was trying to display and realized that i was using a “layout:column” setting, which floats the items in the container left.

During my research on this bug, i stumbled upon a bug in IE6 called the “PeekABoo” bug where content inside a liquid floated bug suddenly disappears. I am not exactly sure the issue is completely related to the problems I was facing but it seems related. In order to get my radiogroups to display, i had to change the value to “layout:auto”. This worked for me and it is the default value if no layout property is specified. In general, I’ve found out that display issues in IE also are related to setting an actual width or height property on panel objects.  Well you know what to try in case you run into the issue and share in the comments whatever solution worked for you. It can be nightmarish to figure out what’s going on, and this is the reason why i decided to share my experiences on this blog.