|Perl: the Markov chain saw|
My Experiences with using CGI::Application and Template::Toolkit To Build an Online Shopping Cartby skazat (Hermit)
|on Sep 20, 2002 at 09:15 UTC||Need Help??|
I hope this little write up helps advocate both of the above modules and also helps anyone who'se beginning a similar project with a starting point.
I recently got a call from a friend of mine, who was starting work on the web site design for a local kids clothing store. He asked me if I could do the backend for the shopping and I agreed. This friend is actually my brother's best friend. My brother is 30 and I am 21, which means I've known his friend since I was six and getting beat up by both of them. This would be actually the third time we've collaborated to create the online catalog and the shopping cart I wrote more than two years ago was getting old and rickety; cursed with the deadly disease of hacking on new features and not taking the time to fix the old bugs. Also to take in account that I've learned much about Perl and how to do these things in the past few years, it was time for the old cart to be taken down and a new one to be put in its place.
Now, this is a local business and as such, had a local business budget. Usually "tight funds" and "Customized Shopping Cart" do not go hand in hand, but in things as critical as shopping carts, I do like to know what the underlying code is actually doing and I hate wading through source code that's not mine. My code may not be the absolute finest in the realms of Computer Science, but I trust it and I trust Perl.
I've also heard horror stories about Miva Merchant and, well not exactly horror stories, but more like GRIM WAR TALES about Miniven-Akopi-Interchange. Miva Merchant was a "drag in drop" feature of this particular hosting provider. I wasn't about to try to install Mini...Change.
So I had a problem that I didn't want to use an already created shopping cart, but I didn't know if rolling my own would fit the budget (and didn't want to absorb the costs of developing a new one)
I finally decided to roll my own when I got a bit of bad news. The company I've worked for 3 years which I started as an intern and at my peak, was the Principal Developer, somewhat dissolved.
No more steady work. Now rolling my own shopping cart made sense, since I'd probably, well, definetly would be using it again with collaborations with the same friend, or even by myself.
Since I like to make things more complicated than they are and am never satisfied with one way to do things, I decided that the application itself would be written entirely in a different fashion than I was used to. I mean, what they hey, you know?
I did the development of this cart during a break in school. I'm, actually a Painting Major at a local Art School here in Denver. Why am I developing web-based applications? I don't know. But I am. It pays for paints and that is a good thing.
I immediately came into a problem about the design of the cart. When you work for a company for three years, you usually acquire quite a bit of company-owned code and resources that made your job oh-so-much more easier. This was indeed the case. In my career there, I created not one, not two, but three different shopping carts, the last being built upon a custom content management system, complete with so many bells and whistles (templating, templating language, database administration, makes coffee, washes your car, anything and everything, all in Perl, of course) I actually use this meta tool for all my last projects; I never started from square one, I started at the last step of the problem. And, it was good.
And it made me lazy.
Which is good in Perldom. I did not want again to start from scratch. I thought, "Gee, I wonder if I could build a similar CMS system using 'off the shelf' parts and have an environment that I'm more used to?" So, I did a bit of research.
The very wise Markjugg has been talking about merits of CGI::Application and I've wanted to learn Template-Toolkit, which was similar to the templating language I developed.
Now I had an
StumblingsCGI::Application has the idea of "Run-Modes"; each run mode is associated with both a screen of the application and a subroutine that does interesting things and also outputs this screen. The module makes you follow a strict design of one run-mode per screen. At first, this seemed limiting and hard to work with (or work around), since, well, I don't like strictness. A bit of a background:
I learned Perl by myself with the help of a few friends whom sort of put me in a loose apprentiship with them, where the exchange of ideas was easy and fundamental ideas could be hammered out.
I thoroughly thank them in my monkish meditations.
I also don't have a CS or Math background. I'm a Fine Arts major that goes to a school specifically for Art and Design. I learned Perl, maybe because Perl seemed like the perfect fit. I could express myself in code the same way I write down spontaneous prose or how I attach a painting. It fit me more that I had to fit it. Thanks, Larry.
When learning, I've made lots and lots of mistakes.
In almost every CGI application that I've created, I've used the following little snippet that nicely emulates CGI::Application's "Run-Mode" idea:
You might recognize this, since it's from the Perl Cookbook. So, you have a nice little switcher oo thingy up top of your script and the subroutines at the bottom. Works pretty darn well, thankyouverymuch.
The other major idea of CGI::Application is the idea that the actual logic behind your application, the actual anything of your application, lives in its own module, which used CGI::Application as it's base class. The script itself is usually no more than 5 lines, this is what my final shopping cart script looks like:
That's voodoo right there.
When I first read the docs for CGI::Application, the step to make a module of the actual code and then to make this little script call the module seemed to be a funky way to complicate things. Why have two files that do the same as my one? Well, in the way I've been doing things, I actually have a CGI script that is alone 7200 lines long.
This isn't including any modules it calls. That's ridiculous for a CGI script and limits any sort of collaboration on the script. With the CGI::Application way, The script doesn't ever need to change, you can just swipe the module it calls. The module even has ways to pass it a config file so you can use the physical module in different instances all over the place. MarkJugg's Cascade does this now, if you'd like an example.
Once you learn how to fetch your CGI object:
and how to actually display the screen (just return the content as a string before exiting the run-mode subroutine), development of the application went by like lightning.
I actually started to play around with Template-Toolkit before creating the shopping cart and I didn't realize that CGI::Application had some hooks directly into HTML::Template, another, simpler templating system. I tried both and it really came into my environment on which one I chose. HTML::Template uses what looks like HTML comments to allow you to place variables that will later be switched out for what they represent.
This really didn't jive with me, since my editor of choice, BBEdit, shows comments as grayed out text. Template-Toolkit uses special thingies wrapped in square brackets and percent signs [% %] as it's template variables. In my editor, those showed prominently as black, while being surrounded by coded HTML. Sure, I could have just tweaked the prefs of BBEdit, but I know Template-Toolkit could do more things that might be of interest to me in the future. After all, I'm a very visual person and black is always in style.
Markjugg argued that Template-Toolkit also allowed you to do more than variable interpolation, but allowed you to also construct fairly complicated loop structures and even embed Perl code right in the template, which ruins the Content is Separated from Design ideal, which was the reason to go through the flaming hoops of CGI::Application and Template-Toolkit in the first place. I decided to still go with Template-Toolkit because even though I promised myself that I wouldn't use logic inside my templates, it was nice to know I could if I wanted to. In a crunch, I could put a little logic in, and polish later. It turns out I never even needed to do this.
As a templating system, I found Template-Toolkit to be very nice. It actually matches almost every feature that my own templating language had, except for how it bind with Perl variables, something mine lacked. After I figured out how this worked, my entire cart turned into simply, taking in information, saving it, and massaging variables I would then feed into Template-Toolkit to display. I cannot tell you how simple this all became. My add_to_cart run-mode looks a little bit like:
In this case, I didn't even display a screen, but changed the "header_type" to redirect the same as saying:
but in CGI::Application-speak.
The cart summary subroutine actually looks like this:
Basically, I'm creating an environment, populating the $vars hashref which will get fed into Template-Toolkit, and having Template-Toolkit output this information in the $html variable. You get Template-Toolkit to fill the $html variable with the parsed template information by passing a ref to a scalar in the process method, instead of nothing, which would then send the parsed template to STDOUT which is a huge no no in CGI::Application, but which would seem perfectly fine for me.
All the templates do is show the environment of the shopping cart and the order in a pretty way. This philosophy actually makes sense and works. The 15 line (which has all sorts of chances to get even tidier) subroutine allows me to have a screen that displays the entire contents of the cart. This is from someone who is known to produce 700 line subroutines. And paint 20 foot walls.
Of course, all the nitty gritty calculations and database stuff is handled by other modules, but having structure at the point of the actual application by having design completely out of any of the code and having a strict structure on how to design the actual application, allowed me to also keep all the other modules neat and tidy. It seems that one of my problems was that I would build a module around a script. This helped me grow out of that bad idea. Good foundation, good everything.
I found that I would use the actual templates as much as possible and my templates would almost be a list of [% INCLUDES %]. Observe the actual complete template for picking up completed orders by the merchant:
Template Toolkit seemed to be flexible enough where I could use the same template for similar chunks of HTML. Using just a bit of the logic, in the form of [% IF %] and [% FOREACH %] tt blocks was enough to make the template ultra flexible, without plopping major logic in the templates themselves. I find this so flexible, I'm basically going to base any web site I do on Template-Toolkit as the management tool. To someone whose first memories are LEGOS, it's absolutely second nature. I also decided that I didn't want Template-Toolkit tags (or as little as possible) in HTML files that I was editing in Dreamweaver, because, well, they look awful. Dreamweaver also likes to munge the Template-Toolkit style tags, switching some characters for their entities, so, I decided that any TT templates were off limit to Dreamweaver. Most of the HTML in the TT templates were easy enough for me to edit by hand anyways. In this situation, going with HTML::Template would have been smart, as HTML::Template tags look like HTML comments and are promptly ignored by Dreamweaver. To get around this, I would make a simple wrapper template that would just have this:
The basic_page.html file would be a file managed by Dreamweaver and it's own templating system. Notice this is much of the same philosophy of CGI::Application. Dreamweaver MX also allows you to tell it what files it cannot touch, by the file endings. I choose 'tt_html' as what I would save my TT templates as, since all of them were snippets and would get munged the wrong way when managed in Dreamweaver MX. This time, I also told BBEdit that files ending in 'tt_html' should be treated as HTML files. Macs rock that way.
Other Fine Resources
To get past the problem of actually managing the Inventory of products, I just made a simple FileMaker database and had my friend fill it up. Filemaker exports in pretty clean CVS, which I fed into an online SQL database online, using Text::CSV and a little help from HTML::Entities to do the heavy lifting.
This allowed me to NOT have to make an interface to the online database. The CMS I made at work at an admin tool, basically for the admin tool, allowing you to build a database design. It took forever and is definetly cool. For testing and development, I also took advantage of Chris Hardie's DB BRowser, which allows you to manage multiple tables without much configuration to the script. Very nice.
The form validation in the shopping cart, and there are a lot of intricate forms in a shopping cart, were all easily handled using Data::FormValidator, which is maintained by Markjugg. This module really helps you almost skip the dull boring task of validating form input and is amazingly flexible.
When an order is placed, an email confirmation is sent. In this confirmation, the order itself is printed out. The problem is, that it's hard to make text-based tables without using Perl Formats, which have to be, as far as I can tell, directed to a file handle or STDOUT, which isn't too clean. I found that a little module called Text::FormatTable work wonders.
Total Development Time: 3 weeks.
Total Development Time For Previous Shopping Cart: 3 months.
The New shopping cart is up and humming and small bugs that have been found have been fixed easily. I have nothing but smiles and joy for both of these modules.
Edit kudra, 2002-09-20 Added a readmore tag