How to Write a Pepper Plug-in for Mint, Part II: Preferences and Secondary Table Rows · 2101 words posted 09/30/2005 11:26 AM

Developers! Developers! Developers! Steve Ballmer and Shaun Inman both know that if you want programmers to embrace your platform, you make the API as accessible as possible. In Part I of this tutorial, we saw how easy it is to write a plug-in for Mint, Shaun Inman’s site statistics package. Now we’ll take a look at building upon our original plug-in to add preferences.

This tutorial builds on Part I. I won’t cover the same ground again, so if you’re building your first Pepper plug-in it’s best to read Part I first. Also, this is not a how-to-write PHP tutorial, but you should be able to follow along if you have a basic understanding of arrays and functions. Finally, you code at your own risk. I can’t help you fix your Mint installation if you break it.

To recap: in Part I we built Parsel, a plug-in to show the languages our site visitors use. It was nice, but it wasn’t very feature-rich. Users consistently requested two changes:

Also, we have two house-keeping measures that don’t affect end users:

Now view the source and let’s get started! As with Part I, I’ve included line numbers so you can follow along.

Updating the Code to Comply with Mint 1.1

As I noted above, Mint 1.1 and above now retrieves config values differently, so we need to update the way we grab them. Shaun has added the function getCfgValue().

The old way:

$this->Mint->cfg['preferences'];

The new way:

$this->Mint->getCfgValue('preferences');

Moving the Language List into the Class File

You can make the case that best practices dictate that you keep your data separate from your controller, and thus we kept a list of languages in a separate file which we loaded via include_once(). You can also make the case that best practices are a pain in the ass for simple applications and add needless complexity.

Having persuaded myself of the latter, I deleted the old languages.php file and added a new function: get_Languages().

The old way:

include_once("{$this->Mint->siteroot}{$this->Mint->cfg['install_dir']}pepper/{$this->info['src']}languages.php");

The new way:

$languages = $this->get_Languages();

Our new get_Languages() function (line 523) loads the list of languages and abbreviations into an array and returns the array. How simple is that?

Now that we’ve taken care of housekeeping, let’s get to the fun stuff: adding new features. As with Part I, I’ll walk through the class function by function, revisiting what has changed and pointing out what’s new.

function install()

As we have seen, install() does just what it sounds like: it installs the plug-in and makes it available to Mint. But we need to make two major changes to our previous install function:

First, we’ll use code written by Colby Makowsky to check whether Mint is version 1.1 or later, and if not, abandon the installation and throw an error. (See lines 75-97). I won’t go into detail about the code here: if you’re following along you can see it’s clever and hacky, but is likely to change with future versions of Mint. In brief, if we detect an older version of Mint we build an array $errMsgs and pass that array to getHTML_PermissionErrors($errMsgs) (line 477), which outputs the error to the screen.

Next, we set our preferences (lines 100-104). I’ll focus on show_as_percent—you can figure out what show_raw_string does on your own.

First, we create a new preference: show_as_percent and we set the value to 1, which means by default our users will see aggregate values as percentages, not as counts.

$prefs['show_as_percent'] = 1;

Second, have have to save that preference:

$this->Mint->savePluginPreferences($this->plugin_id,$prefs);

function onDisplay()

This is largely the same as last time: onDisplay() is fired when a Mint viewer loads our pane. As mentioned above, we now load our language list from a new function instead of an external file.

function onDisplayPreferences()

The code in this function is entirely new to our Pepper: last time we didn’t have any preferences, so there wasn’t anything to display. But this time we have two preferences: show_as_percent and show_raw_string (as with the install() function, I’ll describe show_as_percent and let you figure out show_raw_string on your own). This function expects you to return an array of HTML; Mint automagically outputs the contents of the array for you.

First, let’s look at the finished preference pane.

By default, we want to show percentages, but we also want the user to be able to turn this feature off. The best way to handle this is with a checkbox. So in onDisplayPreferences() we need to handle the following tasks:

Grab the preferences and stuff them into an array (line 213):

$prefs = $this->Mint->getPluginPreferences($this->plugin_id);

Get the show_as_percent value and set the checked value if necessary:

$show_as_percent = ( $prefs['show_as_percent'] ? ' checked="checked"' : '' );

Finally, we build a $preferences array with our HTML and return the array to finish the function.

function onSavePreferences()

