Making P&T: Juniper

This is the fourth and final installment in a series about the design and development behind what is now Pixel & Tonic. Yesterday we looked at how we’re doing documentation. Today we’ll take a look at the other aspects of add-on management and commerce.

November, 2008. Four months since I had moved out and taken a new job. And I was under the impression that my stint with ExpressionEngine was over. I had only developed a couple websites with EE at my previous job. One of them required I build Playa, and when the site was done I released it for free as a token of thanks for all of the other free add-ons people had released. I had no intention of actively supporting or maintaining it – especially since I wasn’t even using EE at the new job.

But then I joined Twitter. ExpressionEngine users followed me, and I followed them back. I started seeing mentions of Playa here and there, and realized that people were actually using it. But they weren’t all nice things – many were having issues. I wouldn’t stand for that, so I started releasing updates. And with that, finding a way to manage releases.

At first, I turned to LG Data Matrix. Each time I released an update, I’d create a new row in an LGDM field – saving only the Version and Release Date in text fields, and the Release Notes in a textarea. A separate MH File field held the latest file.

But I wanted the dates to be stored as actual timestamps – something I could reformat within my templates (for, say, a Changelog feed). And I wanted a checkbox cell to define which rows would show up on my add-on overview pages (in a “Recent Changes” section). So I hacked those cell-types into LG Data Matrix myself. (Later, Leevi was kind enough to include my changes in his 1.1 update.)

Working on LG Data Matrix got me thinking: How cool would it be if the add-on came with an API that made it easy for anyone to create cell-types? Wait… how awesome would it be if there were an API that made entire field-types easy to create? And, once that was established, I could clone LG Data Matrix with that API, and then it could load these field-types itself, so they’d double as cell-types!

A couple months later, FieldFrame and FF Matrix were born. And not long after that, nGen File Field.

Which brings us back to managing releases. With FF Matrix and nGen File, I could now store each individual release’s ZIP file right alongside the rest of its metadata:

FF Matrix

Going Commercial

April, 2009. I released Playa 2, a notable update that brought the add-on far beyond its original scope. So I decided that it was time to start charging.

For payment processing, I did what I think most add-on developers have done to date: take the Simple Commerce module, and hack the hell out of it.

The two big changes were:

  1. Don’t require a user to be logged-in to make a purchase. (I didn’t have any sort of member accounts, and wasn’t interested in dealing with that just so people could buy things.)
  2. Upon completion of a purchase, generate a license key. Store it alongside the other purchase data in exp_simple_commerce_purchases, and make it an available variable for my Customer Email Template.

When a customer completed a purchase, they would receive their License email, which included a Download URL. The URL was essentially a script that searched the purchases table for the license key it was passed, and if a record was found, served up the latest File for the associated product.

Though the system worked (for the most part, anyway), I was never happy with it. I didn’t like how disconnected everything was – release metadata and files being stored in a Matrix field; purchases being handled by a separate, hacked module; and file downloading by its own one-off script. I like to keep things contained, and this was anything but.

Enter Juniper

Pixel & Tonic gave me a clean slate. I could do anything I wanted. So I spent some time identifying what I disliked about the old system, what type of features I’d like to see, and what would be the most straightforward solution for myself and my customers.

Here’s a summary of what I came up with:

  • Since items for purchase are typically associated with entries, so their commerce setup should take place right within a tab on the publish page.
  • This tab should hold two things: the item’s price, and release matrix. The release matrix should record the Version, Release Date, Release Notes, and File for each release.
  • Since license generation is tied to purchases, the module should handle the commerce.
  • Since file downloading is conditional upon either the item being free or the member having paid for it, the module should handle that as well.

Essentially, take each of the components of the old system, and combine them into one module that’s specifically tailored to my own needs (selling software).

The end result is Juniper. Here’s what the publish tab looks like:


For the Releases matrix, I took the opportunity to try a couple new things that might make their way into the next major release of FF Matrix. The most noticeable being that previously-saved content is initially presented in a non-editable state. If you do want to edit a cell, simply clicking on it will swap its static state with an editable field. Hitting the ESC key while that input has focus will revert the cell back. This drastically cuts down the visual clutter of the matrix, and also makes it easier to identify which cells have unsaved changes.

Templating with Juniper

I spent a lot of time on the templating end of the module, which helped me keep my site’s templates straightforward.

With the help of an additional field, for_sale, those Download / Buy Now buttons at the top of each of my add-on pages were as simple as:

<code>{if for_sale == "y"}
    <a href="{exp:juniper:add_to_cart_url entry_id='{entry_id}' return='store/cart'}">Buy Now</a>
    <a href="{exp:juniper:download_url entry_id='{entry_id}'}">Download</a>

{exp:juniper:add_to_cart_url} adds the item associated with the passed-in {entry_id} to the user’s cart (an array stored in the session), and optionally redirects the browser to a specified template. In this case, users are redirected to store/cart, whose template looks a bit like this:

<code>{exp:juniper:cart_form checkout="store/checkout"}
    {if no_items}<p>Your cart is empty.</p>{/if}
    <table class="cart">
            <th scope="col">Product</th>
            <th scope="col">Quantity</th>
            <th scope="col">Price</th>
            <th scope="col">Total</th>
                    <th scope="col"><a href="{path={url_title}}">{title}</a></th>
                    <td><input type="text" name="quantity[{item_id}]" size="2" value="{quantity}" /></td>
                <td colspan="3"></td>
                <td><strong>Grand Total:</strong> ${grand_total}</td>
    <input class="button" type="submit" name="update" value="Update Cart" />
    <input class="button submit" type="submit" name="checkout" value="Checkout" />

The {items} tag pair loops through each unique item in the user’s cart. Both Submit buttons will update the user’s cart with the new item quantities, and then either reload the current template, or redirect to the template specified in {exp:juniper:cart_form}’s checkout= parameter, depending on which Submit button the user pressed.

The other two Juniper-related templates, store/checkout and members/index, look much like you’d expect, so we’ll skip those.

Much to be done

I launched with many of Juniper’s to-do list items unchecked, and have been adding them as I have time. This past week has seen the addition of PayPal integration, some fancy charts behind the scenes, and even public module actions for generating a LG Addon Updater version.xml file and NSM Addon Updater releases.rss files. Purchase management isn’t quite finished yet, which is why I’m running a little late on those Launch Giveaways, and there are quite a few other nice-to-have’s in the pipeline.

But I couldn’t be happier with what I accomplished in this module, especially considering all of the other things I’ve been juggling these last couple months. I feel very confident about Juniper as my new release management/purchasing/downloading platform, and the initial reaction to its checkout experience has been quite positive.


It’s very likely that Juniper will be released in some fashion this year. Given that it’s a very targeted product, how I go about it – the price, the availability, the support model, etc. – are still up in the air. But it’s no secret that I think more EE add-on developers should start charging for their work, and I’m happy to help make that a reality. If it’s something you’re interested in, let me know in the comments.

This concludes Making P&T, our series on the design and development of what is now Pixel & Tonic. If you have any other subjects you’d like to hear about on Blog & Tonic, feel free to share in the comments. But for now, we’re going to get back to doing what we do best: developing add-ons.

comments powered by Disqus