NO MORE CAREER
POLITICIANS!
Get Out Of Our House: Replacing congress with TRUE citizens!
Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Web Hosting

best web hosting - top web hosting sites, thetop10bestwebhosting.com

Czech your Page Rank!
Check Page Rank of any web site pages instantly:
This free page rank checking tool is powered by Page Rank Checker service
Surf's Up!
Visit Egosurf.org and massage YOUR web ego!
My Score: 9,001
Doug's Books

Read (and recommend)

  • Men are from Mars, Women are from Venus
  • The Wisdom of Crowds: Why the Many Are Smarter Than the Few and How Collective Wisdom Shapes Business, Economies, Societies and Nations
  • Blink: The Power of Thinking Without Thinking
  • Head First Design Patterns
  • Transact-SQL Programming
  • What's So Amazing About Grace?
  • Just So Stories (Rudyard Kipling collection)

Reading

  • Prayer: Does it Make Any Difference?
  • Data Mining (Practical Machine Learning Tools and Techniques)
<< September, 2010 >>
SMTWTFS
1234
567891011
12131415161718
19202122232425
2627282930
Search Blog

Recent Comments
Re: Using Google as your CF Mail Server (by Mike at 9/07 4:02 PM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Nithin Chacko Ninan at 9/07 1:34 AM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Nithin Chacko Ninan at 9/07 1:33 AM)
Re: Configuring Apache To Use Multiple Versions of ColdFusion (by Lola LB at 9/06 6:28 AM)
Re: Configuring Apache To Use Multiple Versions of ColdFusion (by ComboFusion at 9/06 5:17 AM)
Re: Railo 3.1 on Windows Server 2008 and IIS7 - Part 3 of 3 (by Jon at 8/27 2:04 PM)
Re: Hosts File Changes Not Acknowledged on Vista 64 (by Spacy at 8/24 3:46 PM)
Re: THE DAY CFUNITED DIED (by ComboFusion at 8/23 10:50 AM)
Re: My Grandpa (by Tasha at 8/10 4:29 PM)
Re: Just What IS a 'Service Layer', Anyway? (by dougboude at 8/02 10:10 AM)
Categories
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

06 September 2010
Configuring Apache To Use Multiple Versions of ColdFusion

This post is for those of you out there who have managed to get two or more versions of Coldfusion running side by side, but are now facing the challenge of HOW on earth you can actually run your apps in both versions using Apache. For example, now you'd like to test your app in CF9 by typing in "localhostCF9/myKillerApp" and in CF8 by typing in "localhostCF8/myKillerApp".  In theory, this is a no brainer, but when it comes down to answering the question of "okay...how do i configure my dev environment to allow me to do such a thing?", this is where I found myself in a fog.

Before you go on, you should know that locally I use Apache as my web server, not IIS, so this post is heavily focused on Apache only. If any of you have accomplished the topic of this post using IIS, feel free to leave a link in the comments!

PREREQUISITES TO THIS POST

This post assumes that you're already at a certain point: Apache is installed and working, and you have multiple instances/versions of CF installed and working. If you DO meet this criteria, you may want to just skip to the "Tying it All Together" section. If not, I have info and recommendations to share regarding meeting this criteria.


STARTER RECOMMENDATIONS, IF YOU NEED THEM

Although my development environment is Windows, I'm not using IIS as my local web server, but rather Apache. If you don't already have Apache installed and/or are not an admin type who enjoys digging in to config files, I highly highly recommend that you install a little product called WAMPServer (http://www.wampserver.com/en/download.php). It is an uber simple app that will install Apache, Mysql, and PHP on Windows, and then provide an easy interface for managing those products. From one tiny interface you can restart services, add/change Apache, PHP, and MySQL versions, edit config files, and add/remove PHP modules. In our case (CF lovers), PHP is ONLY relevant because that is what drives PHPMyAdmin, the app included with WAMPServer that provides a nice easy web-based interface to your MySQL installation (I use it heavily).

If you haven't already gone through the exercise of installing multiple versions of CF, and since that IS your first step in all of this, I highly recommend the lovely Daria Norris' tutorial (found here http://www.cfgothchic.com/blog/post.cfm/my-cfmeetup-presentation-installing-multiple-versions-of-coldfusion) as THE place to start. A couple of things that I did while following her instructions that you may also want to bear in mind:

1. during the install, when you get to the part about connecting to existing web servers, just tell CF to use its own internal server;
2. One thing Daria mentioned briefly that I didn't catch until I listened to her presentation a second time is that you should not specify a root context when installing your instance of CF8; just leave it blank (unless you really understand what that means and are comfortable changing it). If you don't know what a root context even is, no biggie; you'll encounter the question that uses the phrase, and you'll now know to just leave it blank.

MAKE SURE CF IS WORKING RIGHT

Okay, so let's make the assumption that at this point you have installed CF9 and have two instances running: one that is CF9 and another that is CF8. At this point you will have three different admins: one that is the 'super' admin for the master enterprise installation of CF9; one for the new instance of CF9 that you added; and one for the instance of CF8 that you added. Since we told CF that we were using the internal web server, you should be able to reach each of these now via URLs that resemble the following: http://127.0.0.1:8300/CFIDE/administrator/index.cfm, where port 8300 = the super installation, 8301 = the CF9 installation, and 8302 = the CF8 installation. Once you can hit the admins for each install using those URLs, you're ready to proceed. To verify what the correct port numbers are, you can look in the "Instance Manager" portion of the super admin interface

coldfusion 9 administrator showing the instance managerThe CF9 Instance Manager

MAKE SURE APACHE IS WORKING RIGHT
Let's also assume that you have created a URL that Apache uses to hit the local web site we will be using for testing. This post won't delve deep into the details of doing that, but I will share the following snippets.

1. line in my hosts file (C:\WINDOWS\system32\drivers\etc\hosts):

127.0.0.1       localhost

2. line block in my apache config file  :

<VirtualHost *:80>
    DocumentRoot "D:/Inetpub/wwwroot"
    ServerName localhost
</VirtualHost>

(how to edit the config file when using WAMPServer)

Now, place a simple html file at the path you specified as the DocumentRoot above. Make sure you restart your apache server after making the change (as demonstrated in the video above), and close and re-open your browser so it will see the changes to the hosts file. After that, you should be able to browse to http://localhost/[yourtestpage.html] and view it. This will tell you that Apache is working fine and the virtual host is configured correctly.


TYING IT ALL TOGETHER

SO, how do we get CF and Apache talking? The big picture answer is, by telling the JRun server to create a ColdFusion proxy for us that Apache can use to pass requests to for processing. After we get the proxy created, we will then create the Apache virtual hosts we want and instruct Apache to use the proxy for those virtual hosts, handing off requests to the appropriate CF instance.

1. Create the Proxy

The way to get this proxy created is to run the Web Server Configuration Tool. You will find it under Start/Programs/Adobe/Coldfusion 9 (multiserver version)/Web Server Configuration Tool , or you can go directly to C:\JRun4\bin\WSConfig.exe to kick it off. Just click 'Add' and tell it about your web server.

(Hint: If you DID opt to install WampServer, your Apache config file will be found at C:\wamp\bin\apache\Apache2.2.11\conf , and your Apache binary (asked about when you click on the 'Advanced' button) is at C:\wamp\bin\apache\Apache2.2.11\bin\httpd.exe. Do browse these first to verify the paths, just in case yours is a little different)


2. Verify Your Proxy Creation

After you finish, the way to verify that all went well is to take a peek in the folder C:\JRun4\lib and make sure a sub folder called "wsconfig" was created, that itself contains a subfolder (named 1, or 2 probably). If those exist, so far so good.

3. Verify Apache Proxy Configuration

The web server configuration, in addition to creating the proxy, also touched your Apache configuration file and added a few lines. Open your Apache config file (like you did earlier when you created the virtual host) and take a gander near the bottom. Those lines should look similar to this:

# JRun Settings
LoadModule jrun_module "C:/JRun4/lib/wsconfig/2/mod_jrun22.so"
<IfModule jrun_module>
    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51000
</IfModule>

I want you to ignore the "loadModule...." line, BUT remove (copying and saving somewhere temporarily...very important!) all of the JRunConfig lines. After you have removed those lines, save your config file and restart Apache, just to make sure the config file is good up to this point.

(note: IF you wish to have a default version of CF to be used when NOT utilizing one of the version-specific domains/virtual hosts we will be creating, then LEAVE this section of code in place)

4. Make a Tweek to the JRUN.XML Files
Each of the ColdFusion instances that you are running can be found by looking in the  "C:\JRun4\servers" folder. You should see folders there named according to each of the instances you created before. Mine are named CF8 and CF9. Inside of each of those folders, under SERVER-INF, you will find the jrun.xml file (path to my CF8 jrun.xml: C:\JRun4\servers\CF8\SERVER-INF\jrun.xml). For each instance's jrun.xml file, we are going to open it up and make one change, and note one setting within it.


Change 1: Search for the jrun proxy section ("<service class="jrun.servlet.jrpp.JRunProxyService" name="ProxyService">"). Within that section is a setting called "deactivated"; make sure it is set to 'false' (<attribute name="deactivated">false</attribute>).

Item to Note: In that same section, note the value of the 'port' attribute. You will need to know this port number in order to tell Apache what version of CF to use for a specific virtual host. In my case, the attribute looks like this:

<attribute name="port">51000</attribute>

5. Create Your Own Virtual Hosts for Each Version

Okay, finally down to last step: creating domains/virtual hosts that will both point to the same content, yet serve it up using a different version of ColdFusion! We first need to decide on a domain name for each version. Myself, I chose to use "localCF9" and "localCF8" as my CF-specific domain names. So, step 1 in creating a new virtual host is to edit my hosts file. I added the following lines:

127.0.0.1 localCF9
127.0.0.1 localCF8

Save hosts, making sure you don't inadvertently save it as 'hosts.txt'...if using notepad, make sure you do a 'save as' and change the type to 'all files'). Now, let's edit our Apache config file again, adding two more virtual hosts blocks that resemble the following:

<VirtualHost *:80>
   ServerName localCF9
   DocumentRoot "D:/Inetpub/wwwroot"
</VirtualHost>

<VirtualHost *:80>
   ServerName localCF8
   DocumentRoot "D:/Inetpub/wwwroot"
</VirtualHost>

You'll notice that I am pointing both virtual hosts to the same folder as its root...the root of my local environment. I am doing this because I want ANYTHING I browse to via the 'localCFx' domains to be dealt with by the appropriate CF versions. Before you go on, it's a good idea to restart Apache again and just make sure you can browse to localCF9/[yourtestpage.html] and localCF8/[yourtestpage.html] just to make sure all is well with the virtual host configuration. Remember to close and reopen your browser so it 'sees' the changes to hosts file.

6. FINAL STEP
The final step is for us to inform Apache that, anytime it receives a request for a particular virtual host, it needs to hand that request off to a specific Coldfusion version via the proxy that was created for us. Go back and grab the settings you saved from the config file earlier, the ones that look like

    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51000

and paste it INSIDE of each of your virtual host blocks for localCF8 and localCF9. Then, change the Boostrap port number to match what you found in the jrun.xml files for each instance. Here is my final virtual hosts blocks:

<VirtualHost *:80>
   ServerName localCF9
   DocumentRoot "D:/Inetpub/wwwroot"
    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51000
    AddHandler jrun-handler .cfm .cfml .cfc .jsp
</VirtualHost>
<VirtualHost *:80>
   ServerName localCF8
   DocumentRoot "D:/Inetpub/wwwroot"
    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51002
    AddHandler jrun-handler .cfm .cfml .cfc .jsp
</VirtualHost>

Notice they both use the same jrunserver store, but they each are 'talking to' the proxy via a different port. This port number is what tells JRun which instance should receive the request. Also notice that I added an "AddHandler" line to each of the blocks; without this, Apache treated my cfm files like text files. Also, if in step 2 you left a default version of CF in place, you may also have to add the "AddHandler" line to that section if CFM templates aren't served up as expected.


THE FINAL TEST

As a final test, I created a cfm page that dumped the SERVER scope AND dumped an object that I created using a simple CFC that contained only a single "init" method that returned 'this'. I recorded the demo for you to see, highlighting the cf version in each.


FURTHER READING
These sites, along with Daria Norris' tutorial on initially setting up the multiple versions, were of great help to me! And a special thank you to the awesome Mr. Stephen Moretti who managed to get me on the right track initially and who wrote one of the blog posts below.

http://www.cfgothchic.com/blog/post.cfm/my-cfmeetup-presentation-installing-multiple-versions-of-coldfusion


https://www.adobe.com/livedocs/jrun/4/JRun_Administrators_Guide/connectors2.htm


http://nil.checksite.co.uk/index.cfm/2006/5/3/cfmx-multiserver-set-up


http://www.simonwhatley.co.uk/configuring-coldfusion-8-with-apache

Posted by dougboude at 3:37 AM | PRINT THIS POST! | Link | 2 comments



31 July 2010
THE DAY CFUNITED DIED
The Summation of My Experience

As you ALL know, this was the last CFUnited ever, and as an attendee, I am certain that this fact was actually a positive influence on how the conference played out. The camaraderie that is always an integral part of this event was magnified by the ever-present fact that we just don’t know when we may be able to assemble ourselves in these numbers again. I know for myself (and I do believe that I am representative of most of my CF brethren), I felt it to be even more important this year to seek out and get to know new faces, and to push myself outside of my hermit-like inclinations and spend good quality social face time.  During said face time, we all engaged in the discussions of the subjects that this conference’s demise has brought to the forefronts of our minds. Why was it ending? What did it mean for our community? What would be the ramifications? Was it a sign of anything to come? as confident as we are about the future of the language we embrace and earn our livings with, the demise of the grandest exclusively CF event to grace our professional landscape in the past six years has definitely been a cause of at least minimal concern to many. But, through the process of the fellowship, the sessions, the after parties, and the after parties’ after parties, I gained an absolute reassurance that what we have witnessed is nothing more than the results of an economy that’s been on a downward trend for the past three years and is in no way a prognostication of the future of ColdFusion. In fact, what it DOES mean is that now that one of the larger trees in the forest has sadly fallen, the vacancy it leaves us with is the opportunity and catalyst to allow for the currently smaller, more focused, and/or regional conferences to flourish and fill this niche in our professional ecosystem. I firmly believe this, and have found nothing but reassurance for this belief in the past three days of being immersed among my peers and mentors.

ON THE NEGATIVE (BUT POSITIVE) SIDE...

Having highlighted how excellent the conference was, I feel the need to share a less positive opinion based on my observations this year as an attendee. This particular CFUnited seemed to greatly lack in its administration as compared to those in the past that i have attended. The staff seemed more interested in doing their own thing as opposed to being focused on the details relevant to a conference, such as session start/stop times, ensuring sound levels, recording speakers, ensuring that the doors were closed once a speaker began, giving the speaker cues as to when his or her time was nearly up, and just maintaining a positive, prominent presence throughout the event. Fortunately, and I give HUGE kudos to these individuals, there were those individuals who were in attendance that took great responsibility upon themselves to fill in these gaps. They ensured that the registration desks were properly manned and that the attendees were given the proper attention at registration, they went the extra mile and turned what would probably have been blas’e social events into OUTSTANDING social events, they invested their own time to make certain that the fellowship, the tradition, and the kindred nature that we all share as a CF community was recognized and honored. I would give specific shout outs to those who I am aware were instrumental in making this conference as memorable and awesome as it was, but truth be told, everybody in attendance had some hand in that. Together, we made this CFUnited what it should be, and we can be proud of that fact. On the other hand, the actual organizers of the event...honestly, they couldn’t have seemed more disinterested to me. Oh, and the fact that I was solicited via email to help "save" CFUnited by BUYING THE RIGHTS TO IT??? Man, that's like telling your kid you love him and then putting a price tag on his forehead. That's just wrong in my book. Like I said, just my observation and opinions here.

 LOSING MY VIRGINITIES

This conference also happened to be one that resulted in a few major changes for me. Besides the hardly containable inspiration that the sessions were regarding things like alternatives to RDBMS, iPhone development, the exploration of HTML5, and a new found interest in the CF on Wheels and FW/1 frameworks, I also found myself doing things that I was certain I never would do. I, a profoundly staunch PC, discovered that I am now “i-curious”. I want a mac. It stuns me and frankly, freaks me out, every time i hear myself say such a thing, but it’s true: i want a mac. Any of my friends who know me very well are probably picking their jaws up off of the floor at reading that, as I have never even come CLOSE to waivering in my unrelenting despisings (and uninhibited vocalization OF those despisings!) of all things mac. Heck, when they first saw me palming an iPhone they nearly fainted. But after seeing the presentation on iphone development with the Coldbox framework as the backend data services, something just gave up inside of my head and I, for the first time in my life, understood what it felt like to desire a mac. I know, this almost sounds like I’m coming out of the closet, and in many ways it certainly does feel that way to me, too! A strange feeling, like I’m giving in to the thing I shunned for so long...I’m being dramatic, huh?

Oh, the second thing I did that I said I would purposefully NEVER do: I had my picture taken with Ben Nadel. Again, as any of my friends who know me well will attest to, I am in general an “anti-herd” guy; if everybody’s doing it, then to me that’s a sure sign that they’re doing something i do NOT want to do. And, since everybody seemed to be giddy at the idea of having their picture with Ben up on his site, I naturally vowed internally never to do so. But then, I actually got a chance to meet Ben and talk with him a little bit. Gosh darn it, he’s just too nice! :) So, I conceded and participated in a three way with him and Ryan Jeffords. Three way photo opp, that is. :)

WHAT NOW?

So NOW then... let’s all set our sights on the next conference or conferences we’re going to attend, shall we? Our time honored tradition of supporting our community by communally sharing what we all learn individually must continue, and it will do so in the form of ALL of us making it a point to contribute and participate in whatever ways we can. BLOG, y’all. BLOG the things you learn that have made your development life easier. TWEET. Share the tiny 140 character moments of your days that reveal your knowledge, wit, charm, losses, gains, wisdom, and personality to the rest of us. SPEAK. Overcome your erroneous belief that you have nothing of value to offer your peers and mentors and volunteer to speak at your local user group, the online cfmeetup group, other user groups in other cities, and conferences. When you get the word that such and such conference is having a call for speakers, you should be prepared to submit yourself and two or three topics that you have already presented on via the user groups. I know it can be difficult to overcome shyness and such, but believe me, all of us have something of value to offer. Heck, even the very fact that someone who never spoke publicly before is making that effort is in itSELF an inspiration to others, regardless of the topic or how smooth the preso goes!

Oh, let me leave you all with this awesome resource that Charlie Arehart maintains for the sake of his community: http://www.carehart.org/cf411/  There, you can find out about all the latest conferences and other resources whereby a CF developer can get “plugged in” to his or her community and begin partaking and sharing.

CONCLUSION

This conference has left me with a LOT of new friends, personalities and faces to connect with twitter icons, a boat load of technical inspiration, and the loss of at least two virginities. CFUnited 2010 rocked, y’all! If you missed it, you really did miss a lot.

I look forward to crossing paths with you in the future! In the meantime, if you're not already, let's be virtual buddies, eh? My twitter name is dougboude

Posted by dougboude at 1:27 AM | PRINT THIS POST! | Link | 1 comment
13 July 2010
PHP vs COLDFUSION
a brief, biased comparison

I had the privilege of doing a short presentation at our local CFUG meeting last night, the topic being "Coldfusion Vs PHP", so thought I'd share the slides and some brief commentary on some of them. We didn't record the meeting, so what you're about to see is an abbreviated version.

Posted by dougboude at 12:43 PM | PRINT THIS POST! | Link | 9 comments
23 June 2010
D2W Conference Review
Overview

Designer/Developer Workflow Conference - June 19-20, 2010

Conference Review

Whether you're a developer, a designer, or fancy yourself to be a citizen of both worlds, it is nearly impossible for anybody at any place in that spectrum to completely avoid the need to have at least some knowledge of both disciplines. Designers need to have a good understanding of what is possible in the development world so that they know what is and is not realistic design, best ways to represent transitions, animations, and ajax activities in their designs, and how to make their designs re-usable as tangible assets in the final product. Developers need to know how to interpret design specs and prototypes, how to use the tools that make workflow from design to development smoother, and in the case where the developer is essentially the designer as well, how to create prototypes and wireframes that result in re-usable assets. Even if the employer is of such size that the design and development departments are completely segregated, there is still always the need for the two to be able to communicate well and avoid "cultural" conflicts. Any way you slice it, designers and developers are two sides to the same coin, and enhancing each and the workflow between them is a very real need.

The Designer Developer Workflow Conference, the brain child of Designer/Developer/Instructor extraordinnaire Dee Sadler and hosted this month in Kansas City, Missouri,  expertly targeted this exact need. As an attendee and humble speaker at the conference, I can honestly say that out of all the conferences I have attended in the past few years, THIS one provided me a large relative percentage of information I found to be new to me and left me feeling like I had gotten a huge return on my investment. The session topics were hand picked from a large number of submissions and seperated into three genres: designer, developer, and hybrid. In all honesty, I had not previously heard of many of the speakers and so was a little skeptical as to how impressive and relevant the presentation contents would be to me. But after only a couple of sessions it became VERY clear to me that I had walked into a goldmine of information that I had not previously been exposed to. Every single session I attended, without fail, kept me absolutely riveted and had me walking out at the end with a mind full of fresh perspectives and concepts I wanted to explore further.

The venue was absolutely perfect as well, being an ideal balance of upscale and moderation, and not three blocks from Kansas City's "Power and Light District", a several square block area of restaurants, martini bars, pubs, and outdoor entertainment. And might I add that the people of Kansas City...well, they're just doggone nice to look at!

Dee Sadler outdid herself on this project. Despite the immense level of stress and work that must have been upon her shoulders, she was like a calm mother hen guiding her flock of bright and excited speakers and guests through what turned out to be, in this reviewer's eyes, a hugely successful first annual Designer Developer Workflow conference! I am very much looking forward to next year's! Congratulations, Dee Sadler. You rock, girlfriend.

Posted by dougboude at 1:01 PM | PRINT THIS POST! | Link | 20 comments
21 June 2010
D2W Session Review - The Art and Science of RIAs
par Ben Stucki

The Art and Science of RIA's

by Ben Stucki

As I've mentioned before (but it's worth mentioning again), this year's first annual Designer Developer Workflow conference was, in my opinion, a monumental success and an outstanding start to what I'm sure will become an annual tradition. The session topics hovered anywhere between developer-oriented and designer-oriented topics, yet NONE of the sessions ever left off addressing the needs of the other side of the equation; every designer-centric topic contained material relevant to and comprehendable by developers, and vica versa. It was almost like having achieved world peace. :)

So, one of the sessions I attended was presented by an individual I consider to be quite wise in the art of development. His name is Ben Stucki (pronounced "stew key"), and if the man ever runs for president, he's got my vote. Ben delivered a presentation entitled "The Art and Science of RIAs", though in this reviewer's opinion, a more appropriate title would have been "The Art and Science of Good Code", as this presentation delved deeply into Ben's personal philosophies and rules on "Good Code" and, though the plethora of information he shared is indeed relevant to RIAs, was definitely just as applicable across all application genres.

In this session, rather than simply focus on what "Good Code" means, Ben made it a point to do the one thing that is absolutely essential to a coder before he can ever correct the error of his ways: dig all the way down to the root causes of "bad code". Some of those causes arise from the developer's own lackings while others stem from areas outside the developer's control, such as a project manager. These negative outside influences can produce circumstances that affect how the developer chooses to code. For instance, setting arbitrary, unrealistic deadlines, allowing the project scope to change mid-stream, and not listening to developer feedback can all produce effects that cause the developer to produce what Ben referred to as "Technical Debt". Technical Debt is when the pressure of a looming unrealistic deadline basically forces the developer to choose between "pleasing the boss" and "doing it right". It's that manner of coding that gives one the advantage of meeting the deadline now, but will most certainly have to be paid back later. By continuing on in a way that produces Technical Debt, eventually the application gets to the point where it would be easier and more cost effective to scrap it and start from scratch rather than attempt to add any more features or modifications.

The 50 minute presentation was chocked FULL of wonderful tips, guidelines, philosophies, viewpoints, and rules that Ben personally has adopted. Ben's ability to articulate his ideas in both word and illustration are impeccable, and his passion for his art and craft are not only quite obvious, but are also sure to provoke the listener's own zeal and passion to improve in their art as well. If you ever have opportunity to hear Ben speak, be sure and not miss it!

You can follow Ben on Twitter at @benstucki, and visit his blog at blog.benstucki.net

Posted by dougboude at 11:54 PM | PRINT THIS POST! | Link | 20 comments
D2W Session Review - Using Facebook and Twitter to Market Your Creative Services by Lisa Heselton

Using Twitter and Facebook to Market Your Creative Services

by Lisa Heselton

Being the enlightened individual I am, I decided to expand my horizons and attend a session that wasn't directly relevant to programming or prototyping (the equivalent of a guy forcing himself to watch a chick flick end to end for the sole purpose of ensuring that he still has feelings). In this instance, the session I had the distinct privilege of sitting in on was called "Using Twitter and Facebook to Market Your Creative Services", presented by Lisa Heselton (aka @kavka on twitter!)

First off, let me just say that Lisa (if you're reading this), nobody enunciates as well as you do! :) Your consonants are crisper than crisp, your vowels are formed to perfection, and your delivery...that of a true seasoned professional! :)

The overall purpose of this presentation, as I interpreted it, was to gather the collective wisdom and experience of all attendees and share it communally, with Lisa expertly guiding the discussions and contributing a large helping of her own knowledge. She set the stage with a brief introduction to what social media is and a high level view of how it is relative to creative professionals. She then graciously invested a good portion of session time to allowing each and every attendee a few minutes of floor time, introducing themselves, sharing how they currently use social media, and what it is they came to the session hoping to learn. With everyone's position clearly revealed, people began an orderly interactive discussion of their experiences and observations with and of social media, asking their questions of the group as a whole. Under Lisa's guiding hand, everyone walked away having their questions answered and armed with a large collection of fresh ideas and resources.

Personally, being only a casual user of social media and not having any creative services to market, I came away with a major epiphany; an analogy of extreme accuracy that I'd like to thank Lisa for helping to impart to me. The analogy is this: Successfully using social media is EXACTLY like successfully participating in a cocktail party composed of some familiar individuals and many new ones. Just as you would never go this party planning on doing nothing more than speaking your opinion with no intention or interest in listening to responses, you should NEVER allow yourself to utilize social media as a billboard. After all, you're not dropping flyers on the windshields of cars here; you are talking to real people whose need to be engaged in a two way dialog demands your greatest reverence. Just as you would never go to a party with the sole intention of trying to sell the other party goers some product, so should you never take social media for granted and treat it as a captured audience of potential customers. On the contrary, what you SHOULD do is to introduce yourself to the group and to individuals; let people know something about you, personally, regardless of whether or not you would like to generate some business. Everybody can smell a disinterested car salesman from a mile away. Likewise, it takes no special skills to be able to mentally compile a person's collection of tweets and read between those lines to know the true level of their sincerity. Now, it IS okay to talk about your services at a party, but everyone would agree that there is a right way and a wrong way to do it.

I could go on and on with this analogy, and so can you. But I am now convinced that anybody is able to clear up 99% of the mystery of how to properly and successfully utilize social media if they look at it in PRECISELY the same way as they would utilize a casual cocktail party. It's not at ALL a huge mystery! Social media is simply a new way to hold a global, ongoing cocktail party, and if you are part of any social media outlet...then dude/dudette...you're just a person at a party. Have fun with it, but remember proper party etiquette.

Thanks Lisa Heselton! You really rocked this one. ;)

Posted by dougboude at 12:40 AM | PRINT THIS POST! | Link | 22 comments
20 June 2010
D2W Session Review - Prototyping by Chris Griffith

Prototyping: A Component for Success by Chris Griffith

I can't speak highly enough of the Designer Developer Workflow conference, aka "D2W"! Having been using Coldfusion for a long time now, I find more often than not that I glean maybe 10% new knowledge and information from the other conferences I attend. This one, however, has skillfully addressed a niche that I haven't previously been educated on in such a focused, concise manner: The "divide" between development and design.

Let me start this review by saying that Chris Griffith is an outstanding speaker, engaging his audience and enlightening them with well organized material.

In this particular session, Chris addressed, at a high level, the values of and approaches to prototyping, or building out representations of an application before it is actually created. Because Chris works for a company that has a global presence, with both time and language barriers present, prototyping is a vital part of his company's workflow. Whether it's just a quick mockup using Fireworks (a "Fidelity A" level prototype), a basic simulation built in html and with hard coded values (a "Fidelity B" level prototype), or a larger scale, "ready for user testing" type prototype fleshed out with data and working script ("Fidelity C" level), it is Chris' experience that some kind of mockup is essential in order to solidify the vision between stakeholders, designers, and developers. In fact, Chris says that more often than not the prototype should be used during user testing, before resources have been dedicated to building the actual application.

One guideline that Chris offered is to always think of your prototypes as completely expendable and to not invest so much effort and time in them that you couldn't just throw them away and start from scratch. He also,in the interest of project timelines, personally doesn't focus so much on creating re-usable assets in his prototypes, but builds out only what is needed in as short a time as possible. As Chris says, building a prototype should never be allowed to become a monumental task.

Chris uses a variety of Adobe and non-Adobe tools in his prototyping efforts. Whether it's Fireworks, Flash Builder, Coldfusion, HTML, Javascript, or any combination thereof, his philosophy of "use whatever tool makes the most sense for the project at hand" is a wise one indeed.

To sum it all up, Chris admonishes us all to be aware that "the only cost in software development is CHANGE". And, because a picture IS worth a thousand words, smart prototyping is an absolutely essential part of any efficient workflow.

Thanks Chris! I look forward to hearing you speak again soon!

Posted by dougboude at 12:24 PM | PRINT THIS POST! | Link | 20 comments
03 June 2010
D2W Conference: My Speaker Highlights
Dee Sadler sent me the following questions as a way of gathering my opinions and information for my area of the Designer Developer Workflow Conference Speaker Highlights section of the site. I figured I'd go ahead and post them here as well, just to give folks an idea of what my role will be when i'm in Kansas City, Missouri June 19-20.

SPEAKER HIGHLIGHTS: DOUG BOUDE

Who should come to your session and why?

OOP in the CF world is here, it's here to stay, and in my strong personal opinion, any CF programmer who doesn't embrace it, and soon, will find themselves left behind career-wise. For any Coldfusion developer who came from a procedural background, however, Object Oriented Programming...conceptually and practically...can be a daunting career paradigm shift to undertake. Having myself come from a procedural mindset, I know exactly just how frustrating it can be to find, consume, digest, and assimilate these new ideas into one's existing knowledge base. In this session, I will share a concise yet thorough synopsis of some of the more common OO terms and concepts as I have come to understand and use them, in the hope of helping to connect the dots between your existing understandings and those of OOP.


Who is this session for, then? If you have the need for an OO primer, are fully engaged in making the switch to OOP, or have been doing OOP since before it even had a name, this session is for you. For the newbie, it offers a good general dissection of OOP, along with a decent translation of the ideas and terms you will encounter; for the active learner, it holds a Texas size portion of information that can be merged and compared with what you have already gleaned on your own; and for the seasoned vet, it offers the opportunity to get a solid feel for how your less OO-experienced peers "think" about OOP in a CF context, empowering you to be able to more effectively impart the knowledge you possess to "the rest of us".

 
Are there any projects you are working on of interest right now?

As THE IT department at my company, my project list is fairly long, dynamic, and diverse. Much of it consists of duct taping a legacy PHP system to help temporarily streamline the company's corporate workflow. However, I am working on two interesting, "from the ground up" projects that I'd like to share a little bit about.
 
Project one is a Model-Glue application that allows its users to efficiently upload insurance claims in the form of a spreadsheet directly to an industry-prominent third party vendor via their API. This app leverages a database schema that allows individual users to create mappings between their particular spreadsheet layouts and the vendor's XML schema via an intuitive GUI, then transforms the spreadsheet data into the target XML files utilizing XML Stylesheets and XML Schema documents (XSD). All in all, this has been a VERY interesting project that is teaching me a LOT about XSLT, XSDs, and XML transformations in general, not to mention honing my OOP architectural skills.


Additionally, I am in the process of using Fireworks to prototype an entire new system (project two) to replace the legacy PHP system. This new system will be built using the Coldbox 3.0 toolset/framework.

What does workflow mean to you?

Workflow to me is the rhyme, meter, and tempo of doing what I do from day to day, along with the individual applications that I use in the process. I am the artisan, and the processes, methodologies, and tools I utilize to transform raw ideas into functional works of art IS workflow. 

What size company do you work for, and do they practice workflow now? and how?

I work for a small family owned Insurance Adjuster firm based in Boerne, Texas, and have been with them for a little over one year now. All told, there are probably 10 employees including myself. Workflow in the IT Department (which consists only of me), has been non-existent prior to my coming on board. The workflow I have put in place for myself is very dynamic in order to adapt to the company's dynamic business needs, but core to it are the religious use of an off-site code repository (Assembla, a third party SVN provider), Eclipse as my IDE, WAMP as my local development server environment, the usage of CanvasWiki to centralize all of the critical "if Doug got hit by a bus" type information, and Amazon S3/Jungle Disk to back up critical server data off site. Beyond that, I use a lot of Notepad, Editpad Pro, Firebug, some Jing, and Fireworks. 

What message would you have to someone starting in the business right now, or wanting to become a hybrid? A designer or a developer, be specific.

I can't particularly speak to the subject of wanting to become a designer (since I can't relate at ALL to such an idea! Craigslist looks cool to me.), but to the aspiring developer I would have a few words....


Programming is nothing more than solving problems, and I do believe that most people are decent problem solvers. The only difference with application development is in the kinds of tools that you will use; otherwise, you are simply solving problems.


