Case Study: StyleWorks Premium Photoshop Styles
This past March, I decided it was time to put my skills as a Drupal developer to use and launch a new online business. I knew early on that I wanted this business to be product-based, and after several weeks of playing with different ideas, I settled on selling premium Photoshop layer styles. It was the perfect opportunity to combine my love of photography and Photoshop with my passion for web development and Drupal.
Several months of product development later, StyleWorks was born. The site runs on Drupal 6, and integrates with FastSpring for e-commerce capabilities.
Designing the site: To Zen or not to Zen?
After iterating through several hundred designs in Photoshop, I finally had the look I wanted to go with, and it was time to make it come alive in Drupal. But first, a key decision had to be made: Start from scratch, or go with Zen?
Traditionally, I've always built my designs from scratch. I'm somewhat of a optimization junkie, and I love writing really lean code (I used to be a little more extreme about this than I am today. Back when I wrote DrupalModules.com, I avoided using things like CCK, favoring instead to write my own custom modules). So when I first looked at the Zen theme several years ago, my initial impression was "ugh, look at all this extra code! Why are there so many style sheets?" *Uninstall*
Since then, Zen has grown wildly popular, becoming the #1 theme on Drupal.org. I had always attributed this popularity to Drupal's growing audience of non-developers, but after hearing some positive reviews at a recent Drupal developer meet-up, I decided to give Zen another try.
The verdict? Yes, it does have a huge number of style sheets. And yes there is a ton of code. But it actually works pretty well. The CSS is flexible and well designed, and it managed to stay cross-browser compatible even after extensive tweaking.
What do I mean by flexible? Here's an example:
When I decided mid-project to change from a one-column layout to a two-column layout, I didn't need to rewrite any code, Zen took care of it. And later, when I decided to throw out my first design and give the site a completely different look, I was able to implement the changes with only a few edits to the underlying code.
Clearly, I had underestimated Zen.
Zen's flexibility, however, does come with a few tradeoffs.
If you're used to putting everything in one long CSS file, Zen's barrage of 30+ style sheets can be a little overwhelming at first. There are separate CSS files for almost everything (backgrounds, pages, tabs, forms, fields, etc). Fortunately, most of these files rarely need editing, so the actual number of style sheets you'll have open at any one time is closer to six than 30.
Zen's strict separation of CSS rules feels a little inefficient to me. I'm not sure there's a real benefit to having a DIV's background and border rules split across two different files. With so many style sheets, it can be tricky keeping track of which rules are in which file (I found Firebug to be very helpful in this regard).
The flexible column design also has some drawbacks. If you're thinking about resizing the layout, be prepared for a little grade school math. You'll have to add and subtract values in several places to keep the negative margins working. It's explained fairly well in the code comments, but it's annoying to have to keep redoing it while you're experimenting with column sizes.
Despite these quirks (some of which come down to personal preference), I'd have to say that Zen turned out to be a good choice overall, and I'd consider using it again for my next project.
Layout Decisions
Since layer styles are a highly visual product, I needed a layout that could accommodate a large number of images while still leaving room for a product description. So, I decided to go with a simple two-column layout: product images on the left, descriptive text on the right.
The only question was how to get the text and the images to display in separate columns. I looked at a number of different layout solutions, including Panels and Composite Layout, but none of them really felt appropriate.
Ideally, I just wanted to use Zen's column system. One way to do that was to use blocks, but I didn't want to create a new block for every page on the site!
That's when I found a module called Nodes in Block.
Nodes in Block lets you create special blocks that dynamically display content based on the current path. For example, you can configure the same block to show either a poll or an advertisement, depending on where the user is on the site. Controlling what shows up in the block is done through per-node visibility settings (it's very similar to setting up block visibility).
So I created a content type for product images, and another content type for product descriptions, and used Nodes in Block to get them both on the same page (images in the content main area, descriptions in the sidebar via a block). It worked, but having a product split into two different nodes was less than ideal.
Then I had the clever idea of displaying the same node in both areas. All I needed to do was check the $node->nodesinblock
variable in the node template, and I could control which fields would be output to which parts of the page.
Making the node display twice was accomplished by setting the node's "Nodes in Block" visibility to its own path.
Node Structure
Thanks to the Nodes in Block solution, I was able to consolidate my product images and product descriptions into a single content type called a "product demo". The content type itself is fairly simple, relying on only text and file fields.
Text Fields:
- Long Title
- Short Title
- Subtitle
- Teaser Text
- Full Description
- Product Specs
- Price
- Cart Link
File Fields:
- Teaser Image (file)
- Demo Images (file)
- Texture Images (file)
The site also makes use of the traditional "page" content type (slightly modified to include a file field for images). "Page" nodes are mostly used for non-product content, such as the About and License pages, but I also ended up using one for the product bundle page, as it didn't really fit in the "product demo" content type. I may create another content type for these special sales pages eventually.
String Overrides
While building the site, I noticed that certain chunks of text would need to be repeated in several different places throughout the site. A description of what happens after purchase, for example, would need to show up in the middle of every product page. The phrase "Download Now" is also repeated in many places, but what if I wanted to change it later?
Normally, these bits of text might end up hard-coded into the template files, but I really wanted them to remain editable from within Drupal. My solution was to use Drupal's t() function, along with a nice little module called String Overrides.
Now I simply include this small piece of code in my node template...
<span style="color: #000000"><span style="color: #0000BB"><?php t</span><span style="color: #007700">(</span><span style="color: #DD0000">'after_purchase'</span><span style="color: #007700">); </span><span style="color: #0000BB">?></span></span>
...and I have a description that can be instantly updated across the entire site, right from within Drupal.
I suspect there may be other (better?) ways to accomplish this, but the String Overrides method was elegant and quick to implement.
E-commerce Setup
One of my goals with this project was to avoid the normal hassles associated with starting an e-commerce site. Merchant accounts, PCI compliance, and payment gateways are not my idea of a good time.
Browsing around a few small business forums, I kept seeing recommendations for FastSpring, an e-commerce platform that handles everything from accepting credit cards to delivering file downloads. They even take care of collecting the appropriate taxes.
FastSpring seemed to be exactly what I was looking for, so, I decided to give it a shot. I was able to sign up in just a few minutes, and a couple days later, I had my complete product line online and ready to purchase.
Integrating FastSpring's shopping cart with my site was easy. I added a "view cart" link to my primary links and set up a new CCK text field for my products called "cart link". The "cart link" field is simply a URL that controls where the "add to cart" form is submitted to (each product has a different target URL). When the user clicks "add to cart", they're sent directly to FastSpring's secure server, where they can check out, or continue shopping (in which case, they're returned to my site).
I'm still fairly new to FastSpring, but the initial experience has been surprisingly smooth.
Performance: Living with 512MB of RAM
Because this is an e-commerce site, I felt it was important to have it running on its own (virtual) machine. The site is currently hosted on a 512MB VPS, running Ubuntu 9.10.
512MB is a fairly small amount of memory for a server, so getting the system configured correctly was important. If the server runs out of memory, it will start hitting the swap file, effectively killing performance.
The first step was to stop Apache from spawning too many processes. Here are the key settings (I'm using Prefork mode):
StartServers 5<br>MinSpareServers 5<br>MaxSpareServers 10<br>MaxClients 13<br>MaxRequestsPerChild 0<br>KeepAlive Off
With a maximum of 13 concurrent connections, it's important to disable keepalives, or visitors will be stuck waiting a long time for an open slot during a traffic spike.
Next, I optimized the MySQL configuration file, using suggestions from the excellent MySQL tuner script (edit: Dalin suggests this forked version). Here are the important parts:
key_buffer = 32M<br>sort_buffer_size = 4M<br>read_buffer_size = 4M<br>read_rnd_buffer_size = 4M<br>myisam_sort_buffer_size = 4M<br>query_cache_limit = 1M<br>query_cache_size = 16M<br>max_allowed_packet = 16M<br>thread_stack = 192K<br>thread_cache_size = 8<br>max_connections = 20<br>table_cache = 1024<br>skip-innodb
Finally, I installed XCache, a PHP opcode cacher, and configured it with the following settings:
xcache.size = 16M<br>xcache.count = 1<br>xcache.slots = 8K
Additionally, I enabled Drupal's "aggressive" page caching, block caching, page compression, CSS optimization, and JS minification via the JavaScript Aggregator module.
Overall, the optimization effort was a success. On launch day, the site received over 6000 page views from a popular social networking site, and the server remained very responsive throughout.
Module List
Due to the 512MB memory constraint, I tried to keep the number of installed modules as low as possible. In the end, I managed to get the list down to just these 17 (some of which are currently disabled, such as Devel).
Content-related:
- Content
- FileField
- ImageField
- Text
- Nodes in block
- String Overrides
- Insert
- Token
SEO and performance:
- Global Redirect
- Page Title
- JavaScript Aggregator
- Google Analytics
Developer tools:
- Administration menu
- Devel
- Backup and Migrate
- Database Logging
- Update status
Drupal version: Drupal 6.x