I've had to use AJAX (specifically Spry) now with two Model-Glue sites, so I thought I'd share how I've done it. This isn't rocket science per se, but I'm pretty happy with my method and I thought others might like to see it as well. (And the flip side is that if I'm doing something totally stupid, folks will let me know pretty quickly.)
First - let me start with the basics. Any page using Spry will need to:
- Include the Spry libraries
- Load the XML data
Let me first talk about how I included the Spry libraries. I could have simply included the libraries in my layout view. However, the Spry libraries aren't the skinniest files around, so I didn't want to do that. What I've done is simply tell the viewState when to load Spry. So if a page needs Spry, I'll do this:
<cfset viewState.setValue("useajax", true)>
My layout view then does:
<cfset useajax = viewState.getValue("useajax", false)>
.....
<cfif useajax>
<script type="text/javascript" src="/js/xpath.js"></script>
<script type="text/javascript" src="/js/SpryData.js"></script>
</cfif>
For those who don't quite know Model-Glue, all this means is that by default, the Spry libraries will not be loaded. A view has to be explicitly state that it wants to load them.
The next thing I need to do is load the XML. Here is one sample of doing that using Spry:
var dsIssues = new Spry.Data.XMLDataSet("#viewState.getValue("myself")#xml.issues", "issues/issue");
Note the use of Model-Glue syntax to construct the URL. I also decided that all XML based events will be named xml.something. In this case, xml.issues. Now let's look at the event definition:
<event-handler name="xml.issues">
<broadcasts>
<message name="GetIssues" />
<message name="ToXML">
<argument name="viewstatekey" value="issues" />
<argument name="xmlpath" value="issues/issue" />
</message>
</broadcasts>
<views>
<include name="body" template="xml.view.cfm" />
</views>
<results/>
</event-handler>
What's going on here? First I have my generic call to get my data. In this case, it is GetIssues. This is going to fetch a query of data. Now for the cool part. The ToXML message is how I convert the query to XML. I pass in the viewstatekey. This is simply telling the method where to look for data. In this case the value is issues, meaning that GetIssues set it's data in a key named issues. Next I pass in the xmlpath that should be used when creating the XML.
What all of this means is - I can use ToXML in multiple places by simply telling it where the data is and how to construct the XML. It is a generic event that can be used for multiple Spry events. The view (xml.view.cfm) simply returns the XML:
<cfsetting enablecfoutputonly=true>
<cfset xml = viewState.getValue("xml")>
<cfcontent type="text/xml"><cfoutput>#xml#</cfoutput>
<cfsetting enablecfoutputonly=false>
In case you are wondering, the code that creates the XML is simply my ToXML cfc (which will hopefully have a proper project page soon). This is how it was done in the controller:
<cffunction name="toXML" access="public" returnType="void" output="true">
<cfargument name="event" type="any">
<cfset var viewKey = arguments.event.getArgument("viewstatekey")>
<cfset var xmlpath = arguments.event.getArgument("xmlpath")>
<cfset var xmlroot = listFirst(xmlPath, "/")>
<cfset var xmlchild = listLast(xmlPath, "/")>
<cfset var data = "">
<cfif arguments.event.valueExists(viewkey)>
<cfset data = arguments.event.getValue(viewkey)>
<cfset xmlData = variables.xmlCFC.queryToXML(data, xmlroot, xmlchild)>
</cfif>
<cfset arguments.event.setValue("xml", xmldata)>
</cffunction>
As you can see, nothing too fancy here. The main thing to note is how it was built in a generic fashion.
Archived Comments
Looks cool, I like the look of Spry and am looking forward to using it with Model-Glue. What about using Model-GLue and Felx is that possible?
No reason why not. (AFAIK)
Using Flex with Model-Glue (or Mach-II or Fusebox) would, as I understand it, be quite redundant. All you're really "re-using" is the Model layer, becuase Flex (the Flash app running on the client) takes over the duties of Controller and View.
Brian, you don't think you would use the controller in MG? Maybe it would be overkill, but it still seems like something that could be useful.
I don't believe so. The Flex app would become the controller, just making calls directly to your model though your service layer. Going through the Model-Glue controller would add all of the overhead of the MG framework for little gain because none of the other aspects of the framework are being used. MG, Mach-II and Fusebox are what I would call "HTML UI Controller" frameworks. I don't think they're meant to handle pure Flex, web service, or Flash remoting applications.
All that said, two caveats. First, I haven't done this so it is just my opinion/theory. And second, I'm sure it is probably possible to use a MG app as the backend of a Flex app. It just seems like trying to bend it to do something it wasn't really meant to do.
You've done more Flex work than I have Ray so maybe you can discuss this a bit further? The way I understand it, a Flex app works something like this:
Flex app announces event. Flex hits a CFC on the server (a Facade that in turn calls a Service layer component in the application scope) for some data. Data is returned via Flash Remoting. Flex populates a data object. View components in the Flex app that are bound to the data object update themselves in response (such as a data grid). Am I near the mark? Maybe one difference is that I'm talking about Flash Remoting and your blog entry is about XML formatted data for AJAX. They are kind of different beasts I believe.
Thanks.
I think the problem with Flex making direct calls to the model is that it ties Flex a bit too closely to the model. What if the model changes? Having the controller layer aspect in place creates a clear separation between Flex and back end. Now - Flex has a controller aspect to it - but using that means you are syaing you will only have a Flex front end. If you move to HTML, or whatever, then you don't have the controller in place at the server side.
I think you are right on your understanding of Flex (I doubt I am much more experienced in it then you), but in your text where you say, 'flex hits a service layer', I see no reason why that can't be the MG controllers.
Agreed on avoiding the tying of the Flex app to the data model. That is indeed where the Service layer would come into play. Even in my MG, MII or Fusebox apps I use a Service layer, and my Listeners/Controllers don't do any acutal processing, but simply take requests from the framework and forward them on to the Service layer, which in turn handles all the Model chores by calling Gateways (or Reactor), business objects, etc.
In other words, my MG controllers are pretty "dumb", and simply delegate to the Service layer. In this setup, maybe it makes more sense why I think keeping Model-Glue "in the way" so to speak seems redundant. If the model changes, that's fine, because the Service layer interface is meant to stay consistent. In fact, with such a setup, it would be possible to have a MG app running an HTML version of the site, but a Flex app making calls directly to the Service layer though some simple Facade components.
Maybe this would clear it up a bit for me: if you used a MG app as the back-end for a Flex app, and you wanted to use Flash Remoting as the data exchange mechanism, what exactly would the Flex app be "calling" in the MG app? Wouldn't you have to create a static Facade CFC to point the Flex remoting calls to? AFAIK, you can't point a Flash Remoting call at a normal URL, it must be pointed at a CFC method whose access is "remote". What do you think? Thanks.
You are right - you would need some kind of proxy CFC or facade CFC for Flash Remoting to call. One thing that MG needs to add (and it has it, but a bit hidden I believe), is a way to call MG events from outside of MG. So for example, I want to be able to do: result = mg.invokeEvent("goober").
But anyway - you are right - I'd have a facade CFC and be skipping MG anyway. So forget what I said I guess. ;) Although I'd probably still have MG there to support a web based admin.
Very interesting post and comments! This is a bit off topic... but how do you usually model your service layers?
I'd imagine that applications with data persistence would have service objects that had CRUD methods. However, I always get a bit confused on how best to create service objects when objects are composed of each other, i.e. do I need a separate user service, company service, address service etc.
I guess this is the problem with modeling objects from the database structure, which is very tempting when using something like Reactor! Anyway, do either of you have advice on how best to model services? Or know of any indicators that help identify when persistence objects should be aggregated under one service object (if that makes any sense)?
Thanks,
I haven't had to worry about that yet actually. The closest thing I've done for that is the blogproxyCFC I built for my Spry demo. BlogCFC doesn't work well outside it's application context, so I needed the proxy.
Following along the thread of Brian and Rays conversation....
Using MG Unity to get this done means that you have already wired up your "Service" cfc's in ColdSpring. ColdSpring has an object called RemoteFactoryBean. This allows you to have ColdSpring generate your remote facade for you from your main service object. So you can enjoy the benifits of Ajax/Flex without having to write extra code to interface it with the business code you have already written. Through AOP in ColdSpring you can also implement security, logging and even "massage" the data you return.
I made a blog post about it a while ago. Which you can see here http://www.simb.net/client/...
Very cool stuff, Simeon.
Agreed, I had not heard of that capability of ColdSpring!
Well I am glad that you might find it useful. Brian, as a fuseboxer you will be happy to know that I also use these same techniques there. I just tend to set up ColdSpring in my application.cfc rather that in the fusebox initialization. That way if the first request comes in through flex or some remote access method, everything still gets loaded up :)
Ray, good to see you MGer's are catching up! :)
I think the Flex question also addresses the use of web services with frameworks as well. In MG as well as Mach-II, you will have to have a facade CFC that will have to be directly accessed at the controller layer. This will suite FR and webservices.
For web services, I guess you could have a view that generates the WSDL and all, but that seems like a bit of overkill when CF will do it for you.
The more I look at it, creating "Remote Facades" or "wrapper CFCs" in the controller makes the most sense. Although, doing that makes the consuming service an extension of the controller, I would think. That, however, is more of a theoretical issue than practical.
Thanks for the example, Ray, I'm looking at your code and applying it to a MachII application.
Is there any way in ColdFusion to use function pointers? For instance, in my spryListener, I want a simple function called 'proxy'.
<cffunction name="proxy" access="public" returntype="xml" output="false">
<cfargument name="event" type="MachII.framework.Event" required="yes"/>
<cfset var proxyFunction = event.getArg('method') />
<cfreturn variables.spryGateway.#proxyFunction#() />
</cffunction>
This doesn't compile, but illustrates what I want to do. I want to be able to supply any function name to the listener and have it call the appropriate function in the spryGateway (which builds the actual xml).
This would allow me to build Spry datasets like
var dsRecentUsers = new Spry.Data.XMLDataSet("index.cfm?event=spry&method=recentLogins", "users/user");
The spry event notifies the spryListener, passing the name of the function to be called in the spryGateway as the "method" parameter.
If spryGateway is a CFC, just switch to cfinvoke:
<cfinvoke component="#spryGateway#" method="#proxyFunction#" etc>
Nice,
<cffunction name="proxy" access="public" returntype="xml" output="false">
<cfargument name="event" type="MachII.framework.Event" required="yes"/>
<cfset var proxyFunction = event.getArg('method') />
<cfset var myXML = '' />
<cfinvoke component="#variables.spryGateway#" method="#proxyFunction#" argumentcollection="#event.getArgs()#" returnvariable="myXML" />
<cfreturn myXML />
</cffunction>
seems to be working at first blush. The argumentcollection is picking up any additional parameters properly also, ie.,
var dsRecentUsers = new Spry.Data.XMLDataSet("index.cfm?event=spry.facade&method=recentLogins&NumberDays={dsDays::day}", "users/user", {useCache: false});
Hi Ray,
this is really a nice description. I am trying to implement some parts of a MG:U app with AJAX (probably Spry) and want to integrate your idea. The problem is the debugging information provided by MG. Everything works fine while not using it (debug=false in CS configuration). Is there a way to let MG render the view without the debug output and NOT setting it to 'false' globally in the configuration? I haven't found a way to do this yet, so I am interested if anyone else got that working.
Greetings,
Jan from Germany
I don't think so, but that could be useful, kind of like a request override. I'll ping Joe to see.
I just found out by having a look at the MG-Examples of ajaxCFC, that there is the option to get rid of the debugging for a single event by adding:
<cfset request.modelGlueSuppressDebugging = true />
to the xml.view.cfm and everything works fine!
I blogged this. Thanks.