Believe that nothing...no idea, no concept, no fancy term... is too difficult for you to grasp. After all, look at all the other people who do it successfully! And the majority of them are not smarter than you, I guarantee it. Bang your head against a thing long enough, and it WILL go in! I've proven this time and time again to myself.


Lastly, always take pride in the code you will write, and be ever striving to improve your knowledge and technique. It is your craft, your creation, the expression of your creativity, and how well it functions AND is written is (like it or not) a reflection of your skill and ability; of you as a developer.

Posted by dougboude at 2:21 AM | PRINT THIS POST! | Link | 20 comments
Designer Developer Workflow Conference!

In just a little over 2 weeks I'll be in Kansas City, Missouri speaking at the first annual D2W (Designer/Developer Workflow) conference, organized with the specific purpose of helping to enhance and/or bridge the gap in the knowledge and workflow disparities that can exist between the design and development aspects of a project. Though we in the CF/Adobe community are fortunate enough to have several conferences to choose from each year, I can't help but feel that Dee Sadler has very successfully addressed a gap in the genres and educational targets as covered by current conferences, thus further complementing the cornucopia of information available to not only developers, but also (in the case of this conference), designers.

Taking a look at the session schedule reveals what I beieve to be a unique and intriguing array of topics not covered with such focus anywhere else. For many of us developers, there is a very real need to at least be somewhat proficient as a designer in order to perform our jobs. Prototyping applications, organizing the way we transform client requests into functional products, enhancing the way we communicate with designers and stakeholders, being able to utilize the Adobe tools our employers provide us to their full capacity...all areas we can stand to improve in, and all topics covered by this conference. 

 Considering the relatively small conference fee, and the fact that there are still two weeks until the conference starts (meaning airline tickets can still be obtained at discount), I would definitely recommend that you at least check out some of the particulars of what D2W is offering. 

Posted by dougboude at 2:13 AM | PRINT THIS POST! | Link | 20 comments
17 May 2010
CONDITIONAL INSERT IN A SINGLE QUERY

Ever had the need to insert a record, but only wanted to do so if the record doesn't already exist? Typically the first approach to this is two queries: one to check for the existence of the values you want to insert, the second to peform that insert IF the first query returned no records. Well, I was in such a spot today, but hated the idea of having to hit the db twice to perform a conditional insert. Though I'm sure I'm not the first one to come up with this approach, I thought I'd share it in case it helps someone else, too.

I'm using a MySQL database, so the query I'm going to demonstrate is written a little bit MySQL-specific, though it is easily translated into MSSQL-speak as well.

The Scenario

Okay, we're looping through a file where each line represents a record. For each of those records, we want to perform an insert IF that record doesn't already exist. Here's how I'm doing it:

 

<cfloop file="C:\Users\doug\Documents\Downloads\R361.TXT" index="L">
 <cfset carrier = rtrim(ltrim(mid(L,1,8))) />
 <cfset claimnum = rtrim(ltrim(mid(L,9,8))) />
 <cfset priorclaim = rtrim(ltrim(mid(L,18,8))) />
 <cfquery name="qryAddClaim" datasource="#dsn#">
  INSERT INTO priorclaim (carrier,claimnum,priorclaim)
  SELECT '#carrier#','#claimnum#','#priorclaim#'
  FROM `priorclaim`
  WHERE not exists
  (
   select * from priorclaim where carrier='#carrier#' and claimnum='#claimnum#' and priorclaim='#priorclaim#'
  )
  LIMIT 0,1
 </cfquery>
</cfloop>

First of all, please know that I highly recommend the use of <cfqueryparam> tags in every place that you see a variable being output. I ommitted that tag in this example for clarity's sake.

Walking through the code, first thing I'm doing is grabbing my values from the current file line. Next, the query. It is obviously an INSERT query using a select statement. The select statement is simply selecting the actual raw values, but ONLY if a record meeting that exact criteria does not already exist. If no record is selected in the WHERE's subquery, the values are inserted; otherwise, nothing happens at all.

Notice my use of the "LIMIT" attribute. This performs the equivalent action of DISTINCT, ensuring that only a single record is returned. Without limiting the results of the SELECT statement, we would get multiple results and would be inserting massive amounts of duplicates. For MSSQL users, simply add the DISTINCT keyword to your SELECT statement, as in "SELECT DISTINCT '#carrier#', '#claimnum#','#priorclaim#' FROM ...."

Posted by dougboude at 1:33 PM | PRINT THIS POST! | Link | 26 comments
31 March 2010
Easy Way to Integrate ColdFusion into Non-ColdFusion Templates
Ajax to the Rescue!

Have you ever found yourself in a situation where you needed to implement some new feature using one scripting language within a template that is written in a different scripting language, or possibly no scripting language at all (pure HTML)?  Of course, you could just turn the pure HTML template into a CFML template, and at times that is indeed the best route to go, as long as breaking every link pointing back to this page is acceptable. If the target template is written in another scripting language, however, transforming it into a CFML template would probably not be a viable option. Ah, but there is at least one way to still be able to integrate your Coldfusion functionality into any template (HTML OR those written in other scripting languages): Leveraging the power of Ajax.

Typically the first route our minds go when asked to integrate two discrete templates is to look for a way to "include" one within the other. HTML does provide a type of inclusion ability (called 'Server Side Includes' (SSI)), but this approach is typically used to include other HTML documents and would be challenging to make work with a Coldfusion template. Other scripting languages have the ability to include other templates written in the same language, but getting them to play nicely including Coldfusion would also prove challenging. You could also go the route of using an IFRAME within your template, but this approach also presents limitations and hurdles that you may not wish to deal with. The simple solution, and the simplest approach to making it work, again, is Ajax.

The Scenario

In this scenario, the target template is a static HTML site and the code you want to integrate is written in Coldfusion. Our goal is to dynamically display the contents of an upcoming events database table in our Events section of the company's HTML home page. Here's how I approached it:

1. Create a target within the static HTML page where you want the event data to appear. In my case, I created this bit of HTML:

  <h3>Corporate Events</h3>
  <div id="eventTarg"></div>

 

2. Create a CFM template to act as your data provider. Mine is called eventAjax.cfm, and contains several different methods since it is used by other applications as well;

3. "Wire up" your HTML page with your favorite Javascript library. Mine (currently) is Prototype, so my code snippets will reflect Prototype's syntax, but you should be able to easily relate it to your favorite flavor

That's it!  Now, some details...

 

The Data Provider

Some may feel the need to write full blown web services to provide data to the consumer/caller, but personally I feel that most times this is major overkill when a RESTful approach works beautifully. Here is what my eventAjax.cfm looks like, showing only a couple of possible methods:

<!--- suppress any and all output except what we explicitly allow via cfoutput tags... --->
<cfsetting enablecfoutputonly="true" showdebugoutput="false" />


<!--- our switch expression value. this determines what action will take place --->
<cfparam name="meth" default="" />

<cfswitch expression="#meth#">
 <cfcase value="getEventLinks">
  <!--- retrieving all events that have not yet ended and are published --->
  <cfquery name="qryGetLiveEvents" datasource="#dsn#">
   select id,name,descrip,eventfromdate,eventtodate
   from registrationevents
   where <cfqueryparam value="#now()#" cfsqltype="cf_sql_date" /> <= eventToDate
   AND publish = <cfqueryparam value="1" cfsqltype="cf_sql_tinyint" />
   order by eventFromDate
  </cfquery>

  <!--- by default, create this return value... --->
  <cfset links = "no events currently scheduled" />

  <cfif qryGetLiveEvents.recordcount gt 0><!--- if we have records, loop over them and create the 'links' content --->
   <cfoutput>
   <cfsavecontent variable="links">
    <p id="eventlinks" class="eventlink">
     <cfloop query="qryGetLiveEvents">
      <a href="registration.cfm?eventid=#id#" target="_blank">#name#</a><span class="eventText"> <em>(from #dateformat(eventfromdate,"mm/dd/yyyy")# to #dateformat(eventtodate,"mm/dd/yyyy")#)</em><br>#descrip#</span><br><br>
     </cfloop>
    </p>
   </cfsavecontent>
   </cfoutput>
  </cfif>
  <cfoutput>#links#</cfoutput>
 </cfcase>

 <cfcase value="updatePublish">
  <cfparam name="eventid" default=""/>
  <cfif not isnumeric(eventid)>
   <cfoutput>INVALID EVENT ID</cfoutput>
   <cfabort>
  </cfif>
  <cfquery name="qryUpdatePublish" datasource="#dsn#">
   UPDATE registrationevents
   SET publish = <cfqueryparam value="#newval#" cfsqltype="cf_sql_tinyint" />
   WHERE id=<cfqueryparam value="#eventid#" cfsqltype="cf_sql_integer" />
  </cfquery>
 </cfcase>

 <cfdefaultcase>
  <cfoutput>UNAUTHORIZED ACCESS</cfoutput>
 </cfdefaultcase>
</cfswitch>
<!--- helper functions can reside below this line... --->

Reading through this code, it is pretty self-explanatory. Basically, when the ajax call is made, only values explicitly output using CFOUTPUT tags will be returned to the caller. My "getEventLinks" method is returning a pre-formed block of html that my client side javascript will simply dump into its target div.

Wiring up the HTML

Wiring up our HTML page consists of doing only two things:

1. include your javascript library;
2. create a function to be executed after the html has fully loaded;


My step 1 line looks like this:

<html>
...
<title>Company Name Here</title>
<script type="text/javascript" src="js/prototype.js"></script>
....
</html>

 

Step 2 is inline script written at the bottom of the template, just within the <body> tag. I don't believe it HAS to reside within the <body> tag, but that's where i chose to put it. It looks like this:

<script>
 document.observe("dom:loaded", function() {
  params = new Hash();
  params.set("meth","getEventLinks");
  new Ajax.Updater('eventTarg','eventAjax.cfm',{parameters:params,method:'post'});
 });
</script>

 

That's it! Now, everytime the page is loaded, Prototype will execute an ajax call to the template specified and will stuff the results into the targeted div 'eventTarg'. If no qualifying events are found, we'll simply get the phrase "no events currently scheduled".

Here is a screenshot of the html page loaded, using Firebug to see the ajax call that was made and the value returned:

firebug ajax call

Posted by dougboude at 3:52 PM | PRINT THIS POST! | Link | 5 comments
16 March 2010
Leveraging Response Headers in Ajax Calls

If you're doing any sort of Ajax development, then you undoubtedly already are quite familiar with the different ways you can use an Ajax call. For instance, you can have the call return a fragment of fully formed html (such as a table populated with data). For a more advanced user, you may have your Ajax calls returning raw data in the form of JSON or XML and parse it on the client side. In either case, though, your single Ajax call is returning a single value. But what if you want several different discrete pieces of "stuff" returned in a single call? You could get creative, and do something such as this in your backend code:

<cfsetting enablecfoutputonly="true" />
<cfscript>
  stReturnData = structnew();
  stReturnData.htmlBlob = "<table><tr><td>bla bla bla</td></tr></table>";
  stReturnData.statistics = structnew();
  stReturnData.statistics.recordcount = 212;
  stReturnData.statistics.redWidgets = 32;
  stReturnData.statistics.blueWidgets = 54;
  stReturnData.someQuery = qryIRanPriorToThis;
 
  returnVal = serializeJSON(stReturnData);
</cfscript>

<cfoutput>#returnVal#</cfoutput>

The above would work perfectly fine. It would, however, require that you be willing to write some serious Javascript to access the individual pieces of data, and compels you to have to visualize how the data sets are structured and nested as a whole.

Again, nothing wrong with doing something like the above. But, when I find myself wanting to return more than one value with a single Ajax call, more often than not I will approach it by leveraging response headers.

Response headers are like the saddle bags on a Harley. If your Ajax response is the Harley, then the burley tatted dude (or dudette) riding it is your primary response value. This Harley, however, comes equipped with a pre-defined set of saddle bags that contain what I'm calling "peripheral" information, or metadata about the response, AND (this is most critical), the ability for you to add your own custom saddle bags before the response comes riding home to its caller!

I have never had a need to even look at or care about those pre-canned response headers. They contain name value pairs such as "Transfer-encoding:chunked", "content-type:text-html", "Date: Tue, 16 Mar 2010 15:43:35 GMT", and a couple of others. But, in order to satisfy needs such as I described above in the code sample, I DO add my own response headers and stuff them with values my Javascript needs.

Doing this in Coldfusion is very simple and easy (as are most tasks). Re-doing the example above, then, I'm going to choose one of the values to be my burley tatted rider (typically the value consuming the most bytes), and designate the rest to response headers, like so:

<cfsetting enablecfoutputonly="true" />

<cfset ReturnData = "<table><tr><td>bla bla bla</td></tr></table>" />

<cfheader name="recordcount" value="212" />

<cfheader name="redWidgets" value="32" />

<cfheader name="blueWidgets" value="54" />

<cfheader name="someQuery" value="#serializeJSON(qryIRanPriorToThis,true)#" />

<cfoutput>#ReturnData#</cfoutput>

So now my Harley has several new, custom saddle bags attached, each containing the value I set in the "value" attribute of the cfheader tag, and my main rider, or the actual response value is my ReturnData (the blob of html).

Let's look at the Javascript needed to utilize this response. In this example we are going to use the Prototype library to create a new Ajax Updater. Updaters simplify the task of making an Ajax call and stuffing the response value directly into a target element. So let's say we have a div with an ID 'dataDiv' that will hold a table of data. The Ajax updater line will look like this:

new Ajax.Updater('dataDiv',getDataURL,{parameters:params,method:'get');

 

 

Now, the line above deals exclusively with the response value and ignores anything to do with the response headers. In order to access those headers, we have to tell our Updater to pass its response (Harley, rider, saddlebags, and all) to a function that we designate. This is called "using a callback function", and our line of code will now look like this:

new Ajax.Updater('dataDiv',getDataURL,{parameters:params,method:'get',onComplete:processSaddlebags);

 

 

The Updater will update its target div with the response value, then hand the Harley off to the processSaddlebags function, which will look something like this:

function processSaddlebags(ajaxResponse){
  //let's set each of our response header values to a local variable...
  var recordcount = ajaxResponse.getHeader('recordcount');
  var redWidgets = ajaxResponse.getHeader('redWidgets');
  var blueWidgets = ajaxResponse.getHeader('blueWidgets');
  var objQuery = ajaxResponse.getHeader('someQuery');

  //now we can do 'stuff' with these values!
  alert('total number of records in our query: ' + objQuery['ROWCOUNT']);
  //etc....
}

It should be noted at this point that a most MAHVELOUS way for you to get a clearer picture of what is happening when your code makes Ajax calls is to use the Firefox browser with the Firebug plugin. By doing this simple thing, Firebug will provide you with all the details of every Ajax call you make. Here's a screenshot of a sample Ajax call and the kinds of info you get back (notice the custom header 'total' that I added, and the fact that I'm outputting that value in the html as the "Total Records" value):

response headers

screenshot of firebug ajax call showing response headers

response value

screenshot of firebug ajax call showing the response value

Now, all that having been said, there are size limitations to the usage of response headers that you should keep in mind. I could not find a specific reference that talked about the size limit of response headers, but I did do some experimentation to try and approximate what that limit is. I stuffed as many characters as I could into a response header, and the value was truncated at 6,653 characters (including spaces). I created two custom headers with the same 6,653 characters in it, and both were returned, so I'm assuming the limitation is per header (though common sense tells me there is probably a cumulative maximum size as well). Knowing that there IS a limit, my recommendation is that you use headers for smaller pieces of data and never to convey something as large as a JSON data set unless you know that it will be limited in size.

That's it boys and girls! Now go out there and leverage those response headers!

Posted by dougboude at 12:47 PM | PRINT THIS POST! | Link | 2 comments
12 March 2010
Spreadsheet to XML Transformation Utility Overview

I am currently working on a project that has proven to be very challenging, architecturally speaking. I've been learning a lot along the way, and I have come up with some approaches to addressing the challenge that I feel are pretty doggone sweet, so I thought I'd share some of the details in case it helps spark ideas for other people.

Let me describe how this challenge came about, and the challenge itself.

I'm sitting in my office working on something when I hear several people in an informal meeting discussing how much in time and money it will cost them to pay several temps to enter data from a spreadsheet into a remote system. I wrestle with the idea of making work for myself for a few seconds, then inject myself into the conversation and ask them a few questions about the remote system and what ways it provides for inputting data. As it turns out, the remote system has an interface for receiving this same data in the form of an XML file. Simply drop the XML file into a special folder in their system and it is automatically imported. "So, can you turn our spreadsheets into xml files"?, they ask me. "Of course"!, I reply. And so it begins.

As I try to always do, I explored what potential future requests might entail surrounding this process, examined the possibility of this process' usefulness to other companies, and bore in mind the potential of using this process to accommodate OTHER spreadsheet sources and OTHER xml destination formats. The result was a full blown utility application that allows a user to define a spreadsheet layout, map that layout to a targeted xml layout, then be able to import spreadsheets and produce the xml files in the target format, fully populated and validated; upload those files to their FTP destination; and monitor the status of the uploaded files via feedback mechanisms built in to the target system.

Bear in mind this application is not finished, but I have a substantial amount of the architecture completed and code working up to the point of the FTP process, so its the ins and outs of some of those moving parts that I wanted to share with you.

System Overview: How it Works

1. User logs in and chooses 'Upload Claims'
2. selected spreadsheet is uploaded and validated
3. validated spreadsheet is imported via POI
4. xml node to spreadsheet column mappings are retrieved

for each row in the spreadsheet
  5. blank xml file is created by applying an XML stylesheet (xslt) to the xsd on file;
  6. node values are translated from their tokens to actual values
  7. nodes are located within the xml file via their id and their value replaced with translated value
  8. xml files are "cleaned up" (optional), removing all empty nodes and node trees by applying an XML stylesheet

9. completed xml files are FTP'd to their destination (based on client settings)


DETAILS

Maintaining XML Definitions in the System

So, the first aspect I tackled was the best way to maintain target XML definitions. Well, since I know I want to be able to validate the XML files I create, and since the existing methods of xml validation of necessity ARE a definition of the XML, it made perfect sense to me to start with either a DTD or an XML Schema that represents the targeted XML format. DTDs don't have near the depth of metadata that I would need for future use, so I opted to use XML Schemas (or, 'xsd' files). Every potential target XML format this system maintains will be represented by an existing XSD file stored in a subfolder of the app.

If you aren't familiar with XML Schemas, let me tell you they are invaluable if you want to validate your xml in any detail. You define elements, the data types they hold, any rules that should be applied to those values (such as "only one of the items in this list of values", "a date in the following format", etc.), what attributes an element has and the rules for the values THEY hold, what elements are required, how many instances of a particular element are legal, what order elements should appear in, whether an element is required or not, and pretty much ANYTHING else you can think of! If what you need to store about the XML is NOT available as a standard XSD item, then do what I did and create your OWN namespace within the XSD and add whatever other attributes you want!

For the system I am building, one vital step is to ensure that every node, or potential spot for storing a system value, had to have a unique id. Since there was nothing in the existing xs namespace that allowed for that, I created my own namespace named after the app and added an "ID" attribute to every element and attribute defined for the target XML format. Here's the header of my XSD (notice the 'xclaim' namespace defined): 

<xs:schema
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   elementFormDefault="qualified"
   attributeFormDefault="unqualified"
   xmlns:xclaim="http://www.somesite.com" >

 Having defined the xclaim namespace allows me to legally do the following: 

<xs:element name="XACTNET_INFO"  xclaim:id="id_1">

So, in my system, to add a new XML target, simply create an XSD schema file that 'describes' that format, complete with the xclaim namespace's 'id' attribute, and you have accomplished your goal! 

Maintaining Spreadsheet Definitions

I wanted to be able to "head off at the pass" as much confusion or needed validation code as possible, so I opted to 'validate' an uploaded spreadsheet before attempting to process it. What this means to me is that, when a spreadsheet is uploaded I will first check to see that it contains all of the columns (appropriately named) as the user said it should and that all of those columns appear in the correct order. If the spreadsheet passes these two checks, I call it good and we proceed. Otherwise, I reject it and give them a nice message so saying.

In my UI I provide them a way to create and maintain spreadsheet definitions. Essentially they tell the app what the name of the column is and what order it appears in.

phpmyadmin column definitions

Simple enough, eh? 

Maintaining Spreadsheet Column/XML Mappings

Ah, now THIS little bugger was much tougher than one may think at first glance. You see, properly populating your target XML poses many challenges, such as:

-not every column maps directly to only one xml node (element or attribute). This is a one to many relationship, for sure!;

-not every value needed in your xml file COMES from the incoming spreadsheet! Values such as today's date, the client's name, address, or other metadata, etc.;

-not every value needed in your xml file is a SINGLE value, but could very well be a CONCATENATED value! Such as in my case, where the values from three columns needed to be concated along with today's date and placed into a specific element's text, or where one attribute's value had to be the concatenation of a fixed string "COV" and an iterating value (COV1, COV2, etc.)

So you see, designing a storage mechanism to allow such fluidity and yet still be easily maintainable was no easy task to conquer! The solution I came up with accommodates all of the above, but still does have its limitations. Not a perfect solution, but close enough I'd say!

Here then is what the stored column-to-node mappings look like:

column to node mapping table

Some highlights of this approach:

-nodeID values correspond to the "xclaim:id" values spoken of earlier with regard to the XSD file;

-nodeID values with decimal counters indicate that there may be multiple occurrences of that node's branch in the final xml file. The decimal indicates which iteration of that branch the value belongs in;

-I created "tokens" as placeholders for those values that must be dynamically determined at run time. {column-x} refers to the value in column X, {column-x-name} is replaced with the actual column name, {iterator} is an incrementing value, {currentDateTime} is the current date and time, etc.;

-by including a targeted xsd for a node-value mapping (note the xsdID field), I can effectively map a single spreadsheet to multiple destination xml formats;

-the sortorder field is used in cases where a nodeid has multiple values indicated. This means that those multiple values should be concatenated in the order of 'sortorder'.

By using mysql's really awesome group_concat function, I can return a nice list of values, in their proper order. Here's the query I use to retrieve my mappings. Note the use of group_concat: 

   SELECT m.sheetID, m.xsdID, m.nodeID, GROUP_CONCAT( m.nodeValue ORDER BY m.sortorder SEPARATOR '|' ) AS nodeValue
   FROM mapping m
   WHERE m.sheetid = <cfqueryparam value="#arguments.sheetID#" cfsqltype="cf_sql_integer" />
   AND m.xsdID = <cfqueryparam value="#arguments.xsdID#" cfsqltype="cf_sql_integer" />
   GROUP BY m.sheetID, m.xsdID, m.nodeID
   ORDER BY m.nodeID

 The results looks like this:

node value query results

With the query above retrieved, I can now walk through each row of my imported spreadsheet and calculate the appropriate value for the targeted node IDs. :) 

Creating the Blank, or Skeleton XML File

In this system, the only thing we maintain regarding a tarteted xml format is its XSD schema file. Since the XSD schema file is itself an XML file, the logical most expeditious approach to creating our skeleton xml is to leverage XML Stylesheets, or XSLT. I searched high and low for an XSLT that would transform a valid schema file into the skeleton XML it represents and could not find a single example. I thought surely such a thing would have been done many times over! So, since I couldn't find one, I wrote one. It's not all that lengthy really, so here it is in all its glory:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:str="http://exslt.org/strings"
 xmlns:xalan="http://xml.apache.org/xalan"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:xclaim="http://www.somesite.com"
 exclude-result-prefixes="#default xsl str xalan xs xclaim">
 <xsl:output method="xml" indent="no" encoding="ISO-8859-1" omit-xml-declaration="yes" />
 <xsl:template match="/">
  <xsl:element name="XACTDOC">
   <xsl:apply-templates />
  </xsl:element>
 </xsl:template>
 <!-- getting our elements, but excluding complexTypes with a name attribute (entity definitions, as i refer to them) -->
  <xsl:template match="xs:complexType[not(@name)]/xs:sequence/xs:element|xs:complexType[not(@name)]/xs:all/xs:element|xs:complexType[not(@name)]/xs:choice/xs:element">
   <xsl:call-template name="outputElement">
   <xsl:with-param name="isEntity" select="false()" />
   <xsl:with-param name="parentID" select="@xclaim:id" />
  </xsl:call-template> 
 </xsl:template>
 <xsl:template name="outputElement">
  <xsl:param name="isEntity" />
  <xsl:param name="parentID" />
  <xsl:choose>
   <!-- if the element we are looking at has a type specified, and that type is not a predefined xs: type (must be an entity!)... -->
   <xsl:when test="@type and substring(@type,1,3)!='xs:'">
    <xsl:element name="{@name}">
      <xsl:call-template name="outputEntity">
      <xsl:with-param name="thisname" select="@type" />
      <xsl:with-param name="parentID" select="@xclaim:id" />
     </xsl:call-template>
    </xsl:element>
   </xsl:when>
   <xsl:otherwise><!-- dealing with an element that is NOT pre-defined -->
    <!-- does this element have a type attribute that is enumerable? -->
    <xsl:variable name="hasEnum">
    <xsl:choose>
     <xsl:when test="count(xs:complexType/xs:attribute[@name='type']/*/xs:restriction/xs:enumeration) > 0">
      <xsl:value-of select="true()"/>
     </xsl:when>
     <xsl:otherwise>
      <xsl:value-of select="false()"/>
     </xsl:otherwise>
    </xsl:choose>  
    </xsl:variable>

    <xsl:variable name="thisnode">
     <xsl:element name="{@name}"><!-- output the new element tag -->
      <!-- if this element will contain text... -->
      <xsl:if test="@type or count(xs:simpleType/xs:restriction|xs:complexType/xs:restriction)>0">
       <xsl:value-of select="@xclaim:id" />
      </xsl:if>
      
      <!-- output all the attributes this element has. if it has a type attribute and the values are enumerable, put a marker in -->
      <xsl:for-each select="xs:complexType/xs:attribute">
       <xsl:attribute name="{@name}">
        <xsl:choose>
         <xsl:when test="$isEntity">
          <!-- if this attribute is from a pre-defined entity, create a value placeholder for it that is unique yet predictable -->
          <xsl:value-of select="concat($parentID,'-',substring-after(@xclaim:id,'_'))" />
         </xsl:when>
         <xsl:otherwise>
          <xsl:value-of select="@xclaim:id" />
         </xsl:otherwise>
        </xsl:choose>
       </xsl:attribute>
      </xsl:for-each>
      <!-- this makes it all recursive! -->
      <xsl:apply-templates />
     </xsl:element> 
    </xsl:variable>
    <xsl:copy-of select="$thisnode" /> 
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template> 
 <xsl:template name="outputEntity">
  <xsl:param name="thisname" />
  <xsl:param name="parentID" />
  <xsl:for-each select="/xs:schema/xs:complexType">
   <xsl:if test="@name=$thisname">
    <xsl:for-each select="xs:sequence/xs:element|xs:choice/xs:element|xs:all/xs:element">
     <xsl:call-template name="outputElement">
      <xsl:with-param name="isEntity" select="true()" />
      <xsl:with-param name="parentID" select="$parentID" />
     </xsl:call-template>
    </xsl:for-each>
   </xsl:if>
  </xsl:for-each>
 </xsl:template>
 <xsl:template match="xs:annotation/xs:documentation">
 </xsl:template>
</xsl:stylesheet>

This XSLT is not quite generic enough to work with any XSD, but I hope to make it so before I'm finished (if anybody else wants to make that happen, please do!).  The XSD I'm applying this XSLT to is fairly complex, and includes some "named complexTypes" as well. For those who aren't aware (I know I wasn't), a named complexType is almost the equivalent of defining a variable, and then re-using that value throughout the rest of your document simply by referring to the variable's name. For instance, if I have a chunk of my schema that defines an address block, and there are several places within my xsd where this exact same xml is repeated, rather than actually write it each time i can write it one time, give it a name such as "addressblock", and then wherever I want to include that I simply refer to it by name.

Creating my skeleton XML then to fulfill step 5 in the system overview, I simply use the following line of code:
 

 <cfset skeletonXML = xmltransform(rtrim(ltrim(ToSTring(targXSD))),ltrim(rtrim(xslt))) />

 

CONCLUSION - FOR NOW

There are a LOT more details to all of this that I haven't touched on, like the xpath statements I use to find and populate the values of nodes, how I reproduce node trees that have multiple occurrences, etc. I'll save more of the nitty gritty details of that sort of thing for another post. In the meantime, if you have any questions or input (especially on how to make my xslt more efficient!), please do leave a comment for me! Most of this was fairly new territory for me, so there's likely lots of room for improvement and efficiency, and I'm all ears.

Posted by dougboude at 2:22 PM | PRINT THIS POST! | Link | 0 comments
23 February 2010
Real World Benefits of Encapsulation
I, as most if not all of you do, realize that to do any new development and NOT encapsulate my code would border on absurdity and I would be painting myself into a very narrow corner with regard to maintenance and future project expansion. This post, however, is focused on those projects where you are thrown headlong into an existing codebase that is not based, even loosely, on any kind of object oriented principles, has little to no rhyme nor reason to its architecture, and yet you are tasked with making functional modifications to it without breaking it. A little like playing "pick up sticks", right? Good luck with that! But, by leveraging encapsulation, I was able to accomplish just such a task with relative ease.

For those who may not know, encapsulation is a term that means exactly what it says: containment and separation. Remember those plastic easter eggs that you put things inside as a kid? Whatever you put in there, be it a toy, candy, grasshopper, or whatever, immediately became encapsulated, separated from and unaffected by anything outside of itself (barring direct sunlight, but we'll assume your code will never be subjected to that).

It isn't difficult to port such a definition over to the programmer's world, as we soon learn that by encapsulating specific types of functionality, we can re-use and even share code, thus preventing us from having to waste time re-creating work. In the real world, however, as is often the case, theory doesn't always mesh well with reality. Due to time constraints, lack of experience, laziness, or the inability or lack of authority to modify certain aspects of existing codebases, we very often do not implement encapsulation as we could and should. In a recent project of mine involving an older codebase, the absolute benefits of encapsulation became more than apparent, so I thought I would share the experience.


The task was to modify the import process in the administrative area of a large, "pre-Object Oriented" CMS (which was now living on a ColdFusion 7, "Obect Oriented-friendly" server). In order to minimize the invasiveness of the surgery I was about to perform, I wrote a CFC/class to handle all of the major work, the plan being to isolate the individual switch statement in the thousand line template responsible for the old import process and inject my modifications there and there only. The import process was no simple task, either, and it took a LOT of trial and error to get my code solidified to account for all possible anomolies. But, by encapsulating all of the new functionality in a CFC, I was able to write a quick and dirty test template that utilized my CFC to perform the test imports in isolation from the rest of this app, thus ensuring that all of my code mods, hacks, dumps, and random outputs had NO impact whatsoever on the live app. Once I had my test imports running successfully, complete with statistics being compiled and code written to properly output those stats, all I had to do was drop the relevant portions of my test template into the live app, make sure I had a fast and bullet proof rollback strategy in case something went awry (ALWAYS have a rollback strategy...never trust your own code), and run one final test with my CFC implanted in the live app. Yes, the app I had to modify had no test or qa server; I had to operate directly on the live patient, with no anesthetic.

So, even though you may be often called upon to perform mods to existing, older codebases, if the app lives in an environment conducive to Object Oriented approaches (or even if it doesn't! Don't forget about the ability to encapsulate via Includes if you must!), your path of least potential fallout, self-documentation, and future modifications made easy, is to heavily leverage encapsulation.
Posted by dougboude at 11:46 AM | PRINT THIS POST! | Link | 1 comment
19 February 2010
Registered Ajax Responders Not Responding Properly
Ajax Fundamentals
If you use the Prototype Javascript library and you chain your Ajax calls together at times, you may have run into the issue that I just dealt with regarding your registered responders not responding correctly! Since I spent more time than I care to share trying to figure out why my global responders weren't working like I thought they should, I thought I'd share what I learned. Hope it saves somebody some time! :)

Prelude

You may not be familiar with some of the terms I have used already, or some that I will use. If you feel you already have a good grasp on the lexicon surrounding Ajax and JS libraries, skip ahead to The Scenario; but if not, then allow me to clear up some of the fog before we press on. Please excuse me if I spend time on things that you may feel are "common knowledge", but in my experience there really is no such thing and I want to make sure every reader is thinking in the same context.

The Basic Nutshell of Ajax Chaining

In Prototype and other similar libraries, an Ajax call is typically of one of two flavors: simple request, and an update request.

Simple Requests

A simple request is one in which you ask your JS to make an http call to the server and hand you back the results that were delivered, whatever they be. At that point you will have written more Javascript to actually deal with those results manually. For example, let's say you display a select list containing possible food categories to your user. When the user selects a category, an Ajax request is made passing in the selected category, and a JSON representation of a record set containing foods in that category is returned. That JSON object is handed off to another function you wrote which will 'walk' through the foods and populate yet another select list with the specific items.

Update Requests

An update request is one in which you are asking your JS to make an http call to the server and stuff the results, whatever they be, directly into a DOM element (a DIV tag, a SPAN tag, etc.). Let's make a slight change to the example cited above. In this scenario, we already have a DIV tag in our html that we have designated as the place where our select list should appear. So, instead of having our server code return a raw JSON representation of the data, we write our server code to produce the HTML for the fully populated select list ("<select name='selFoods'><option value='1'>Crescent Rolls</option><option value='2'>Dog Biscuits</option>....</select>"). It is this string that our Ajax call receives, and this returned value is stuffed directly in to the DIV tag we designated as the recipient. Make sense?

Registering Responders

