Twitter: raymondcamden


Address: Lafayette, LA, USA

Flex Mobile/ColdFusion Example - Art Search

06-25-2011 7,753 views Mobile, Flex, ColdFusion 6 Comments

During my presentation earlier this week, I showed an example of a Flex Mobile application that spoke to ColdFusion for it's data. It was a simple application (search and detail records for art work), but I thought I'd take the time to explain it a bit more. It shouldn't be considered a 100% complete application (and I'll end with some notes about what I'd consider adding to make it complete), but I hope it can give you an example what it's like to mix Flex and ColdFusion on the mobile platform.

Let me begin by talking about the application's features. When the application starts up, it will contain a text field and a search button.

Here's the code just for the field and the button. Note how I disable the button until something is typed:

view plain print about
1<s:TextInput id="searchTerm" width="100%" />
2<s:Button id="searchButton" label="Search" width="100%" click="doSearch(event)" enabled="{searchTerm.text!=''}" />

Searching is done via ColdFusion and Flash Remoting. Setting that up is pretty trivial. I begin by creating a RemoteObject in Flex:

view plain print about
1<s:RemoteObject id="artService" destination="ColdFusion" source="testingzone.flexpreso.artservice" endpoint="http://127.0.0.1/flex2gateway/ ">
2</s:RemoteObject>

The ID there, artService, gives me a way to refer to my ColdFusion code (coming up in a bit). If you look back up at the button code, you can see clicking runs doSearch. Let's look at that.

view plain print about
1protected function doSearch(event:MouseEvent):void
2{
3    artService.search(searchTerm.text);
4}

So you may be asking yourself. Where's the "when done, do this" code? Here's an interesting little trick I really love. I'm going to display the results of the call in a List control, check it out:

view plain print about
1<s:List id="resultsList" width="100%" height="100%" visible="{resultsList.dataProvider.length>0}" dataProvider="{artService.search.lastResult}"
2         labelField="ARTNAME" click="doDetail(event)"
3         showEffect="Fade" hideEffect="Fade"/>

There's a bit going on here, but pay particular attention to the dataProvider. I basically create a binding between my control and the last result of calling the search method on my API. I don't have to actually listen out for it. It's automatic. Here's an example of how it looks when a search is performed.

The last bit I want to call out is the doDetail handler. That's run when a user clicks on a search result.

view plain print about
1protected function doDetail(event:MouseEvent):void
2{
3    var artid:int = resultsList.selectedItem.ARTID;
4    navigator.pushView(ArtView,{artid:artid});
5}

There isn't anything terribly special about this - but note the navigator.pushView line. This is an example of how Flex 4.5's view based framework can make it easy to build a multi-page mobile application. The pushView call there allows me to quickly load a new view up as well as pass data. What's cool then is that the hardware's back button will automatically know where to return. You'll see a quick example of that in a sec. Ok, so before I go any further, here is the complete page so you can see it in context.

view plain print about
1<?xml version="1.0" encoding="utf-8"?>
2<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
3        xmlns:s="library://ns.adobe.com/flex/spark" title="Art Search" destructionPolicy="never">

4
5    
6    <fx:Declarations>
7        <s:RemoteObject id="artService" destination="ColdFusion" source="testingzone.flexpreso.artservice" endpoint="http://127.0.0.1/flex2gateway/ ">
8        </s:RemoteObject>
9    </fx:Declarations>
10
11    <fx:Script>
12    <![CDATA[
13
14        import mx.rpc.events.FaultEvent;
15        import mx.rpc.events.ResultEvent;
16
17        protected function doSearch(event:MouseEvent):void
18        {
19            artService.search(searchTerm.text);
20        }
21
22        private function updateResults(evt:ResultEvent):void {
23            trace("Done");
24        }
25        
26        public function handleFault(evt:FaultEvent):void {
27            trace(evt.toString());
28            //Do something nice!
29        }
30
31        protected function doDetail(event:MouseEvent):void
32        {
33            var artid:int = resultsList.selectedItem.ARTID;
34            navigator.pushView(ArtView,{artid:artid});
35        }
36        
37    ]]>

