Wayne asks:
This is a pretty cool question. Quite a few services these days let you embed some code on your page to get some form of functionality. Google Adsense and Analytics are two examples from the big G. WuFoo has a very slick form embed service. My own Harlan Ad Server does this as well. So how can we do this with a ColdFusion service?I want to give users of one of my sites code (HTML? JS?) that they can embed on their HTML pages to call a ColdFusion form on my server. The object is to begin a registration process on there web pages that will then take the end user to my CF site after they have entered some starting information (name, birthdate, email address).
I created a CFC that outputs the form to do this and I can invoke it from a ColdFusion page and it works fine. However I cannot figure out how to embed this on another servers HTML page. I also need to pass a variable along with the form information (right now I'm just passing the URL of the client's server to figure out which client is using the form).
First off - you asked, how would the user embed the code - HTML or JS. For Google's Analytics and Adsense services, and my Harlan product, the solution is JavaScript. You embed a script tag that points to JavaScript on the external server. You can easily point to a CF file (as I did with Harlan) and then use document.writes, or other methods, to output stuff back to the screen.
WuFoo however uses an iframe. The benefit of this is that you can then point directly to a page that outputs simple HTML (or HTML and JS) and have an easier time displaying content. I kind of like this approach so I thought I'd whip up a quick demo for Wayne using it. Let's begin though with the component that will be our main service. I've created a method, displayForm, that will output an extremely simple form.
<cffunction name="displayForm" access="remote" returnType="string" returnFormat="plain" output="false">
<cfset var s = "">
<!--- borrowed from http://www.cflib.org/udf/getCurrentURL --->
<cfset var thisURL = getPageContext().getRequest().GetRequestUrl().toString()>
<cfsavecontent variable="s">
<cfoutput>
<form action="#thisURL#?method=processForm" method="post">
</cfoutput>
Your Name: <input type="text" name="name"><br />
Your Email: <input type="text" name="email"><br />
<input type="submit" value="Send It!">
</form>
</cfsavecontent>
<cfreturn s>
</cffunction>
Nothing too complex about this. The meat of it is simple HTML. Do note I used returnFormat in the cffunction tag. This means if the URL doesn't specify a returnFormat, the default of plain will be used. I only did this to make the calling code a bit simpler. (Sure, most folks will just cut and paste your embed code, but the smaller we make it the less chance they have of accidentally screwing it up. Users being what they are. Did I say that out loud?)
The action of the form is the URL itself. I did this by borrowing the code for getCurrentURL() from CFLib.
So to embed this, I, um, "innovated" the code WuFoo used, stripped it down a bit, and came up with this:
<h2>Welcome to My Site</h2>
<p>
Please fill out this form below so we can learn about you and spy on your spending habits, etc.
</p>
<iframe allowTransparency="true" frameborder="0"
scrolling="no" style="width:100%;border:none"
src="http://www.raymondcamden.com/demos/embedform/form.cfc?method=displayform">
Sorry, you don't support iframes.</iframe>
<small><a href="http://coldfusionjedi.com/">Powered by Camden Technology</a></small>
<p>
My Footer.
</p>
Ignore the HTML on top and bottom of the iframe. I just wanted a realistic page wrapper around the iframe. Note that the URL for the iframe actually works. You should be able to put this code on your own server and run it. The code in the small block is just an example of attributing the embedded code. This is what WuFoo uses. It doesn't serve any real purpose here though.
So at this point we have a CFC that will output HTML for a form. We have our own page, which could be a static HTML page, a JSP page, or even, lord forbid, a PHP page. Doesn't matter. Once run, it will render the iframe and call out to my CFC.
If you remember, the action of the form was the CFC itself, but we had used an action of processForm. Now at this point, what you do is really up to your business needs. Wayne had said he wanted to take the initial data and start the user answering other questions. So I figured I'd just take the form fields, add them to the session scope, and then push the user to the new page on my server. Here is how I did that.
<cffunction name="processForm" access="remote" returnType="string" returnFormat="plain" output="false">
<!--- borrowed from http://www.cflib.org/udf/getCurrentURL --->
<cfset var thisURL = getPageContext().getRequest().GetRequestUrl().toString()>
<!--- remove the file name from thisurl --->
<cfset thisURL = listDeleteAt(thisURL, listLen(thisURL,"/"), "/") & "/form2.cfm">
<!--- process form fields --->
<cfset session.name = form.name>
<cfreturn "<script>top.location = '#thisURL#'</script>">
</cffunction>
Once again I grab the current URL, but this time I strip out the file name and use form2.cfm. I copy one form field to the session scope. I didn't bother copying the email form field. Two notes on this. I obviously have an Application.cfc file with sessionManagement enabled. I won't bother showing that code. Secondly - I used a session variable in a CFC. Isn't that a big No-No? Am I going to incur the Neverending Wrath of the OO Lords? Maybe. But I think that this is an ok use of the session scope within the CFC. I only need to store the value so that the next set of pages can work with it, and since this gets the job done, I'm happy with it.
The last step is to send them to the next page in the process (whatever that process may be). To handle that, I simply return some javaScript that will reload the current browser to the form2.cfm page on my server.
Now let me answer Wayne's second question - figuring out which client the data comes from. There are a few ways of handling this. Wayne checks the URL of the calling server. What you could do instead is generate embed code for the user that passes a client ID in the iframe code. So if I was client id 42, my embed code would look like so:
<iframe allowTransparency="true" frameborder="0"
scrolling="no" style="width:100%;border:none"
src="http://www.coldfusionjedi.com/demos/embedform/form.cfc?method=displayform&clientid=42">
Sorry, you don't support iframes.</iframe>
<small><a href="http://coldfusionjedi.com/">Powered by Camden Technology</a></small>
Note the clientid at the end of the URL. I would then modify the CFC to notice the argument, and use the clientID in the form action post, or simply add a hidden form field with the value. (Just be aware that this is something that could be modified on the other server.)
I hope this helps. Another example of this in action may be found at SlideSix.com. Any one building ColdFusion-based services like this?
Archived Comments
Nice solution, Ray.
I would typically just give them some sample form code for a service/API, instead of generating one *for them* via JS, iFrame, or other. Some partners complained they don't want to require users to have JS enabled; others refuse to use iFrames (won't touch that holy war with a ten-foot poll ;)
Also, webmasters usually want to fully customize the HTML form, and may not want to be tied to your particular solution. Other than CSS, they may want to have their own client-side validation, may want to use AJAX, Flash, server-side POSTs or whatever to pass the data to your system. Delivering the HTML form *for them* limits the possibilities severely.
I'd just publish a dead-simple HTML example form POST, detailed docs on the expected fields (data types, max and min requirements, etc.). And in the case you described, just auto-generate a "key" or ID that they can POST in the form. Then, make sure your API has extensive and accurate error-handling that returns XML or XHTML that the partner can easily deal with. Accuracy is important here because other developers, as you well-know Ray, will scrutinize the heck out of every little mistake you might have made.
btw Ray - your blog software doesn't seem to correctly allow plus signs (+) in emails: http://cfzen.instantspot.co...
Well, for style I think you have a few options. You could easily use some documented styles that are blank so the site could define them and make them pretty. You can also do what AdSense does and allow folks to customize the output. I'd disagree with you that all users want to fully customize their forms - I think some will, for sure, but many also just want a 'drop it in and work' type solution. I mean look at WuFoo. :)
Email checking should now allow +. My blogware had that fixed for a while, but this blog is a bit out of sync with the latest BlogCFC.
Good points about the style options.
I agree that all partners probably don't want to deal with customizing these types of solutions. The problem is, though, if you want the widest possible audience to use your API, but you limit them to only a standard HTML form (no fancy ajax, flash, flex, silverlight, or other possibilities), then somewhere down the road you'll come across a more sophisticated partner who wants something more flexible. And then you may ask yourself, "why didn't I just make a more flexible/generic solution in the first place?", ... and then you'll have to write something new to accommodate these requests for customization. Is it obvious I've been down that road a number of times before, hated it, and don't want to repeat it again? ;-)
Oh... ok. So I think we may be talking different things here. You are talking about a full API, whereas Wayne specifically was asking for an 'embedable' type solution that leads folks to his server to complete a process. I think they are really different things. They _could_ share similar end results, but it's different audiences really.
We're about to release a product that uses a similar technique -- the client gets a Javascript snippet to paste into his web site, and we generate the dynamic code on the fly to drive the functionality.
We actually first implemented something very similar to this about seven years ago; we had a client with two large web sites, and we had the contract to convert one to ColdFusion to implement a bunch of enhancements. They didn't have the budget to migrate the other, but they wanted some dynamic functionality, so we implemented it via Javascript very similar to what you describe. It was very State of the Art in 2001. LOL.
I've done something similar to this embeddable service, but with a JavaScript solution., in a couple of instances. The less interesting instance is just pulling a "dynamic quote of the day" for the footer of a site; the more interesting instance is to pull a session-aware header from our CF-based corporate site to our Java-powered blog site.
http://www.mollerus.net/tom...
Hi guys,
just a note on IFRAME: it's been deprecated in XHTML.
Try using the OBJECT-tag instead.
Good luck!