Tuesday, September 15, 2009

Flex : Downloading a Binary (Excel) File

Context:
It was surprising for me to know that flex calls like navigateToURL(urlReq,"_self"); are stateless calls. This essentially means that I cannot attach a listener or hook my custom method calls upon events. So, if you are trying to download a dynamically generated binary file using Flex and want the UI to notify the user of any errors / progress of the operation, you cannot.

Finding the solution wasn't straight forward - I noticed a hand ful of developers asking the same question on various forums. Here is how I have solved this problem.

The Approach:
There were two approaches to solve this problem at high level.

  1. Use serverside component to catch all the errors (including throwable- if you are using Java) and spool it as part of the response OR generated file. However, this is not a fool proof approach coz it will not be able to handle error conditions like server going down or network error etc.
  2. Better approach would be to leverage FileReference class that comes bundled with Flex. This will allow users the download the file from a URL. One can also pass parameters while creating URLRequest in case the file needs to be generated dynamically (which was precisely the case for us)

The code that does the trick:









01 private var fileRef:FileReference;
02 private var popupPanel:Panel=null;
03 private function sendRequest():void
04 {
05 var urlStr:String = "/myWebService/services/GenerateFile"
06 urlStr = urlStr + "?param1=someValue1";
07 urlStr = urlStr + "&param2=someValue2";
08 urlStr = urlStr + "&param3=someValue3";
09 urlStr = urlStr + "&param4=someValue4";
10 urlStr = urlStr + "&param5=someValue5";
11
12
13 initPopup();
14
15 try
16 {
17 var urlReq:URLRequest = new URLRequest(urlStr);
18 urlReq.method = URLRequestMethod.GET;
19
20 fileRef = new FileReference();
21 fileRef.addEventListener("complete", commpleteHandler);
22 fileRef.addEventListener("open", showPopup);
23 fileRef.download(urlReq, reportType.text+".xls");
24 }
25 catch(error:Error)
26 {
27 Alert.show("Could not generate file", "Error");
28 }
29 }
30
31 private function initPopup()
32 {
33 var vb:VBox = new VBox();
34 vb.setStyle("paddingBottom", 5);
35 vb.setStyle("paddingLeft", 5);
43 vb.setStyle("paddingRight", 5);
44 vb.setStyle("paddingTop", 5);
45 vb.percentHeight=100;
46 vb.percentWidth=100;
47
48 popupPanel=new Panel();
49 popupPanel.width=this.parentDocument.width / 2;
50 popupPanel.height=this.parentDocument.height / 5;
51
52 popupPanel.x = this.parentDocument.parentDocument.width / 3
53 popupPanel.y=this.parentDocument.parentDocument.height / 3;
54
55
56 var reportStatus:Label = new Label();
57 reportStatus.text="Please wait while file is generated.";
58 reportStatus.move(100, 100)
59
60 vb.addChild(reportStatus);
61
62 var cancelButton:Button = new Button();
63 cancelButton.setFocus();
64 cancelButton.label="Cancel";
65 cancelButton.x = popupPanel.width/2 - 40
66 cancelButton.y = popupPanel.height/2 - 20
67 cancelButton.addEventListener(MouseEvent.MOUSE_UP, cancelHandler);
68 vb.addChild(cancelButton)
69
70 popupPanel.title = reportType.text + " Status"
71 popupPanel.addChild(vb);
72 }
73
74 private function cancelHandler(event:Event):void
75 {
76 try
77 {
78 fileRef.cancel();
79 PopUpManager.removePopUp(popupPanel);
80 }
81 catch(error:*)
82 {
83 Alert.show"Could not cancel the file generation.", "Error");
84 }
85 }
86
87 private function commpleteHandler(event:Event) : void
88 {
89 PopUpManager.removePopUp(popupPanel);
90 }
91
92 private function showPopup(event:Event) : void
93 {
94 PopUpManager.addPopUp(popupPanel,this,true);
95 }




A few things to keep in mind:

  1. The code above uses only two types of listeners "complete" and "open" - you may want to use various other events supported here e.g. "progress", "select" etc
  2. The code above demonstrates the functionality - may have not been coded the best :)



Flip Side of using FileReference:

One flip side I have noticed about this implementation is the File Save As dialogue box (attached a screenshot below. This dialogue may turn out to be annoying for users where they just want to download the

Another thing to keep in mind is that when clicked cancel button in the code sample above - if a time consuming process was invoked at the server side - it will not interrupt. The method call - fileRef.cancel() method will only cancel the HTTP request. Application server will not be able to identify this and halt the process it started upon receiving the request

Conclusion:

If there is a requirement to track the state of the URL call and build functionality dependent on it (e.g. showing progress bar etc.) FileReference object e.g. showing the progress bar for file being downloaded or notify user of the HTTP Errors or a server side error in the file generation if this was for a dynamically created binary file based on user request