This function (line 244) allows the user to save any changes she’s made to preferences (the very prefs we learned to display in the previous function). The function is automatically fired when the user clicks done; since the preference pane is actually a form, we simply check to see which values were posted and put them in the prefs array. The built-in function savePluginPreferences() does the heavy lifting of actually saving our updated preferences to the database.

Let’s skip over onCustom(), since we haven’t called it yet. We’ll return to it shortly.

getHTML_LanguagesRecent() and getHTML_LanguagesCommon()

These are the functions we wrote from scratch in the first part of the tutorial. Instead of native Mint functions they’re our own. Let’s take a closer look at the changes in getHTML_LanguagesCommon().

Recall that we now give the user the option to show language groups as percent instead of as counts. Since we’re saving this option as a plug-in preference, we need to retrieve the preference. First, we set the plug-in preferences into a $prefs array, and then we grab the show_as_percent flag (lines 350-351). Next, we set the label for our output table based on the pref we just retrieved (line 352).

Also recall that we want to show sub-data: not only the language group, but also the languages within the group. Fortunately, Mint has a built-in flag for its tableData array:

$tableData['hasFolders'] = true;

When we describe our language column in the thead array, we give it a CSS class of stacked-rows. This combination of hasFolders and stacked-rows tells Mint how to display the rows and sub-rows—notice the small arrow that appears on the left side of each row, indicating that the row can be expanded. Again, we’re just building on pre-set descriptions already present in the Mint API.

Based on the value of our $show_as_percent flag, we calculate percentages or counts accordingly. But the next big change comes along when its time to assemble our table data. It’s worth comparing the old and new versions:

Old:

$tableData['tbody'][] = array(
$language_row_final,
$r['total']
);

In the old version, we simply build an array with the name of the language and the total count. Think of it as simply column A and column B.

New:

$tableData['tbody'][] = array(
$language_row_readable,
$num_output,
'folderargs'=>array(
'action'=>'getranges',
'lang'=>$r['str_language'],
'total'=>$total
);

(Ignore the slightly different variable names). In the new version, we also assign values for the first and second columns, but we add a new element to the array: folderArgs. The first element, action, is required, and tells Mint which action to pass to onCustom() when the row is clicked—in this case, getranges. The second and third arguments, lang and total, are the arguments Mint will pass to the getranges function in the _POST scope.

Here’s the beauty of AJAX (and I promise that’s the only time I’ll use that word in this tutorial): if you were building Mint the old-fashioned way, you might assemble a massive, multi-dimensional array in PHP and pass it to the browser. You’d show and hide table nodes onClick(), but such a large amount of data would make the page load slowly. Instead, by using the XmlHttpRequest object Mint ignores the secondary data when the page loads, and only retrieves the language sub-rows when the user requests them. Faster page loads, and data as needed at your fingertips.

Schweet.

function onCustom()

Let’s jump back up to line 254 in the class file. Function onCustom() is fired when the user clicks a language family row to expand the specific language variant sub-rows beneath. onCustom() simply acts as a handler to catch the event and pass the data in the _POST scope to a new custom function: getHTML_Ranges().

function getHTML_Ranges()

Just like getHTML_LanguagesCommon(), this function (beginning line 413) is a custom function written by the developer—it’s not included in the Mint API, and it could have been named anything (assuming it’s consistent with PHP function naming rules). getHTML_Ranges() simply takes the two-letter abbreviation for the selected language and looks for matching regional variants in the database. As with our other getHTML functions, we simply assemble a $tableData array, use Mint’s built-in generateTableRows() function and pass it back as an $html variable.

A presentation note: with getHTML_Ranges() and getHTML_LanguagesCommon() we use formatPercents(), another built-in Mint function that formats our percents values to make them easy to read (line 457).

Confused? Me too. Let’s review how we stepped through all of those display functions:

That’s it. Writing a Pepper plug-in for Mint mostly depends on understanding when functions are called, and distinguishing between built-in API functions and those you have to write yourself. Let me know in the comments if you don’t understand a portion of this tutorial.

P.S. If you want to view the most current version of the Parsel source, you can always snoop around the svn repository. As you can see, Parsel still contains some inelegant hacks. For example, I only select the left two characters to determine the language family (line 365). Doing so renders incorrect results for outliers like AST (Asturia) and the query can surely by improved by using MySQL REGEX. So if you’d like to contribute to development and you’re familiar with subversion, leave a note in the comments and I’ll give you commit permission.

* * *