38    </fx:Script>
39    
40    <s:layout>
41        <s:VerticalLayout gap="5" paddingTop="5" paddingLeft="5" paddingRight="5" />
42    </s:layout>
43
44    <s:TextInput id="searchTerm" width="100%" />
45    <s:Button id="searchButton" label="Search" width="100%" click="doSearch(event)" enabled="{searchTerm.text!=''}" />
46    
47    <s:List id="resultsList" width="100%" height="100%" visible="{resultsList.dataProvider.length>0}" dataProvider="{artService.search.lastResult}"
48             labelField="ARTNAME" click="doDetail(event)"
49             showEffect="Fade" hideEffect="Fade"/>

50    
51</s:View>

Let's look at the detail now for the art work. When you click one of the results, you get a view with information about the art and the actual picture.

Woot. Fancy, right? Ok, not so much. But let's take a look at the code. When the view loads, I need to immediately get information about the art work. Now technically, I could have gotten that information back when I searched. My data isn't that heavy. But typically your search will probably just return the bare minimum it needs to render results and your detail will need to make another call for more information. My Art View begins by saying, when loaded, run a function:

view plain print about
1<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
2        xmlns:s="library://ns.adobe.com/flex/spark" viewActivate="init(event)">

I also define a pointer to my service. (And yes, this is a duplication of code and something I should address.)

view plain print about
1<fx:Declarations>
2    <s:RemoteObject id="artService" destination="ColdFusion" source="testingzone.flexpreso.artservice" endpoint="http://127.0.0.1/flex2gateway/ ">
3        <s:method name="getDetail" result="showDetail(event)" fault="handleFault(event)" />
4    </s:RemoteObject>
5</fx:Declarations>

Notice in this version I've explicitly defined a result and fault handler for my getDetail call. This is just another way to handle calls. I could have used that with my search call as well. My init function is simple:

view plain print about
1protected function init(event:ViewNavigatorEvent):void
2{
3    artService.getDetail(data.artid);
4}

And the result than simply grabs the values from the remote call.

view plain print about
1protected function showDetail(event:ResultEvent):void
2{
3    title = event.result.name;
4    artImage.source = new URLRequest(event.result.image);
5    priceText.text = "Price: " + myCFormatter.format(event.result.price);
6    descText.text = event.result.description;
7}

At this point it probably make sense to just display the entire view. Here it is:

view plain print about
1<?xml version="1.0" encoding="utf-8"?>
2<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
3        xmlns:s="library://ns.adobe.com/flex/spark" viewActivate="init(event)">

4    <fx:Declarations>
5        <s:RemoteObject id="artService" destination="ColdFusion" source="testingzone.flexpreso.artservice" endpoint="http://127.0.0.1/flex2gateway/ ">
6            <s:method name="getDetail" result="showDetail(event)" fault="handleFault(event)" />
7        </s:RemoteObject>
8        <s:CurrencyFormatter id="myCFormatter" />
9    </fx:Declarations>
10    
11    <fx:Script>
12    <![CDATA[
13        import mx.rpc.events.FaultEvent;
14        import mx.rpc.events.ResultEvent;
15        
16        import spark.events.ViewNavigatorEvent;
17
18        protected function showDetail(event:ResultEvent):void
19        {
20            title = event.result.name;
21            artImage.source = new URLRequest(event.result.image);
22            priceText.text = "Price: " + myCFormatter.format(event.result.price);
23            descText.text = event.result.description;
24        }
25        
26        protected function handleFault(event:FaultEvent):void
27        {
28            // TODO Auto-generated method stub
29            
30        }
31        
32        protected function init(event:ViewNavigatorEvent):void
33        {
34            artService.getDetail(data.artid);
35        }
36    
37        private function goBack(event:MouseEvent):void {
38            navigator.popView();
39        }
40
41    ]]>