It is quite typical (and useful) to show to your users some kind of "working" image to indicate that the Ajax call is in progress. We may also wish to disable certain items on the user interface while calls are in progress, or overlay an opaque div to prevent the user from clicking anything. The thing we want to do and/or display while an Ajax call is in progress is what we refer to as our "Responder". Within our Javascript, we will tell our library something along the lines of "hey, anytime an Ajax call is created (started), I want you to do this; and anytime you see an Ajax call finish, I want you to do this". The act of telling our library what we want to happen automatically regardless of the Ajax call is called "registering responders". You define (register) them one time, and they just work. :)

Why Would I Ever Want to Chain My Calls?

I'm glad I asked! Let me just give you a working example to help illustrate why we might do this on occasion, and how we do it.
In my application, I am allowing a user to upload a data file. When the upload of the file is complete, I then need to kick off a series of things to occur, and I want to update a DIV with the current status of each call as they happen. The first call is to validate the uploaded file, so I write an Ajax simple request that tells the server where the file was uploaded and what kind of file it is supposed to be. This call will either return a true or false, depending on whether the file passed or not. If it failed, I want to tell the user that the file was bogus and cease any further processing. If it passed, I want to kick off another Ajax simple request that tells my app to go ahead and proceed with step 1 of the file processing. That call will return success or failure along with status messages, and then will kick off the third step in the processing.

Now, the way we "connect", or "chain" these calls together is by taking advantage of the fact that our library knows when a call is finished. When we write the JS for call 1, we tell it "when you receive a response from the server, pass that response on to Call 2 for evaluation". Call 2 is written to check the success status, and if all is well will make an Ajax simple request which itself has been told to pass its results on to Call 3. Here's an abbreviated example of what I'm talking about (some code ommitted for brevity):

function Call1(){
   new Ajax.Request(validateUploadURL,{parameters:params,method:'post',onSuccess:Call2});
}

function Call2(response){
   var objRetval = response.responseJSON;
   if(!objRetval.success){
        $('status').update(objRetval.msg);
        return;
   } else {//call successful! carry on
     new Ajax.Request(fileProcess1URL,{parameters:params,method:'post',onSuccess:Call3});
   }
  
}

function Call3(response){
   var objRetval = response.responseJSON;
   if(!objRetval.success){
        $('status').update(objRetval.msg);
        return;
   } else {//call successful! carry on
     new Ajax.Request(fileProcess2URL,{parameters:params,method:'post',onSuccess:TheEnd});
   }
  
}

function TheEnd(response){
  var objRetval = response.responseJSON;
  $('status').update(objRetval.msg);
}

See how they're all connected? Chain Chain Chaaaaaaainnnnnn... yeah.

The Scenario

In my scenario, I have a file upload process that is composed of three different Ajax requests, all chained together in a conditional, synchronous fashion (I know there are other ways to approach it, but this is the one that fit my needs the best). I have registered global responders to show/hide an animated spinner to indicate when a request has started and finished (I 'show' it when the request starts, I 'hide' it when its finished). Now, in my mind, these global responders should toggle that gif every time a request starts or stops, so in this scenario, it should show/hide three different times. But, that isn't how it worked. The gif would show during the initial call, and hide after that call was finished; Then it never showed again, despite the fact that there were multiple additional calls made! Here is how I registered my responders:

    Ajax.Responders.register({
          onCreate: function() {
            showUploadWorking();
          },
          onComplete: function() {
            hideUploadWorking();
          }
        });


After much experimentation, it occurred to me that perhaps even though the responder was global, maybe I needed to be concerned with manually 'watching' the active request count variable. I don't understand why I SHOULD have to, but that did turn out to be the solution. Here is the properly working responder registrations:

    Ajax.Responders.register({
          onCreate: function() {
            showUploadWorking();
          },
          onComplete: function() {
              if(Ajax.activeRequestCount == 0){
                  hideUploadWorking();
              }
          }
        });

Notice the only difference is in the onComplete function...I am 'watching' the activeRequestCount variable (which as you may have guessed is simply a counter telling us how many requests are currently not yet finished) and conditionally hiding my responder.

Doug out :0)
Posted by dougboude at 11:20 AM | PRINT THIS POST! | Link | 1 comment
04 January 2010
Finally Found a Use for CFTHREAD

You know how ColdFusion is so robust that oftentimes there are those tags and functions that exist, yet you've never once had an occasion to use them? Well, today I used a tag for which I've previously not had a need: cfthread. I figured I'd share my use case and implementation in case it piques the curiosity of other CFTHREAD virgins.

The Scenario

Our company has a client who requested that we automate the portion of the process whereby a document originating from them is returned to them by us after we're finished with it. They've built some kind of "folder watching" process on their end, and so asked if we could sFTP the final file to their server. Digging in to the legacy code that performs the final processing, I identified the place where I could surgically place the code to perform this work. But, I didn't want to make the user to encounter any additional delays in page load time.

The Solution
Enter CFTHREAD, a sweet little tag that will allow me to very simply execute a chunk of code asynchronously and unattached from the page it resides within. By simply encompassing the autonomous bit of code within cfthread tags, CF will run it in parallel to the page itself. But, though I did want the upload itself to occur without hindering the execution of the remainder of the page, I DID want to give my user a final message indicating whether or not the upload was successful. In other, more relevant words, i wanted this autonomous process to join back up with the original request at the end, and update a message div on the page appropriately.

Since my standalone process was for sFTP purposes, I'm going to use that snippet of code in my example. Here is the section I needed to run on its own:

<cfset ftpingfile = false />
<!--- if this is a targeted client AND the estimate was approved, FTP it to the target site --->
<cfif thisCarrier IS targCarrier AND (PDFMAccRjt IS "A" OR PDFCAccRjt IS "A")>
 <!--- fetch the ftp settings for client... --->
 <cfquery name="qryGetSettings" datasource="#dsn#">
  select ftpsite,ftpusername,ftppassword,ftpDestFolder
  from clientTable
  where carrier = <cfqueryparam value="#targCarrier#" cfsqltype="cf_sql_varchar" />
 </cfquery>
 <cfif qryGetSettings.recordcount eq 1 AND qryGetSettings.ftpsite IS NOT "">
   <!--- get path to the PDF file --->
   <cfset pathToPDF = "#expandpath("\PDF")#\#InvHolder#.pdf" />
   <cfif fileexists(pathToPDF)>
    <cfset ftpingfile = true /><!--- need this flag for evaluating things at the end of this template --->
    <!--- create new file name... --->
    <cfset newfilename = "#claimnum#-01--#dateformat(now(),"mmddyyyy")##timeformat(now(),"HHmmss")#.pdf" />
    <cfthread action="run" name="FTPit">
     <cfoutput>
      <cfset objFTPProperties = {
       Server = "#qryGetSettings.ftpsite#",
       Username = "#qryGetSettings.ftpusername#",
       Password = "#qryGetSettings.ftppassword#",
       Secure = true
      } />
     </cfoutput>
     <cfftp
      action="open"
      connection="objConnection"
      attributeCollection="#objFTPProperties#"
      />
      <cfftp
      action="putfile"
      connection="objConnection"
      localfile="#pathToPDF#"
      remotefile="#qryGetSettings.ftpDestFolder##newfilename#"
      transfermode="auto"
     />
     <cfftp
      action="close"
      connection="objConnection"
     />
    </cfthread>
   </cfif><!--- if the estimate pdf exists --->
 </cfif><!--- if we found this carrier's ftp info --->
</cfif> 

 

 

 

 

So, while the above code is executing (assuming we met our conditional criteria), the remainder of the page runs. At the end of the template, I use the following code to join the upload request back to the original request:

<!-- div to hold status message for parallel process... -->
<div id="uploading" name="uploading" style="color:red;">
 <cfif ftpingfile> 
  <br><br>Please wait. Uploading file to #targCarrier#... <img src="images/spinner.gif" align="absmiddle" /><br><br>
 <cfelse>
  <br><br>NO PDF UPLOAD ATTEMPTED. EITHER NO PDF WAS FOUND TO UPLOAD, OR THE CLIENT HAS NO FTP SETTINGS IN THE SYSTEM<br><br>
 </cfif>
</div>

<cfflush><!--- output the content of the page thus far to the browser so the user has something to see/do until our process finishes --->

<!--- if we attemptd to ftp a file... --->
<cfif ftpingfile>
 <cfthread action="join" name="FTPit" />
 <cfif cfthread.FTPit.status IS "Completed">
  <script>
   document.getElementById('uploading').innerHTML = "<br><br>Estimate Successfully Uploaded to <cfoutput>#targCarrier#</cfoutput>!<br><br>";
  </script>
 <cfelse>
  <script>
   document.getElementById('uploading').innerHTML = "<br><br><strong>There was a problem uploading the estimate. Here are the details:</strong><br><cfoutput>#JSStringFormat(cfthread.FTPit.error.detail)#</cfoutput><br><br>";
  </script>
  
  <!--- send details of failed attempt to Doug --->
  <cfsavecontent variable="failedFTP">
   <cfdump var="#cfthread#">
  </cfsavecontent>
  <cfmail to="
dboude@adomain.com" from="administrator@adomain.com" type="html" subject="failed FTP attempt for invoice #InvHolder#">
   FTP process failed. Here are the details:
   <br>
   <cfoutput>#failedFTP#</cfoutput>
  </cfmail>
 </cfif>
</cfif>

That's it! So while the user is looking at and potentially interacting with their page as usual, the status message div I placed will be updated appropriately as soon as the upload finishes. Just to clarify, the browser will still show its "loading" status until the threaded ftp process finishes, since we told CF to join it back to the original request. But, because we joined it at the end, and we cfflushed prior to that, the user will see their typical output in the meantime.

Posted by dougboude at 4:15 PM | PRINT THIS POST! | Link | 3 comments
21 December 2009
A SWEET Little MySQL Function: Group_Concat

The Scenario

You're writing code to perform authentication for a web-based app. Your users live in one table (Site_User), your list of site permissions live in another table (systemRoles), and the two are related via a junction table (jctUserRole) that manages the many to many relationship there. Your backend database: MySQL version 5 or better.

So, in an ideal world, you will be able to write a single query that will both authenticate the user AND retrieve a comma delimited list of their roles contained in a single field; if their credentials are good, your result will only have a single record in it. Typically, however, because of the way many to many relationships are normalized, the "one query" you want will be returning multiple rows...one row for each permission the authenticating user is related to. For example, here is the typical way one would write the authenticating query:


  SELECT u.id AS userid, u.firstname, u.lastname, r.rolename
  FROM Site_User u
  INNER JOIN jctUserRole j ON j.userID = u.id
  INNER JOIN systemroles r ON r.id = j.roleID
  WHERE u.username =  'username'
  AND u.password =  'password'

 

This produces a result that looks like this:

standard mysql query for authentication

We would then be required to write additional code in order to more properly "package" up this user's list of permissions for use within the system.

But enter the wonderful world of MySQL 5, and the 'group_concat' function. Group_concat allows you to concatenate values into a list based on a specified grouping, and will produce a list of unique values delimited by whatever character you choose (but defaults to a comma). So transforming the above query just a little bit, we get:

 

  SELECT u.id AS userid, u.firstname, u.lastname, GROUP_CONCAT( r.rolename ) AS roles
  FROM Site_User u
  INNER JOIN jctUserRole j ON j.userID = u.id
  INNER JOIN systemroles r ON r.id = j.roleID
  WHERE r.username =  'username'
  AND r.password =  'password'
  GROUP BY u.id, u.firstname, u.lastname

 

And a result set that now looks like this:

mysql authentication query using group_concat

Man, SO much cleaner and nicer, eh? Now you can simply treat the "roles" value as a list...explode it into an array if you want, or leverage ColdFusion's myriad of List functions to interact with it.

Since discovering group_concat, I've found several other uses for it as well, particularly when producing reports for my internal clients...it saves me a lot of iterating over result sets in order to produce value summaries.

You can read the documentation for this function here

Posted by dougboude at 11:29 AM | PRINT THIS POST! | Link | 26 comments
17 December 2009
Using a CFC as a Configuration File
Leveraging The Decorator Pattern
SO, you too find yourself needing to write something that could really benefit from having its settings contained in some external resource. Let's see, what are the choices...we could possibly store our settings in the database and retrieve them at application initialization, if our architecture permits that; we could create an xml file that contains our settings in a nice, nested, readable manner; we could (as one person I mentioned this to suggested) put our settings into an INI file (ya think this person does a little too much .NET? :) ); or we COULD, as I have recently become enlightened to, put our settings into a CFC. Since I am especially partial to the idea of being able to have the choice of hard coding my settings and/or leveraging Coldfusion's dynamic personality to populate them, I went the way of using a CFC.

Allow me to preface the remainder of this post by saying thank you to those out there who have already traveled this road and have made their work open source. By dissecting several different code samples and doing a lot of experimentation, I have been able to enlighten myself as to the mechanics and theory behind it.

SCENARIO
For the sake of illustration, let's say I'm writing a plugin that will be used to provide shopping cart functionality to whatever app is implementing it. Of course, every app's needs, environment, and settings are different, so our plugin needs to be configurable. My goal then is to create a CFC that will contain a nested structure/array of settings that my shopping cart plugin CFC can utilize to control how it is implemented. Sounds simple, right? Or is it... :)

For those who want to skip right to the heart of the matter, you may do so now. For those who, like me, enjoy the layered learning process and the excitement of examining the pieces before putting the puzzle together, let's take a quick look at essential the concepts that allow using a CFC as a configuration file to work.

THE ESSENTIAL CONCEPTS

SCOPE
You already know about scopes in CF and are well versed in how and when to use them, I'm sure. You know, for instance, that if you create a variable within a template and ommit the scope, it is automatically put into the variables scope. You should also be aware, however, that any functions you create within your template also occupy a place within the variables scope. Take this template, for instance, and the accompanying dump:
<cfset thisvar = "Hello" />

<cffunction name="returnIt" access="private" returntype="any">
    <cfargument name="incoming" type="any" />
    <cfreturn arguments.incoming />
</cffunction>

<cfdump var="#variables#" label="MyTemplatesVariables" />


dump of variables scope in a coldfusion template

Interesting, eh? Keep this in mind.

Now, you may also already be aware of the fact that within an instantiated CFC you have scopes as well. I won't spend any time elaborating on those scopes or the differences between them (did that in another post a while back). But I WILL hit you with a short CFC and a dump of its own internal variables scope. Based on our little experiment above with our template, look at the CFC internals and formulate an idea of what you would expect to see. You may see something surprising in this one.

Component A.cfc
<cfcomponent output="false">
    <cffunction name="Foo" access="public" output="false" returntype="any">
        <cfargument name="nuttin" type="string" />
        <cfreturn arguments.nuttin />
    </cffunction>
   
    <cffunction name="Man" access="private" output="false" returntype="any">
        <cfargument name="nuttin" type="string" />
        <cfreturn arguments.nuttin />
    </cffunction>
   
    <cffunction name="Chu" access="remote" output="false" returntype="any">
        <cfargument name="nuttin" type="string" />
        <cfreturn arguments.nuttin />
    </cffunction>
   
    <cffunction name="getMyVariablesScope" access="public" returntype="any">
        <cfset var theseVars = "" />
        <cfsavecontent variable="theseVars">
            <cfdump var="#variables#" label="ComponentAVariables" />
        </cfsavecontent>
        <cfreturn theseVars />
    </cffunction>
</cfcomponent>


Code to instantiate A and retrieve and output its variables scope:
<cfset objA = createobject("component","A") />
<cfoutput>#objA.getMyVariablesScope()#</cfoutput>


dump of variables scope from within a coldfusion cfc

As you probably expected, there in the variables scope are all the functions of the CFC (regardless of what access type we gave them). Additionally, we have the often shunned THIS scope...and looky what THIS is harboring. That's right boys and girls, a reference to (not copy of) the CFC instance. THIS, as you may or may not know, IS accessible from outside the CFC instance. So if we modify slightly the code we used above to instantiate the object, and add a line to give our object another attribute, a subsequent call to getMyVariableScope will reflect the results.

Code to instantiate A and retrieve and output its variables scope:
<cfset objA = createobject("component","A") />
<cfset objA.someFunkyParam = "Doug Boude Rocks" />
<cfoutput>#objA.getMyVariablesScope()#</cfoutput>


dump of variables scope from with a coldfusion cfc

This is perfect behavior for what we're wanting to do! Oh...what is it we are wanting to do? Let's get to the next and last essential concept and see!

THE DECORATOR PATTERN
Here's a term I'm sure most of you have heard of. If you haven't heard specifically of this pattern, I'm sure you've at least heard of design patterns in general. Well, you can and should think of this one exactly as its name sounds. Let's say you figure out that the fastest way to meet chicks is to buy a dog (since you can't afford to buy a baby), so you grab a "mildly malformed" West Highland White terrier from the "we finance anyone" pet store down the street. You invest a few more bucks and take little Timmay to the groomer. When you pick him up later, he is now sporting a lovely red tartan neckerchief and smells of Eau de Tim McGraw. Timmay has been decorated! He's still a midly malformed Westie, only NOW he ALSO has been given the ability to attract hot chicks, AFTER he was born. I know, it was an elaborate analogy, but this is how I entertain myself. So then...since in our mind's eye we want our Config.CFC to be beautifully simple for the end user to populate and we don't want them to have to ignore (and try not to touch) lots of other support methods or worry about inheritance dependencies, what we ideally need to be able to do is provide them a clean, simple CFC that contains only a configure method. When they populate it (or code it to grab its values from elsewhere), we'll take it and then DECORATE it with a method that will allow us to retrieve that same configuration info! Okay, enough of concepts and ideas. On to the solution.

THE SOLUTION
The key players in this scene are:
"ShoppingCart.cfc", a plugin designed to provide shopping cart functionality to any application;
"Config.cfc", a cfc whose sole purpose in life is to provide the developer with a single place in which to maintain relevant ShoppingCart settings;
"configTest.cfm", a cheesy template who just kinda glues this stuff together for us.

First, configTest.cfm.
<cfset objShoppingCart = createObject("component","ShoppingCart").load("Config") />
<cfdump var="#objShoppingCart.getSettings()#">


Straightforward. We're creating our shoppingcart cfc, calling the load method and passing in the class path to our configuration cfc. Then, calling shopping cart's getSettings method just to prove to ourselves that it worked.

Config.cfc.
<cfcomponent output="false">
    <cffunction name="configure" access="public">
        <cfscript>
            settings = {
                environment = {
                    DSN = "dbserver",
                    EmailServer="mail.onelove.com"
                },
                cartSettings = {
                    maxCartItems = "15",
                    salesTax = "true",
                    cartHeading = "One Love to Rule Them All",
                    cancelMessage = "Aw baby, why you leavin?",
                    thankyouMessage = "Stay Cool",
                    daysLeftTillXmas = ceiling(datediff("h",now(),createdate(2009,12,25))/24)
                }
            };
        </cfscript>
    </cffunction>
</cfcomponent>


This is a "hard coded" version, where all the settings are just there. You'll notice at least one of them taking advantage of a PRIME reason to do configuration this way: dynamic values!

ShoppingCart.cfc (scaled down to ONLY the relevant methods needed to illustrate the point of this blog post).
<cfcomponent output="false">
    <cfset variables._settings = "" />
   
    <cffunction name="load" access="public" returntype="any" hint="I load settings from an external CFC">
        <cfargument name="configpath" type="string" required="true" />
        <cfscript>
            var objConfig = createobject("component",arguments.configpath);
            //need to run objConfig's configure() method in order to actually create the
            //configuration settings structure
            objConfig.configure();
           
            //decorate objConfig with our local _getConfig  method :)
            objConfig.getConfig = variables._getConfig;
           
            //execute the method we just added to objConfig to retrieve objConfig's 'settings' struct
            variables._settings = objConfig.getConfig();
           
            return this;
        </cfscript>
    </cffunction>
   
    <cffunction name="getSettings"
        access="public"
        returntype="any"
        hint="I am the method used to get our local configuration values.">
           
        <cfreturn variables._settings />
    </cffunction>
   
    <cffunction name="_getConfig"
        access="private"
        returntype="any"
        hint="I am the private method that will decorate our configuration object!">
       
        <!--- we're counting on our configuration object to have a variable called 'settings'... --->   
        <cfreturn variables.settings />
    </cffunction>
   
</cfcomponent>


You'll want to study this one a bit, and let me point out a few things.

The last method, "_getConfig", serves no other purpose than to decorate our configuration object. Now, you may be thinking, "but I could just put that method inside my configuration object and tell my users to just ignore it!". Yeah, you could; but that's cheesy. So, to avoid smelling of aged Camembert, we hide this method here and add it to our configuration object within the "load" method.

Here's the results of calling configTest.cfm:
dump of coldfusion structure

SUMMARY
In a nutshell, because an object always sports a THIS scope, AND because local functions always exist in the VARIABLES scope, it is a simple matter to attach a new method to an object. If the method you are attaching is written correctly, it can access the methods and values within the object that all of the native methods can. By leveraging this very cool relationship, you can move configuration settings to a CFC.


POST SUMMARY
You may also be thinking, "Dude, I or someone else I don't trust could wreak so much havoc in my code by decorating objects with well-written methods!". And dude, I do believe you're probably correct. I haven't researched much into the security ramifications, or how many ways one might find to defile the sacraments of "clean OO"; but, I know that with CF comes great power, and with great...oh, you know. We've chosen the more lenient world of CF, so it's up to us to respect those freedoms and work with them accordingly. If you don't feel good about that, well, there's always the more communistic "typed" languages, with their greater complexities and unbending rules. :) Hey, some people prefer domination ;) To each his own.
Posted by dougboude at 12:41 PM | PRINT THIS POST! | Link | 0 comments
01 December 2009
(User) Interface-Driven Architecture vs Model Driven Architecture
When Universes Collide

Part of what makes me a professional developer, in my opinion, is the fact that I always make it a point to learn and evolve. When what I knew and was certain of yesterday becomes intertwined with some new thing I learn today, great things happen and my understandings and persepectives change. Recently, that very thing occurred regarding my mindset on how to approach any new development project, so I thought I would share the experience and the results.

First, Some Definitions

(User) Interface-Driven Architecture  (aka IDA, care of Mr. Clark Valberg, esquire :) )

(User) Interface-Driven Architecture means that the first thing I do is create a visual, hopefully somewhat interactive prototype of what my finished product will be; I build a mockup of the GUI that demonstrates and represents the functionality of the app as seen from an end user's perspective. At this time, I don't even think about the code that will power the app (at least I try hard not to), but rather focus on the end results. Once the prototype is approved by the client, it is then reverse engineered by the architect and transformed into a living, breathing application.

Model Driven Development

Model Driven Development takes an opposite approach. Forcing yourself to not even consider what the user interface might look like or how it might behave, you architect the application's model based solely on use cases, static class diagrams, and whatever other artifacts you have been given that describe what goals the app will accomplish.You remove yourself so far from the user interface, in fact, that you architect your classes in such a way that ANY user interface could consume your API and all scenarios, present and potential, would be covered.

A Little History

My earlier development years were spent coding mostly by instinct, as I had no formal training on the subject, no mentor whom I desired to emulate, and nobody telling me how they wanted me to do it. So, depending on the project and what kind of direction had been given me, I would often find myself starting a project in different places. If I had been given a clear scope of the functionality the app was to provide, I would typically begin by designing a relational database schema to accommodate that scope. If someone had drawn me a picture on a napkin and handed it to me telling me to "build this", I'd start with an html GUI that simulated functionality so that I could make sure the vision of the requester was the same as mine. And if I was left to my own devices as to how I would approach development, then it just depended on whether or not I was in the mood to design a database or write code as to which I did first.

The next natural step in my personal evolution was to attempt to leave chaos behind and adopt standards and methodologies to allow me to be more efficient and DRY. This abandonment of solely using instinct as a methodology (which actually didn't work too badly for me) occurred when CF 7 was about 6 months old. At this point I began listening to the OO buzz, attending conferences, opening my mind to ideas and concepts that were new to me, and leveraging CFCs and frameworks; all in an effort to apply rhyme and reason to my approach to development. Even so, there was still no clear starting point; every project began differently.

So, about two years ago, I settled on the fact that (User) Interface-Driven Architecture was THE way to go. By designing the customer-approved, semi-functioning GUI first, I could avoid the developer's most arch nemesis, Scope Creep, and ensure that both the customer and myself both had the exact same end result in mind. It makes perfect sense, right? It does indeed. But, does it always? This is where my road forks.

For the past six months, our local RIA user group has been building a media player as a group using AS3. The group manager is basically leading the sessions, as he is a seasoned AS3 architect, and it has been within these sessions that I have come to see and appreciate the value and application of the Model-driven approach. It all gelled for me during our most recent session when one of the attendees who was having some difficulty following the abstractness of the classes and their relationships raised his hand and asked that we stop working on the model and build the GUI that would consume it. For him, seeing the GUI would help him assimilate all of the model pieces we had been writing for the past several months. The group manager then took a moment to expound upon why he thought it good to purposefully NOT create the GUI. In a nutshell, creating the model with the GUI in mind would cause us to design a backend that would be limited to only what the GUI needed and would hinder our creativity as we thought out the specifics of our model's behavior, organization, and flexibility.

The two poles of my thinking collided at that moment, and out of the momentary synapsial confusion that followed emerged the same two ideas, intact. This time, however, rather than me viewing them as opposing ideas where only one could rule, I saw them as inverse to one another, yet equal in their relevance and application. Which one ruled, then, depended entirely upon the circumstances and goals of the specific project at hand.

(User) Interface-Driven Architecture evolved in response to the need to help clients answer the one question they have such a hard time with: "What is it that I really want?", and it is in this context where this approach rules. With a visual, interactive interface in place at the beginning of the project, there is little room for unplanned scope creep to stall or even derail a project's progress. If the goal then of the project is to "build this customer exactly what he wants", then this is definitely the approach I would go with, hands down.

In the opposite universe, there are times when the goal of the project is to produce something more of an enterprise nature that must be able to expand and contract, bear dynamic loads, and present a consumable API to third parties of unknown origin. This scenario then clearly shines the spotlight on the architecture of the model. Although the user interface always matters, if we were to design it first and then attempt to build a model that accommodated only its needs, we would in many senses be forcing ourselves to think inside of the box that the GUI built around us, and thus likely end up with a product  much less robust than it could have and should have been. For a project such as this, then, I say that the Model-driven approach is the way to go, hands down.

 


Conclusion

As you can tell, I still feel very passionate about (User) Interface-Driven Architecture, and will continue to use it and apply it where I see it as a best approach. It is very sound, and very effective at producing happy customers and a nearly unmovable project scope, except where it is concentual. I now also feel very passionate about Model Driven Architecture as well, and find it to be a more liberating experience, if you will, when designing applications of a grander, more enterprise scale. This approach places absolutely no bounds on your ability to explore possibilities and configurations, the only confines at all (if you can call them that) being the resulting API your project will expose, if one is to be created.

Of course, I'm always learning and evolving, and one day in the future I may have a completely new perspective on application architecture. It never hurts, though, to share your epiphanies along the way; you never know what will be produced when your epiphany collides with someone else's universe.

Keep evolving, my friends.

Posted by dougboude at 2:18 PM | PRINT THIS POST! | Link | 10 comments
30 November 2009
PayPal IPN Coldfusion CFC
ppIPN
Thursday morning I overheard our finance lady mentioning an online form she had created to handle registrations for a conference we're hosting in a few months. My curiosity piqued (since usually things like this end up on my plate, at least in part), I inquired further and found that she was also including a "Pay Now" PayPal button so the registrants could complete the process. I decided to make some work for myself and suggested that we save the registration information to our database (as opposed to just emailing form content to designated people, as was the plan), and leveraging PayPal's Instant Payment Notification (IPN) service to automatically capture payment information as it happens.

In a nutshell, PayPal's IPN works like this:

1. A customer creates an order record on your site and is then sent to PayPal to provide payment for the goods or services they are purchasing from you;
2. Upon completion of their payment, a "silent ping" takes place from PayPal to the URL of your choice. This ping contains several pieces of information about the transaction.
3. The code at the URL you told PayPal to ping then pings PayPal back again with the exact same information posted to it; If the information you sent back is valid, PayPal will respond simply with the word "Valid"; if the information you pinged back was NOT valid, PayPal will respond with "Invalid".
4. If your response from PayPal was "Valid", you should feel good about proceeding with the update of your customer's purchase record in the database using the information contained in the original ping; If the response was "Invalid", handle that as you will.


Even though the process is fairly simple, writing the code to deal with IPN can be somewhat challenging, made mostly so by the fact that it's an "invisible ping". Troubleshooting any kind of asynchronous process (which is what IPN is) requires special approaches. So, since I already went through the process to produce what I call "ppIPN.cfc", I figured I'd go ahead and share it (if you want, you can skip directly to the download). I tried to keep it easily configurable to accommodate different apps, but at the very least it's a good set of starter code if it doesn't meet your needs as is.

ppIPN.cfc actually consists of TWO CFCs: ppIPN.cfc and ppSettings.cfc, which ppIPN extends. As you probably guessed, ppSettings is where you will put the different variables that affect how your implementation of ppIPN will work. I made it a separate CFC so that you didn't have to maintain potentially sensitive information within the one component, and so that you could put ppSettings wherever your heart desires.

USE

Note:
ppIPN assumes three things:
1. that a record for a customer already exists in your table, and the payment notification ping from PayPal is for the purpose of updating that existing record with transaction details;
2. that you are sending the ID of your customer's transaction record to PayPal by using PayPal's "CUSTOM" form field (<input type="hidden" name="custom" value="16" /> or ...&custom=16&....)
3. your cfmail tag doesn't require a mail server, username, or password to be specified (they are already set up in your cfadmin)

To use ppIPN, simply place it and the corresponding ppSettings.cfc on your webserver where they can be called via http. Adjust the 'extends' path in ppIPN.cfc if you located your ppSettings.cfc anyplace other than the same folder where ppIPN.cfc lives.

Next, edit your PayPal account to enable IPN and enter the following URL as your post back URL:

http://[your web server]/ppIPN.cfc?method=postback

Lastly, edit your ppSettings.cfc to accurately reflect your environment.

That's it!

Here are the settings and a sample:

dsn - name of the datasource you wish to use
receiverEmail - the primary email address associated with your paypal account. (Though there can be several email addresses associated with your account, it is the primary email address that will be included in the IPN call from paypal, regardless of which address the payment was actually sent to)
notifyEmails - comma delimited list of email addresses you wish for notifications and status messages to go to
adminemail - the email address that will be used in the FROM of emails sent by ppIPN
ppURL - the url to use when re-posting data back to PayPal for verification

tblPayment - name of the table you're using to store transaction information
fldID - the name of the identity field for the table named above. ppINC assumes this will be an autoincrementing integer
fldFirstName - the name of the first name field in the tblPayment table. This will be the purchaser's first name.
fldLastName - the name of the last name field in the tblPayment table. This will be the purchaser's last name.
fldTransactionID - the name of the field in tblPayment in which you will be storing PayPal's transaction ID value. Should be varchar.
fldPaymentAmount - the name of the field in tblPayment in which to store the payment amount (PayPal's 'mc_gross' variable value). ppINC assumes this field is of type DOUBLE
fldPaymentDate - name of the field in tblPayment to store the transaction date. ppINC assumes type DATETIME


Here is a sample ppSettings.cfc:
<cfcomponent output="false">
    <cfscript>
        instance = structnew();
        instance.settings.dsn = "absolutezero";
        instance.settings.receiverEmail = "sclause@northpole.com";
        instance.settings.notifyEmails = "sclause@northpole.com,rreindeer@northpole.com";
        instance.settings.adminemail = "administrator@northpole.com";
        instance.settings.ppURL = "https://www.paypal.com/cgi-bin/webscr";
       
        //DATABASE SETTINGS
        instance.settings.tblPayment = "REGISTRATIONS";
        instance.settings.fldID = "ID";
        instance.settings.fldFirstName = "FIRSTNAME";
        instance.settings.fldLastName = "LASTNAME";
        instance.settings.fldTransactionID = "PPTXID";
        instance.settings.fldPaymentAmount = "PMTAMOUNT";
        instance.settings.fldPaymentDate = "REGDATE";
    </cfscript>
</cfcomponent>


TESTING
Here is a sample form that can be used to test your installation of ppIPN outside of using a PayPal Sandbox (feel free to add additional PayPal fields, but currently these are the only ones ppIPN looks for). To ppIPN, this form's post appears the same as a post from PayPal:

<html>
    <head>
        <title>
       
        </title>
    </head>
    <body>
        <form action="ppIPN.cfc?method=postback" method="post">
            <input type="hidden" name="payment_status" value="completed" />
            <input type="hidden" name="mc_gross" value="155.00" />
            <input type="hidden" name="txn_id" value="asdfsdgerweresfggzz" />
            <input type="hidden" name="receiver_email" value="rniemeier@masonclaims.com" />
            <input type="hidden" name="payer_email" value="dougboude@gmail.com" />
            <input type="hidden" name="custom" value="16" />
            <br>
            <input type="submit" value="submit" />
        </form>
    </body>
</html>


HOW PPIPN WORKS
1. Your customer performs a checkout on your site, creating a record in your transaction table without a payment status. Via whatever mechanism you select, they are sent to PayPal to complete the transaction, along with the ID of their record tucked within the "custom" field. VERY IMPORTANT: DO pass along the ID for the transaction record in the CUSTOM field!
2. Customer completes their transaction on PayPal. PayPal pings ppIPN with several different values.
3. ppIPN captures the incoming values, arranges them into a query string, and posts them back to PayPal.
4. PayPal validates the values ppIPN passed back and responds with either "Valid" or "Invalid".
5. If "Invalid" is received, ppIPN wraps up all the relevant values received and emails them to the addresses specified in the "notifyEmails" setting.
6. If "Valid" is received, ppIPN performs several additional checks:
  is the status 'Completed'?
  does this a unique transaction id that does not already exist in the db?
  is the incoming receiver email value equal to that specified in the settings?
If the answer to the above is 'yes', we proceed to update the db. Otherwise, we put together a nice status email and send it out.


ADDITIONAL VALUES FROM PAYPAL

There are many variables that get returned to you via IPN calls from PayPal; ppIPN only captures and saves a few of them. To find out more about the other variables available to you, you can visit this page on PayPal's site: http://bit.ly/paypalIPN

If you wish to capture more values than what ppIPN is currently capturing, it will require some editing of the cfc. To save you some time, here are some hints on what you'll need to touch should you desire to alter what data is currently persisted:

1. Edit ppSettings.cfc and add the additional fields you want to capture (use existing settings as a guide);

2. Edit the query in ppIPN.getPaymentRecord; add your additional fields to the SELECT clause (using the variable names, not the variable values); Be SURE to alias those fields!

3. Add code in ppIPN.doValid, just below line 113, to set the values you wish to capture. In this area, you will utilize the Alias values you created in step 2. Note: All incoming PayPal variables are initially captured to the global structure "instance.incomingArgs", so any and all values that arrived in the call will be there.

Here is how to capture a new value for persistence:
<!--- set this record's value for txn_type... --->
<cfset existingPaymentRecord.tran_type = instance.incomingArgs.txn_type />

In this example, tran_type is the alias we gave to our transaction type field. The corresponding value we want to save coming from PayPal was captured in the instance.incomingArgs.txn_type field. txn_type is the name PayPal provided, and can be found in the IPN documentation.

4. Edit ppIPN.updatePaymentRecord, altering the query to include your new fields. You can use the existing field/value pairs as an example for adding additional ones.

That's it!

Hope this saves someone a little time.

Download the ppIPN ZIP
Posted by dougboude at 11:11 AM | PRINT THIS POST! | Link | 6 comments
17 November 2009
Coldbox 101 Training Review

This past weekend I had the privilege of attending Ortus Solution's Coldbox 101 training class in Grapevine, Texas. It is true that I did pay for the opportunity, but the price was obscured and more than justified by the quantity, quality, and cohesiveness of the knowledge that was shared. Coldbox's master architect himself, Luis Majano, was the instructor, and it was with the same level of zeal and attention to detail that he puts into his framework that he also enlightened and empowered his students on Coldbox. In this case, the "no student left behind" policy was absolutely successful. With a student body composed of everyone from absolute CF beginner to CF expert, and from designer to "design-blind", Luis ensured that the content was both palatable and applicable to one and all.

I came to the class being what I would best describe as semi-proficient in Coldbox. I've written a large Coldbox app, done a few Coldbox presentations, blogged about some aspects of Coldbox, and was even hired recently to present a full day instructional course on Coldbox to a local group of San Antonio developers. But, as with any subject that one teaches themselves, there are always gaps in the knowledge you glean from reading docs and your own experiences. It was these gaps I came to the training hoping to fill, and fill them I did. I was given solid insight into how the framework itself is architected, allowing me to fully visualize the request lifecycle and the framework components that interact with and manipulate it; I honed my knowledge of ALL of Coldbox's core concepts, such as interceptors, views,layouts, viewlets, and plugins; I gained insight into the workflow and approaches that Luis himself uses on a daily basis; and my mind was opened up to a whole new facet of the development lifecycle that I personally have always avoided: unit testing.

Coldbox is SO keen on the idea of unit/integration testing that it has the built in ability to simulate itself! In a nutshell, Coldbox makes it VERY simple and easy to test entire events (the equivalent of fuseactions, if that's a more relevant term for you) without the need for the developer to write any additional code. It was truly beautiful to see integration and unit tests run, and just as easy to write them.

I also had an opportunity to flip through the pre-copy of the official Coldbox book, and I must say I had a hard time handing it off to the next student. The Table of Contents was lengthy and robust, and the associated content was clear, concise, and easy to read. Oh, and it is in color! The plethora of code snippets really jump out at you, and  highlighted items are truly highlighted. I would say that the book is definitely one that you will want to have on your desk as a reference. If I understood correctly, we should be seeing it released into the wild in short order. :)


The excitement that new knowledge brings is great to those of us who love it and derive our livelihood from it, and I could easily spend a day sharing all of the very useful information and understandings I received from attending this class. I wasn't asked not to, nor was I asked to sign any NDAs; but for the sake of honoring the information that Luis earns some of his sustenance from, I'll refrain from divulging any more details of the class. I will, however, leave you with this thought...


If you are doing Coldbox development and you have not attended Luis' Coldbox 101 training class, I guarantee you that what you are writing is not all that it could be. It won't be architected as well as it could be, it won't be leveraging all of the shortcuts and features that it could be, and it won't be top of the line. That isn't to say that you aren't capable of writing a good Coldbox app, but without the in-depth understandings that this class offers, your app will very likely not be a great Coldbox app.

It's challenging to get department heads to budget in training sometimes, but my fellow developers, if your shop is or is considering being a Coldbox shop, then it is worth the effort to justify this training to them. I typically ask myself the question, "if it was my  money, would I spend it on this?", and regarding this class the answer is an unequivocable YES! If you would like any additional input to help justify the training, I and I'm sure anybody else who attended this training class would be more than happy to provide our own personal feedback and testimonial.

If you are serious about writing solid, load-bearing apps in Coldbox, do this for yourself: attend Coldbox 101 training.

Doug out.

Coldbox Certification

Posted by dougboude at 10:33 AM | PRINT THIS POST! | Link | 2 comments
10 November 2009
MySQL Query to Find the Following Thursday of a Given Date

I found myself needing to update a MySQL table today with a calculated date, so thought I'd blog the sql in case it saves someone else some time later.

The Scenario

You have a table that contains a date field, but you need to know the date of the Thursday following that date. In this query, the number 5 represents the 5th day of the week (sunday=1, Monday=2, etc.), so if your scenario is looking for a different day of the week, just substitute your day's number everywhere you see the number 5 occurring in mine. The number 12 in the query below is really 7+5, so again, substitute your day's number for 5 in the equation 7+5 and plug in the result where you see 12. If you're looking for Saturday (day 7), you would put a 14 in place of my 12.

The Query 

SELECT adjusterpaydate,
dayofweek(adjusterpaydate)-5 as diff,
adddate(adjusterpaydate,if(dayofweek(adjusterpaydate)-5<=0,abs(dayofweek(adjusterpaydate)-5),12-dayofweek(adjusterpaydate))) as followingThursday
FROM `invoicepayment`
WHERE adjusterpaydate is not null

To update an existing field in the table with the calculated date of the following Thursday, I used a modified version of the above query that looks like this:

update `invoicepayment` set checkdate = adddate(adjusterpaydate,if(dayofweek(adjusterpaydate)-5<=0,abs(dayofweek(adjusterpaydate)-5),12-dayofweek(adjusterpaydate)))
where adjusterpaydate is not null

Posted by dougboude at 5:17 PM | PRINT THIS POST! | Link | 0 comments
20 October 2009
Adding a Railo Site in W2k8/IIS7/Tomcat Environment

I took the time to document the steps I went through when adding a new Railo-enabled IIS7 site, so thought I'd share them. This brief tutorial assumes that you have already previously installed Railo in this environment and that the site you are creating has either its own domain or subdomain name. My specific scenario is that I'm using Railo 3.1x on Windows Standard Server 2008 with IIS7 and Tomcat 6. You may have to adjust some of the steps to fit your particular environment.

Adding a New Railo Site

1. Create a new site in IIS

    a.create a folder that will be the root of your new site;

    b. Add the site in IIS and point it to the folder you created above;

add web site in iis7

2. Create a small test.html page and drop it in to your new site's root

3. add the new site name to either your HOSTS file or your DNS (if you manage that yourself)

edit hosts file

4. Open a browser and browse to your test.html file, just to test things up to this point.

testing hello world

5. Add a virtual directory to your site called "jakarta" that points to the folder in which your isapi_redirect-1.2.28.dll (or equivalent) redirector resides. (if you don't know what the redirector is, you may need to visit the prior tutorials on installing Railo the first time.)

add virtual directory iis7

6. Add an ISAPI filter to your site that points to the redirector DLL mentioned in the previous step

add isapi filter iis7

7. Restart the site in IIS

8. Create a small test CFM template in your site's root

9. Add a HOST entry to your Tomcat6/conf/server.xml file

add host entry server xml tomcat

10. Restart Tomcat (you'll now notice that a WEB-INF folder was created in your new site's root! This is a very good sign)

restart Tomcat windows

restart Tomcat

web-inf folder railo added

web-inf folder was added automatically!

11. Browse to your test CFM template. You should be having success at this point!

test cfm template

Posted by dougboude at 12:11 PM | PRINT THIS POST! | Link | 7 comments
05 October 2009
Weird 404 Error on IIS7 When Accessing Flat File

We recently migrated a site from a freeBSD environment to a Windows environment. This morning accounting shared an error with me they were receiving when trying to access a system generated flat file. Typically, they select a few options and click a button, and are then presented with a page that contains a link to the generated flat file. Their process is to then right click the link and perform a "save target as" action. Now, however, right clicking the link and choosing 'save target as' produced a 404 error.

 I first suspected that perhaps there was an issue with pathing since obviously 404 indicates a file not found. Going in the back door, though, there the file sat in all its glory, and with all the content it should have had.

Turns out that the real issue was the fact that IIS7 did not have a clue as to the mime type of the file generated. The previous developer had been quite fond of using the ".dat" extension when generating flat files, and, although the code was creating the file just fine, when attempting to navigate to it for a "save target as" action IIS7 didn't have a clue what to do with it. I would have thought IIS might have defaulted to a mime type of text, but instead it just pretended the file didn't exist.

The solution: add ".dat" to the list of mime types for that particular site, with a type of "text/plain".

Posted by dougboude at 10:16 AM | PRINT THIS POST! | Link | 1 comment
02 October 2009
Security Snippet for canvasWiki

I spent two days waiting to see if anybody would respond to my call for "code donations" regarding adding real security to canvasWiki, but no code came flying my way. Of course, in that time I could have just written my own, but I was being lazy. I did end up having to do it myself, though, so thought I'd share in case it saves someone else time later.

My security is based on a user having n roles. Since canvas is hard coded for sysop, admin, user, and all (via the "RoleList" parameter in the CanvasConfig bean in Coldspring.xml) those are the roles I used as well.

Table scripts (mysql):
user table

CREATE TABLE `user` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(35) NOT NULL,
`lastname` varchar(35) NOT NULL,
`username` varchar(35) NOT NULL,
`password` varchar(35) NOT NULL,
PRIMARY KEY (`userid`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `user` (`userid`, `firstname`, `lastname`, `username`, `password`) VALUES
(1, 'doug', 'boude', 'dougboude', 'mamamiarocks'),
(2, 'suzy', 'queue', 'squeue', 'ihearthuckabees');

 

 

 roles table

CREATE TABLE `roles` (
`roleid` int(11) NOT NULL AUTO_INCREMENT,
`rolename` varchar(35) NOT NULL,
`description` varchar(75) DEFAULT NULL,
PRIMARY KEY (`roleid`)) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `roles` (`roleid`, `rolename`, `description`) VALUES
(1, 'sysop', NULL),
(2, 'admin', NULL),
(3, 'user', NULL);

 

 

 junction table

CREATE TABLE `jctuserrole` (
`userid` int(11) NOT NULL,
`roleid` int(11) NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `jctuserrole` (`userid`, `roleid`) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 3);

 

 

 MY version of the model/UserRecord.cfc's "login" method: 

<cffunction name="login" access="public" hint="I log this user into the site" output="false" returntype="boolean">
 <!--- make this do some type of real authentication if desired--->
 <!--- you can find the plain-text passwords that match these users in the load() function --->
 <cfquery name="getUser" datasource="glock">
SELECT r.userid, r.firstname, r.lastname, GROUP_CONCAT( rr.rolename ) AS roles
FROM user r
INNER JOIN jctuserrole j ON j.userid = r.userid
INNER JOIN roles rr ON rr.roleid = j.roleid
WHERE r.username =  <cfqueryparam value="#getUserName()#" cfsqltype="cf_sql_varchar" />
AND r.password =  <cfqueryparam value="#getPassword()#" cfsqltype="cf_sql_varchar" />
GROUP BY r.userid, r.firstname, r.lastname
 </cfquery>
 <cfif getUser.recordcount eq 1>
  <cfset setFirstName(getUser.firstname) />
  <cfset setLastName(getUser.lastname) />
  <cfset setUniqueId(getUser.userid) />
  <cfset setRoles(getUser.roles) />
  <cfset setIsLoggedIn(true) />
 </cfif>
 <cfreturn getIsLoggedIn() />
</cffunction>

 (note: Notice my sweet usage of MySQL's awesome "GROUP_CONCAT" function! Turns that field's values into a list...EXACTLY what we need!)

Besides having the above, you also have to set all of the security type parameters in the Canvasconfig bean of Coldspring.xml appropriately to enforce security. They're self-explanatory.

That's it!

Beyond that, I did make a minor tweek to views/dsp.navigation.cfm and views/layout.main.cfm in order to display my user's name and to make the logout link more prominent.

dsp.navigation.cfm change:

just after line 20 ("<div id="navcontainer">"), added this short if statement:

 <cfif UserRecord.getIsLoggedIn()>
  Logged in as:<br><cfoutput>#UserRecord.getFirstName()# #UserRecord.getLastName()#<br>Roles: #UserRecord.getRoles()#</cfoutput>
 </cfif>

 layout.main.cfm
Just after line 31 ("<a href="#webpath#/index.cfm">#appTitle#</a>"), added this short if statement:
 

<cfif UserRecord.getIsLoggedIn()>
  <cfoutput>
   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
   welcome #UserRecord.getFirstName()# #UserRecord.getLastName()#!
   <div style="width:90%;text-align:right;"><a href="#webpath#/index.cfm?event=logout" style="font-size:16px;font-style:italic;">Logout</a></div>
  </cfoutput>
 </cfif>

 

Posted by dougboude at 11:04 AM | PRINT THIS POST! | Link | 0 comments
21 September 2009
Configuring Multiple Redirectors for Railo,Tomcat,and IIS7 on W2K8

Unless you've already gotten at least partially into Railo, this post will probably be very irrelevant to you. If, on the other hand, you have Railo installed on IIS7 using Tomcat with multiple domains/subdomains where you use mixed languages (PHP AND CFML, for example), you might find the following of interest.

My Scenario

I have two domains being served up by IIS, both of which must be able to execute CFML using Railo. Domain1 is primarily a PHP app, so the default page is index.php, as configured within IIS. My second domain, sub1.Domain1 is a strictly CFML app, so for that one I have index.cfm as the default page.

How IIS and Tomcat Communicate

IIS7 is "connected" to Tomcat via a DLL that performs request redirections. For example, anytime a request is made for a Railo-enabled domain, this DLL checks to see if the template being called should be processed by Railo or not. This "check" is done by checking a list of predefined filters, such as "/*.cfm" and "/*.cfc". Now, in order for a request to my domain that doesn't specify a template to pass this redirect filter, it is necessary to add this filter entry: "/*". This will ensure that any request will be passed to Railo for processing. This is also where the issue arises for me....

The Challenge

My Domain1 has a default page of index.php, so when a generic request is made to that domain (http://Domain1), I do not want it being passed on to Railo. So, I simply omit the generic "/*" filter and voila! Only valid Railo requests will be passed on. Ah, but now when I set up my second domain which serves up a strictly CFML app, I DO want the generic request passed on since my default page is a CFM template. The filter criteria I have in place, however, prevents it. What to do, what to do.

Resolution

The way I resolved this issue was to simply set up a second instance of the redirector DLL and associate THAT with my second domain. The relevant configuration file for the redirector DLL lives in Tomcat's "conf" folder and is called "uriworkermap.properties". So, I created a second uriworkermap.properties file, this one WITH the generic filter ("/*"), and pointed my second DLL's "isapi_redirect-1.2.28.properties" file to that second uriworkermap.

Worked like a charm!

One other potential advantage I think this may have also addresses a question put forth by someone regarding the redirector's performance under load. It seems to me that if I gave each of my sites/domains their OWN copy of the DLL to use, that I would be creating the ability for IIS to perform these redirections asynchronously rather than serially. Not sure if that's truly the case or not, but in theory it sounds right to me.

If anyone has a better way of addressing the issue described above, I'm all ears.

For more details on setting up the redirector DLL for IIS7 and Tomcat in a Windows environment, visit 'Railo 3.1 on Windows Server 2008 and IIS7'

Posted by dougboude at 12:13 PM | PRINT THIS POST! | Link | 0 comments
17 September 2009
Batch Script to Backup MySQL Database

In setting up a new IIS7/Windows 2008 server, I had a need to automate the backup of my MySQL database and so pieced together a little batch file that I then scheduled via the Task Scheduler. The file first removes all backup files that are more than a week old, then performs a sql dump and finally zips the dump. Files are named according to the date and time they were created. Anyway, it took me quite a while to find all the pieces and parts I needed, so thought I'd post it here as a starting point for others who may be wanting to do the same.

Oh, one prerequisite for this particular script to function: You have to have a copy of winzip installed (I have version 12) WITH the optional command line utility, which is a separate download (http://www.winzip.com/downcl.htm). Hey, the whole deal costs $29 bucks...spring for it!

MySQLBackup.BAT

@echo off
for /f "tokens=1" %%i in ('date /t') do set DATE_DOW=%%i
for /f "tokens=2" %%i in ('date /t') do set DATE_DAY=%%i
for /f %%i in ('echo %date_day:/=-%') do set DATE_DAY=%%i
for /f %%i in ('time /t') do set DATE_TIME=%%i
for /f %%i in ('echo %date_time::=-%') do set DATE_TIME=%%i
rem Killing all files older than a week old...
forfiles /D -8 /M *.zip /C "cmd /c del @fname.zip"
"C:\mysql\bin\mysqldump" -u username -p"password" dbname >C:\mysqlbackup\%DATE_DAY%_%DATE_TIME%_database.sql
wzzip C:\mysqlbackup\%DATE_DAY%_%DATE_TIME%_database.zip C:\mysqlbackup\%DATE_DAY%_%DATE_TIME%_database.sql -mex

Posted by dougboude at 9:30 AM | PRINT THIS POST! | Link | 1 comment
16 September 2009
Choosing an SSL Certificate

I was in need of procuring an SSL Certificate today to enable portions of our domain to be accessible via HTTPS, but a google search turned up far too many choices for the unseasoned guy to choose from. So, I turned to Austin's "Refresh-Austin" google group (where hundreds of techies of various disciplines lurk night and day) and asked their advice. Since it was useful to me, I thought I'd share it here in the form of a post.

Without further adieux...

can anybody recommend a good SSL cert provider? There are hundreds out there...any of them stand out as having great prices and being good to work with?
Thanks!
Doug Boude

The only one with cheap prices, that I know of, is godaddy.
regards,
Warren

I know that typically in life, "you get what you pay for" ... any glaring reason NOT to go with a godaddy SSL cert???
Doug Boude

I'm not a big fan of godaddy, a big non-fan of chained SSL certificates, and I've had luck with RapidSSL: www.rapidssl.com

- d.

I've tried godaddy, verisign, rapidssl.com, enom geotrust ssl certs, and instantssl.com.
I also am a fan of rapidssl. The godaddy checkout requires the ability to navigate a horrible UI. I believe they resell starfield tech ssl certificates anyway.

Felipe R
Creative Director
padreMedia

I am cheap. You cant beat $29.99 a year.

Warren

I've used Trustwave extensively for wildcard certs ($798 for 3 years) and standard certs ($225 for 3 years). I haven't shopped around in a while, but they were cheapest for wildcards.
https://ssl.trustwave.com/solutions-overview.php

Victor

There is nothing wrong with a $29 dollar certificate except having to use chained certificates. Savvy visitors also recognize that you didn't go through the full background check procedures utilized by the more expensive certificates. I agree in working with whatever is in your budget. I just always felt it important to not cut corners for clients with large budgets. I purchase their name brand certificates with all the authentication hoops despite the extra cost.
"The reason for having these expensive certs from these companies is that you are paying for that level of trust. If i was giving out certs for free there would be no reason at all to trust me. However having a big name like verisign as the provider of your cert is like wearing brand name cloths, its a status symbol and it brings with it a level of trust, which is very important for ecommerce sites to have." -
http://ask.slashdot.org/article.pl?sid=01/03/18/1855230

Felipe R
Creative Director
padreMedia

I used rapid ssl many times. $69.. easy.. When I don't want to have a the chained certificate, I use geotrust.
I honestly see no reason for not using rapid ssl. I haven't had any users ever report not being able to accept the certificate.

www.rapidssl.com
and
http://www.instantssl.com/ssl-certificate-products/ssl-certificate-index.html

I believe godaddy just resells starfieldtech.com's certificates.
I remember seeing their name when I purchased the ssl through godaddy.

Felipe R
Creative Director
padreMedia

 

I am a fan of Geotrust, and here's why. They are kinda of second to the almighty expensive Verisign and quite pricey themselves. But, there is a way around that.

If you signup for a reseller account (surprisingly free) you get some really good priced high quality certificates they aren't $28 but they do have the verified link ones for less than 100

Dustin

 

I'm sorry to tell you, but Geotrust was bought by Verisign 2-ish years ago. And their excellent customer and sales service started going downhill from there....

-d-

Back when I was buying certs for disparate client sites, before I switched to using my own hosting provider's certs (www.pair.com), I used Thawte (www.thawte.com) and was happy with their pricing and service. Honestly, I've not used them in a few years, but back then they were reliable and customer support was responsive.

Cheers,

Art


 

I ended up buying a wildcard cert from my host, Gearhost.com. It was $179 for a year. Considering the fact that with a wildcard cert (*.mydomain.net) I can basically secure n number of sub-domains, I thought it was a good deal. :)  Also turns out, as I completed the whole "authenticate me" process, that Gearhost is reselling rapidSSL certs anyway! lol

Doug out

 

Posted by dougboude at 12:08 AM | PRINT THIS POST! | Link | 0 comments
13 September 2009
Managing Multiple Development Platforms with Limited Windows Laptop Resources

Since I do a lot of my development work on my laptop, and since the thought of having services running and consuming my precious memory that are NOT necessary all the time bugs the bajeebies out of me, and since I do development in CF and .NET on my machine (and use different versions of SQL server for each), I wrote some batch files to start and stop the services required for each platform. Some others might find them useful (at least as starting points), so I thought I'd share them here.

Batch file to start/stop CF with SQL Server:

CFDev.bat

@echo off
IF [%1]==[ON] GOTO START
IF [%1]==[OFF] GOTO STOP
:START
 net start "ColdFusion 8 ODBC Agent"
 net start "ColdFusion 8 ODBC Server"
 net start "ColdFusion 8 Application Server"
 CALL SQL2005.bat %1
 @echo "CF Started!"
 pause
 GOTO :EOF
:STOP
 net stop "ColdFusion 8 ODBC Agent"
 net stop "ColdFusion 8 ODBC Server"
 net stop "ColdFusion 8 Application Server"
 CALL SQL2005.bat %1
 @echo "CF Stopped!"
 pause
 GOTO :EOF
:EOF

(the above batch file calls SQL2005.bat...)
SQL2005.bat

@echo off
IF [%1]==[ON] GOTO START
IF [%1]==[OFF] GOTO STOP
:START
 CALL SQL2008.bat OFF
 net start "SQL Server (SQLE)"
 net start "SQL Server Browser"
 net start "SQL Server VSS Writer"
 GOTO :EOF
:STOP
 net stop "SQL Server (SQLE)"
 net stop "SQL Server Browser"
 net stop "SQL Server VSS Writer"
 GOTO :EOF
:EOF

I have two shortcuts on my desktop, one titled "Start CF" the other titled "Stop CF", with these properties, respectively:
C:\CFDev.bat ON
C:\CFDev.bat OFF


For my .NET stuff, SQL 2008 is required. Here are my batch files for those required services:

dotNetDev.bat

@echo off
IF [%1]==[ON] GOTO START
IF [%1]==[OFF] GOTO STOP
:START
 net start ".NET Runtime Optimization Service v2.0.50727_X86"
 CALL SQL2008.bat %1
 @echo ".NET Ready!"
 pause
 GOTO :EOF
:STOP
 net stop ".NET Runtime Optimization Service v2.0.50727_X86"
 CALL SQL2008.bat %1
 @echo ".NET Stopped!"
 pause
 GOTO :EOF
:EOF

(the above batch file calls SQL2008.bat)
SQL2008.bat

@echo off
IF [%1]==[ON] GOTO START
IF [%1]==[OFF] GOTO STOP
:START
 CALL SQL2005.bat OFF
 net start "SQL Server (SQLEXPRESS)"
 @echo "SQL 2008 Started"
 pause
 GOTO :EOF
:STOP
 net stop "SQL Server (SQLEXPRESS)"
 @echo "SQL 2008 Stopped"
 pause
 GOTO :EOF
:EOF

Again, I have a "Start DotNet" and "Stop DotNet" shortcut on my desktop with the following properties:
C:\dotNetDev.bat ON
C:\dotNetDev.bat OFF

That's all folks!

Posted by dougboude at 1:42 AM | PRINT THIS POST! | Link | 1 comment
12 September 2009
Railo's Administrators: Server vs Local

In CF, we have a single administrator to manage settings that affect all of our different CF web sites. If we configure a datasource, ANY CF app running on our web server can see and use that datasource. No new info here, right?

RAILO'S SERVER vs LOCAL ADMINISTRATOR

In Railo there is a slight twist to administration that you need to be aware of and know how to think about in order to make good choices in your configurations. Railo provides you with two kinds of administrators: Server, and Local. The easiest way to think about these and visualize them is to think of the Server administration as being your GLOBAL settings; anything you do when in the Server admin will be seen by any and all CFML/Railo-enabled web sites on your server. If for example I want all of my sites to utilize the same email server settings, I should set that up in my Server admin to make it globally visible.

The web admin for each site is used to configure settings that should be specific to that site only. So if for instance I were to set up a mapping called "includes" in my local web admin for Site1 and point it to "folderx", and then configure a mapping with the same name in the local admin for Site2 pointing to "folderZ", the app's running on each site would both use an "includes" mapping that pointed to different places. If on the other hand ALL of my sites and their apps should have an "includes" mapping that points to the same place, the best place to configure that would be in the Server admin. Is it gellin'? The nearest thing CF admin had for us like this was the ability to configure different sandboxes, but that is pretty limited if I'm not mistaken and didn't offer anything even close to Railo's paradigm shift.

ACCESSING THE RAILO ADMINISTRATORS

Here are the two URLs you need to get to the respective administrators:

http:[your site name]/railo-context/admin/web.cfm

http:[your site name]/railo-context/admin/server.cfm

So if I have three sites (masonclaims.com, tipcalcpro.com, and dougboude.com), their respective local administrators' URLs would be:

  • http:masonclaims.com/railo-context/admin/web.cfm,
  • http:tipcalcpro.com/railo-context/admin/web.cfm,
  • and http:dougboude.com/railo-context/admin/web.cfm.

 Getting to my server's Server admin can be done by using ANY of the following URLs (they will all save their settings in the same, central, location):

  • http:masonclaims.com/railo-context/admin/server.cfm,
  • http:tipcalcpro.com/railo-context/admin/server.cfm,
  • or http:dougboude.com/railo-context/admin/server.cfm.

YOUR WEB-INF FOLDER

One more little tidbit that's useful to be aware of: all of the settings you administer via these interfaces are stored/saved within the WEB-INF folder structure that got created whenever you configured your site to be "Railo-aware". Local settings will be stored in the applicable site's WEB-INF folder, and the global server settings will be stored...in another WEB-INF folder located somewhere else (not sure on the location of this one, but it is NOT within any of your site folders, that much I know).

With only a small handful of exceptions, you should never ever have to go into the WEB-INF and do any manual tweeking whatsoever; just use the admin interfaces for that purpose.

On a side note, I think you will be VERY pleased with what you see in the administrator interface! it all looks very, very familiar and works nearly exactly the same way as the CF admin we're used to seeing, at least in my experience so far.

If anybody has any corrections or additions, please do add them to the comments below! :)

Posted by dougboude at 10:47 PM | PRINT THIS POST! | Link | 3 comments
10 September 2009
On Falling Behind...

This blog post began as a simple comment to Ray's blog post "Are We Falling Behind", but darn him, he moved me such that it turned into multiple paragraphs!

So, here's the commentary that the afore mentioned post and the thoughts of all its commenters evoked from me.

 

In My Beginning, There Was Procedural

In 2004 MX6 made a leap in evolution that opened the doors many native OOers had been waiting for, and they began to weave all sorts of new and strange CF tapestries. At that time, all of my CF coworkers were buzzing about "MVC", all the time, constantly; it was SO annoying! I was coding as I always had and didn't see any gaps in my ability to produce good stuff; I'd never used a CFC before and had no intention of starting at that time. That, coupled with the fact that I unequivocably tend to ALWAYS be wary of whatever direction the herd is heading (I've read about those caveman bison hunts!), kept me perfectly content just as I was. Fast forward to 2006. What was a buzz is now a dull roar. Fusebox has taken a quantum leap. Model-Glue, Reactor, Mach II, Coldspring...they are now incarnate and catching plenty of attention. My job at that time had me maintaining legacy code, so I spent my days tracing variables through myriads of includes, trying to find their origins...nothing that involved any new dev, for the most part. Then the directive came down to rebuild all this legacy spaghetti. Myself and two other team members were mandated to recreate this app, in a fashion that would allow it to be easily configurable, easily connect to multiple database backends, easily support multiple clients and THEIR clients, be easily skinnable, and have certain aspects of it be consumable as web services by corporate and other satellite offices. It was at that point that the ton of bricks fell and I knew that it would be a very bad idea to try and weave this masterpiece from scratch, so the team and I decided that "frameworks it would be".

At The Crossroads

Ah, but which framework should we choose? None of us had any experience with any of them; we had only read blog posts and accumulated rough ideas, foggy understandings, and enough theory to fuel lunch time discussions. To decide the matter, we architected out a small application with basic security and crud, and we built that app with each of the frameworks we were considering. What that accomplished was to solidify the vague understandings we had, and it let us find the framework that most suited our own personal development style (terminology, syntax, organization, etc.). Since that first app in Model-Glue, I have written several more using that framework and some others; but I have also written a decent number strictly "plain vanilla", simply because their scope was smaller than the problem a framework addresses. The beauty of things now, though, is: I have a choice.

I have to make a confession: the directive to build an enterprise-size app was not my only motivation to learn the frameworks; that was just my paid opportunity to do so. My stronger, deeper, and truer motivation at that time was that dull incessant roar I mentioned, the one that was always in my ear and eventually DID manage to make me feel like I was being left behind, professionally. Was I, or was it perception only? Well, if in my future job quests I continually find that I am unqualified for the position due to my lack of specific CF skills, despite the fact that I've been using CF for a decade or more, then I would have to say that yes, I WAS left behind. If I'm well able to procure suitable employment without having added that knowledge to my quiver, then no, I was not left behind. I don't have a good enough feel for the pulse of the CF job market to know which it truly is, nor am I any sort of prognosticator on this matter, but I DO know that I have absolutely zero regrets having invested the time in stretching my brain to accommodate both the old AND the new.

Change IS Scary, But Possible

Making the leap to this strange new world is daunting, without a doubt, and anybody who tries to tell you otherwise is doing that smoke-blowing thing. My own leap was still recent enough for me that I remember the doubts, frustrations, and pain of persistently banging my head against this apparent brick wall. But eventually, with enough experimentation, reading, experimentation and reading, it DID sink in! There was no shortcuts over that mountain, so I just took that first grip and pulled until I could reach the next one. As I learned things I blogged about them, leaving behind what understandings I'd gleaned in hopes that those following me could gain an easier foothold. But the simple truth with this is, as with every other thing in life that's worthwhile: if you want it, you can get it, but to get it, it is gonna cost you something. Accept this gauntlet, then take off running. I guarantee you, it is impossible to fail to learn OO in a CF context if you determine in yourself that you are going to do it. There's just too many people who have gone before you, no smarter than you are, who have managed to accomplish it; there's just too many fellow CFers who are able and willing to assist you along the way. You can't fail at this, should you choose to do it.

Opting Out: Is It An Option?

But what if you should choose NOT to do it? Let me preface the rest of this by saying that I do indeed love all my fellow CF developers equally, and I'd love to drink a pint with each and every one of you. But, just speaking from my gut, I believe that if you are not in a constant state of professional growth, which includes frameworks and OO methodologies, you will minimally eventually find yourself to be part of a CF minority, if not already. And I do believe that that minority will find itself at the lower end of a continually expanding income gap between those who made the time for this particular vein of professional self-improvement and those who did not.

I'm not condemning anybody for something they do not know (Lawd knows there's PLENTY that I don't know!), but what I am doing is encouraging one and all to take/make the time to get more than just an arm chair knowledge of what is happening all around you in the CF world; acknowledge that ever increasing dull roar of OO that you can't help but hear, and (in this case only), take a sip of this kool-aid. You will never regret knowing more tomorrow than you do today.

Doug out :0)

Posted by dougboude at 5:42 PM | PRINT THIS POST! | Link | 2 comments
04 September 2009
Railo 3.1 on Windows Server 2008 and IIS7 - Part 3 of 3
BOK 2 of 2

This is the third of three posts on installing Railo 3.1 on Windows Server 2008 with IIS7. The previous post can be found here.

Okay, in our last episode, we focused our incredible shrinking brains solely on getting Tomcat and Railo set up and working as a standalone entity. We also did the preliminary work of getting IIS7 set up with whatever domains we plan on giving the ability to execute CFML requests. In this final chapter we are now going to do the dirty work of bridging the two together. As a reminder, here is my view of how the two entities (IIS and Tomcat) will be communicating:

rails tomcat and iss working together

The key to this bridge is "The Connector", a DLL whose job in life is to evaluate incoming http requests and determine if said request should be redirected for processing or not. I use the word redirected, but it isn't a true redirection in the sense of sending the caller to a different URL. Rather, it is more so deferred processing; if the incoming request matches a pre-defined filter list, then that request will be deferred to Tomcat for processing, the resulting html being returned to IIS for delivery to the caller.

Here's an overview of what we're going to accomplish:

  • acquire and install the DLL;
  • "wire up" the DLL to IIS7;
  • tell the DLL what requests to be watching for;
  • tell Tomcat what domains it should care about and where it can find those domains' content;

Before we begin, go ahead and make sure Tomcat is shut down. If you need to know how, see step 6 in the previous post.


Get The DLL
1.
Download the DLL from
http://www.ip97.com/apache.org/tomcat/tomcat-connectors/jk/binaries/win32/jk-1.2.28/isapi_redirect-1.2.28.dll . If that specific file is removed or superceded by a later version, or you find that you need the win64 version, just pare the url back to the appropriate place so you can drill down yourself.

2. Once you have the DLL, create a folder called 'scripts' under your inetpub folder and drop the dll in there.

3. Now we have to create the configuration files (.properties files) that the DLL will use to communicate with Tomcat. When you create these, make sure that a ".txt" extension doesn't get tacked on to the end, k?
   a. Create C:\inetpub\scripts\isapi_redirect-1.2.28.properties
   b. Paste the following into that file:

# Configuration file for the Jakarta ISAPI Redirector
# The path to the ISAPI Redirector Extension, relative to the website
# This must be in a virtual directory with execute privileges
extension_uri=/jakarta/isapi_redirect-1.2.28.dll
# Full path to the log file for the ISAPI Redirector
log_file= C:\Program Files\Tomcat6\logs\isapi_redirect.log
# Log level (debug, info, warn, error or trace)
log_level=debug
# Full path to the workers.properties file
worker_file= C:\Program Files\Tomcat6\conf\workers.properties
# Full path to the uriworkermap.properties file
worker_mount_file= C:\Program Files\Tomcat6\conf\uriworkermap.properties
#rewrite_rule_file= C:\Program Files\Tomcat6\conf\rewrites.properties
##########################################################

   c. Create C:\Program Files\Tomcat6\conf\uriworkermap.properties and paste the following into it:

# uriworkermap.properties - for use with IIS
/*=wlb
/*.cfm=wlb
/*.cfc=wlb
/*.cfml=wlb

 

d. Create C:\Program Files\Tomcat6\conf\workers.properties and paste the following into it

 

# workers.properties.minimal -
# This file provides minimal jk configuration properties needed to connect to Tomcat.
# The workers that jk should create and work with
worker.list=wlb,jkstatus
# Defining a worker named ajp13w and of type ajp13
# Note that the name and the type do not have to match.
worker.ajp13w.type=ajp13
worker.ajp13w.host=localhost
worker.ajp13w.port=8009
# Defining a load balancer
worker.wlb.type=lb
worker.wlb.balance_workers=ajp13w
# Define status worker
worker.jkstatus.type=status
####################################

 

Okay boys and girls, now we've got everything in place from The Connector back to Tomcat; it's time for us to connect The Connector to IIS! (note: I am assuming at this point that you are able to navigate to localhost and your other domains already, viewing at least a default html page)

Tell IIS It's Okay To Run the DLL
1.
In IIS Manager, click the main host, then double click the "Isapi and CGI Registrations" Icon
2. In the right hand panel, click the 'Add' link, then fill in the path to your dll as well as a name (any name will do). Be sure to check the
little box!
Link Up Your Filter to Your Site
3.
Click on your server's default site ("Default Web Site") and double click the ISAPI Filters icon.
4. In the right hand panel, click the Add link and fill in the name of your filter (the name you gave it in the previous step) and the path to the
dll.
Create a Very Special Virtual Directory in Your Site
5. Right click on your Default Web Site and select 'Add Virtual Directory'. Name it 'Jakarta', and point it to C:\inetpub\scripts

6. Click on the Jakarta virtual directory, then double click on the 'Handler Mappings' icon


7. In the right hand panel, click on the link named "Edit Feature Permissions", then check the "Execute" box and close.


8. Restart IIS, restart your default web site (just for good measure), restart Tomcat, drop in a test index.cfm file into your default web root, and see if you can navigate to localhost/index.cfm! It should work at this point.

Repeat Steps 3 through 7 for Remaining Domains
Now, go back through steps 3 through 7 to get your remaining domains ready to use Railo. Skip step 8 for the time being (at least the part about
browsing to an index.cfm), because we have to dive back into Tomcat before those domains will know what to do with a .cfm file.

Your Other Domains and Tomcat
Tomcat comes pre-configured with localhost, so for that domain we don't have to do anything. But for any other domains you may be wanting to run on
this server, we're going to have to tell Tomcat that they exist and a few things about them. Your domains will now be referred to as 'Hosts', and we'll be working inside C:\Program Files\Tomcat6\conf\server.xml to set them up.

Let's say that IIS is also hosting a domain called MyDomain.Net. For development purposes, you will also be referring to this domain locally as "MyDomain-Test". Your domain's web root is located at C:\inetpub\wwwroot\mydomain.net, and you already have an entry in your hosts file (MyDomain.net      MyDomain-Test) to allow you to successfully navigate to MyDomain.net via http://MyDomain-Test.

What we need to do is create another <Host> entry in our server.xml file that defines this host AND its known alias. Here is the snippet you should paste in to your server.xml file (values replaced appropriately with your own, of course), just below the localhost entry:

<Host name="MyDomain.net"  appBase="webapps"
    unpackWARs="true" autoDeploy="true"
    xmlValidation="false" xmlNamespaceAware="false">
    <Alias>MyDomain-Test</Alias>
    <Context path="" docBase="C:\inetpub\wwwroot\mydomain.net"/>
</Host>

(note: make sure you understand the Context tag and the Alias tag in the above snippet. You can have multiple Alias tags, but you must make sure that the alias actually resolves before its of any use here. Either DNS entries are in place, or the Hosts file is being used for local resolution. Context is important because that is what tells Tomcat where to look for the files being requested.)

Save server.xml, restart IIS, restart your web sites, then restart Tomcat. You should now be able to successfully navigate to http://MyDomain.net/index.cfm as well as http://MyDomain-Test/index.cfm!

Troubleshooting Hints
Hopefully everything went well, but if not, don't panic. First, check your Tomcat log file to see if it had any issues starting up due to configuration
errors (type-os, paths, etc.). You can find the log file at C:\Program Files\Tomcat6\logs\catalina.2009-09-04.log (or whatever today's date is for you). Remember that in this log file, latest entries are appended to the bottom. You can also eliminate some things by verifying that you can browse to your domain in IIS (calling a standard html page, not the cfm page), and in Tomcat by browsing to your domain with a specified port. In our sample above, we could perform this troubleshooting by browsing to http://MyDomain.net/default.htm , and http://MyDomain.net:8081/default.htm . If both of those resolve correctly, then try browsing directly to your .cfm page with http://MyDomain.net:8081/index.cfm. If that parses correctly, we know that Railo is doing its job and that the issue is probably somewhere in the redirection portion of things, meaning we should review our web sites' IIS settings again and the values in the properties files related to our DLL.

TIPS AND POINTERS
Default Files
In my scenario, I have to run both PHP files AND CFM files from the same domain. IIS already knows what to do with PhP files via an ISaPI mapping
associating the PHP executable with files of that extension. And, the way the Tomcat connector dll works, it looks at every request to that domain to determine if it should redirect or not. SO then...what if I want the url http://MyDomain.net to default to index.php instead of index.cfm? Two pieces to that puzzle.

1: Within IIS, you can specify the order that default documents should be looked for regarding any given site. Just click the site, then the "Default Document" icon  If you want

index.php to be the default document, simply make sure index.php appears before index.cfm, or leave index.cfm out of the list altogether. 

That's half of the issue. The other half is the dll that sniffs every single request to determine if it should redirect or not. The snippet I gave you earlier for C:\inetpub\scripts\isapi_redirect-1.2.28.properties included the line " /*=wlb ". This line essentially tells the redirector, "redirect everything to Tomcat". Remove or comment out that line, and then ONLY the requests for files with the specific CFML extensions will be redirected for processing.

flex2gateway
I had a conversation with Paul Kukiel regarding an issue he was having getting flex2gateway to resolve without having to specify the port. Now, Paul
was using IIS6 in that instance, so this solution doesn't apply, but I found that I myself wasn't able to get flex2gateway to resolve in my scenario either. The solution was simply adding this line to  C:\inetpub\scripts\isapi_redirect-1.2.28.properties:  " /flex2gateway/*=wlb "

Basically, any process you want to run that needs Railo to do the work, add an appropriate filter to the DLL's properties file so that it will know to redirect the processing to Tomcat.

That's all folks, and quite a lot it is. Any corrections or comments or suggestions or other tips and tricks are welcome!

 

 

 

 

 

 

Posted by dougboude at 6:24 PM | PRINT THIS POST! | Link | 17 comments
Railo 3.1 on Windows Server 2008 and IIS7 - Part 2 of 3
BOK 2 of 2

As I mentioned in my previous post on installing Railo with IIS7, my own recipe for success in getting Railo set up on Windows 2008 Server with IIS7 was actually a conglomeration of several sources, along with a lot of additional research and experimentation on my part. Let me go ahead and give kudos right now to those sources: blog posts on www.hockeypfef.net,  web-rat.com, and corfield.org - thanks! And with a special shout out to Todd Rafferty (aka WebRat) who walked with me a good portion of the way.

One last thing before I dig in to this subject: the explanations and illustrations that follow are my own personal viewpoint and not necessarily technically accurate, though they work very well for me! Also, since there are a painfully huge number of steps involved with all of this, it is entirely possible that I may have forgotten to write one down, in which case I'm sure that whoever tries to duplicate this process will point that out to me and I'll modify the instructions as needed.

Getting The Big Picture

One thing that I know helps me so very much is to have an understanding (at least a vague one) ahead of time of how all the pieces fit and work together. Without this, you are blindly following someone else's step by step instructions, and if your situation deviates one iota from that of the original publisher's, you don't have a clue how to address that difference. So before I lay out the step by step, I want to explain the inner workings to you. If you'd rather just jump directly to the step by step, feel free.

HOW IT WORKS

The Actors

The main actors in our production of "Saving Private Railo" are:

  • IIS 7 (on Windows Server 2008)
  • Railo 3.1
  • Java Runtime Environment (JRE) version 6, update 16
  • Tomcat version 6
  • a little guy we're going to refer to as "The Connector" (isapi_redirect-1.2.28.dll, if you must know his real name!)
  • and several miscellaneous configuration files (text files named either *.properties, *.xml, or *.conf)

The Script

So what are the roles of all these guys? Let me illustrate how I understand it to work, and then we'll dissect it in more detail.

IIS and Tomcat Each Rule Their Own Kingdoms
We all know what IIS is and how it works; you make a request to it via the port it's listening on (typically port 80) and it returns the requested
html to you. Tomcat works the same way. It is set up to listen for http requests on a certain port. So, if you request a page using that port, Tomcat will respond instead of IIS. (Note: Tomcat comes with a default html page, typically used to let you know that your Tomcat install worked. It also has a few links on it for admin purposes, but I had no need to use it for any purpose other than the first one). In my example here, Tomcat is listening on port 8081. (Note: by default Tomcat gets set up to listen on port 8080, but IIS already has 8080. Rather than mess with making IIS STOP listening on 8080 I just made Tomcat listen on 8081). To "the rest of us", Tomcat appears to be nothing more than yet another web server, and in some respects that's exactly what it is. But, it's also a Servlet Engine, meaning that it has the ability to (internally) execute specific java code in response to specific requests before it returns the rendered html. In our case, that "specific java code" is Railo. Now, if Railo just happened to be a pre-compiled, stand-alone executable application, we'd have no need for Tomcat as our intercessor whatsoever and we'd simply do what we have always typically done and tell IIS "hey, if anybody requests a file with this extension, call this executable to process it first". But, since Railo is really nothing more than a whole BUTTload of Java jar files sitting in a deeply nested directory structure somewhere, it depends upon a servlet engine in order to run. Here is my illustration of how IIS and Tomcat live side by side:

how IIS and Railo live side by side

As you can see by my illustration, in essence each of these servers live within their own little bubbles. They act independently, listen to different ports, serve their own content, do their own things; they ARE independent of one another!

Enter "The Connector"
The Connector is a Dynamic Library Link (DLL) file written to run in a Windows environment whose purpose in life is to re-write URLs. It takes an
incoming URL and, based on properties you define for it (in a .properties file, no less), will re-write, or forward, the url to another destination. It is this functionality with which we tie requests made to our IIS server to Railo. By telling our connector that any request for a .cfm, .cfml, or .cfc file should be directed to Tomcat on port x, we now have a way to serve CFML pages using IIS as a proxy! Here is a revised illustration of how I envision the process of serving a .cfm template:

how IIS and Railo are connected

From a birds eye view, this is how the whole scene is laid out and how the actors interact with one another. Now for the specifics of how I set this up. I will try to minimize the commentary within the actual instructions in order to keep them more readable. If I can't resist adding comments and notes, I'll add them afterwards, with correlating annotations in the instructions.

STEP BY STEP INSTRUCTIONS

1.Prepare your web server. Have your domains set up and responding via appropriate URLs with at least a "hello world" html file.
2.Download and install the Java Runtime Environment (JRE). When you arrive at the (
http://java.sun.com/javase/downloads/index.jsp) Java download page you will be accosted by a multitude of choices. Choose this one: download this JRE
3.Download and install Tomcat(
http://tomcat.apache.org/download-60.cgi). Choose this link on that page: choose this Tomcat file to download

When you install it, remove the spaces and such from the folder name it tries to use. I named mine tomcat6 (as suggested by Mr. Rafferty)

4. Now, immediately copy a file called msvcr71.dll from your c:\Program Files\Java\jre6\bin directory TO your c:\Program Files\Tomcat6\bin directory. Don't ask my why this is necessary (because I do not know!), but it is.


5.Alter Tomcat to use port 8081 instead of the default 8080 (which IIS already has). Do so by editing C:\Program Files\Tomcat6\conf\server.xml and
looking for the <Connector tag with port="8080". Change it to 8081 and save the xml file. tomcats server.xml file

6.Start the Tomcat service. The best way to do this is by executing C:\Program Files\Tomcat6\bin\tomcat6w.exe.starting tomcat on windows

The app looks like this: starting and stopping tomcat on windows

 You should just leave this app open since you will have to do frequent restarts of the service to make configuration changes take effect.

7.Test Tomcat! navigate to http://localhost:8081 . You should see:

 testing tomcat installation on windows .

Assuming this worked as planned, go ahead and stop the Tomcat service as described in step 6.

8.NOW, let's get Railo *1*. Download it from here: http://www.getrailo.org/index.cfm/download/  As with most of the items in this, there are far too many choices on that page. Choose this one: choose the railo jar files for windows

9.Extract the zip file to C:\Program Files\Tomcat6\railo . When you're done, it should look like this: railo jar files after unpacking

10.Now we have to tell Tomcat that Railo exists. *2* This step is a lot of cutting and pasting, so just blindly follow the instructions. You can tune back in when it's time to restart Tomcat. IMPORTANT! We're getting ready to edit some XML files, boys and girls. For whatever reason, some of the XML files in Tomcat's conf directory have an opening tag that causes Tomcat to fail starting up in Windows. Open each XML file and make sure that the opening tag is exactly like this: <?xml version='1.0' encoding='utf-8'?>  . Mine had different encoding values here and there, and it gave me grief until Mr. Rafferty pointed it out to me. Okay, on with the show:
   a. open C:\Program Files\Tomcat6\conf\catalina.properties
   b. Change this line: common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
   to this line: common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,${catalina.home}/railo/*.jar
   c. Save & close catalina.properties
   d. Open C:\Program Files\Tomcat6\conf\web.xml.  Look for the <servlet> section (should be around line 90) and paste the following into that section
(don't overwrite what's already there, just add this):

 

<servlet>
    <servlet-name>GlobalCFMLServlet</servlet-name>
    <description>CFML runtime Engine</description>
    <servlet-class>railo.loader.servlet.CFMLServlet</servlet-class>
    <init-param>
        <param-name>configuration</param-name>
        <param-value>/WEB-INF/railo/</param-value>
        <description>Configuraton directory</description>
    </init-param>   
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <servlet-name>GlobalAMFServlet</servlet-name>
    <description>AMF Servlet for flash remoting</description>
    <servlet-class>railo.loader.servlet.AMFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

 

   e. Now find the existing <servlet-mapping> sections and add a few of your own. Paste in:

<servlet-mapping>
    <servlet-name>GlobalCFMLServlet</servlet-name>
    <url-pattern>*.cfm</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>GlobalCFMLServlet</servlet-name>
    <url-pattern>/index.cfm/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>GlobalCFMLServlet</servlet-name>
    <url-pattern>*.cfml</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>GlobalCFMLServlet</servlet-name>
    <url-pattern>*.cfc</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>GlobalAMFServlet</servlet-name>
    <url-pattern>/flashservices/gateway/*</url-pattern>
</servlet-mapping>

   f. Lastly, find the <welcome-file-list> section and paste:

 <welcome-file>index.cfm</welcome-file>

 

  g. Save web.xml and close it.

11. Start Tomcat. Make sure it started without any issues by checking the log file, because even if you have something wrong in your configuration, it will still appear to start. The log file you need to look for is here:  C:\Program Files\Tomcat6\logs\catalina.2009-09-01.log (of course, the date will be different in the file name). Scroll down to the very bottom (most current info is at the bottom in this log). Look for the line containing "org.apache.catalina.core.AprLifecycleListener init" ... this is the start of the log entry. You should see something like the following if all went well:

 

 

 

 

 

Sep 4, 2009 9:58:08 AM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Tomcat6\bin;.;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\Program Files\PHP\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;c:\mysql\bin;
Sep 4, 2009 9:58:08 AM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8081
Sep 4, 2009 9:58:08 AM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 482 ms
Sep 4, 2009 9:58:08 AM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Sep 4, 2009 9:58:08 AM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.20
Sep 4, 2009 9:58:10 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8081
Sep 4, 2009 9:58:10 AM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Sep 4, 2009 9:58:10 AM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/15  config=null
Sep 4, 2009 9:58:10 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 2331 ms

 

If there WERE issues, you'll see lines such as this mingled within:

SEVERE: Error starting static Resources
java.lang.IllegalArgumentException: Document base C:\inetpub\wwwroot\host-manager does not exist or is not a readable directory
SEVERE: Error in resourceStart()
etc.

If you do see issues, do your best to try and figure out what the error line is telling you. There's a decent chance you'll be able to do a facepalm and get it cleared up (type-o, etc.)

 

12. Assuming Tomcat started with no errors, let's test the Railo install by seeing if we can hit the Railo administrator! Try browsing to this url: http://localhost:8081/railo-context/admin/server.cfm
you should see: railo administrator screenshot

VERY COOL! Congratulations, you now have Railo installed and working. But, only by browsing directly to it via the Tomcat server (which is only half of our battle). Now it's time to connect IIS7 to this puppy. Due to the length and size of this post, I'm going to defer the next set of steps (getting IIS7 connected) to yet ANOTHER post, which can be found here.


ANNOTATIONS

*1* - Installing Railo itself, as with all other ingredients in this recipe, provides too many choices for the common man/woman. SO, I am only covering the choice I made, which was to simply grab all the Railo jar files and drop them into a Tomcat subfolder. Other choices include a "war" installation...not sure of why i would want to choose that one instead. If interested, see Todd Rafferty's step by step instructions at

http://web-rat.com/blog/post.cfm/installing-railo-on-tomcat-the-windows-edition

*2* - There's a LOT more to telling Tomcat about Railo than just pointing it to the JAR files, eh? We also had to tell Tomcat specifically about the different railo servlets (we have one for cfm/cfc templates and one for flash remoting). We then had to do some "servlet mapping", telling Tomcat which servlet we should let process which kind of requests.

 

 

 

Posted by dougboude at 11:45 AM | PRINT THIS POST! | Link | 4 comments
02 September 2009
Railo 3.1 on Windows Server 2008 and IIS7 - Part 1 of 3
BOK 1 of 2

Over the course of the past few days, I have spent a total of about 9 solid hours getting Railo installed on a Windows 2008 server running IIS7, configured for multiple domains and aliases. Having been through this process, I wanted to contribute my experiences and the details thereof in the form of this and a second blog post, hopefully to help others who want to traverse this same path either do it with less pain, or decide to avoid the pain altogether.

Now that I am on the back side of this install, I am left with two distinct bodies of knowledge that I want to share. The first is the overall experience: the pain, the joy(minimal), the reasoning behind choosing to run this gauntlet, and whether or not this is the right choice as far as cfml parsers go. This will be the subject of this blog post.

The second body of knowledge I want to share, which will be the subject of a second and third blog post, consists of the details and nuances of the installation, with the steps involved and some screen shots.  I did find two or three blog posts that almost matched up to my particular scenario, and/or DID match my scenario but left out what were vital pieces of information and understanding for me. So I dissected these posts, dug in to Tomcat documentation, did a lot of combinatory play, learned things I NEVER wanted to know, experimented until I wanted to cry, tapped into an awesome Railo resource (thanks Todd Rafferty!), and finally...FINALLY achieved success. The first of the last two posts can be found here.

The Scenario
Let me begin with my particular scenario. I am a one man show who inherited a horrendously written PHP app that is currently being hosted with an equally horrendous hosting company on a FreeBSD server. My current mission then is to get our app migrated to a new host. Not being a fan of green screens, blinking cursors, and commands that when spoken aloud sound like you're choking to death (AWK! GREP!), I of course opted for a Windows 2008 Server running IIS7 (my comfort zone). Also not being a fan of PHP and fully intending on doing all future development in CF, I needed something that would allow me to execute CF code. I could ask my company to drop $1,000 on CF Server, but why do that when there are at least two free alternatives floating around? I've been seeing lots and lots of activity in the Railo community and hearing good things about its performance and ability to do nearly everything that Adobe CF Server does, so why not go for it? So, I rolled up my sleeves and leaped headlong into the burning fiery furnace to wrestle this hombre down.

Choices, Choices

I always hate it when I go to a restaurant, order something from the menu, and then get presented with a hundred questions by the waitress about the specifics of my order. "Fries or tots. mmhmm. Salad or soup. what kind of soup? toast or biscuit. White or wheat". AUGH! Choices are good, but TOO many choices can stop you dead in your tracks sometimes. A Railo install is a recipe consisting typically of at least four distinct items: an operating system, a web server, a Java Servlet Engine, and Railo itself. It is at this very beginning point in the process of using Railo when the water becomes incredibly murky, as there are far too many choices out there for each of these items (except Railo itself). Operating system...take your pick! You have people using CentOS, Windows Vista, Windows 2008, Ubuntu, Mac OSX, and the list goes on. Web Server? Seems that the majority of people are using Apache, but in theory, ANY web server theoretically will do. IIS6 and 7 are available, and in minimal use based on my research. Java Servlet Engine? If you're not a Java geek already, then you will probably have almost no good knowledge on which to base your choice here except recommendations from acquaintances. Myself, I like to be able to think "down the road", but not being a Javaphyte, choosing a servlet engine was pretty much a coin toss. Well, except for JRUN...the general concensus is to avoid that one. Besides JRUN though, you can use JBoss, Glassfish, Resin, Tomcat, and probably a dozen other ones I haven't even heard of. I've been told that Resin is the simplest to use, but limits what you can do "down the road", while Tomcat is a good compromise between complexity of use and the doors it opens up for you. So then, HOW does one decide what specific "recipe" they should use? You're probably already set on the OS you're using, so that's a done deal. Web server: I'd choose the one I'm most familiar with, since you will have to get down and dirty with it. Servlet engine? After a long period of Googling, I decided to go with Tomcat, for the sole reason that it is the one for which I found the most instruction and documentation.

Railo: indeed a cheap alternative to CF Server. In terms of cash, anyway. But uber expensive in terms of the intellectual overhead required to wrap your mind even partially around it and just get it installed and configured at a basic level! I know, some would disagree with that assessment; but I say, after having run this guantlet and installed Railo on a windows server running IIS7 with multiple domains and aliases, that unless you have a passion for system administration and/or are as comfortable staring at blinking cursors and command lines as you are in a GUI environment, then setting up Railo (in my environment, anyway) is painful. Why is it so painful? In a nutshell, because we have always been shielded from the nasty, ugly Java that actually makes CF Server work. Railo, on the other hand, is raw and open and REQUIRES that you not only stare into its filthy bowels, but also plunge in both hands to tie some precisely placed knots in its writhing intestines. If you want to use Railo, you can't be squeemish and must be a firm believer in the phrase "no pain, no gain".

The looming question then, after having gone through the process of just getting it installed: Is it worth the pain? Well, you WILL gain a clearer understanding of how CFML parsers work behind the scenes (like we wanted to know!); You WILL save the $1,000 it costs to buy CF Server; You WILL be able to execute CFML, and have it do so faster than CF Server can do it. But, you will also be committing yourself to an additional level of server administration that "the rest of us" may not be comfortable dealing with. Again, unless you're good with having to hand edit multiple, cryptic configuration files (the equivalent of trying to add and remove walls in a house made of playing cards without it falling down), don't mind having to manage web server changes in places other than just your web server, find that system administration brings you joy, or you're just a glutton for punishment (my category), then you will probably want to just sit back and wait until the railo community comes out with version 2 of a windows installer (version 1 doesn't exist yet, but typically version 1 of anything is rough around the edges). I'm definitely not dogging Railo, not at all; Just wanted to let folks know how it went for me so they can make an informed decision going into it.

Post 2 to follow soon...

Doug out :0)

Posted by dougboude at 4:59 PM | PRINT THIS POST! | Link | 3 comments
25 August 2009
Strange Validation Issue with CFARGUMENT

I was chatting with a buddy of mine today about an issue he had encountered when passing a list of numbers into a cfargument whose type was set to "numeric" (CF 8). For whatever reason, he could pass in the string "1,6,7" and it would actually validate as a numeric value. When passing in a fourth number, such as "1,6,7,9", it threw an error based on the argument type, which it rightfully should have done when being passed "1,6,7".

To try and narrow down the reason behind this behavior I whipped up a quickie function to test with. Here's the code:

<cffunction name="test" access="public" output="false" returntype="any">
 <cfargument name="thevalue" type="numeric" required="yes" />
 <cfset var retval = structnew() />
 <cfset retval.isnumeric = isnumeric(arguments.thevalue) />
 <cfset retval.rawvalue = arguments.thevalue />
 <cfset retval.dothemath = arguments.thevalue - 0 />
 <cfset retval.theval = val(arguments.thevalue) />
 <cfreturn retval />
</cffunction>

<cfset valList = "1,1,6|0,1|1,1,6,9|0,0,1" />

<cfloop list="#valList#" index="i" delimiters="|">
 Passing in: <cfoutput>#i#</cfoutput><br>
 <cftry>
  <cfset retval = test(i) />
 <cfcatch type="any">
  <cfset retval = "ERROR: " & cfcatch.detail />
 </cfcatch>
 </cftry>
 <cfdump var="#retval#">
 <hr>
</cfloop>

Here's the results:

results of a string validating against a numeric argument type

I tried many more combinations of numbers as well, and couldn't see any common denominators nor figure out what might be going on under the covers. Just thought I'd share this in case anybody had some input that might explain such behavior.

Posted by dougboude at 3:40 PM | PRINT THIS POST! | Link | 2 comments
28 July 2009
Hosts File Changes Not Acknowledged on Vista 64

I spent the better part of yesterday wrestling with what should not have been a problem in the first place. But, since it was, and since it managed to burn up the majority of a perfectly good day of development, I share this information in hopes that it will spare someone else my frustration.

The Scenario

You're OS is Windows Vista Home Premium, the 64 bit edition. You are attempting to set up a local dev environment using Apache and MySQL, but no matter what you try, the changes you make to your hosts file just will NOT become visible to your ping attempts! For instance, you add an entry such as "127.0.0.1   local.mydev", but when you go to a command window and try to "ping local.mydev", you get "host not found". SO not cool.

Editing the hosts file

Okay, a few words about editing the hosts file in Vista. Vista is very self-protective, even to the point of trying to protect you from yourself, so editing the hosts file requires that you do so with admin privileges. Ah, but just being logged in as an admin isn't enough; you have to start notepad (or whatever editor you're using) using the "run as administrator" option. Click Start, enter 'notepad.exe' in the search box, when it shows up in the results, right click it and choose 'run as administrator'. THEN you can open your hosts file and edit it. Make sure when you go to save it that you change the file type to *.any and overwrite the original file; By default notepad really really wants to create hosts.txt (which your browser would ignore). If you plan on making hosts file changes frequently, you may as well just set up a shortcut on your desktop that already has admn privs and already points to your hosts file. Create a new shortcut with the target property of "C:\Windows\System32\notepad.exe c:\windows\system32\Drivers\etc\hosts". Then click the 'Advanced' button on the shortcut tab and check the "Run as administrator" box. Now everytime you need to edit the hosts file, you'll already have admin privs to do so.

The Solution

SO THEN, back to the issue. At this point you know that your changes to the hosts file are being saved, but still no positive ping action taking place. After googling for hours and trying all SORTS of things, here are the steps I followed that gave me positive results.

1. First, make certain that the system is looking at the same hosts file you are. The odds that it's looking somewhere else are slim, but best to eliminate the possibility anyway. To verify hosts file location, you'll be launching regedit and looking for the value of the registry key "DatabasePath" in the Tcpip/Parameters (full key path: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters). The value should look similar to %SystemRoot%\System32\drivers\etc .

2. Stop the "DNS Client" service

3. Make sure that the hosts file name is all lower case. It shouldn't make a difference, but the information I found all references it that way and on Vista, it was actually named Hosts originally. Changing it's name can be a booger because of OS security, so to do so, see step 4....

4. Create a new hosts file. I tried using notepad, but could not get it to omit the ".txt" extension no matter what I tried, so ended up using EditPad Pro (freeware). Just delete or rename the old file, then create this new one, adding in the entries you need.

5. Go to a command window and test by trying to ping one of the entries you created. With the DNS Client service stopped, Vista should use your hosts file. At this point in the game, you should definitely have positive results.

6. Restart your DNS Client service, then re-test in a command window just to make sure it's still working as designed.

If you follow the steps above and it still doesn't work, I have no clue what to tell you except to perhaps try re-ordering the steps, toss in a reboot or two, try executing some ipconfig /flushdns commands. Some order of all of the above is what finally got me where I needed to be.

Oh,and if you learn anything in the process that I haven't caught on to, please come on back and leave that tidbit in the comments section of this post. If you do, all of your wildest dreams will come true.

Doug out.

Posted by dougboude at 10:50 AM | PRINT THIS POST! | Link | 4 comments
30 June 2009
Small But Seriously Irritating Export to Excel Issue

Alright boys and girls, here is an issue and its solution that had me going NUTS for a couple of hours. I actually wrote and implemented three DIFFERENT solutions trying to find a way around the problem, only to find out in the end that I kept carrying the true issue with me.

The Scenario

My user clicks a link and a window pops up allowing them to save or open some exported data as an Excel spreadsheet. Of course, it all worked fine on my machine (two of them, actually), but when my boss tried it on his, he got an error. Not cool. Back to the drawing board.

The Challenge

My first attempt (which worked fine for me) was to create a true excel spreadsheet by streaming the data in binary to a file (it's a bit complicated, but cool). Excel 2007 was able to open it just fine, but my boss' older 2003 could not. So, I decided I'd just fall back on the old reliable CSV type file rather than try and troubleshoot the first approach in too much depth. I coded my CSV export and for the life of me could NOT get my Excel to open it without first flashing me with two nasty error messages, paraphrased here:

"The file you are trying to open '[file name here]' is in a different format than specified by the file extension."

"Excel has detected that '[filename here]' is a SYLK file, but cannot load it."

What the heck??? All we're talking about is some text separated by a comma. And Excel doesn't recognize the format??? Content headers are right; file extension is right; Open it up in a text editor and it sure looks fine to me; What's going on?

I reworked the code to use tabs as delimiters; same symptoms.

The Solution

After dicking with it for a couple of hours, comparing known working CSVs with the one I generated, comparing non printable character strings such as line feeds (which is what I suspected the issue to possibly be), I finally figured out that the TRUE problem was (and I grit my teeth in frustration when saying this): the name of the first column in my data.

NEVER allow the first column in your data to be called "ID". AUGH!!!! (had to scream). Apparently, using "ID" is a trigger to Excel that your file contains a symbolic link to another spreadsheet somewhere. I know, I know, for every OTHER type of file that Excel opens it relies on the file's EXTENSION to determine the type; but not in this case.

Moral of the story: NEVER allow the first column in your CSV data to be called "ID".

Lesson learned, I'm now one small step closer to Nirvana.

Posted by dougboude at 12:30 PM | PRINT THIS POST! | Link | 23 comments
26 June 2009
Disappearing IE Popup Window During Save/Open Dialog

okay, I had a major wrestling match today when attempting to allow my users to automatically save or download a dynamically generated Excel spreadsheet, so thought I'd share the details in case it helps someone else save some hair pulling.

The Scenario

You have a page with a link on it. Clicking this link produces a popup window, the code behind it querying a database, writing an excel file to disk, setting the content headers, and then prompting the user to open or save the file.

The Symptoms

In IE (i'm using version 8), your user clicks the link to "Export Data", popup window appears, and then promptly closes without so much as a "how do you do". (in other browsers, such as Chrome and Firefox, everything behaves just like it should; user clicks link, popup appears, user is immediately prompted to either open or save the file)

The Solution

After trying all MANNER of values for headers, it turns out the magic combo for making it work (in my case, anyway) had two parts to it:

1. DO cache the content. This is accomplished by making sure you DO NOT have a "Pragma: no-cache" header, and including a header like this: Expires: Thu, 01 Dec 2010 16:00:00 GMT (or any other future date...perhaps you could automatically calculate it to be one minute in the future or something). Seems that IE, if you have said NOT to cache content, can't find the file by the time it gets to the dialog because it already uncached it. Strange behavior, but true.

2. Check your browser settings to ensure that you have "Automatic prompting for file downloads" ENABLED. By default it is DISABLED. Here's a screenshot of that setting to help you locate it:

changing automatic download settings in IE 8

After I did those two things, my download link behaved as it did in the other browsers I tested.

Just for clarification, here are the headers and values I ended up with (this is PHP code, so just deal with it):

header ("Content-type: application/vnd.ms-excel");
header ("Content-Disposition: attachment; filename=".$tmpfilename);
header ("Expires: Thu, 01 Dec 2010 16:00:00 GMT");

Okay, that's it. Hope it helps someone else save some time.

Doug out

 

 

Posted by dougboude at 5:23 PM | PRINT THIS POST! | Link | 2 comments
11 June 2009
My Twelve Steps to a Coldbox App

Yesterday I had the privilege of providing my first consultation job as an instructor on the subject of Coldbox. The team that hired me are at the stage in their project where they're ready to start writing code (database is designed, UML and usecase diagrams are complete, mockups have been created), and they wanted to "experience" my thought processes, the questions I ask myself, my approaches to implementing functionality, and how I troubleshoot as I make my way from an idea to a working app in Coldbox. So, we spent the entire day together building an app from scratch, using Coldbox.

In preparation for the task, I spent the past week investing a LOT of time re-reading the Coldbox docs, experimenting, building out a small sample application that the team and I scoped out beforehand, and making notes about my thought/decision-making processes along the way.

Taking notes about your own personal development process is actually quite interesting and insightful, and since my audience found it useful and enlightening, I thought I'd share a high level view of it with the rest of the community as well. What follows is a bullet point list of how I personally generally go from idea to app.

Let's take a walk in my head, shall we? Don't be afraid....

1. let's get the current version of the coldbox framework (www.coldboxframework.com)


2. grab copy of the skeleton app template from the Coldbox framework download (coldbox\ApplicationTemplate)


3. make sure I have a mapping to Coldbox (mine is in application.cfc using this.mappings[...)


4. browse to the skeleton app and make sure that I get the pre-canned "You are now running Renew version 2.6.3..." home page.


5. Now, let's make a grocery list! What do I mean?....
The Coldbox framework (really more of a robust toolkit) does a LOT...which aspects, features, abilities do we want to incorporate into OUR application? Until you know what Coldbox has to offer, you can't answer this question. So, read through the online wiki my friend; open the ColdboxCheatSheet.pdf that comes in the framework's 'install' directory and take a good gander at all of the methods, plugins, interceptors, and other items listed there; Open up the Coldbox.xml.cfm file and just read the different settings that are listed there; Peruse the framework's directory structure, its config files, plugins, interceptors, autowiring, model management, views and layouts, usage of convention. As you learn about each new aspect, think about the app you are building and decide if it is one of the features you want to be sure and incorporate.


For this particular application, here's the grocery list I came up with when considering app features with framework features:

  • I will use the built in Coldbox model management as opposed to a third party IOC framework;
  • I will not use an  ORM framework for this app (though if the project warranted one, I most definitely would!);
  • I will be using Ajax so I'll want to take advantage of Coldbox's multiple layout ability;
  • I will be using my own security interceptor instead of the one that comes with Coldbox;
  • I will not use the environments interceptor;
  • I will be sure and enable the sidebar debugger;
  • I will make exclusive use of the MessageBox plugin for all system messaging

6. Okay, now I'll take a first pass through my config.xml.cfm file and alter the settings I know about at this point.

  • Enable model setter injection;
  • add custom settings for my javascript library path, my css path, my images path;
  • define the datasource object;
  • and enable the sidebar interceptor.

7. At this point, I feel the need to go ahead and create my database table structure and get that to a solid starting point.

8. Let's tweek the default layout and make placeholders for the different components it must organize (header, footer, nav, content, messages, dynamic content area, etc.).

9. Now i want to replace the pre-canned default view with one of my own...my app's own "you are here" landing page. On that page I'm now going to take the time to output lots of global data that I'll likely need to know how to get at later, like the datasource name, the name of the current event, etc. I will use this output as a cut-and-paste reference later.

10. Let's add some security with simulated authentication! Get a basic "login/logout" form to work without actually doing any database calls. The objective here is to just get the appropriate session variables in place, get them added to the Event object at the appropriate time, make sure our events are being intercepted and examined by security code at the appropriate places, and that rendered output is based on authentication status where it makes sense. Once it all works in simulation, add in the needed model objects to perform actual authentication and replace all simulation code.


11. Now I will focus on navigation (if it's anything but simple nav, such as database-driven, hierarchical nav) and ensure that all code is in place to create and output navigation appropriate for the user and state.


12. From here on out, I simply start going down the list of remaining functionality, typically ordered from "least specific" to "most specific", giving all end-user functionality priority over administrative functionality. The easy way to do this is to simply work my way through my navigation items, building out one at a time. To illustrate in more detail how I build out a specific piece of functionality (we'll say a CRUD operation), here's the check list:

  • build the handler with basic event functions to handle our nav links
  • build out the view page for managing an item
  • add a function for saving an item that saves then returns to the calling event
  • create a model service for the item...wire it up(using Coldbox's Model Integration feature
  • add the model service to the modelmapping.cfm
  • wire up the handler, finish up the handler functions that call the corresponding/appropriate service object's methods


In a nutshell, this is my general thought process as I build an app, with emphasis on building a Coldbox app. How does my "to do" list match up with everybody else's? I'd be interested to know!

Posted by dougboude at 10:58 AM | PRINT THIS POST! | Link | 5 comments
26 April 2009
Auto-Escaping Characters When Outputting JS Function Calls

I'm blogging this little snippet mostly so that I have a place to find it the next time I need it, but perhaps it'll come in handy for someone else as well.

I'm creating some Javascript function calls on the fly as I output some query results. One of the parameters in the JS function is the value of an item's title which may at times contain characters JS tends to barf on, such as the single quote: '

In order to automatically escape such characters as I create my JS call, I used CF's ReReplace function. This function utilized a regular expression in order to do a search and replace, so I just created a simple regex that contained a list of all the characters i wanted to be automatically escaped.

Snippet time.

The specific code that performs the replacement:

ReReplace(Ucase(videotitle),"(['|.|;|?])","\\\1","all")

Distilled version of the output code:

<cfoutput query="qryVideos">

<a href="##" onclick="playVideo('#videoID#','#ReReplace(Ucase(videotitle),"(['|.|;|?])","\\\1","all")#','#vidPathRoot#/#videoPath#');return false;">watch this video</a>

<br>

</cfoutput>

Posted by dougboude at 6:49 PM | PRINT THIS POST! | Link | 4 comments
10 April 2009
Resolution to 'Mysterious' Bash Script Error
(to skip to the very heart of this post, click here. otherwise humor me and read until you get to it  )

So I was sitting there working on a CF-based resume builder app when my boss walks in and asks me to take over responsibility for our company's database and then implement a solid backup plan and policies. I'm still new to this job and so haven't been privy to anything database as of yet, but, how hard can it be? I heartily accept (because accepting requests makes the boss happy) and then proceed to track down all the particulars of our database so I can make this all happen.

The skinny is that our current web application is a very large and disorganized hodge podge of PHP files that run against a mySQL database sitting on an over-priced, shared hosting Linux server somewhere. I FTP the PHP files to my pc and dig through them for some clues about our database and find hard-coded (all OVER the place) the server name, username, password, and database name. "Cool", I say to myself, "I can just use the mySQL gui and continue to explore this puppy. Well, apparently direct connectivity to anything but "localhost" was blocked, so I had two choices for access: phpMyAdmin or telnet sessions via puTTY.

phpMyAdmin does have a feature to allow me to create all the scripts needed to recreate all tables and insert their data. But I wanted a "real" backup as well via mysqldump if possible, so decided to use puTTY and see what I could make happen.

Bear in mind that the last time I did any meaningful work from a command line was somewhere in the late 90s, and I consider doing things that way "last resort", cryptic, and way over complicated. (my personal opinion is that individuals who purposefully CHOOSE to do things the harder more cryptic way when a more user friendly approach exists probably do so in order to make themselves feel smarter. Yeah, I could use the old DOS 'debug' command to find my way into an embedded bios in a piece of hardware I have installed and run its processes that way...but when I have a gui available that does the same thing??? c'mon). Okay, so anyway, I'm staring at a lovely black window with a green square cursor, and an hour later I have managed to piece together what I think is a good start to a bash shell script that will do a "check table" on all of my database's tables, run repair on any that report an error, do a mysqldump on my database, and then ftp the resulting file to a remote server. But, I have a problem (one that consumes the next two and a half hours): my script won't run without immediately throwing a very ambiguous and meaningless error.

I add "-x" after the initial "#! bash" statement so I can maybe get more info, but there's none to be had. After many, many iterations of the same line of code (trying to do a mysql "show tables"), I accidentally happened upon a blurb somewhere out in the ether that caused me to consider the character(s) being used as linefeeds in my script file. I ran a quick regex replace, replacing all \r\n with \n, and lo and behold the script worked!

The Moral of the Story: If you HAVE to write shell scripts in a PC environment, make sure you do a regex replace of all \r\n with \n!

Oh, and do I feel smarter for having managed to create a working shell script from within a PC environment? No. I mean, I do feel a sense of accomplishment that I hung on to this bull and rode it until it submitted to me. But more so I rather feel frustrated that I have to jump through so many fiery hoops to accomplish what ought to be mostly straightforward. Why's the world gotta be so complicated? Oh well, that's another rant altogether.

Hope this saves someone else some time!

Doug out.
Posted by dougboude at 4:22 PM | PRINT THIS POST! | Link | 2 comments
30 March 2009
Changing Scriptaculous' Sortable ID Parser

My scenario:

I'm using Scriptaculous and CF8 to create a "stacked" PDF based on a user's re-ordering of a list of uploaded files (you can check out the live app here). In order to do this simply, I'm making the file name part of the sortable list item's ID value, like so: ID="file~#filename#" . Well, Scriptaculous has a built in way of dealing with sortable item ID values, where it's looking for some text followed by an underscore followed by a numeric value. In order to break away from this assumption, the latest version of Scriptaculous (1.8.2) offers an option called "format" that allows you to specify a regular expression to override the default one.

So, here's my code that creates the unordered list of files:

   <cfdirectory action="list" directory="#uploadedfiles#" name="qryFiles" />
   <div id="filelist">
    <ul id="filestack">
     <cfoutput query="qryFiles">
      <li id="file~#name#" class="fileitem" ext="#listlast(name,".")#">#name#</li>
     </cfoutput>
    </ul>
   </div>

and here's the JS I use to turn this list into a sortable:

Sortable.create('filestack',{format:/^file~(.*)$/});

Executing the following JS will show you that the regex is correct:

alert(Sortable.sequence('filestack'));

Not hard stuff, but it took me a while to piece it all together so I thought I'd share in case it saves someone else a little time.

Posted by dougboude at 11:30 AM | PRINT THIS POST! | Link | 0 comments
12 March 2009
Easy Way to Grab Remote SQL Data

Okay, this is just too cool so I thought I'd share it.

Let's say you have access to a database on a remote server somewhere, and there's data there that you would love to have a copy of locally. Options, options, options...well, if we aren't using SQL Express we have lots of options for migrating the data. But in our case, we ARE using SQL Express, so we don't have easy access to linking servers and other more advanced commands. Wouldn't it be just perfect then if we could simply do a SELECT * INTO MyNewLocalTable from [remote table]? Turns out that I CAN if I turn on an option that is turned off by default. So first, I execute these two lines in a query window (I'm using SQL Server Management Studio Express):

sp_configure 'Ad Hoc Distributed Queries', 1
RECONFIGURE

 

Then, I execute THIS query (well, one like it with suitable values substituted):

SELECT * INTO MYDATABASE.dbo.MyNewTable
FROM  OPENDATASOURCE(
   'SQLOLEDB',
   'Data Source=[url to remote server here, including port if needed];User ID=[sql username];Password=[sql password]'
   ).REMOTEDATABASE.dbo.SourceTable

 

Worked like a charm! Now I have a local copy of the remote data. In my case I migrated a little over 80,000 rows and it took about a minute or less. Saweeeet.

Doug out.

Posted by dougboude at 5:47 PM | PRINT THIS POST! | Link | 31 comments
09 March 2009
Using Fireworks CS4 for Interface Driven Development
For those who are interested, I gave a 45 minute presentation at the Alamo Area CFUG last week on using Fireworks CS4 as a supplement to the Interface Driven Development methodology. I'm by no means an expert, but I do believe that what I've learned over the last several weeks might prove beneficial to other developers, especially those who are "one man shows" building products from the ground up. Oh, and a special shout out to Dee Sadler, whose gracious sharing of knowledge pointed me in the right direction.

Useful links are given at the end of the recording that point to add ons required to do some of the things I demo'd. Also, Connect didn't do a very good job at keeping video and audio in sync, so the audio tends to be ahead of the video by a few seconds.

Hope you find it helpful!

Doug out.
Posted by dougboude at 3:47 PM | PRINT THIS POST! | Link | 2 comments
02 March 2009
Gotta Give Adobe Kudos for Persistence!
My trial version of Fireworks CS4 expired yesterday, so I went to the Adobe store to explore upgrade possibilities from version MX2004. I was greeted by a very nice (but suspiciously semi-robotic) sales associate named (also suspiciously based on some of the english) "Randy". I found some humor in Randy's blatant insistence that I make my purchase RIGHT NOW, so thought I'd share the short thread. In the following conversation, I am "Visitor".... Enjoy!

Please hold as we route your chat to an Adobe Representative.
Welcome to Adobe.com! My name is Randy. May I assist you with your selection today?

Randy: Hello, how can I help you?
Visitor: i'm wondering if i can upgrade to fireworks cs4 from fireworks mx2004
Randy: Hi there.
  (long pause here...)
Randy: I'll be glad to help you with that.
  (another long, long pause here...)
Randy: Let me check that for you.
  (yet another long pause...)
Randy: You can upgrade from Fireworks MX 2004 to Fireworks CS4 for US $149.
Randy: Shall I forward direct link to upgrade it?
Visitor: yes, thank you
Randy: Okay.
Randy: Please click here to view the purchase link for 'Fireworks CS4 '.
Randy: Please go ahead with the order processing and let me know if you need any help.
Randy: I'll standby to hear the order number and the total order value since it helps me to verify the order and confirm that the order has gone through successfully.
Randy: Please let me know if you need any help.
Randy: Is that okay?
Visitor: i won't be ordering today. have to route it through purchasing.
Randy: I request you to place the order now. Since it helps me to verify the order and confirm that the order has gone through successfully.
Randy: It will take only few minutes to complete the order process successfully..
Randy: If you wish, I'll stay online and provide any information needed which may help you to take the decision.
Visitor: i won't be ordering today. have to route it through purchasing.
Randy: If you wish, I'll stay online while you discuss to get the approval and provide any information needed which may help you to take the decision.
Visitor: everybody has gone home for the day. There's nobody for me to discuss it with at this time.
Randy: Okay, please contact us on this live chat when you're ready to buy. We will be happy to help you.
Randy: I'm happy to help you. Do you have any other questions for me?
Visitor: no thank you
Visitor: but I appreciate your nagging persistence.
Randy: You're welcome. It's my pleasure to help you today.
Randy: Thank you for visiting Adobe.com today! We'd like to hear your comments. Please click on the 'Close' button in the upper right corner and take a moment to complete a short survey. Thank you!
Posted by dougboude at 6:53 PM | PRINT THIS POST! | Link | 3 comments
04 February 2009
Presented My First Talk on ColdBox Tonight!

In case anybody wanted to attend but was unable, I just wanted to make it known that I did present at the Alamo Area ColdFusion Usergroup meeting tonight on the topic of the ColdBox framework. I was given 45 minutes, and finished precisely within my time limits, except for a few questions at the end that I had to field.

A link to the recorded presentation can be found at UG TV ( http://www.carehart.org/ugtv/ ), and is titled "A 45 Minute Discourse on the Subject of ColdBox". You'll have to try and overlook the slightly skewed sound and video tracks...just something we seem to have to live with when using Connect.

I am very interested to know what people think of the content, and any critiques that might help me improve my presentations in the future. With broad topics and limited time, it's hard to know where to focus sometimes.

Thanks!

Doug  :0)

Posted by dougboude at 12:41 AM | PRINT THIS POST! | Link | 1 comment
28 January 2009
First Meeting of the San Antonio RIA User Group Rocked!

Last night (Tuesday, January 27th, 2009) marked the birth of the San Antonio RIA User Group. We met in a large, rustic room on the first floor of JungleTech's office building. A large two story adobe-style hacienda complete with ancient oaks, hand-tiled covered patios, and a pool with a waterfall, the accommodations provided to the group lent a personable ambiance to the meeting that, in my opinion, enhanced the learning experience. Josh Helpert, the group manager, mingled comfortably among the guests before the meeting started, getting to know a little about their backgrounds and their interest in the topic of the evening: Introduction to ActionScript 3. Then, with snack plates and drinks in hand, everyone took their place in one of the leather and wrought-iron chairs arranged in a large semi-circle around the projection screen and gave their attention and minds to the presenter.

Only one other individual in the meeting besides Josh had any background in ActionScript at all, but by the end of the presentation every one of us had an excellent grasp on the subject. Josh's background as a formal instructor combined with his solid experience, knowledge, and passion for Flash and ActionScript ensured that every individual received exactly the delivery, analogies, and verbage that helped him or her assimilate the information at hand. It was both brilliant and considerate on Josh's part, as well as a joy to partake of, as he took into account the background information he had gleaned during the initial social interactions and wove in terminology that he knew certain individuals could relate to in order to "connect the dots" for them between their world and the one being introduced to us. In addition, Josh made sure and not bore his audience by reading powerpoint slides, but rather did a LOT of coding as we watched, sometimes pausing during the process to ask US, his audience, what he should do, how he should do it, and why. He included everyone in the process, made it not simply a lecture, but a complete audience participation project from which we all learned mightily.

The outline of items covered included a brief history of the evolution of ActionScript and a bullet point comparison of differences between versions 1 and 2 and the paradigm shift that occurred in version 3. We then discussed what an .fla file is, where ActionScript code lives, tools to use to write AS3 code, and how to connect it all together. A solid exploration of basic syntax and data types followed, all topped off by all of us building a simple SWF that utilized ActionScript event dispatchers and listeners.

As I said, Josh's style, knowledge, and obvious passion for sharing knowledge made this initial meeting a complete success and laid the foundation for a group that holds a lot of promise and purpose for anyone in the San Antonio area interested in the topic of Rich Internet Applications. Upcoming potential topics such as using ColdFusion to build backend systems, exploring Ajax and some of the Javascript libraries used for that purpose, and Flex were mentioned. With a casual but serious smile, Josh gave me the "heads up" that I might be tapped to present on one of these in the future, as his vision for this group is that everybody in it take some ownership of it. If you have even the remotest interest in this topic, you really need to make it a point to attend. The meetings are scheduled for the third Tuesday of each month. Details can be found at http://riausergroupsa.wordpress.com/

I'll conclude my review by saying that this group was actually born about two months later than it should have been, due to the inordinately lengthy process that Adobe has instituted in order for a group to become sanctioned (or perhaps it's simply a matter of they not supplying enough manpower to meet the needs at hand, I'm not sure which). In the end, after having applied and our application "lost", we were told that in the meantime someone else in the area had applied to be a sanctioned Flex user group, so we would have to "work it out amongst ourselves". Rather than delay any longer the official formation of the group, we simply opted to be non-sanctioned, and so when you come to our meetings you should NOT expect to hear any Adobe propoganda or receive yet another handful of Adobe logo-fied swag. What you CAN expect, however, is to be immersed in an atmosphere of learning, teaching, passion, and experience that promises to ensure the one thing all professional say they want: growth.

Posted by dougboude at 7:15 AM | PRINT THIS POST! | Link | 0 comments
22 January 2009
Special Character/Unicode Issue in Ajax Data Retrieval

My most recent project has caused me to have to be "unicode aware" at times (something I've never had to do before), and so I am learning a lot about encoding and display of special characters as I go along. My latest challenge related to this topic involved a User Manager section I created, wherein the users could very well have names that contain special characters (foreign names). This particular section performs its updates, deletes, and inserts via Ajax calls and client-side JS manipulation of a JSON data set. My Ajax is performed via the Prototype library, my code is all ColdFusion living within the Coldbox framework, I'm using Coldspring to manage my object relationships, Transfer is my ORM, and my backend database is MSSQL 2005.

The Challenge: Data that contained special characters was being successfully inserted/updated via my Ajax calls, but the JSON data set returned via those calls did NOT contain those special characters (or contained an incorrect interpretation of them, like numbers, question marks, etc.). A quick check of the database verified that the data was indeed stored in the tables properly.

Setting the Stage
At this stage in the game for me, the smorgasbord of terms, acronyms, and concepts revolving around properly handling unicode is a bit foggy for me. (On a side note, I WISH someone who has the full understanding would put together a simple "checklist" of "Things you need to do in order to handle special characters in ColdFusion"!) From what I currently understand, the physical template you write has to be "encoded" properly (set within the IDE you are using); The database you are using has to have the proper encoding(called Collation in MSSQL 2005); The fields in your table have to be of the proper type to store unicode text(ie: 'nVarchar' instead of 'Varchar', etc.); your browser has to have the proper languages associated in order to display certain sets of special characters(Tools/Internet Options/Languages in IE); your JS functions, if living in a separate file, must have the page encoded properly(again, via your IDE); your ColdFusion datasource has to have the checkbox for "Enable High ASCII characters and Unicode for data sources configured for non-Latin characters " checked; and to top it all off, after having handled all of that, your JS functionality still yet needs to have ITS encoding types set in the proper place.

That sounds like a LOT of fiery hoops just to be able to deal with special characters, right? Well I'm with ya...it's on the verge of being a nightmare for someone who's never had to deal with it. And I do realize that for some of you reading this, the first time YOU tried to deal with special characters, everything just frickin "worked right out of the box" and you probably didn't have to do but one or two of those things, at the most. I say that you got lucky that things were configured just so for the particular character set you were dealing with, and even though from your perspective it didn't seem like that big of a deal, the fact is having an understanding of what's going on behind the scenes can be pretty doggone important anyway, just in case you suddenly get the directive to start storing characters from some other encoding scheme that you AREN'T prepared for out of the box.

Okay, so back to my challenge. Here's the nutshell of how my process flows:

My initial page load is provided with a query of all of the users in the system. That query is then translated to a JS object using a line in my template like the following:

<script>
 //make our initial data set available to JS...
 var objUsers = <cfoutput>#serializeJSON(qryUsers,true)#</cfoutput>;
....

When a user is chosen for edit, I load up the values for that user from the client-side data set into form fields, allow them to be edited, then submit the form values back to Coldbox via an Ajax call where the record gets updated. After the update occurs, my event grabs a fresh copy of the user query (which now contains the updated record), serializes it, and returns it as a JSON string to the Ajax call. Here's the line of JS that performs the Ajax call:

new Ajax.Request(saveURL,{parameters: myparams, method:'post',onCreate:showWorking,onComplete:postSave});

Here is the line in my handler(controller) that returns the data to the call:

<!--- grab a fresh copy of the users to pass back as json to the call, sans a view --->
<cfset arguments.event.renderData(type="plain",data=serializeJSON(variables.userService.getAllUsers(activeOnly=false),true),contentType="application/json) />

 

Bear in mind that the "getAllUsers" method call you see is the exact same method call being used during the initial page load to retrieve the data, which DOES contain the special characters as it should.

So here is where the problem manifests itself. The JSON string that the "postSave" method is provided with has the special characters stripped out! Poof, they are just gone. Okay, so let me go and investigate some of the optional parameters that Prototype provides for its Ajax.Request method and see if any of them might apply in this situation....  Ah, here are a few! 'encoding', 'evalJSON', 'sanitizeJSON'. Well, playing with all three of these resulted in zero changes to the symptoms. Sheesh, I've encoded everything I can possibly think to encode...what else is there? After a lot of google time, skimming page after page of semi-related (but not directly relevant) info, I came across a tiny little tweek to the contentType being returned that I tried, and lo and behold it frickin worked! Here is the new line that returns a CORRECT data set to my Ajax call:

<cfset arguments.event.renderData(type="plain",data=serializeJSON(variables.userService.getAllUsers(activeOnly=false),true),contentType="application/json; charset=UTF-8") />

The difference: adding in "charset=UTF-8" to the contentType of the data being returned. Apparently THAT'S what JS was looking for all along.

I hope this helps someone else avoid a huge loss of time. And again, for those of you out there who know this stuff inside and out and can actually visualize how it all works in your head, it sure would be an assett to the community if you could put that info into a kind of "checklist" a person could use to make sure they have all of their Unicode ducks in a row when trying to deal with special characters! Pretty please?

Doug out.

Posted by dougboude at 10:54 PM | PRINT THIS POST! | Link | 6 comments
17 December 2008
Getting a Complete List of Timezones from Java

I've been doing some research on i18n, locales, timezones, and all that jazz lately. Today I wanted to build a dropdown list of possible timezones, and came across a bit of Java code that I converted to CFSCRIPT. Thought I'd share it in case anybody else finds it useful. I will say that there are a LOT more timezones than I would have ever guessed (592 to be exact), and the results of my snippet probably aren't useful as-is to populate a dropdown; but, at least you do have a way to access what your CF server's JVM knows about, and you can filter it as you like.

Oh, I ran this code under CF8; not sure what it does under earlier versions.

The Code:

<!--- create list of valid time zones using java... --->

<cfscript>

tz = createobject("java","java.util.TimeZone");

aTZID = tz.getAvailableIDs();

today = now();

aTZs = arraynew(1);

for(i=1;i<=arraylen(aTZID);i=i+1){

tmptz = tz.getTimeZone(aTZID[i]);

stThisTZ = structnew();

stThisTZ.id = aTZID[i];

// Get the display name

stThisTZ.shortName = tmptz.getDisplayName(tmptz.inDaylightTime(today), tz.SHORT);

stThisTZ.longName = tmptz.getDisplayName(tmptz.inDaylightTime(today), tz.LONG);

stThisTZ.readableName = tmptz.getDisplayName();

 

// Get the number of hours from GMT

rawOffset = tmptz.getRawOffset();

stThisTZ.offset = rawOffset / (60*60*1000);

stThisTZ.offsetMinutes = abs(rawOffset / (60*1000)) % 60;

 

// Does the time zone have a daylight savings time period?

stThisTZ.hasDST = tmptz.useDaylightTime();

 

// Is the time zone currently in a daylight savings time?

stThisTZ.inDST = tmptz.inDaylightTime(today);

arrayAppend(aTZs,structcopy(stThisTZ));

}

</cfscript>

<cfdump var="#aTZs#">


screenshot of the output:
screenshot of output of timezones retrieved via java timezone object

Posted by dougboude at 7:16 AM | PRINT THIS POST! | Link | 3 comments
13 December 2008
Very Useful Snippet

 

<cfset objMojito = createObject("component","bar.drink").init(glassType="24oz");

<cfscript>
   objMojito.add("freshLime",.75).add("crushedIce","toFill").crushAndStir();
   objMojito.add("rawSugar",2).add("crushedIce","toFill").add("puertoRicanRum",2).stir();
   objMojito.add("clubSoda","topOff").garnish();
</cfscript>

<cfset objDoug.consume(beverage=objMojito,rate="moderate",shareWithSpouse=true) />


<cfoutput>#objDoug.getSatisfactionRating()#</cfoutput>

Posted by dougboude at 1:20 AM | PRINT THIS POST! | Link | 2 comments
10 December 2008
Using Database-Driven Configuration Settings in Coldbox
My approach

The Scenario: My Coldbox/Coldspring/Transfer application has settings that I want to make maintainable via a user interface. Now, the Coldbox.xml.cfm file does make provision for me to add as many custom settings as I want to my app, but I don't want to require the end user to know how to edit this xml file (nor would I trust them to). I came up with a solution to this challenge that I'm really happy with, so I thought I'd share some snippets in case it proves useful to others.

Step 1 is to ensure that you have a table designed to hold these configuration settings. My database has a table named "configuration", with the fields ID, settingName, and settingValue.

CREATE TABLE [dbo].[configuration](
 [id] [int] IDENTITY(1,1) NOT NULL,
 [settingName] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [settingValue] [varchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 CONSTRAINT [configuration_PK_UC1] PRIMARY KEY CLUSTERED
(
 [id] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

Of course, make sure this table is registered in your Transfer.xml.cfm file:

<package name="configuration">
 <object name="configuration" table="configuration" >
  <id name="id" type="numeric" generate="false"/>
  <property name="settingName" type="string" column="settingName" nullable="false"/>
  <property name="settingValue" type="string" column="settingValue" nullable="false"/>
 </object>
</package>

Next, I had to find the equivalent of the "onApplicationStart" within my Coldbox app because it is on application start that I wish to read in and load my settings. In Coldbox, this is an interception point named "afterAspectsLoad", which runs during application startup just after all plugins are created (important in my case especially since I'm relying on the "ioc" plugin to get Transfer for me). In order to leverage this interception point,  you'll need to create an interceptor and drop it in to your "interceptors" directory. Here is the interceptor I created that loads up my app's settings:

<cfcomponent name="customConfig"
    hint="I load settings stored in the database"
    output="false"
    extends="coldbox.system.interceptor">
   
 <cffunction name="Configure" access="public" returntype="void" hint="" output="false" >
  <cfset instance.ioc = getPlugin("ioc") />
 </cffunction>
 
 <cffunction name="afterAspectsLoad" access="public" returntype="void" output="false" hint="I load in any application settings from the database">
  <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" />
  <cfset var qryConfigSettings = instance.ioc.getBean("transfer").list("configuration.configuration") />
  <cfset var tmpVal = "" />
  <cfloop query="qryConfigSettings">
   <cfif isJSON(settingValue)>
    <cfset tmpVal = deserializeJSON(settingValue) />
   <cfelse>
    <cfset tmpVal = settingValue />
   </cfif>
   <cfset setSetting(settingName,tmpVal) />
  </cfloop>
 </cffunction>
</cfcomponent>

As you may have noticed, I'm allowing my setting values to be JSON strings if needed, just in case I want to pass in an array or structure of values for a particular setting.

 

 

The last item of business is to let Coldbox know that our interceptor exists. We do this by registering it in the <Interceptors> section of Coldbox.xml.cfm, like so:

<Interceptor class="myapp.interceptors.customConfig" />

That's it! re-initialize your application (&fwreinit=1) and all of your settings will be available anywhere within your app.

 

Posted by dougboude at 3:54 PM | PRINT THIS POST! | Link | 0 comments
Element.show/hide anomoly in Prototype

I am a lover of the Prototype Javascript framework, I must say. But today I found a rather irritating little tidbit that diverted my attention from "real" work. Thought I'd share it just in case it saves someone else a little hair pulling.

The Scenario: You have a button that, onClick, performs an Ajax.Updater call. For aesthetic reasons, you have a hidden div with a spinner gif in it that you unhide while the call is working, then when complete, you hide it again. A common practice, right?

<div id="working" style="display:none;"><img src="images/spinner.gif" /></div>

Well, when I initially laid out my page I just put my css styles inline until I got them the way I wanted. Afterwards I moved the styles out to an external file. That's when I noticed that my "working" div was no longer showing during the ajax call. The call was taking place, the JS function that performed the call hadn't been touched, yet no spinner. Here's my Ajax call:

new Ajax.Updater('targetDiv','index.cfm?param1=bla),{
 method:'post',
 onCreate:function(){
  Element.hide('container1');
  Element.show('working');},
 onComplete:function(){
  Element.hide('working');
  Element.show('container1');
 }
}
);


Having nothing else to try, I removed the style from my external file ( #working{display:none;} ) and put it back inline with my div tag (<div id="working" style="display:none;">...) and voila! I get my spinner again. Ah, one more thing to try...let me put the style back in the external sheet and then modify my onCreate callback function a little:

onCreate:function(){
Element.hide('container1');
$('working').style.display = 'inline';}

Manipulating the display setting more directly, NOW I get my spinner again. Sheesh, so what's the Element.show method doing, anyway? I dig through prototype.js and find that, while the 'hide' method is setting the element's display property to 'none',  the 'show' method is simply setting the display property to ''; nothing, empty string. Which works fine as long as my style is defined inline, but moving to an external style sheet breaks it. I observed this behavior in both IE and Firefox, current versions, so I don't believe it's a browser compatibility issue.

 

The short quick answer is, you have three options for being able to properly show/hide elements using Prototype:


1. keep the display style inline for elements you want to show/hide';
2. move your style external, then use the more direct method of swapping display styles ($('mydiv').style.display = 'inline');
3. move your style external, omitting the 'display:none' item, and use Prototype itself to hide the element on page load, like so:

<script>
 Element.observe(window,'load',function(){Element.hide('loginWorking');});
</script>

If you let Prototype do the hiding, it shows and hides just fine.

 

Thoughts?

Posted by dougboude at 2:49 PM | PRINT THIS POST! | Link | 1 comment
07 December 2008
Coldbox Interceptors and Custom Interception Points Demystified

Although ANYTHING you could ever want to know about coldbox is definitely in its documentation, I find that it is still necessary sometimes to have to piece things together bit by bit..."...line upon line, line upon line; here a little, and there a little:...." as the prophet Isaiah said... in order to fully comprehend them. In this post I'd like to share what I have distilled from the docs regarding the subject of using interceptors and custom interception points in Coldbox.

The Scenario:  you are building your first Coldbox app and you decide that you want to generate your navigation from your database only after the user has authenticated (before that, they get no nav). How we retrieve and/or create our navigation isn't relevant to this post (I'll likely be sharing my navigation interceptor in another post), but WHEN and WHERE creation occurs IS, so allow me to break it on down as I have come to understand it.

If you weren't already aware, an interceptor in Coldbox is simply a CFC with methods that can be executed anywhere in the Coldbox request lifecycle, independent of the logic you already have "hard coded" to run for that event. In terms that we can all probably relate to, think of interceptors in EXACTLY the same way that you think of application.cfc. You know that code within that CFC will run "just because the file exists" and you don't have to explicitly call it in your app. Methods like  "onRequestStart" or "onRequestEnd"...their code automatically gets executed at specifically defined moments in the request's lifecycle simply by virtue of their name and the CFC they happen to live in. Same with a Coldbox Interceptor, except unlike application.cfc, you have to tell Coldbox that your interceptor exists first.


It's important at this point that we briefly talk about interception points in Coldbox. You know what "onSessionStart" is, you know what "onRequestEnd" is, you know what "onError" is, etc. ... well take a gander at "preRender". This is an interception point built in to Coldbox that "happens", or is announced, before content is rendered as viewable. (On a side note, there are a whole lot of OTHER interception points that exist natively in Coldbox; you should take the time to read through the list of them in the docs before you start developing.) In order to take advantage of "preRender" then and make something happen at that point, I have to do three things:

1. Create an interceptor CFC and drop it into my "interceptors" folder in my Coldbox app;
2. Create a method in that interceptor called "preRender";
3. Register my interceptor in Coldbox's "coldbox.xml.cfm" file so that the framework knows it exists.
how to register a custom interceptor...

<Interceptors>
 <Interceptor class="myapp.interceptors.navigation" />
...

Voila! Now every time a request is made, whatever code I have in my "preRender" method in my interceptor will execute! In my case, "preRender" in my navigation interceptor is going to generate and make my navigation available to the rest of the app.

Okay, I said in my scenario description that I only wanted my navigation created IF the user was authenticated. Of course you can guess that my "preRender" method does a check to see if that has occurred or not, and acts accordingly. But, what about the actual login event itself? If you trace the process, the user will be authenticated AFTER the preRender event, and so even though they have successfully logged in, will NOT see the navigation! Not kosher.

I dealt with this by using one of Coldbox's "Custom Interception Points". At first, it seemed like such a  foreign idea that little ol' me would have the power to add an interception point within the request lifecycle. After all, weren't things such as "onRequestStart" items that only the framework or the CF Server itself had the privilege of manipulating? But here's where the mystique of interception points, even those used in application.cfc, went away for me. (Hopefully you are familiar with the children's game "Red Light/Green Light", because my understanding and explanation of an interception point is based on it). An interception point is nothing more than a framework or CF Server playing a game of "red light/green light" with our app. The call to the app (http://myapp/?event=index) is the little kid, running forward like he was told to do. At certain points, though, the framework calls out "greenlight!", and any bit of your code that you had set up listening for "greenlight" (containing a method called "greenLight()") executes, right there and then, independent of the code associated with the event being called, and even before the request itself has completed. In my case, then, all I did was write a bit of code in my login process that called out "onLogin!" as soon as authentication occurred. I then added a method to my navigation interceptor called...what do you think? Yep, "onLogin", that made sure the navigation was available for the user at the end of the request. Here, then, are the steps to adding and using your own custom interception points in Coldbox:

1. Tell Coldbox that you would like to use an interception point called "onLogin", or "onLogout", or whatever you want to call it;
2. Add a line anywhere in any of your handlers (controllers) that ANNOUNCES your custom interception point;
3. Create one or more methods in any of your interceptors named the same as your custom interception point.

That's it! Here are some code snippets of where and how to do each of the three steps above:

1. In coldbox.xml.cfm, in the "Interceptors" section, add a line like the following:

<CustomInterceptionPoints>onLogin,onLogout</CustomInterceptionPoints>

 

2. In your handler (controller), announce your interception point like so:

<cfset announceInterception('onLogin', icData) />

 

(icData is a structure containing whatever values I want to pass along to my interception methods)

3. In your interceptor(s), create a method like so:

<cffunction name="onLogin" access="public" ....

 

That's it! Hope it wasn't TOO confusing...it took me a while to really wrap my head around the flow of it all, but once you do it's not so bad. If anybody has anything to add, or if I've left you scratching your head, please feel free to comment.

Doug out

Posted by dougboude at 2:32 AM | PRINT THIS POST! | Link | 3 comments
26 November 2008
Auto-Synchronizing Your SQL DB Across Multiple Development Machines
how I roll

Building upon my last post regarding the setup of dual development environments and how to keep them in sync, this time I want to focus on keeping the development database in sync and how I have approached it.

In my setup, I have an office PC and a home laptop, both running SQL Express as my database server. The goal: to have all changes performed from either location be reflected on both. Here's a diagram (I love pictures) of my current setup:

In reality, a database is nothing more than a file, so in theory then, if we can manage to keep that file synchronized across different machines, we should then be able to keep our database synchronized as well. With that in mind, here are the steps taken to accomplish my goal:

MACHINE 1
1. Create an account with FolderShare (
www.foldershare.com), or your favorite file syncing service;
2. On machine 1, create a folder to house your database (when you create a database in SQL Express, it wants to know WHERE the actual database file should reside on your hard drive...you'll be pointing it to this directory when the time comes);
3. Create a share on the FolderShare site that points to the directory you created in step 2;
4. Fire up SQL Management Studio, connect to your local database server, and create a new database (again, being sure to make sure the location of the data and log files are the directory you created);

All done on the first machine! Straightforward stuff. Now for the other machine.

MACHINE 2
1. On machine 2, create a folder to house your database, just as you did on machine 1. You can name it anything you like, and it can live on any drive you like.
2. Using FolderShare, direct the share you created earlier to be synchronized with the folder you created on this machine;
3. Go get a cup of coffee and give FolderShare time to synchronize the directories (copy down the MDF and LDF sql database files);
4. IMPORTANT!  Open up your Control Panel -> Services utility and scroll down to the "SQL Server (SQLEXPRESS)" service. Right click it and choose properties. Click on the "Log On" tab, and click the "Local System Account" radio button. Click 'OK', then RESTART THE SQL SERVICE;

changing SQL Server's log on credentials

changing SQL Express log on credentials
5. Fire up your SQL Management Studio and connect to your local database server;
6. Right click 'Databases' and choose 'Attach'. Click the 'Add' button in the resulting dialogue, then navigate your way to the folder you created in step 1;
7. Select the MDF file that should now be there and complete the attach process.

screen shot for attaching the database
attaching a database in SQL Express Management Studio

VOILA! You are now using a synchronized copy of the same MDF that your other dev machine is! This is the exact setup I have running, and it's working pretty doggone good if I do say so myself.

Okay, all that having been said, please note a few caveats I have found when using this arrangement:

1. Only ever have one SQL Management Studio open at a time. If you are doing inserts or modifying tables from both machines at once, funky things happen and FolderShare suddenly becomes suicidal. In theory you should only be working from one machine at a time anyway, so this shouldn't be too much of an annoyance.
2. Remember that synchronizing takes a few minutes to kick off sometimes, so you'll have to allow a little time when going from one machine to the other before your changes will be visible.
3. If you do NOT perform the services step outlined in the tasks for machine 2, your database will attach, but it will be read-only, and that won't be much help to you.

That should be it! If anybody else has any tricks they use for keeping their dev environments in sync, we're all ears!

Posted by dougboude at 7:43 PM | PRINT THIS POST! | Link | 8 comments
23 November 2008
Multi-Worksite Productivity Configuration
how I roll

I am a self-employed individual. It wasn't until just last month, however, that I actually procured an "away from home" office space. It was at that point that I began to ponder the logistics of working at an office AND being able to work from home as well. How would I keep files in sync? How would I be able to do a day's work from home and then do a couple more hours at night without accidentally overwriting something, or needing something from the office computer while working on my laptop in the kitchen? Following is a fairly simple set up that my friend Boyan Kostadinov shared with me, and it has really been quite an efficient configuration.

Let me first show you a diagram of my current setup, a list of the tools I'm using in order to maintain synchronicity, and then I'll delve into more detail for those who are interested.

diagram of how to set up multiple workspaces and keep them in sync

Details
My personal need is to be able to have the following be synchronized wherever I'm working from:

  • browser bookmarks;
  • auto-fill username/password combinations (courtesy of Roboform!);
  • my local web root;
  • project directories connected to SVN repositories

Let me address these one by one.

Bookmarks
Delicious (del.icio.us) is a web-based application whose sole purpose one earth is to maintain your personal bookmarks in a central, universally accessible loation: the web. By relying on Del.icio.us as my primary bookmark storage facility, I don't have to worry about keeping browser bookmarks in sync. Delicious provides lots of add-ins that make using it a relatively easy process. web site:
http://del.icio.us

Usernames/Passwords
Roboform is one of those apps that once you start using it you wonder how you ever got through your day without it. Among other things, Roboform watches your browsing habits and, when it sees you performing what it believes to be some sort of authentication, offers to save your credentials so that you don't have to enter them each time (this info is securable). I don't know about you, but I visit and use a LOT of sites that require authentication. Now when I hit one of them, a little window pops up providing me with the possible logins for that site. I select it, click okay, and I'm in! Roboform stores these username/password combos in a file, so what I did was make sure that this file lives in a directory that I am syncing with my other machine. So if on one machine I add or modify one of these username/password combos, the other machine automatically sees it and vica versa. Roboform has a free version (which I use). You can download it and read all about it at
www.roboform.com. Speaking of synching directories...

Directory/File synchronization
I can't thank my friend Boyan enough for turning me on to THIS web site!
www.foldershare.com . It's a Microsoft hosted application that allows you to share folders on your local machine, but via a web-based proxy. In a nutshell, you browse to the folderShare site and log in. Next you tell it you want to create a new share. It lets you browse your local machine and choose the folder to share, then you give that share an alias. Voila! Now from my OTHER machine I can log in to folderShare, click on the share I had previously created, and tell it i want to synchronize that share with a folder on my current machine. In short order folderShare copies the files down to my local drive and I'm in business. Any changes i make on one machine are automatically copied to every other machine syncing to that folder. FolderShare also allows you to share folders with individuals, so when Boyan has a cool video he wants me to see, he drops it into the folder he shared with me on his machine and I can then lose a few minutes of productivity, just like that!

SVN
Most of what I do involves web development projects that are stored in off-site SVN code repositories, though the actual development takes place locally. Free services such as www.Assembla.com (the one I use) allow one to remove some of the worry out of development by maintaining versioning of your code and the ability to quickly recover in the event of local hardware failure. Using SVN involves connecting one of your local directories to the remote SVN repository (via an "SVN Checkout"), and then manually performing updates and commits as you see fit. When I first started working from two sites, I was having to make sure I remembered to commit whatever changes I had at the end of the day to the SVN repository, then when I got home I would have to remember to do an Update before I started working. Well, forgetting one or the other of these steps a couple of times quickly became a waster of precious hours trying to get things back in sync, so I simply shared the project folder via folderShare. The work process now goes more like this:

I go to the office and make changes and additions to my project folder. All of those changes are automatically sync'd to the project folder on my laptop. (Bear in mind that BOTH of these folders are a "checkout" of my SVN project, meaning they contain all of the versioning metadata files.) I go home and the next morning decide I want to work from the kitchen table. So I make more changes to my project folder and then do an SVN commit to get all of the cumulative changes safe and sound off site. My laptop folder icons indicate that my project folder is all in sync with SVN (little green check marks on all the folders). I go to the office the next day and what do you think my project folder indicates with regard to SVN? Yes, it has all green check marks, meaning my local copy is in sync with my SVN copy, and the commit was done from home on the laptop. Pretty sweet, eh?

If anybody wants more detail on any aspect of what I just shared, feel free to email or IM me. Also, I am positive that many of you have implemented yet more multi-worksite productivity enhancers, so I'd love to hear about them and the tools you've found to make your life easier.

Hope this helps someone.

Doug out.

Posted by dougboude at 4:20 PM | PRINT THIS POST! | Link | 3 comments
22 November 2008
I Have a Dream....

The collective abilities of the many always surpass the few. This is common knowledge, and pervades every aspect of life so much so that we probably don't even take note of it. A pride of lions takes more game than one hunting solo; the most correct answer is the distillation of many people's opinions rather than that of a single individual; the list could go on ad finitum. So why then is it that so many people who catch the entrepreneurial bug tend to want to go it alone? Rather than seek out like-minded individuals to strengthen and further their causes, they segregate and ostrecize themselves, professionally speaking, and attempt to "build the ark" all by their lonesome, dreaming of the day when they alone (and those few souls with whom they select to share their success) will find themselves sitting atop Mount Ararat?

Okay, a somewhat deep intro into the subject of this post, but I do believe the principle is relevant. I have good ideas. I'm sure many, many of you out there have good ideas, too. Ideas that, if we ever find ourselves with all of the needed time, resources, expertise, and perpetual motivation to make the idea tangible, we'd be gazillionaires.The fact is, though, that the solo road from concept to real product is very, very long and most never complete the journey. So then: why not break away from the pack and become part of a very small, very select alliance of like-minded inviduals, pooling your resources, planning together, and executing that plan as a single unit? It's a model that has served nature quite well, with a success rate that has brought mankind himself to his present state.

I myself have caught the entrepreneurial bug, the innate desire to turn my ideas into reality and what were once only dreams into realistic, achievable goals. Having this desire, I often explore different plans of execution, trying to find the best way to invest my resources so that I create a stair step approach to reaching my desired end. But  no matter how I slice it, traversing that road as a solo individual is a lengthy prospect. If I had one, or two other individuals, though, who had the same goals and with whom I could combine allocated resources, I know that we would shorten that road exponentially. We would each bring to the table our own professional and personal networks; or own talents, skillsets, and areas of expertise; our own collection of ideas that we have been mulling over and evolving for the past umpteen years; and our own cache of resources to contribute to the cause. We would form our own elite "brain trust", advancing the causes that would become a legacy to our posterity.

Now, if my treatise has given rise to any hot sparks of interest or a chorus of "hallelujah" whispered under the breath, then perhaps we should begin a dialogue to explore our chemistries, alignment of goals, and how well our ideas complement one another. I for one am READY to make something happen, but would love for it to be the passion of several individuals rather than just myself, for the benefit of all involved.  Any takers?

Posted by dougboude at 12:41 PM | PRINT THIS POST! | Link | 3 comments
18 November 2008
TinyMCE Refusing to Display Icons
or, A Descent into Madness

Okay, this post is as much informative as it is a rant, so bear with me while I vent as I share.

Let's talk TinyMCE. Typically I use FCKEditor, but for my current project I'm going with TinyMCE. How hard can it be, right? An editor is an editor is an editor. I download it, I add the two simple lines needed to transform my textarea into a full blown editor, and voila: it's an editor. But the menu has NO icons to be seen! First impulse is that I obviously have a bad path for the images. But no, TinyMCE uses "embedded sprites" or something like that to display its icons, so it isn't a pathing issue. If I right click where an icon should be, I see it for a moment, but then it's gone again.

The symptoms

tinyMCE not showing its icons

What it should look like

tinymce editor with icons displayed

So I wade through dozens of google results that look like they *might* provide a clue. One is a thread on the TinyMCE forum where the symptoms are the same as mine. I read through the replies looking for that silver bullet, but the only clue I find is that the user eventually solved their problem by removing the div tag from around the textarea. Not a solution for me, because I kinda NEED my divs to provide structure to my layout (I thought we all did), but it does give me reason to believe that perhaps I have implemented some css that TinyMCE doesn't like. So I move my textarea to other parts of the layout and reload until I finally get the icons to show up. Aha! I've narrowed it down to the div area that causes the symptoms, <div id="inner">! So let's see what horrific css i've applied to that div that broke TinyMCE.

#inner {display:block;margin-left:-200px;margin-right:-210px;padding:5px;}

Well, it doesn't LOOk so horrible at first glance. Let me give that div a new ID and add a new line to my css, adding style elements until I see it break again. So now in my layout I have
<div id="innerr">
and in my css I have
#innerr {display:block;}

Reload. Okay, I see my icons. Add another element:
#innerr {display:block;margin-left:-200px;}

Reload. still see my icons. Add another:
#innerr {display:block;margin-left:-200px;margin-right:-210px;}

Reload. Wow, still see my icons. Surely it can't be the padding that's killing it! Let me add it:
#innerr {display:block;margin-left:-200px;margin-right:-210px;padding:5px;}

Reload.
I STILL see all the icons. I have just completely duplicated the style that was applied to div "inner", where my TinyMCE icons would NOT show themselves, and yet I now DO see the icons. Wait, let me try one more thing...

<div id="inner"> (rename the id back to the original value)

I rename my div ID back to "inner". It has the same exact style as "innerr". But my icons disappear again. WHAT THE HECK?

Obviously ID "inner" is reserved for TinyMCE in some way. Perhaps that was actually mentioned somewhere in the docs, but I didn't see it. So anyway, if you are using TinyMCE and experience surreal symptoms such as disappearing icons, step 1: change your div ID's and class names and see if the symptoms go away. It's likely some style or name you've used that stepped on TinyMCE's tiny little toes.

Posted by dougboude at 1:17 PM | PRINT THIS POST! | Link | 2 comments
15 November 2008
My First Excursion into using Ajax with Coldbox
opinions solicited

I had my first successful forray using Ajax in Coldbox today! After reading a LOT, checking out some examples, and then letting my natural developer's instinct take its course, I ended up with a design that left me really wondering if I had taken a wrong turn or had a moment of brilliance . As I said, it works great, but is it an ideal pattern to use or did I cross one of those invisible "development ethics" lines with my approach? I'll let you be the judge. Before I share the details, I am assuming that the reader already has at least a basic working knowledge of Coldbox views, viewlets, handlers, and Ajax. That said, here be the details...

The Scenario

Upon arriving at my Coldbox app, the visitor will be dropped off at my default layout. Here's how it's organized:
basic coldbox layout

We'll be focusing on the "login/logout" area since that is the portion I applied Ajax to. As you can see, in that area of the layout I am rendering by name the view named "login". My login.cfm view is actually a Coldbox "viewlet", meaning that it performs a private Coldbox call whenever it is loaded. Here's the pseudo-code of my login viewlet:

run the 'authentication.login' event;
grab the current user's login status;
if the user is not logged in
     show the login form
else
     show the logout form
end if

and here are the two faces that the viewlet can show:
face 1 of the login view

 

The execution of the private call to "authentication.login" (as seen in the pseudo code) is for the sole purpose of gathering current user status so we can control the flow and output of the login viewlet. Here's the actual handler method being called that accomplishes this:

<cffunction name="login" access="public" returntype="void" output="false">
 <cfargument name="Event" type="any">
 <cfset var oSession = getPlugin("sessionstorage") />

 <cfif not oSession.exists("loggedin")>
  <cfset oSession.setVar("loggedin",false) />
 </cfif>
 <cfset arguments.event.setValue("loggedin",oSession.getVar("loggedin")) />
 <cfif not oSession.getVar("loggedin")>
  <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogin") />
  <cfset arguments.event.setValue("User",oSession.getVar("User")) />
  <cfif oSession.exists("sec_message")>
   <cfset arguments.event.setValue("loginmessage",oSession.getVar("sec_message")) />
   <cfset oSession.deleteVar("sec_message") />
  </cfif>
 <cfelse>
  <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogout") />
  <cfset arguments.event.setValue("User",oSession.getVar("User")) />
 </cfif>
</cffunction>

Now, here's where the Ajax comes in to play. Both the login and logout form have as their submit action a call to a Prototype Ajax.Updater. Here are the actual functions:

// functions for the login view
function logmein(thisURL){
 var loginParams = $('loginform').serialize(true);
 new Ajax.Updater('loginlogout',thisURL ,{parameters: loginParams, method:'post'});
}
function logmeout(thisURL){
 new Ajax.Updater('loginlogout',thisURL,{method:'post'});
}

The nutshell is that the Updater will execute the call to 'thisURL' and place the result, whatever it is, into the target 'loginlogout' div.

 

 

Okay, I have two more relevant methods in my Authentication handler: doLogin and doLogout. Here is the pseudo-code for 'doLogin':

attempt to authenticate using the credentials supplied...
if login failed
   set session.loggedin = false;
   set session.securityMessage = "Login failed. Try again.";
else
   set session.loggedin = true;
end if
//actual code used to perform the following...
<cfset arguments.event.renderData(type="plain",data=getPlugin('renderer').renderView('authentication/login')) />

This last line is taking advantage of two cool things Coldbox allows:

 

1. The ability to render a view (or viewlet) within the handler and create a content variable;
2. The ability to render Data back to the caller rather than a view.

In my case, since "login" is a viewlet (making its own private call to the 'login' method in order to get current state variables), I'm simply rendering it inline. Then, I'm feeding the results of that rendering (a blob of HTML) to the renderData method, which is then passing that blob of HTML back to my Ajax call. Who, in turn, updates my target div with it; in this case, either a login form with a message displayed, or a logout form and a welcome message. Here is my rendition (in pictures) of how it flows:

'doLogout' performs almost identical actions...setting the session variables appropriately and then returning the HTML results of rendering 'authentication/login' inline.

As I said, it works great. But I am very interested in the opinion of others as to how they feel about the appropriateness of this approach. Any thoughts?

Posted by dougboude at 1:22 AM | PRINT THIS POST! | Link | 1 comment
10 November 2008
Using Variables in Coldspring.xml with Coldbox

As you can tell from  my last two posts, I am getting pretty deep into Coldbox used in conjunction with Coldspring. One of the things that Coldbox does for us is pass in our configuration settings to the Coldspring bean factory when it initially loads our beans (from Coldspring.xml), thus allowing us to use configuration variables, like so:

<bean id="transferFactory" class="transfer.TransferFactory" singeleton="true">
 <constructor-arg name="datasourcePath">
  <value>${TransferSettings.datasourcePath}</value>
 </constructor-arg>
  ....

The only shortcoming with this is that because we're using Coldspring's DefaultXmlBeanFactory.cfc, it will only do variable replacements when they are found within <value> tags. Brian Kotek addressed this issue with a CFC found in his Coldspring Utilities collection (http://coldspringutils.riaforge.org/ ) , specifically with a CFC called "DynamicXMLBeanFactory.cfc". Using this instead of Coldspring's default bean factory allows you to place variables in other places within your Coldspring.xml. I've used this CFC before in another project, so now my challenge was to figure out how to implement it in Coldbox so that I could make my Coldspring.xml file more dynamic.

 

After a couple hours of tinkering around, AND having to make a minor modification to DynamicXMLBeanFactory.cfc to account for it being used in the Coldbox environment, here are the steps:

1. Place a copy of the modified version of DynamicXMLBeanFactory.cfc in the Coldbox/System/Extras/Coldspring folder. The Coldspring folder won't exist, so go ahead and create it;

2. Add a setting to your Coldbox.xml.cfm file like so:

<YourSettings>
 <Setting name="ColdspringBeanFactory" value="coldbox.system.extras.coldspring.DynamicXMLBeanFactory" />
  ....

That's it! If you want to make sure it's working, create a Coldbox.xml setting such as this:

<Setting name="modelRoot" value="myapproot.model" />

and then add that variable to your Coldspring.xml as part of a bean's class path, for instance:

<bean id="authenticationService" class="${modelRoot}.services.authenticationservice">

If the app fires up without error, you're in business!

 

 

 

Hope this helps someone.  :0)

 


P.S.
If anybody is interested in the changes I made to DynamicXMLBeanFactory and why, they are as follows:

1. Since DynamicXMLBeanFactory extends Coldspring's DefaultXmlBeanFactory, and since Coldbox is hardwired in its "ioc" plugin to interact with DefaultXmlBeanFactory's interface (specifically calling the method "loadBeansFromXmlFile", which does not exist within DynamicXMLBeanFactory), I had to overload that method in DynamicXMLBeanFactory like so:

<cffunction name="loadBeansFromXmlFile" returntype="void" access="public" hint="I am overloading this super class method">
  <cfargument name="beanDefinitionFile" type="string" required="true" hint="I am the location of the bean definition xml file"/>
  <cfset loadBeansFromDynamicXmlFile(arguments.beanDefinitionFile,getDefaultProperties()) />
</cffunction>

2. DynamicXMLBeanFactory was executing an "expandpath" on an already expanded path (Coldbox is already passing in the fully expanded path to the Coldspring.xml file ), resulting in an error of "file not found". Because of this, I had to comment out line 98 in the "getReplacedColdSpringXML" method.

Perhaps there was a more elegant method for implementing Brian's CFC, but the only two choices I saw was to either modify the framework (NO! BAD MAN! NO!), or the CFC. I opted for the CFC. What I was thinking, though, is that perhaps it would be good if Coldbox allowed not only the path to the IOC's beanfactory class to be a setting, but also the name of the bean loader method that should be called (after init)? Just a thought.

Doug out

Posted by dougboude at 5:00 PM | PRINT THIS POST! | Link | 1 comment
09 November 2008
Basic Security in Coldbox using Transfer and Coldspring - Part II (of II)
wiring and autowiring

In the first post on this subject, I shared my 10,000 foot view of what pieces needed to go where in a Coldbox app in order to implement security. A lot of the code in that post was probably unfamiliar looking to many people (I know it would have been to me a couple of days ago!). So in this post I want to share how I implemented Transfer and Coldspring in the security process, clarify what some of that code was doing, and whatever other details about it I may have also come to know.

I'm assuming that you already know what Coldspring is, what Transfer is, and the basics of an event-driven framework. For example, Coldspring is a framework that allows you to manage the relationships between your CFCs (CFC A needs a configuration bean injected into it, the configuration bean needs to be initialized with parameter X, etc.); Transfer is an ORM framework that stands between you and your database (in a good way) and lets you write your code "sans SQL"; and the basics of event-driven frameworks is that they have core components built into them that allow you (via the methods they expose to you) to reach into their very soul and leverage "core functionality", like retrieving global configuration settings, retrieve system beans, etc. Okay, all that having been said, here's the dialogue I had with my frameworks in order to get them all on the same page of the workbook (Personification to be followed up with real code snippets, don't worry. It just helps me simplify things when I animate the inanimate.  ):

1. "Hey, Coldbox;(via Coldbox.xml.cfm...) I'm using Coldspring to manage my components for me. What's that? You'll make your configuration settings available to Coldspring to use in its XML configuration file? When you fire up Coldspring you'll go through its configuration XML and replace any curly-braced variables with matching values from your own configuration BEFORE you initialize Coldspring? Aw, that's sweet. Thanks!"

2. "Hey Coldspring;(via Coldspring.xml.cfm...) I'm gonna need you to produce a few objects from our host MVC framework's core, Okay? What objects would those be? Oh, well, I'll be needing the Coldbox factory object since that's how you'll need to retrieve the rest of the framework's objects for me. I'll need you to ask the Coldbox Factory for an instance of the Coldbox framework itself since it has all the core methods that many of my model object will need; and I'll want a discrete instance of the Coldbox SessionStorage plugin as well so that my model objects can access my app's persistent scope. Let's see, what else...oh yes, I'll be using an ORM, so i'll need you to produce an instance of Transfer via its own factory. And then I'll just tell you about my model objects as I get them added to the app. Thanks, Coldspring! You're a real bud."

3. "Hey Transfer,(via Transfer.xml.cfm...)  I got a whole BOATLOAD of database objects I need you to make available to me. It's a very, very long list, so just read through it at your leisure."

My frameworks are very good listeners and so once I figured out exactly how to say what I wanted to say, they complied fully with my desires. So let's look at how I made them understand, shall we?

Dialogue 1
Within the Coldbox.xml.cfm file, in the <Settings> section, I made sure the following settings were like so:

<Setting name="IOCFramework" value="Coldspring" />
<!--IOC Definition File Path, relative or absolute -->
<Setting name="IOCDefinitionFile" value="config/coldspring.xml.cfm" />
<!--IOC Object Caching, true/false. For ColdBox to cache your IoC beans-->
<Setting name="IOCObjectCaching" value="false" />

 

As far as the rest of that dialogue, as long as I make sure the setting exists within the Coldbox.xml.cfm file, Coldbox will automatically replace any curly-braced variables in my Coldspring.xml.cfm file for me. For instance, in my Coldbox.xml.cfm file in the <YourSettings> section, I have the following Transfer values:

<YourSettings>
 <Setting name="TransferSettings.datasourcePath" value="/emailmanager/config/datasource.xml.cfm" />
 <Setting name="TransferSettings.configPath" value="/emailmanager/config/transfer.xml.cfm" />
 <Setting name="TransferSettings.definitionPath" value="/emailmanager/model/definitions" />
</YourSettings>

You'll see in the Coldspring snippets below the variable placeholders that correspond to these settings; it'll make more sense there. 

 (Note: presently, only curly-brace variables found within a <value>${myColdboxSetting}</value> tagset will be replaced. Don't put curly-brace vars in your bean's class path, for instance, and expect them to work. There is a way to do this which I'm investigating now, but by default it does not.)

Dialogue 2
We informed Coldspring of several different things, so let's look at them a chunk at a time.
"...produce a few objects from our host MVC framework's core...Coldbox factory object...Coldbox framework itself...a discrete instance of the Coldbox SessionStorage plugin...." Here is the XML to accomplish the previous:

<bean id="ColdboxFactory" class="coldbox.system.extras.ColdboxFactory" />
<bean id="Coldbox" factory-bean="ColdBoxFactory" factory-method="getColdbox" singleton="true" />
<bean id="oSession" factory-bean="ColdBoxFactory" factory-method="getPlugin" singleton="true">
 <constructor-arg name="plugin">
  <value>sessionstorage</value>
 </constructor-arg>
</bean>


If you aren't familiar with Coldspring's ability to produce a bean by executing a method referenced on a previously defined bean, well, you're looking at it in action (note the 'factory-bean/factory-method' attributes). I myself wasn't aware of this ability, so was quite happy to have discovered it. First we're defining Coldbox's object factory; then we're creating an instance of Coldbox by calling the Factory's "getColdbox" method; lastly I'm grabbing an instance of Coldbox's built-in "sessionStorage" plugin the same way, calling the generic "getPlugin" method and passing in the name of the plugin I want.

Next portion of that dialogue...
"...I'll be using an ORM, so i'll need you to produce an instance of Transfer via its own factory...." Here's that code (notice the curly brace vars; each one equates to a setting created in the Coldbox.xml.cfm file): 

<bean id="transferFactory" class="transfer.TransferFactory" singeleton="true">
 <constructor-arg name="datasourcePath">
  <value>${TransferSettings.datasourcePath}</value>
 </constructor-arg>
 <constructor-arg name="configPath">
    <value>${TransferSettings.configPath}</value>
 </constructor-arg>
 <constructor-arg name="definitionPath">
    <value>${TransferSettings.definitionPath}</value>
 </constructor-arg>
</bean>

<bean id="Transfer" factory-bean="TransferFactory" factory-method="getTransfer" singleton="true" />
<bean id="Datasource" factory-bean="TransferFactory" factory-method="getDatasource" singleton="true" />

Defining Transfer's own factory, then defining the other Transfer beans as results of executing Transfer Factory methods. Noice, eh?

 

 As far as the model, so far I have only one bean defined, and that is my "authenticationService" bean. Here's that Coldspring.xml:

<bean id="authenticationService" class="emailmanager.model.services.authenticationservice">
 <constructor-arg name="transfer">
  <ref bean="Transfer" />
 </constructor-arg>
 <constructor-arg name="oSession">
  <ref bean="oSession" />
 </constructor-arg>
</bean>

As you can see, I'm passing in an instance of Transfer and Coldbox's session manager upon instantiation. By doing so, my service object can perform database calls and manage persistent values.

 

 Dialogue 3
Transfer relies on an XML file to get its information about your database entities (tables) and their relationships to one another. I won't share the entire Transfer.xml.cfm file here since I've already got most of my entities defined for my app (long list), but here are the tables relevant to security:

<objectDefinitions>
 <package name="user">
  <object name="user" table="appuser" >
   <id name="id" type="string" generate="false"/>
   <property name="username" type="string" column="username" nullable="false"/>
   <property name="password" type="string" column="password" nullable="false"/>
   <property name="firstname" type="string" column="firstname" nullable="false"/>
   <property name="lastname" type="string" column="lastname" nullable="false"/>
   <property name="email" type="string" column="email" nullable="false"/>
   <property name="isActive" type="boolean" column="isActive" nullable="false"/>
   <property name="timezone" type="string" column="timezone" nullable="true"/>
   <onetomany name="usergroup" lazy="true">
       <link to="usergroup.usergroup" column="userid"/>
       <collection type="array">
         <order  property="id" order="asc"/>
       </collection>
   </onetomany>
  </object>
 </package>
 <package name="usergroup">
  <object name="usergroup" table="usergroup" >
   <id name="id" type="string" generate="false"/>
   <property name="groupid" type="string" column="groupid" nullable="false"/>
  </object>
 </package>
</objectDefinitions>

Now that I have all of that set up, let me share again the code from my authentication handler(controller) and authenticationService cfc (that I shared in my previous post on this topic)  that peform the login function.

 

 authentication handler

<cffunction name="doLogin" access="public" returntype="void" output="false">
 <cfargument name="Event" type="any" />
 <cfset var loggedin = "" />
 <cfset var rc = arguments.Event.getCollection() />
 <!--- attempt to authenticate using the credential supplied... --->
 <cfset loggedin = variables.authenticationService.login(username=rc.username,password=rc.password) />
 <cfif not loggedin><!--- login failed...send them back --->
  <cfset arguments.event.setValue("loginmessage","Login Failed. Please try again.") />
  <cfset arguments.event.setValue("loggedin",false) />
  <cfset arguments.event.overrideEvent("login") />
 <cfelse>
  <cfset arguments.event.setValue("loggedin",true) />
 </cfif>
 <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogout") />
 <cfset arguments.event.setView("login") />
</cffunction>

The line to home in on is the one that begins "<cfset loggedin = ...". It is there that, from within my handler, I am accessing the authenticationService bean we defined in Coldspring.xml.cfm. You'll recall though that I did NOT define a relationship between my handler and authenticationService. How then did my handler get access to it?

In Model-Glue, we would have provided our controller a setter and getter for the authentication service, then Model-Glue itself would have auto-injected that bean for us. Well, Coldbox to the rescue, we have the same type of functionality available to us! Coldbox actually allows two ways to auto-inject Coldspring-defined beans: via setters and getters, as we are probably accustomed to; and via the <cfproperty> tag. Now, typically cfproperty doesn't really do much for us at all; but in a Coldbox handler it's a big ol' flag that says "inject it here! inject it here!. Okay, here's the additional line in our authentication handler that performs this magic:

<cfproperty name="authenticationService" type="ioc" scope="variables" />

I opted NOT to go the setter and getter route because of the fact that if I happened to have created a setter and getter that had the same name as a bean i defined, it would attempt to auto-inject whether I really wanted it to or not. In order to avoid this, I am choosing to explicitly name my auto-injections via the <cfproperty> tag. 

Okay, so our doLogin event is calling the authenticationService's 'login' method, passing in the username and password supplied; Let's look at authenticationService now.

<cffunction name="init" access="public" output="false" returntype="any">
 <cfargument name="transfer" type="any" required="yes" />
 <cfargument name="oSession" type="any" required="yes" />
 <cfset variables._transfer = arguments.transfer />
 <cfset variables._session = arguments.oSession />
 <cfreturn this />
</cffunction>

<cffunction name="login" access="public" returntype="boolean">
 <cfargument name="username" type="string" required="yes" />
 <cfargument name="password" type="string" required="yes" />
 <cfargument name="isActive" type="boolean" required="yes" default="true" />
 <!--- create a transfer user bean, passing in the username and password, then give it back --->
 <cfset var objUser = variables._transfer.readByPropertyMap("user.user",arguments) />
 <cfset retval = false />
 <cfif objUser.getID() IS "0"><!--- login failed... --->
  <cfset variables._session.setVar("loggedin",false) />
 <cfelse>
  <cfset variables._session.setVar("loggedin",true) />
  <cfset variables._session.setVar("user",objUser) />
  <cfset retval = true />
 </cfif>
 <cfreturn retval />
</cffunction>

 We instructed Coldspring to inject Transfer and oSession into this bean upon creation, and made provision for it via the init method. Pretty standard Coldspring stuff. In our login method, we call upon Transfer to create for us the "user.user" bean, giving it our argument collection to use as criteria (my arguments are named exactly as fields in that table). Also, in case you're wondering why we didn't ask Transfer for the "user" bean instead of the "user.user" bean, it's because in our Transfer.xml.cfm file we defined a package called "user" and within that package an object named "user". Hence the path "user.user".

 

 So, Transfer will create the requested bean and, if it matched the criteria passed in, the bean will be populated; otherwise it'll be empty, with a zero value for the primary numeric key ("ID"). After Transfer gives it back, I check the primary key. If it's zero, login obviously failed. If it's not, success! Stuff the user bean into session and return a boolean to the handler (controller).

I do hope you were able to follow this okay. I can't tell you how long I had to bang my head against it for it to sink in and gel, so I hope it saves someone else a few hours of digging through docs.

Doug out.

Posted by dougboude at 1:58 AM | PRINT THIS POST! | Link | 7 comments
Basic Security in Coldbox using Transfer and Coldspring - Part I

There's no denying it: the plethora of documentation and examples that exist for the Coldbox framework is invaluable and undoubtedly covers every possible scenario to one degree or another. As I press on into a project where the use of Coldbox, Coldspring, and Transfer has been mandated to me, I'm finding that, despite the documentation and tutorials, I'm still having to search around and piece together for myself some of the information I seek. So, to complement the existing volumes of Coldbox-centric information and for my own personal reference purposes, I'm going to post some of what I'm learning as it becomes coherent to me.  Tonight's post is focused on an overview of how to implement basic security in Coldbox using Transfer and Coldspring. I won't delve much into Transfer or Coldspring with this one (that post will follow soon) so that we can focus on the 10,000 foot view first.

As I mentioned, the first "block of functionality" I tackled was the implementation of security, so let's use that as our illustration. There is a lot of documentation on the subject and even a few example apps I found. But, the documentation left me with too much gray area as I attempted to relate what I read with my current level of understanding of the framework, and the example apps, though they worked wonderfully, didn't explain to me HOW they were working. I had to dissect the apps using my limited knowledge of Coldbox and painstakingly trace the event flow from one area of the framework and application to the next, referring to the documentation and even examining some of the framework's CFCs internally. The end result for me was the foundation for my own "flavor" of basic security implementation, and it this that I share.

First, my own visualization of the Coldbox event lifecycle. I realize that the true lifecycle in all its detail could consume many a diagram page, but for the purposes of someone simply desiring to add in a bit of functionality, I do believe I've illustrated all that is required.

The Coldbox event lifecycle highly simplified

A request comes in and right away Coldbox executes the interceptors it knows about. Interceptors are very aptly named, as they do just that: intercept the request before it gets too far and executes whatever methods are appropriate. In our case, we're interested in knowing right up front if the user attempting to fulfill an http request is authenticated or not. Intercept them, then, we shall.

Coldbox comes out of the box with a pre-built security interceptor (a CFC that is executed at what we typically know as the "onRequestStart" point), but being at the newbie point I am, I found it a bit too deep to assimilate. In order to understand how things really work, therefore, I created my own. I called it "security.cfc" and dropped it into my app's "interceptor" directory. The next step was to tell Coldbox that I have an interceptor in place. This is done in config/coldbox.xml.cfm, in the <Interceptors> section. Mine looks like this:

<Interceptors>
 <Interceptor class="interceptors.security" />
</Interceptors>

I eluded to it, but now is a good time to talk about one of the things that makes Coldbox unique among the MVC frameworks we CFers have to choose from. If you've not used any framework before that relies on what's typically referred to as "convention" (Ruby on Rails, Fusebox 5.x sans xml), then you are used to defining your events down to every last detail: what messages to broadcast, what methods to execute in what CFC, etc. But Coldbox rather follows the "convention" approach, which is simply naming your CFCs and methods in such a way that the framework knows what to do all by its lonesome. This really isn't a new thing to you at all though, because if you've used Application.cfc in any of your apps, then you have practiced this very thing! Consider this...did you ever have to TELL your app to run application.cfc? Nope, because of the fact that you NAMED it application.cfc, the framework (in this case, CF Server) looked for it and executed it. Inside of your application.cfc you had some methods, right? You had one named "onApplicationStart", one named "onRequestStart", one named "onSessionStart", etc. Why? Because you knew that if you named your method appropriately, the "framework" would execute it at just the right time in the http request lifecycle. This is absolutely no different in Coldbox. Inside our interceptors we create methods that are named according to when we want Coldbox to execute them. Granted, Coldbox uses a few names of its own to indicate a specific point in the event lifecycle (such as 'PreProcess'), but they boil down to the same concept as what we're accustomed to with Application.cfc. So, I told Coldbox "hey, I have an interceptor I want you to execute for me at the beginning of the event lifecycle", then within that interceptor I created methods to run at specific points in time. In the case of my Security.cfc interceptor, I have create a single method called "preProcess" (guess when it's going to be executed). Within this method I am checking to see if the current user is logged in. If yes, do nothing; if no, redirect to my login event. In my simple scenario, "logged in" means i found session.loggedin and it was set to 'true'. Here's my interceptor method:

 

 

<cffunction name="preProcess" access="public" returntype="void" output="false">
 <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" />
 <cfset var loggedin = false />
 <cfset oSession = getPlugin("sessionstorage") />
 <cfif (NOT oSession.exists("loggedIn") OR NOT oSession.getVar("loggedIn")) AND listfind("authentication.doLogin",arguments.Event.getCurrentEvent()) eq 0>
  <cfset arguments.Event.setValue("loginmessage","Please Log In") />
  <cfset arguments.Event.overrideEvent("authentication.login") />
 <cfelse>
  <cfset arguments.Event.setValue("message","we're logged in!") />
 </cfif>
</cffunction>

 

 

 

Let's assume the user is NOT logged in. My interceptor resets the current event to my login event, "authentication.login", and processes it. POP QUIZ: Based on the event name "authentication.login", what CFC and method within that CFC do you suppose Coldbox will be executing? I'm sure you guessed right, so let's go look at the 'Login' method within my 'authentication.cfc'.

Hmmm, where IS my authentication.cfc? It's not an interceptor...where is it? Ah, it's where Coldbox ASSUMES it will be (there's that "convention" thing again!): sitting in the "handlers" directory. We Model-Gluers and other MVC framework people might think of it more commonly as "controllers", but the Coldbox vernacular is "handlers", as in "event handlers". Here is the Login method:

<cffunction name="login" access="public" returntype="void" output="false">
 <cfargument name="Event" type="any">
 <cfset arguments.event.setvalue("loginmessage","You need to log in!") />
 <cfset arguments.event.setValue("loggedin",false) />
 <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogin") />
 <cfset arguments.event.setView("footer") />
 <cfset arguments.event.setView("login") />
</cffunction>

 

 

 

Being the good controller/handler that it is, it's doing next to nothing except setting a few values in the event bucket and tossing some views into the view collection so that we can properly give the user a login form.

Now we see the login form, nothing fancy, just a prompt for a username and password. The user enters it, hits the submit button, and off we go to our form's action: authentication.doLogin .

<cffunction name="doLogin" access="public" returntype="void" output="false">
 <cfargument name="Event" type="any" />
 <cfset var loggedin = "" />
 <cfset var rc = arguments.Event.getCollection() />
 <!--- attempt to authenticate using the credentials supplied... --->
 <cfset loggedin = variables.authenticationService.login(username=rc.username,password=rc.password) />
 <cfif not loggedin><!--- login failed...send them back --->
  <cfset arguments.event.setValue("loginmessage","Login Failed. Please try again.") />
  <cfset arguments.event.setValue("loggedin",false) />
  <cfset arguments.event.overrideEvent("login") />
 <cfelse>
  <cfset arguments.event.setValue("loggedin",true) />
 </cfif>
 <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogout") />
 <cfset arguments.event.setView("login") />
</cffunction>

Ignore the details for a moment, and let it suffice to say that we are grabbing the credentials out of the event bucket, submitting them to our model for authentication, and getting back a boolean value indicating success or failure. Again, our controller/handler method is doing next to nothing (as it should), except setting some values for the view to use.

 

 

Wanna see the "login" method in the model?

<cffunction name="login" access="public" returntype="boolean">
 <cfargument name="username" type="string" required="yes" />
 <cfargument name="password" type="string" required="yes" />
 <cfargument name="isActive" type="boolean" required="yes" default="true" />
 <!--- create a transfer user bean, passing in the username and password, then give it back --->
 <cfset var objUser = variables._transfer.readByPropertyMap("user.user",arguments) />
 <cfset retval = false />
 <cfif objUser.getID() IS "0"><!--- login failed... --->
  <cfset variables._session.setVar("loggedin",false) />
 <cfelse>
  <cfset variables._session.setVar("loggedin",true) />
  <cfset variables._session.setVar("user",objUser) />
  <cfset retval = true />
 </cfif>
 <cfreturn retval />
</cffunction>

We're passing it two parameters and setting a third by default, then passing those parameters to Transfer to see if it can successfully populate a User bean with them. If yes, store the user bean to session so we can utilize it other places and return a boolean 'true'; if not, store the empty user object to session (in case we still want to use it somewhere else...at least we know it will exist) and return a boolean false.

 

 

That's it in a nutshell! If I were me looking at this post two days ago, however, I would be very hungry to know the details of how I set up Coldspring and Transfer to play together, and how it is I am accessing my session scope and Transfer from within my model. In order to encapsulate the purpose of this post (give an overview of basic application flow for the purpose of implementing security), however, I shall end it here. But my next post will be the nitty gritty of how I wired all these pieces together.

Hope this helps someone, and do feel free to contribute your own wisdom to the comments!

Posted by dougboude at 12:30 AM | PRINT THIS POST! | Link | 3 comments
07 November 2008
SQL Forward Engineering with Visio 2003 Professional
made a little simpler

Finding the shortest route from diagram to tangible product can be tricky depending on the tool you use to create your Entity Relationship (database) diagrams. I'm using Visio 2003 Professional for mine, and so I went on the hunt for a way to transform diagrams into SQL 2005 Scripts (Visio Professional doesn't include any export features for ER diagrams). The final solution isn't the absolutely most elegant, but I'd call it live-able at least. The basic steps are:


1. Export your diagram to XML;
2. Apply an XSL stylesheet to the XML that generates SQL 2005-compatible scripts.

Exporting XML from VISIO 2003 Pro

Visio Professional doesn't include the luxury of exporting to XML, so I discovered a very sweet third party tool aptly called "Toolbox" made by a generous company named Orthogonal (the tool is free!). Installing Toolbox creates a floating toolbar in Visio that allows you to export the currently open ER diagram. To install simply download the Orthogonal "Toolbox" product, run the setup, restart Visio (if it was open during install), and open an ER diagram. The toolbox should be floating there in your window. If you don't see it, you can go to View,Toolbars and make sure it is checked as being visible.

Creating the SQL Scripts

In my scenario I opted to just go the route of using Internet Explorer to view the finished XML code since it will automatically apply any referenced stylesheets. Because Toolbox let us choose a stylesheet to apply at the time of export, a reference was added to the XML file so when we view it in IE we see a nicely formed SQL 2005 script to create our tables and their relationships. Here's a snapshot of the Toolbox dialog box:

The finished product:

xml created by appling xslt to Toolbox XML file

On the subject of the XSLT...Orthagonal provides an XSLT file that creats the script, but without any linebreaks or anything to make it readable. I found a modification to this XSLT on this guy's site , and I modified it a little further in order to account for autoincrementing identity fields, table names that might also be reserved words (who ever does that?), and default values for bit type fields. My version is at the end of this post.

Final Notes
The XML exported by Toolbox doesn't seem to capture anything you put into the "default value" for entity columns. Because of this, I hard-code a rule into my XSLT that says if the field type is bit and NULL is NOT allowed, make the default value true. Also, although Visio does allow you to say that an column is an identity column, it does NOT give you a way to specify that it should autoincrement. I added one more rule to my XSLT that says if the column is INT and is IDENTITY, script out the autoincrement functionality as well.

The XSLT file has some HTML embedded in it in order to allow for an eye appealing output when viewed in IE. If you are going to be applying the stylesheet via another mechanism, you'll probably want to go through the XSLT and strip out the generated HTML first.

TIP: If you weren't aware of it, you can have multiple pages within one Visio ERD document (as in the screenshot at the beginning of this post). The Toolbox export will export one xml file for every diagram on every tab, so if you want to get things done in one shot just consolidate your Visio documents into one!

That should be it. Here are the relevant links for you to get started creating SQL 2005 scripts from your Visio 2003 ER diagrams:
my version of the XSLT file

link to get Toolbox
sample of the XML produced by Toolbox (without xslt applied)

sample of the XML produced by Toolbox (WITH xslt applied!)

Posted by dougboude at 6:04 PM | PRINT THIS POST! | Link | 17 comments
04 November 2008
Elegant Approach to Disabling Submit Button on Forms

A while back I had a project that required the submit button of the login form to be disabled unless both the username field AND the password field had values. There are probably several ways to skin that cat, but my good friend Boyan Kostadinov offered a more elegant solution (he's always good for the most elegant approach!), and I thought I'd share it with whoever else may benefit.

This solution uses the Prototype library (you need version 1.6). It also uses a small "login.js" file that Boyan wrote which looks for anything with the class "enableOnEntry" (the key to making this work) and then cycles through the form elements within that class, adding their IDs to an array (actually a delimited list, but it is split into an array later). Boyan then binds event listeners to each of the form fields which, while typing in values, will check to see if the submit button should be enabled or not. Anyway, you can read through the JS file if you like, but here's how to implement it, along with a working demo in this post:

1. acquire and reference the following js files in your template (in this order):
    prototype.js
    login.js
2. ensure that your form tag has the class "enableOnEntry";
3. ensure that your form has exactly one submit button (of type 'submit', not 'button');

That's it!

Working Demo:

For your convenience, here is the code used to create the working sample above:

<script src="/js/prototype.js"></script>
<script src="/js/login.js"></script>
<fieldset style="width:200px;">
<legend>Login Form</legend>
<form class="enableOnEntry" name="login" id="login" onSubmit="alert('form submitting...');return false;">
 Username:<br><input type="text" id="username" name="username" value="" /><br><br>
 Password:<br><input type="password" id="password" name="password" value="" /><br>
 <br>
 <input name="submit" id="submit" type="submit" disabled="true" value="Login" />&nbsp;&nbsp;<input type="reset" value="Clear" onClick="$('submit').disabled = true;" />
</form>
</fieldset> 

 and here are links to the js files:

 

 prototype.js

login.js

Hope it helps.

Posted by dougboude at 5:55 PM | PRINT THIS POST! | Link | 3 comments
24 October 2008
iPhone - Mac programmer wanted for telecommute work

Hi all. Just a quick post to see if anybody IS or KNOWS a Mac/iPhone programmer. I realize those can be two very different animals, but if you are aware of an individual who might be interested in some freelance work in that arena please drop me a line via my "Email Doug" link. The work will be telecommute and probably long term, but I'm only helping a local company here find some good contractors so am not privy to a lot of the project details.

Thanks!

Doug  :0)

Posted by dougboude at 6:39 PM | PRINT THIS POST! | Link | 1 comment
13 October 2008
Sneaking Spiders Past Security

The Scenario
You've created a web site that is secured, but you want the search engine spiders to be able to crawl the content. Okay, in this case it's not secured in the sense of needing a username and password, but is secured by requiring that the visitor first acknowledge some terms and conditions before they can access any other portion of the site. This initial requirement of a button click seems to stop all web spiders in their tracks, and thus your site's content never gets added to that search engine's indexes. 

I posted a question regarding this situation on HouseofFusion and did get one interesting answer, but it didn't really address my dilemma. The answer I received was that it is possible to configure Google Analytics to be able to log in to your site in its efforts to index content, but in investigating this further I found that it is only for the purposes of serving ads contextually, not search engine results. Since this site isn't serving any ads and my goal is to help people find their way to the site's front door based on the content BEHIND the door, I needed another answer. Here's what I ended up doing:

In my application.cfc, I have code in the onRequestStart method that checks to see if the user had already acknowledged the disclaimer (by looking at a session variable set to 'true' when they do). If true, allow the original request to go through; if false, redirect to the disclaimer.  I then created an additional, private method in my application.cfc that I called "isSpider" that checks the cgi.http_user_agent against a list of known spider agents, returning either true or false. So, before I check my session variable's value, I first call the isSpider method. If the visitor IS a spider, I set the session variable to true before I do the redirection check against it. Here are the relevant methods:

<cffunction name="onRequestStart" returntype="void" output="false">
 <cfif isSpider()>
  <cfset session.acknowledged = true />
 </cfif>
 <cfif not session.acknowledged>
  <cflocation url="acknowledgeDisclaimer.cfm" addtoken="no" />
 </cfif>
</cffunction>

 

 

<cffunction name="isSpider" access="private" returntype="boolean" hint="I check the user agent string for the occurrence of any of the known spider user agent values">
 <cfloop index="s" list="#application.spiderlist#">
  <cfif findnocase(s,cgi.http_user_agent) gt 0>
   <cfreturn true />
  </cfif>
 </cfloop>
 <cfreturn false />
</cffunction>

 

 


In my onApplicationStart, I create the string of partial spider user agent values:

<cfset application.spiderlist = "Googlebot,Yahoo,msnbot,AOL,Ask Jeeves,Lycos" />

 

It is true that there are literally hundreds of other spiders running around out there, but I chose to select only the top six that show up in my site analytics as being the ones most people find my other sites by rather than attempt to validate all possible indexers. I also opted to simply check the user agent for any occurrence of a specific substring rather than match against the entire string, for efficiency's sake, since each particular search engine can have several different user agents (and those could change at any time!). For instance, Google has (to the best of my knowledge) the following User Agent values for its spiders:

  • Googlebot-Image/1.0 ( http://www.googlebot.com/bot.html)
  • Googlebot/2.1 ( http://www.google.com/bot.html)
  • Googlebot/2.1 ( http://www.googlebot.com/bot.html)
  • Googlebot/Test ( http://www.googlebot.com/bot.html)

Hence, my choice to simply search the user agent for the string "Googlebot" in order to determine if it was a Google spider or not.

I found what appears to be a VERY comprehensive list of spider user agent values (and other metadata) at this url: http://www.user-agents.org/index.shtml . They also offer RSS and XML feeds if anybody wants to do something really cool with the data.

I also used the following spider simulation site in order to test my code changes: http://tools.summitmedia.co.uk/spider/
Their user agent value looks like the following: "K2-Summit (+http://tools.summitmedia.co.uk/spider/) leond@summitmedia.co.uk" , so I just added the value "K2-Summit" to my spiderlist variable in order to let them bypass the disclaimer acknowledgement.

Though the site I based this post on doesn't require username and password authentication, I do believe it would be a simple matter to apply the same principle to a site secured in that manner; when a known spider arrives (one that YOU want crawling your site), simply issue them a visitors pass in the form of manually set credentials and let them do their job!
 
I am by no means a search engine guru, so if anybody out there knows a better way, sees any gaping, dangerous holes in my solution, or just has any suggestions or comments, please do share!

Doug out.

Posted by dougboude at 3:22 AM | PRINT THIS POST! | Link | 2 comments
08 October 2008
Is Your ColdFusion User Group Lame?

For the last several months it has been my distinct opinion that the ColdFusion user group down here in San Antonio is, to put it frankly, lame. Attendance is high when we have more than three people show up (including the manager and co manager), the agenda is non-existent really, and all of our best meetings are held at the Flying Saucer (local college hangout with hundreds of kinds of beer) or TGIs (killer apple tinis). It's important that you understand I am not casting blame or pointing fingers...I believe our manager really does care and puts forth the effort needed. Nevertheless, this has been my personal assessment.

Last night was our September meeting. The manager called me and asked me to make sure and be there early because he was stuck in jury duty and wasn't sure when he would be out. So, I arrived at our regular meeting place (a training facility) only to find the room we always use occupied by a class. The sign that's usually taped to the door pointing the way to the meeting room wasn't there either. So I hung out in the lobby and waited for any other members to show up so we could discuss options. Eventually two guys walked in looking for the user group meeting. They were new faces, had never been to one of our meetings, and were interested in the topic of Flex that our manager had sent out as something we were going to talk about. I explained the situation to them, and for the next 45 minutes had an incredibly deep conversation revolving around Action Script 3 and Flash development, the forte of both of these guys. Our manager arrived soon after and we all went to the Flying Saucer where we spent the next two and a half hours geeking out (I say that affectionately), discussing specifics about projects we were working on, experiences we've had, and our own technical autobiographies. Some good connections were made on several levels, and I know that something positive will come from having gotten to know these guys over a few barley wines.

The real purpose of this post comes now and has to do with an epiphany I had on the way home. I obviously have been of the opinion that my user group is lame, most of that stemming from the fact that NOBODY hardly ever shows up for it. Up until last night, somewhere inside of me I harbored a kind of grudge against those others in this city who I KNOW use Coldfusion on a daily basis yet do not bother to show up and take part in the improvement of our craft as a whole. When I would try and understand why they so very often choose not to be there, I would always think "it must be because they don't get anything from our meetings", and would put my mind to coming up with ways to make the meetings more useful in a practical sense. Last night, though...it was a very small group, 5 in total (another guy joined us later after I called and invited him); yet the meeting was SO GOOD! And no, it wasn't just the barley wine. It was the dynamic of the conversations...I was LEARNING from these guys, I was being sharpened and improved simply by partaking of their experiences and sharing, and I was contributing like things from my own areas of expertise. So, boys and girls, here are my thoughts post-epiphany on the subject of user group meetings now...

If the reason you don't attend your user group meeting is because you don't think you're going to get anything from it...then shame, shame on you. The audacity one must have to believe that the user group is there solely for his or her benefit and partaking! Au contraire mon frere, au contraire. The user group is only as good as its members, bottom line. If you want it to be useful to you, then contribute to it in the form of BEING there and SHARING what you have learned! It's the manifestation of the old saying, you reap what you sow. If you want to gain something from the user group, then man, GIVE something to the user group! As I shared, our meeting last night was so inspiring and enlightening, opening my mind up to things I've not yet exposed it to and showing me the possibilities in these other avenues; but it was not solely because I sat idly by and just listened...I participated.  Your user group will only ever be as good as the members who support it, so if you, like I have done for so long, believe your user group is lame, then stop pointing fingers or casting blame! Attend the meetings and make them what you want them to be. Offer to give presentations, offer feedback and ideas to your managers, be outspoken and a participant rather than a spectator. In a nutshell, either do your part or stop complaining (even if its only to yourself).

And to those of you who DO attend your meetings but tend to worry over the fact that many others do not: stop worrying about it. Simple truth is, you probably are better off without the dead weight anyway.

Just my take.

Doug out.

Posted by dougboude at 2:16 PM | PRINT THIS POST! | Link | 6 comments
08 September 2008
Converting FusionChart to Image - Disappearing Chart Labels

Fusioncharts, as of version  3, exposed via Javascript a method called saveAsImage(), which is what one uses when one wishes to convert the native Flash object to a JPG. I recently finished up a project where I had to do that very thing, and ran in to one little (or not so little) hiccup that perplexed me for quite some time. Following are the scenario, symptoms, and the solution I found.

After the user clicks your "print" button on the display page, a new window is spawned in which we recreate and render our charts one at a time (The way I'm converting all of the charts into one printable page is a little convoluted, but you can read the dirty details of THAT in this post). After an individual chart is rendered, we use Javascript to call that chart's "saveAsimage()" method. This method transforms the Flash object into jpg binary data and metadata, and submits the results to a backend for transformation into an actual jpg. What we want to focus on in this post, however, is the Javascript that renders the chart and calls the saveAsImage() method, because it is within this small section of code execution, oh Best Beloved, where the symptoms are created and later manifested after JPG creation.

The symptoms:
Pie charts which are transformed to images are missing some of their labels!

Though I can see all of the labels just fine when the Flash object is rendered, the final image is missing one or more of those labels! Not cool.


Take a gander at this Javascript snippet:

<script type="text/javascript" language="javascript">
 <cfoutput>
  //here's our chart xml in json form...
  var jschart = #serializejson(chartxml)#;
  function makechart(chartid){
   var myChart = new FusionCharts("/FusionCharts/" + jschart[chartid]['SWF'], "drilldownchart", "420", "420", "0", "1");
   myChart.setDataXML(jschart[chartid]['CHARTXML']);
   myChart.render("printchart");  
  }
  function FC_Rendered(DOMId){
   $('drilldownchart').saveAsImage();
  }
 </cfoutput>
</script>

I'm using Coldfusion for my web pages (thus the seemingly foreign tag '<cfoutput>').

 

I arrive at my page with the chartxml and other needed parameters nested in the equivalent of a hash object;

I transform it using Coldfusion to a JSON format that Javascript natively can read;

on page load, I  then create a new FusionCharts object named 'drilldownchart' and render it within a pre-existing div named 'printchart'.

The function "FC_Rendered" is a FusionCharts method that exists to allow one to execute code AFTER a chart has completely rendered. In my case, once the chart exists I want to immediately start the process of converting it to an image via the saveAsImage() method. Oh, and I'm using the Prototype framework as well, thus the utility function "$('drilldownchart')". It's the equivalent of saying "document.getElementById('drilldownchart')" for those who aren't familiar with Prototype.

So, the code executes flawlessly and I'm happy with it. Until I see the final result.
Where is my label???

I first thought that perhaps it was a font issue, converting from Flash to JPG, so I experimented by changing the label style to different sizes and font faces; no dice. I then asked myself the question, "what if the conversion is happening just before that label actually gets rendered?". Theoretically, this should NOT be possible since I'm using FusionCharts' built in FC_Rendered event method. But just to be safe, let me modify my Javascript as follows in order to add a slight delay....

<script type="text/javascript" language="javascript">
 <cfoutput>
  //here's our chart xml in json form...
  var jschart = #serializejson(chartxml)#;
  function makechart(chartid){
   var myChart = new FusionCharts("/FusionCharts/" + jschart[chartid]['SWF'], "drilldownchart", "420", "420", "0", "1");
   myChart.setDataXML(jschart[chartid]['CHARTXML']);
   myChart.render("printchart");  
  }
  function FC_Rendered(DOMId){
   setTimeout("saveimage()",0);
  }
  function saveimage(){
   $('drilldownchart').saveAsImage();
  }
 </cfoutput>
</script>

By adding a setTimeout call in between the render completion and the saveAsImage() call, my final result came out exactly like it should have. You'll note that I even set the timeout delay to ZERO MILLISECONDS, so just the meager amount of time it took to even call the setTimeout method was enough time to allow the capture of all the metadata from the Flash chart.

 

Now my image looks like it should, with all labels intact. Aaaaah!

There you have it!

Doug out.

Posted by dougboude at 12:48 AM | PRINT THIS POST! | Link | 1 comment
22 August 2008
Just What Is 'Application Logic', Anyway?
as opposed to 'Business Logic'

In a previous post I did on "what is Business Logic", I used the analogy of the application's controller making a request for information from the model, and how the process that the model executes in order to produce the requested information IS the business logic itself. Since I had taken the time to expound on the term "Business Logic", a reader asked me if I would expound on what, in contrast, exactly Application Logic is, so what follows is an analogy of the term as I understand it....

Let's start a company, shall we? Sapient Grey will be primarily a think tank, but with an in-house development group with the capabilities to make the visions of the core committee a reality. Our corporate motto, "Sapient Grey is People..." will be burnished in giant brazen letters alongside a customized version of Rodin's famous sculpture "The Thinker", all of which will adorn the east-facing side of our brand new ten story red granite office building.... Oh, wait. We don't have an office building yet. Okay then, so we'll hire an architect to design our office building, being sure to include a private elevator and an elaborate zen-style atrium or courtyard in the center. In the meantime, while the building is being designed and constructed, you and I and our other elite colleagues will fly ourselves to Tahiti to brainstorm Sapient Grey's visions, goals, agenda, and lay out a detailed business plan.

Our architect is creating detailed blueprints for the office building, figuring out how the plumbing will need to be laid out, how the electrical systems will be segregated and tied together, how the interoffice communication system will be organized, and how we will implement security at all levels. The systems and designs that our building architect is handling is a precise analogy to what "Application Logic" is within a web application. How security is implemented, how we will manage and access state and persistence, how we will handle storage and retrieval of data....this is application logic. Like our office building, which when complete could house and accommodate anything from an elite night club to an investment firm, the application logic is independent of the actual business logic that executes within it.

Back in Tahiti, we and our elite colleagues have nailed down our mission, vision, and corporate culture statements, and have managed (despite the infinite flow of top shelf pina coladas) to draft a three hundred page document which architects Sapient Grey's primary divisions, corporate policies, managerial policies, communication policies, and human resources policies. We know, generally at this point, what our company will be doing and all the internal workings that will allow it to function efficiently. In our analogy with a web based application, we have just architected our application's Business Logic.

So, although at first glance the terms Application Logic and Business Logic can seem to be very ambiguous and even overlapping (they were for me, anyway), in reality they are not. Using the analogy of Sapient Grey and its office building, it is a rather simplistic exercise to properly categorize any aspect of a web based application's logic as one or the other. Just ask yourself, "is it part of the building our company resides in, or is it part of our company and would go with us DESPITE the building we happen to office in?"

Just my take on things. Input and/or clarification soliticted from any other enlightened beings.

Doug out.

Posted by dougboude at 7:56 PM | PRINT THIS POST! | Link | 2 comments
02 August 2008
Printing Multiple FusionCharts Charts/Maps
One approach to jumping through the fiery hoop

This post is for others who are using FusionCharts/FusionMaps in their web development (if you're not, this post is PAINfully long and could be deemed a waste of your time to read). FusionCharts/Maps is an amazing product for the price, and does some very very cool stuff when it comes to charting and creating thematic, interactive maps. It does however present some unique challenges when it comes to actually PRINTING these items, due to the fact that every Map and Chart is an embedded Flash object.

I have recently had to tackle this very problem and having come up with what I believe to be a workable (though not totally ideal) solution, thought I'd share it with whoever finds it useful.

(Consider hiring Doug for your Fusioncharts/Fusionmaps project!)

Here's the scenario...
I have a data details page that provides a lot of data tables and such, as well as a dropdown of the available charts. When a chart is selected from the dropdown, I grab the corresponding Chart XML from a Javascript Associative Array and feed it to a newly created FusionChart object which I then render within my target div. That part works beautifully!

some screenshots...


A bit of the data display page the user sees...

An example of the kind of charts FusionCharts produces...

 

 And yet another chart example...

Feel free to play around with the live app. To access the functionality specifically being talked about in this post, you will go to the search page (where you will see a map rendered) and then click on any state to drilldown to that data. The main Print button may not be wired in, but if not, there should be one at the very bottom of the drilldown output that you can click to fire off the process.

Printing this page out is another matter, because the charts being rende