I've made no secret of my love for ColdFusion Builder extensions. To me - this is the single most important feature of ColdFusion Builder. Version 2, currently in public beta, adds additional features to extension developers, including support for views (woot!) as well as other miscellaneous features. I've developed a few public extensions myself (you can see mine and many others on the RIAForge category page) and have developed various libraries/utilities to help my development. I finally decided to formalize this with a library I'm calling builderHelper.
builderHelper is a simple CFC that abstracts out a lot of the common tasks I've run into when developing extensions. To work with it, you simply initialize it with your IDE XML info:
<cfset helper = new builderHelper(form.ideeventinfo)>
Once initialized, you get a whole set of helpful utility functions. For example, imagine you have an extension that works in both the editor view as well as the Navigator. You can quickly check to see which mode is being run:
<cfif helper.getRunType() is "projectview">
If the user did use the Navigator (labelled "projectview"), you may need to know if they selected a file or folder, and which file or folder they selected.
<cfset selection = helper.getSelectedResource()>
<cfif selection.type is "folder">
<cfset files = directoryList(selection.path, true, "path")>
<cfelse>
<cfset files[1] = selection.path>
</cfif>
Or perhaps they used the editor and you need to know the selection:
<cfset selection = helper.getSelectedText()>
<!--- for now, we do the entire file, period --->
<cfset contents = fileRead(selection.path)>
Helpful, right? One of the new features in ColdFusion Builder 2 is a callback url. This callback url is - well - a url (duh) that your extension can use to send special commands to. These commands allow you to do things with Builder itself. Need to open a file? You use the callback url. Need to refresh a folder because your extension changed crap? Use the callback url. Want to know what tables exists in a datasource? Ditto.
This system has a simple XML inteface. So for example, to refresh a folder you send an XML packet containing the folder. My builderUtil makes this even easier though. Consider:
<cfset helper.refreshFile(f)>
As a full example, I've included a zip to this blog entry for an extension I wrote called "Fix Smart Quotes." I'm doing tech editing on a jQuery Mobile book and some of the code samples include Smart Quotes. (If there was ever anything more misnamed before than I don't know what it is...) I can fix those easily enough by selecting a smart quote, hitting ctrl-c, going to search, doing a replace, etc, and it takes me all of 10 seconds, but I thought - why not use an extension and do it even quicker. The code below is the full handler for this extension. Notice that I never have to deal with XML once. All the grunt work is done via the helper.
<cfset helper = new builderHelper(form.ideeventinfo)> <cfif helper.getRunType() is "projectview">
<cfset selection = helper.getSelectedResource()>
<cfif selection.type is "folder">
<cfset files = directoryList(selection.path, true, "path")>
<cfelse>
<cfset files[1] = selection.path>
</cfif> <cfloop index="f" array="#files#">
<cfset contents = fileRead(f)>
<cfset contents = replace(contents, chr(226) & chr(128) & chr(157), """", "all")>
<cfset fileWrite(f, contents)>
<cfset helper.refreshFile(f)>
</cfloop> <cfoutput>Processed #arrayLen(files)# file(s).</cfoutput> <cfelse>
<cfset selection = helper.getSelectedText()>
<!--- for now, we do the entire file, period --->
<cfset contents = fileRead(selection.path)>
<cfset contents = replace(contents, chr(226) & chr(128) & chr(157), """", "all")>
<cfset fileWrite(selection.path, contents)>
<cfset helper.refreshFile(selection.path)>
</cfif>
You can download this extension below. I probably will not put this up on RIAForge as it's pretty limited. The extension includes builderHelper itself obviously so you don't need to download it from RIAForge.
Archived Comments
Cool stuff Ray! Having a common component that all extension developers can use is definitely the way to go, I'm kind of surprised that nobody jumped on it after your first CF Builder extension competition :) This should be a good time saver for developing new extensions.
This is great. Having all the custom functions most of us are already using while building CFB Extensions, but in a single package everyone can agree on. Now if only someone would come up with a framework and best-practices for building extensions we'd be all set :)
Hey, quick question. Why not just ask the CFB dev team to just include something like this already for you in the request scope while building CFB extensions? I'm mean, we're still in the beta phase, no? Maybe they'd consider putting it in rather than have pretty much every single CFB extension developer putting in the same hacks. Just a thought.
Good work on the mod :)
"Now if only someone would come up with a framework and best-practices for building extensions we'd be all set "
Not sure if we need a framework per se. That may be overkill. Best practices? Sure. I've been thinking about that for a while. Not sure if it is "too early" to start a list.
"Why not just ask the CFB dev team to just include something like this already for you in the request scope while building CFB extensions?"
Well remember that the extension is run on the cf server, so in order for it to work it would need to ship with cf, not cfb, or ship with cf and make you responsible for copying it over.
"Well remember that the extension is run on the cf server, so in order for it to work it would need to ship with cf, not cfb, or ship with cf and make you responsible for copying it over."
form.ideeventinfo doesn't ship wtith CF server. CFB Extensions cannot run without a CF server. When they do, they make that variable in the form scope available. So why not extend it a little better (to do what we're all hacking our code to do) and move it to the request scope?
Am I missing something?
Well, form.ideeventinfo is just a FORM post to your CFM. They couldn't post a CFC as well. (Unless I'm misreading you.)
What I'm saying is that CFB extensions require a CF server in order to run (which is already pre-configured). Why not have CFB do some checking in the background for me instead of my having to do the same code every time for every extension? If CFB can send me form.ideeventinfo, why not send me form.ideeventinfo.currentUrl? I mean, if we're all going to do the same code anyway (which most of us already are) to get the current url, why not simplify it and just make some of those helper functions available to us already? Isn't this what makes CF stand-out from other web languages (simplifying common things for us that we'd be writing tons of code for anyway)?
It doesn't bother me if CFB doesn't do this. I don't mind placing this CFC in all my extensions (thanks by the way). I'm just trying to look at the overall picture. Wouldn't it be beneficial to just have something like the variables built into the product if we're all planning to run the same code anyway just to make our extensions function in what should (IMO) be fundamentally available to all CFB extensions?
Well, keep in mind that they are sending some of this info. Like for example, my code to get the current mode is just making parsing the XML a bit friendlier. I'd say half of my CFC is just that.
The other big half is callback processing, which can't happen/shouldn't happen on every request. So it wouldn't make sense there.
Now - to your specific point - "current url" - yeah - I can see that making sense. CFB has to know it - you know - run it - so why not pass it.
Yeah, I was thinking just a couple specifics like current url, current directory, and webroot. Sorry, I should have specified.
These are pretty trivial to code and I bet would take a whole 10 minutes to code the data into CFB.
disclaimer: I am not a java developer, nor do I pretend to know Adobe's approval process to get new features entered in during the beta phase. So I don't actually know how long it would take to code in :). But my guess is that it's still pretty trivial and won't break anything.
Actually, and again, I REALLY need to blog a 'whats new in extensions', I think web root IS in there now. Project Root is for sure. They added a few small details to the XML.
No, it's just projectroot, but that _is_ new to CFB2 (afaik).
Ahh. I likely take projectroot for granted because I didn't start coding extensions until CFB2.
Just realized I titled this entry builderUtil - but the file is builderHelper. *doh*
Updated released detailed here: http://www.coldfusionjedi.c...