42    </fx:Script>
43
44    
45    <s:actionContent>
46        <s:Button label="Back" click="goBack(event)" />        
47    </s:actionContent>
48    
49    <s:layout>
50        <s:VerticalLayout gap="10" paddingLeft="10" paddingRight="10" paddingTop="10" />
51    </s:layout>
52
53    <s:Label id="priceText" />
54    <s:Label id="descText" width="100%" />
55    
56    <s:Image id="artImage" width="100%" height="100%" />
57    
58</s:View>

So the only thing I didn't show yet was the ColdFusion. My service is a CFC with two methods: search and getDetail. Here's the entire file.

view plain print about
1component {
2
3    remote query function search(required string term) {
4        var q = new com.adobe.coldfusion.query();
5     q.setDatasource("cfartgallery");
6     q.setSQL("select artid, artname from art where artname like :term or description like :term");
7     q.addParam(name="term",value="%#arguments.term#%",cfsqltype="cf_sql_varchar");
8     return q.execute().getResult();    
9    }
10    
11    remote function getDetail(required numeric id) {
12        var q = new com.adobe.coldfusion.query();
13     q.setDatasource("cfartgallery");
14     q.setSQL("select artid, artname, description, price, largeimage from art where artid = :id");
15     q.addParam(name="id",value="#arguments.id#",cfsqltype="cf_sql_integer");
16        var qresult = q.execute().getResult();    
17        var result = { "id"=qresult.artid, "name"=qresult.artname,
18                     "description"=qresult.description, "price"=qresult.price
19                     };
20        //handle image
21        result["image"] = "http://127.0.0.1/cfdocs/images/artgallery/" & qresult.largeimage;
22        
23        return result;
24    }
25}

What I like about this is it isn't anything special, which is kind of the point. The same CFCs you've been - hopefully - writing for a while can usually be just used as is via your mobile applications. Sometimes you'll literally just need to add remote access to a method and be done with it. Most of the time you'll probably need to do some tweaking, but what I'm hoping is clear here is that the actual CFML isn't anything special in regards to having it work in the mobile space. This is probably no surprise. You've seen this in all my jQuery (AJAX) demos. But it bears repeating. ColdFusion is the absolute easiest way to build services for a variety of clients.

So I mentioned that this is a bit incomplete. What would I modify to make this more "production" ready?

  • There's simple support for adding a splash screen. I could add that.
  • My fault handlers are horrible incomplete. You really, really should plan for faults. For an application like this, I could ship a local database of art data. That way if there is some fault on the server side, or, more likely, the user is offline, they can still view data. It may be older data, but it's better than nothing.

I've included the FXP for this project in the attached zip. This allows you to easily import it into Flash Builder if you want to play with it. I also included the code as is (because you may not have Flash Builder) and a copy of the CFC.

Download attached file

6 Comments

  • Commented on 06-25-2011 at 11:43 AM
    Please keep doing these!
  • Commented on 06-27-2011 at 1:03 PM
    Thanks Ray, this is something I would really like to start playing with and the examples are good motivation!
  • James #
    Commented on 07-02-2011 at 2:23 AM
    Hi Ray

    Thanks for the walkthrough. It's a big help. Do you know if there would be any case sensitive issues when passing query and structure data from coldfusion into my flex project?
  • Commented on 07-02-2011 at 6:14 AM
    First off - your Gravatar is awesome. I can't respond to anything else before I mention that.

    Secondly - yes - in terms of keys - they are upper cased. So if I returned this:

    <cfset s.name = "ray">

    It is going to be NAME in Flex. I believe if you do s["name"] case is preserved, and there is also a config setting in CF (down in one of the xml files) that let you auto lcase stuff as well.

    My confidence in the above is around 90% - so double check.
  • Commented on 08-01-2011 at 3:23 PM
    @Ray You are correct that s["key"] will result in the case of your struct key being preserved.
  • Commented on 02-04-2012 at 2:22 PM
    Don't forget to double up your addParam in the CFC as per your blog (http://www.raymondcamden.com/index.cfm/2009/9/29/G...) -- unless of course you want to mess around with Adobe's CFC.

    Thanks for this -- it was VERY helpful.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty