In Part 1: Setting up the collection and event gateway, I showed you how to keep your Verity document collections updated automatically using the DirectoryWatcher event gateway. When a document change occurs, you use the DirectoryWatcher to detect this change and update the Verity collection. The only part missing is an intuitive interface for searching these collections.
In Part 2 you will see a simple example of how Flex 2 can enable a richer search interface to interact with ColdFusion and Verity.
This article assumes you are familiar with setting up a basic Flex project and exchanging data between Flex 2 and ColdFusion. To find out more, read Kyle Quevillon's excellent article on Moving data from ColdFusion CFCs to Flex 2 applications.
To make the most of this article, you need the following software and files:
Figure 1 shows the state of the application after two searches have taken place. The first search was on the keywords "ColdFusion" and the second search was on the keywords "Flex 2." Each search creates a new tab with the criteria as the label. Inside each tab is a datagrid displaying the results. This enables the user to tab back and forth between the results. In a standard HTML page, this would not be very easy, and the user would have to use the Back button to return to previous searches if they wanted to go back to a previous result.

Figure 1. The state of the search application after two searches.
This article assumes you have already read part 1 and have a Verity collection called "AdobeArticles" created.
RemoteObject tag in line 55 and change the source attribute to adobeArticles.VeritySearch (this is the name of the CFC with dot notation to denote its location in the adobeArticles folder). Take note that if you change the location of the CFC, you'll need to update it in the RemoteObject's source attribute value. Close searchBar.mxml. When the user types search criteria and clicks Search, the application calls veritySearch.cfc.
The following listing shows the searchCollection function code (you can also see this code in the sample files included with this tutorial).
<cfcomponent>
<cffunction access="remote" name="searchCollection" returntype="struct">
<cfargument name="criteria" type="string" required="true">
<cfargument name="maxrows" type="numeric" required="true">
<cfsearch
collection="adobearticles"
name="vResults"
status="vStatus"
criteria="#arguments.criteria#"
suggestions="5" maxrows="#arguments.maxrows#">
<cfset result = structnew()>
<cfset result.records = vResults>
<cfset result.status = vStatus>
<cfreturn result>
</cffunction>
</cfcomponent>
This function takes two arguments:
criteria: Specifies the keywords the user has entered in the Flex-based interface.maxrows: Specifies the maximum number of rows the user wants returned.The cfsearch tag is used to search an indexed collection. The following attributes control the parameters of the search.
collection: Specifies the name of the collection to search.name: Specifies the name of the search query.status: Specifies the name of the structure variable into which ColdFusion places search information, including alternative criteria suggestions.criteria: Specifies the search criteria to use against the collection.suggestions: Specifies whether Verity returns spelling suggestions for possibly misspelled words. Additionally, you can specify one of the following values:
always: Verity always returns spelling suggestions.never: Verity never returns spelling suggestions.Note: Your application will experience a small performance penalty for retrieving suggestion data.
<cfset result = structnew()> <cfset result.records = vResults> <cfset result.status = vStatus>
This is a structure that holds both the result records and the search status information. ColdFusion passes this structure to Flex.
SearchBar.mxml is a self-contained component that calls veritySearch.cfc.
<mx:Button id="searchBTN" label="Search" width="25%" click="doSearch(event)"/>
When the user clicks the button, the the application executes the doSearch function.
private function doSearch(e:MouseEvent):void{
veritySearchRO.searchCollection(searchTXT.text, resultLimit.value);
}
The doSearch function calls the RemoteObject tag and passes in two arguments.
searchTXT.text is the search string the user has typed.resultLimit.value is the value of the numeric stepper.
<mx:RemoteObject
id="veritySearchRO"
destination="ColdFusion"
source="adobeArticles.VeritySearch"
showBusyCursor="true"
fault="handleFault(event)"
result="handleResult(event)"/>
The attributes of the RemoteObject tag are:
id: The RemoteObject tag has an ID attribute of VeritySearchRO, by which it is referenced.source: This is set to adobeArticles.VeritySearch (the name of the CFC, with dot notation to denote its location). Since you placed the CFC in a folder under your webroot called components for example, then the path is "adobeArticles.veritySearch."fault: If an error occurs, the handleFault() function is executed.result: When a result is returned from the CFC, the handleResult() function is invoked.
private function handleFault(e:FaultEvent):void{
Alert.show("Cannot search documents at this time.","Search Error");
}
If an error occurs and this function is executed, the function simply shows an alert box with a message.
private function handleResult(e:ResultEvent):void{
var sri:SearchResultItem = createSearchItem(e);
var se:SearchEvent = new SearchEvent("SearchEvent",sri);
this.dispatchEvent(se);
if(e.result.STATUS.SUGGESTEDQUERY == undefined){
suggestionLBL.text = "No Suggestions";
suggestionBTN.visible = false;
}
else{
suggestionLBL.text = "Did you mean "+'"'+e.result.STATUS.SUGGESTEDQUERY+'"'+"?";
suggestionBTN.visible = true;
suggestionHint = e.result.STATUS.SUGGESTEDQUERY;
}
}
When a result is returned from the RemoteObject call, the handleResult() function is executed. This function does several things, but its essential purpose is to dispatch a search event that contains information about the search that just took place.
First, you put the search results into an object called SearchResultItem. This object holds the criteria, the records returned, and the extra search information verity provides. Next, test your code to see if the variable STATUS.SUGGESTEDQUERY is defined. If it is, you know that a search suggestion was returned. Finally, you show a "Did you mean [suggested term]" and a button to execute another search using the suggested term.
Now let's say you want to dispatch an event out of this component. Because you want to send the searchResultItem object along with the event, you use a custom event called SearchEvent.
var se:SearchEvent = new SearchEvent("SearchEvent",sri);
this.dispatchEvent(se);
Create an instance of a SearchEvent and put the SearchResultItem you just created inside the event and dispatch it. In short, this component handles executing a search, packages the results of the search into a custom event object, and dispatches the event. The root application listens for the search event dispatched from the SearchBar component.
private function handleSearchEvent(e:SearchEvent):void{
searchHolder.addSearchItem(e.sResultItem);
}
This function takes the SearchResultItem object and inserts it into an ArrayCollection. The SearchResults component is bound to this ArrayCollection, which is the data provider to the repeater for the tab navigator.
<mx:TabNavigator width="100%" height="100%">
<mx:Repeater id="resultRepeater" dataProvider="{dp.sHolder}">
<mx:VBox label="{resultRepeater.currentItem.searchCriteria}">
<mx:Label text="Searched {resultRepeater.currentItem.resultStatus.SEARCHED} documents and found {resultRepeater.currentItem.resultStatus.FOUND} matches"/>
<mx:DataGrid x="0" y="0" width="100%" height="100%" dataProvider="{resultRepeater.currentItem.resultSet}" dragEnabled="true">
<mx:columns>
<mx:DataGridColumn headerText="Matching Documents" itemRenderer="Renderers.ResultRenderer"/>
</mx:columns>
</mx:DataGrid>
</mx:VBox>
</mx:Repeater>
</mx:TabNavigator>
In short, the Repeater component is bound to the ArrayCollection that contains each search item. The Repeater loops over each item in the ArrayCollection, creating a new navigator tab and data grid to hold the results.
While this example is searching on a limited number of PDF files, a corporate intranet could have thousands of files. Having a way to bookmark a document that you have found for future reference is a nice feature. To accomplish this, the project uses shared objects. Shared objects are the Flex equivalent of cookies, but can do much more. Shared objects, unlike cookies, can store complex data structures.
Drag-and-drop is a common user interface feature in desktop applications. Flex 2 makes implementing drag-and-drop functionality very easy. To enable the user to drag a record into the Favorites data grid is very simple.
Open SearchResults.mxml and notice the DataGrid tag has an attribute dragEnabled="true". This enables the data grid to act as a drag initiator so the user can drag items out of the component.
Open Favorites.mxml and look at the DataGrid tag. Here you see dropEnabled="true" as one of the attributes. This enables the data grid to act as a drop target so the user can drop items into the component.
This is a simple example of creating a rich search interface to integrate with Verity. Flex 2 opens up endless possibilities for this application. For example, you could add the following features:
DirectoryWatcher event gateway, the document would automatically be added into the collectionAs you can see, the list of features to expand this application are endless, and with Flex 2 you can expose these features in a rich one-screen interface.
Brian Szoszorek has been developing ColdFusion applications since 2000. He is a certified ColdFusion developer and the chief technologist of New Point Media. He has worked and provided consulting services to companies such as New Era Cap Company, XL Capital, Spire Systems, and Adobe Systems. Brian is a strong evangelist for how ColdFusion, Flex, Adobe AIR, and the Flash Platform as they provide higher productivity and richer, engaging experiences compared to competing technologies.