ExtJS 4.0: Keeping a row at the top position in a GridPanel

I’ve had the need for a grid I am working on keep a row representing the user currently logged in as the top row in the grid. Mind you there is no paging on this grid. I am using a slightly outdated version of ExtJS, i.e 4.0. I tried listening to the viewready event on the grid itself or its view but those events were not firing. Seems the viewready event is only available on the view as of ExtJS 4.1. To alleviate this problem the next best thing available to me was the refresh event, which event though would be called many times over did the job of informing me of when the view rows are actually displayed, which the afterrender event does not provide.

I ended adding the following listener to my grid’s viewConfig definition:


viewConfig:{
     loadingUseMsg:true,
     listeners:{
          refresh:{
                fn:me.moveUserRowToTop,
                scope:this
          }
    }
}

and the moveUserRowToTop function does this:


moveUserRowToTop:function(gridView, eOpts)
{
var store = gridView.getStore();
var currentUserRecordIndex = store.find('id',currentUser.get('id'))
if(currentUserRecordIndex > 0)
{
    var currentUserRecord = store.getAt(currentUserRecordIndex);
    store.removeAt(currentUserRecordIndex);
    store.insert(0, [currentUserRecord]);
    gridView.refresh();

}

}

So as you can see even though the event gets fired a lot depending on your grid usage, mine is average it will only move the row if it is present and at an index greater than 0, which is exactly my use case.

Hope it helps

Advertisements

How to POST cross domain and access the returned data using ExtJS

Please refer to my earlier post for background on this project.

Here is how to post data to a server on a different domain and receive data back using ExtJS (with inspiration from the GWT implementation).

First the FormPanel. The fields are standard and in the JS file we define two variables that are critical to making this work as they’ll allow us to keep track of what’s happening:

//These two variables are critical to the success of this operation

var xdm_formSubmitted = false;
var xdm_sameDomainRestored = false;
var renderDivId='myWidget';

var consigneeForm = new Ext.FormPanel(
{
 id: 'customerWidget',
 renderTo:renderDivId,
//By default, ExtJS uses XmlHttpRequest, this has to be a standard submit POST
standardSubmit: true,
 url: 'http://www.iamonanotherdomain.com/addThisCustomerForMe',
 border: false,
 hideBorders: true,
 items:
[
{
 xtype: 'textfield',
 name: 'customerFirstName'
 },
{
 xtype: 'textfield',
 name: 'customerLastName'
 }

],

bbar:
[
//This is where the form post is triggerd
{
 xtype: 'tbspacer',
 width: 300
} ,

{
 text: 'Save',
 handler: function()

{
 var form = Ext.getCmp('customerWidget').getForm();
 form.getEl().dom.target=iframePostContainerName;
 //On Submit we set this variable to true, you'll understand why in the event handler function
 xdm_formSubmitted = true;
 form.submit();
}
}
]

Notice that before submitting, we are assigning the target of the post to an iframe that exists on the page (form.getEl().dom.target=iframeName). This is the second and crucial step of the process. The iframe we are using is defined in the same js file:


//This hidden iframe is used to receive the results of the form POST:

{
 hidden: true,
 renderTo:'myHiddenIFrame',
 html:'<iframe id="' +iframePostContainerName + '" name="' +iframePostContainerName+ '"   onload=\"setupIFrameOnLoad(\'' + callbackFunction +  '\')"></iframe>'

}
//The callbackFunction variable is passed to the script by the calling page.

Which brings us to the third critical piece of the process which is to attach an event handler function to the onload event of the iframe, and this is where all the magic will happen. As you can see i’ve name my event handler function setupIFrameOnLoad and here is the definition:


setupIFrameOnLoad = function()
{
 //If the form was submitted and we have loaded data from our own domain, we are good. Thank you for coming
 //and here is your data! It's gonna be 5 dollars, Thank you!
 if(xdm_formSubmitted && xdm_sameDomainRestored)
 {
      //You can access your data now using the window.name property, but
      //I've got other plans in my callbackFunction so until such time...
      var callbackFunctionCall = callbackFunction + "()";
      eval(callbackFunctionCall);
 }
 //If this is the initial response from the POST, we are still in the POST server's domain
 else if(xdm_formSubmitted && !xdm_sameDomainRestored)
 {
       //Now you know we're about to restore the local domain right?
       xdm_sameDomainRestored = true;
       //localResourceUrl is passed by the calling page and points to a local 1px image or empty page
       document.getElementById(iframeName).contentWindow.location = localResourceUrl;
 }
 else
 {
       return ;
 }
}

What is happening here is basically the solution to the cross-domain issue in being able to pass data back to the calling page. Once your form is submitted, the response will be posted back to your iframe within your form page. Since the response is coming back from the server, security on the browser will keep us from accessing any of the properties on the window. To be able to access the data in window.name(which could be in JSON format if you wanted), the solution is simply to load a local resource file from your environment. It could be a simple blank 1px image or an empty HTML file, anything really to switch the iframe’s source content to be on the same domain where you are calling from. Once the iframe has reloaded you can simply access your data in the window.name property. To close the cycle, the response from your server needs to be a script which sets the window.name property. Using Spring, here is how my action was set up:

</pre>
private void addThisCustomerForMe(HttpServletRequest req, HttpServletResponse response) throws Exception
{

Long customerId = customerController.addOrUpdateCustomer(req);
response.setContentType("text/javascript");
OutputStreamWriter os = new OutputStreamWriter(response.getOutputStream());
StringBuffer buf = new StringBuffer("");
buf.append("<html>");
buf.append("<script type=\"text/javascript\">");
buf.append("  window.name="+customerId+";");    // can set json data here if you want
buf.append("</script>");
buf.append("</html>");

os.write(buf.toString());
os.flush();
os.close();

}

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:
 []

}

&nbsp;

&nbsp;

Until such time!

 

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.

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

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.

Disappearing ExtJS buttons in IE 6

I officially hate IE6 and know the reason why now, ExtJS. Working with this library has opened my eyes to the deficiencies in the browser. Enough has been said on the topic so let me get to the meat of the matter.I spent a crazy amount of time figuring out why one button i was trying to set up would not display in IE6. It would some some kind of image, but would only show the entire button when the button was hovered on with the mouse. I tried setting the width, the height, the line height with no results. Here is how i was trying to set up my button:


var lookupButton = new Ext.Button({
text:'Lookup',
style:'float:right'
})

Doing so, i got an error in IE6 that stated:

el.owner.createDocument.createRange Object doesn’t support this property or method.

It turns out this is a bug in IE where trying to insert a block level element after a plain text creates this error. The solution for me was to add a span around the text i was creating like:


{
width: this.displayWidth,
html: '<span>' +  String(this.value) + '</span>'

}

Hope it helps!