Flex: Open Attachments From The View
As part of the Contact Manager app I showed how you can display a paper clip icon in the column of a datagrid to represent documents with attachments.
Let's take this one step further and allow the user to download any one of the attachments straight from the view, without having to open the document.
If you re-open the demo app you should see a new option in the columns-to-show dropdown called Attachments:
If you enable this column you'll see a dropdown box in the last column of any row that contains attachments. Clicking on a file name will launch/save it (depends on your browser preferences).
Not an essential feature in every app but a nice example of something simple to achieve that adds value in certain circumstances.
How Did I Do It?
First thing I did was change the XML that defines both the columns and the data for the grid/view. Here it is:
Notice theirs a column of type "download" which has a value of "files" and an attribute called "separator". This tell it to look for the "files" node of each document node and explode it using the value passed as a separator.
In the XML above it would give us an array of two file name strings - one for a PDF and one for a Word doc. It should be fairly obvious how this XML is produced in a Notes view, no?
In Flex, from within my View component, when looping the <columns> XML to build each column for the grid I added the following code:
if (column.hasOwnProperty("@type") && column.@type=="download"){ var dlRenderer:ClassFactory = new ClassFactory(net.codestore.flex.DownloadColumnRenderer); dlRenderer.properties = { columnName: column.valueOf(), itemSeparator: column.@separator||"; ", view: this }; col.headerRenderer = new ClassFactory(net.codestore.flex.DownloadHeaderRenderer); col.itemRenderer = dlRenderer; col.resizable = false; col.width=55; }
The code for the DownloadColumnRenderer class looks like this:
<?xml version="1.0" encoding="utf-8"?> <mx:Box xmlns:mx="http://www.adobe.com/2006/mxml" horizontalAlign="center" verticalAlign="middle"> <mx:Script> <![CDATA[ import net.codestore.flex.View; [Bindable] private var _columnName:String; [Bindable] private var _separator:String; [Bindable] private var _view:View; public function set columnName(colName:String):void{ _columnName = colName; } public function set view(view:View):void{ _view = view; } public function set itemSeparator(separator:String):void{ _separator = separator; } override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); if (data && data.hasOwnProperty(_columnName) && data[_columnName].toString().length>0){ button.visible = true; button.dataProvider = data[_columnName].split(_separator); } else { button.visible=false; } } ]]> </mx:Script> <mx:PopUpMenuButton id="button" icon="{IconLibrary.PAPER_CLIP_ICON}" click="_view.openDocumentAttachment(data.@id, button.dataProvider[0])" itemClick="_view.openDocumentAttachment(data.@id, event.label)" height="20" width="45" cornerRadius="2"/> </mx:Box>
Notice that we've passed to the DownloadColumnRenderer the instance of the View component in which it lives. It's this instance of the View that we call the openDocumentAttachment() method on. This method looks like this and lives inside View.mxml:
public function openDocumentAttachment(docId:String, fileName:String, save:Boolean=false):void{ var _fileRef:FileReference = new FileReference(); var urlReq:URLRequest = new URLRequest(); urlReq.url = parentApplication.basePath + "0/"+docId + "/$file/"+fileName; if (save){ _fileRef.download(urlReq); } else { navigateToURL(urlReq, "_blank"); } }
All quite straight-forward and a quick example of how useful custom renderers can be in AdvancedDataGrids.
YMMV.
Very nice Jake.
Did you consider using the XML to structure the files? I mean use <files><file>file1.doc</file><file>word.doc</file><files>
Not a big fan of using a separator as you never know when it might sneak in.
Reply
Now you mention it, although I know how I came to avoid child nodes in grid-feeding XML, I'm not sure why I didn't use the format you suggested in this case.
The trouble with datagrids in Flex is that a column can't deal with child nodes. Each <document> can only have nodes one level deep if they're to be shown by the grid.
I have a draft blog for some time soon about a workaround I came up with related to the above, but, yeah, you're right - it might well be better to use your structure. Like you say the separator isn't foolproof.
Jake
Reply
Great code, like the others... had you think on implement a check box on view to multiple selection (I did) and a multi group collumn ?
Have any idea how to save the configuration of columns show or others to don't lose them when run "refresh" ?
Congratulations by your family and best regards.
Reply
Hi Bruno,
I did do a checkbox selection some time back:
http://www.codestore.net/store.nsf/unid/BLOG-20091028-0634/
Mutli-group columns should be easy to do. Just add another GroupingField to the array in the ActionScript.
Funnily enough I have a draft blog in progress which describes how to remember the column order and visibility.
Jake
Reply