I had originally posted this as a comment over on her blog, but it keeps disappearing mysteriously:

Evening. I don't normally read your blog.

1) "If Apple doesn't approve your book, you can't sell it anywhere else." I don't think that's entirely true, really. If Apple won't sell your book, you still have ownership of all your content, and you still have a choice: you can recreate it in any of the other non-existant EPUB3 creators, you can export to PDF (or Text) to shop it around to a design or preproduction company who can reformat it for a cost, or shop it around to another publisher who would then, also, reformat it per their needs (at cost). Apple's disapproval of your content doesn't dash your hopes of being published - it just dashes your hope of using Apple's technology to distribute your book.

2) "If you create an ebook in iBooks Author, can you then copy out the content and create a Kindle book in some other tool?" I would say "Yes". Apple is not copyrighting your content, it's taking control over the presentation of your content, as created by iBooks Author. I don't think that's any better, mind.

4) "iBA ebooks will work only on iBooks on iPad" I don't really see this as an important distinction: heavily-laid out PDFs are difficult to read on the iPhone's small screen too, enough so that it's usually not even worth the bother. Apple didn't bother either.

5) "It certainly can't export to any other format." iBA can export to PDF and TXT, and as another commenter mentioned, the source/bundle contains an XML file that can be parsed and converted to other formats as well, given enough effort by an enterprising hacker.

6) "Apple's iBookstore currently serves only 32 countries." As someone who only speaks English, "I don't much care". I know, I know, what a horrifically biased and locality-centric discriminism. But, it's nothing new. DVDs, Blu-ray, video games, hell, even goddamn power plugs, are locality-centric too, and you have to pay an arm-and-a-leg to get around it. Most people don't. I suspect that most of the normal end-users of iBA won't really see a loss in not serving to English-speaking users in Russia, or recoup their cost of translation to serve native-speakers in South Africa.

7) "Apple iBookstore is not that great." Heh, no complaint here. Note, however, that the only ebook store I've used that was actually useful has been Amazon's, which only works because, erm, well, it's not an ebook store. Google's eBooks isn't much better than iBooks either. Your lament here can apply to pretty much any current eBook store.

10) "This is about books (for teaching our children!) which in my opinion should not be controlled by any company or government." "for teaching our children!" is a disingenuous and heart-stringing tactic - there's nothing in iBA that forces you to write textbooks for children. Or high-schoolers. Or college people. Why aren't you as angry at Inkling for being iPad only? For being a closed-source format? For offering textbooks on, gasp, how to mix drinks?! FOR OUR KIDS! I suspect a good portion of the individually-created iBA books (vs. corporate books, like DK, Pearson, etc.) will be non-textbook-y and crappy (and those that ARE written by individuals and ARE "textbooks for children" will likely unleash a backlash of "how dare Apple let just anyone create books to teach our children!? OUR CHILDREN!").

I think the "right" approach, for me at least, is to change the Eponymous You's goal statement from "I want to write a book" to "I want to write a book FOR THE iBookstore". The added clarifying statement, RIGHT NOW, opens up an entirely different world... one that includes a WYSIWIG book creator and added interactivity that is difficult to implement or non-existent in other readers. With that goal statement in place (as opposed to the far-easier "I want to write a book"), the "only" downside is that Apple can deny your book. Otherwise, the other laments nearly disappear or become acceptable.

The Drupal Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded. The recently released 7.x-1.4 update focuses on changes that make theming easier.

In my first post about #theme_wrappers, we fixed up bad HTML in #prefix and #suffix to give themers an easier time. This post, about render arrays, is no different. In fact, most of the tweaks in Achievements 7.x-1.4 are not very obvious, or even useful, to module developers. It's only when you sit down with a themer who wants to change the entire look and feel that you realize your carefully-coded "looks good in Garland!" output is not as useful as you think.

Why render arrays are better

You've probably read about render arrays in the Drupal Developer's Handbook already, but the documentation doesn't really give a developer a decent reason why they should use them. It also doesn't talk about $content or splitting your variables into "data" and "presentation". I'll cover both below.

First, let's look at the wrong approach:

    $build['image'] = theme('image', array('path' => array('misc/druplicon.png')));

If this was passed to a theme file, the themer would simply print $image; and be done with it. The themer could even link the image if they wanted, but they wouldn't be able to add any classes or attributes to the image, since calling theme() directly always returns rendered HTML. Here's what the above looks like as a render array:

    $build['image'] = array(
      '#theme' => 'image',
      '#path' => 'misc/druplicon.png',
    );

To get this to display in the theme, we'd use print render($image); instead. In this case, the theme is telling Drupal to render the HTML, not your module. Why is this important? Ignoring the nebulous "I CAN'T ADD CSS CLASSES, SNIFF!" themer lament, consider the following small tweak to our original bad example:

    $image = theme('image', array('path' => array('misc/druplicon.png')));
    $build['image'] = l($image, 'node', array('html' => TRUE));

Here, we've simply added a link to our dummy image and the themer gets the fully rendered HTML to print out. Everything is toasty... unless the client or theme doesn't want or need the image to be linked. The themer now has no choice: they either have to use a regular expression to strip out the unwanted link or they have to recreate the $image variable in his own, well, image. Not only is that fragile (it's essentially copying module code to the theme and that code might change in a future version), but it also mixes too much logic with too little presentation. If the image's path isn't available to the theme as its own variable (it isn't, in the above example), the themer will still have to parse your rendered HTML to find the image's path first. Yuck.

Let's convert the above to a render array:

    $build['image'] = array(
      '#theme' => 'image_formatter',
      '#item' => array(
        'uri' => 'misc/druplicon.png',
      ),
      '#path' => array(
        'path' => 'node',
        'options' => array('html' => TRUE),
      ),
    );

Now, if the themer wants to remove the link, they can listen in a theme override, a preprocess, or even the dreaded hook_page_alter() and simply unset($variables['image']['#path'])'. No more link, without duplicating any upstream code or recreating things themselves. This is a real-life example, by the way - I just finished an optional submodule of Achievements which removes all links to the default leaderboards. I couldn't have easily done that if I used either of the link approaches below:

    $build['achievement_title'] = l($title, 'path/to/achievement');
    $build['achievement_title']['#markup'] = l($title, 'path/to/achievement');

But with the following render array:

    $build['achievement_title'] = array(
      '#type' => 'link',
      '#title' => $title,
      '#href' => 'path/to/achievemnt',
    );

I can make those links linkless:

  unset($variables['achievement_title']['#type']);
  $variables['achievement_title']['#markup'] = $variables['achievement_title']['#title'];

Data, presentation, and $content['a'] not $a

This doesn't mean that every single variable you pass to the theme should be a render array: there's clearly a difference between variables that represent data (an image path, the $node or $user object, a Unix timestamp, etc.) and variables that are meant for presentation (the linked image, the node's filtered teaser, a human-readable date, etc.).

There's a move afoot to more clearly indicate these two types by using a $content variable as a container for all the supplied render arrays. Drupal core does this in some places (node.tpl.php, for one), but not all, and it's slowly becoming a preferred practice in the theming world. Another benefit is the ability to use print render($content);, which says "render everything I haven't already rendered", allowing themes to display things that they, or the parent module, might not know about (new data added by a third party module in a preprocess, etc.). I've yet to implement this in Achievements, but I'll likely get there for the next release.

The Drupal Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded. The recently released 7.x-1.4 update focuses on changes that make theming easier.

One of those changes was implementing #theme_wrappers. Like many other modules, Achievements has various HTML displays that are essentially containers of things: a group of unlocked achievements, a group of categorized achievements, etc. For stronger theming, one usually wraps those in an extra <div> so that CSS folks can control the container's display. In the past, I've usually accomplished this with:

    $build['achievements'] = array(
      '#prefix' => '<div class="achievement-groups">',
      '#suffix' => '</div>',
    );

Simliar code to the above exists in Drupal core (book.module, field.module, etc.) so I never gave it a second thought. However, for a themer, the above is problematic because there's no way to tweak that HTML without dipping into a hook_page_alter() and rewriting or munging the #prefix entirely. Since I've been away from Drupal theming for so long, I never knew there was a stronger alternative by using #theme_wrappers. Not surprisingly, Drupal core uses this pattern alongside the "bad" #prefix version, so a good contender for a "Novice" patch would likely be "Replace all uses of #prefix divs with #theme_wrappers".

Like other theme functions and files, #theme_wrappers requires a hook_theme() definition:

    function achievements_theme() {
      return array(
        'achievement_groups_wrapper' => array(
          'render element'  => 'element',
        ),
      );
    }

In this case, we'll use a regular ol' theme function for the callback:

    /**
     * Default theme for the wrapper around a user's achievements page.
     *  
     * @param $variables
     *   An associative array containing:
     *   - element: A render containing the user's achievements page.
     */
    function theme_achievement_groups_wrapper($variables) {
      return '<div id="achievement-groups">' . $variables['element']['#children'] . '';
    }

And finally, we can tweak the above $build render array to use it:

    $build['achievements'] = array(
      '#theme_wrappers' => array('achievement_groups_wrapper'),
    );

In this case, the resultant HTML will be exactly the same, but now a themer can more easily override it just by defining THEMENAME_achievement_groups_wrapper(). This is a far cheaper method (mentally and performant-wise) than having to futz with hook_page_alter().

The Achievements module offers the ability to create achievements and badges similar to systems seen on Xbox 360, Playstation 3, Foursquare, Gowalla, GetGlue, and more. For a Drupal site, this could mean commenting a certain number of times, starting a forum topic, visiting the site every day of the week, or anything else that can be tracked and coded.

Here's what's new in version 7.x-1.3, released today.

Relative leaderboards and latest achievements

The biggest changes in 7.x-1.3 happened to the leaderboards. You can now provide an (optional) "relative leaderboard" which will show a logged-in user their current rank as well as (optionally) a number of ranks before and after their position. For folks who like achievements or mapping their rise to greatness, this will give them a little something more to strive for, a continual heartbeat of their movement through the site-wide rankings. It can also encourage rivalries as users flip-flop back and forth in their point gains and positions. Some users won't give a crap at all, certainly, but for those that do, relative leaderboards can increase a site's stickiness.

Another addition to the leaderboard is "latest achievement" which is exactly as it sounds: it'll show the latest unlock a user has earned. This is important because it increases discoverability of your site's achievements. In previous versions of the module, the only mention of individual achievements was located on a tab in the user's profile -- not exactly the easiest place to find. By including recent achievements on the leaderboard, we're increasing awareness of what you're offering, as well as providing subtle and potential todos for your users and curious visitors.

Hooks for developers

Besides the required hook_achievements_info(), I didn't add any other achievement hooks in the earlier releases because I didn't trust I had a firm grasp of how Achievements module would "end up" based upon my initial roadmap. With said roadmap nearly complete, the latest release of Achievements now includes some additional hooks for more integration. As is best practice, I'm starting off "small and obvious", but will add more as needs arise. hook_achievements_info_alter(), hook_achievements_unlocked(), and hook_achievements_locked() all do what they sound like and are further documented in achievements.api.php.

Small footprint

At its heart, Achievements is a monitoring and statistical module: it listens for events, logs statistics, and does analysis on incoming data. When you have hundreds of achievements, all this intervention could slow your site down. Thankfully, the module has been designed under the oppressive gaze of better performance experts than I and has Examiner.com, one of the largest Drupal 7 sites, as an in-progress client. The new release of Achievements has yet another performance improvement that ensures heavy traffic sustainability, and the new features above cost just one additional database query (per page, but only if you're using the block with relative leaderboards enabled). Future releases of Achievements will continue this dedication to a svelte codebase.

Take a look, bub

You can read about the full feature-set at the Achievements module project page which also includes screenshots of the default interface. Don't hesitate to contact me or create an issue with your pie-in-the-sky feature requests and questions.

Achievements 7.x-1.2 has been released:

  • Achievement unlock notifications are now JS fadeins and offline-able.
    • We no longer use drupal_set_message() to inform of an unlock.
    • A new achievement-notification.tpl.php controls the appearance.
    • Unlock notifications now fade in and out at the window's bottom right.
    • We now track whether a user has seen an achievement unlock notification.
    • If they haven't, they will the next time they login or access the site.
  • Leaderboard top rank counts can now be tweaked in the relevant configs.
    • This is in preparation for "relative leaderboards" planned for 7.x-1.3.
  • Unlocked achievements on user/#/achievements can now be unsorted.
    • That is, instead of "move to top" they can remain "as defined in code".
    • Defaults to "move to top". Config at admin/config/people/achievements.
  • Block 'achievements-leaderboard' renamed with underscores not dashes.
    • If you're using this block, you'll need to place it again upon upgrade.
  • Fixed a few PHP warnings when a user has yet to unlock any achievements.
  • Achievement unlocks are now logged in watchdog.
  • Variables are now properly deleted on uninstall.

Achievements 7.x-1.1 has been released (with screenshots for installer trepidation):

  • Achievements can now be categorized into groups.
    • Grouped achievements will be displayed within jQuery UI tabs.
    • If groups exist, ungrouped achievements "upgrade" automatically.
    • See achievements.api.php for more on how to define achievement groups.
  • Achievements can now have images.
    • Three possible display states: locked, unlocked, and hidden.
    • Admins may set the default images at admin/config/people/achievements.
    • An 'images' array has been added to hook_achievements_info() definitions.
    • Per-achievement images can override the default on a per-state basis.
    • CSS tweaks were made for a more flexible achievement display.
    • Default images have been provided for each of the three states.
  • Administrators can now manually give and take achievements from users.
    • It's problematic on progression-based achievements and internal statistics.
    • hook_achievements_info() gets 'storage' to define where statistics are kept.
    • If 'storage' is not specified, assume it exists under the achievement ID.
    • See admin/config/people/achievements for the disclaimer text on usage.
    • See achievements.api.php for more on 'storage' and a revised HOWTO.
  • A new permission, "Earn achievements", has been added.
    • It is REQUIRED and NECESSARY for all roles that can unlock achievements.
    • Core functions check for it so you shouldn't need it in your own code.
    • If you think you need to check, use achievements_user_is_achiever().
    • Removing this permission from a role does NOT delete data or ranks.
    • It does stop, however, the collection of new data, points, or unlocks.
  • achievements/leaderboard/NONEXISTENT now returns a 404.
  • Locked achievements are now displayed on a user's achievements tab.
  • No more warnings when viewing a user with no achievement unlocks.
  • 'id' is no longer duplicated in hook_achievements_info() definitions.
  • achievements.tpl.php has had its PHP moved to template_preprocess_hook().
    • New variables have been added to streamline the display code.

The first "official" version of my Achievements module is now available for Drupal 7.x. Achievements originated as a Drupal 6 module from 2008 which I just never got around to properly supporting with a Drupal project - it has lived the past three years as a tarball attached to an issue. Due to some recent interest from others, however, I've since finalized a new 1.0 with lots of incremental improvements, with a particular eye toward supporting very large installations. These same interested parties will be satisfying a number of new features and improvements over the coming weeks, including:

  • Per-achievement images and the ability to categorize your milestones.
  • Manual/administrative granting/removing of achievements.
  • A JavaScript popup, ala Xbox 360 or World of Warcraft, instead of drupal_set_message().
  • Progress meters for grindish achievements.

...and a healthy bit more.

Notable links enjoyed today:

  • "The Life of Gargantua and of Pantagruel (in French, La vie de Gargantua et de Pantagruel) is a connected series of five novels written in the 16th century by François Rabelais. It is the story of two giants, a father (Gargantua) and his son (Pantagruel) and their adventures, written in an amusing, extravagant, satirical vein. The text features much crudity, scatological humor, and violence. Lists of explicit or vulgar insults fill several chapters. The censors of the Sorbonne stigmatized it as obscene, and in a social climate of increasing religious oppression, it was dealt with suspicion, and contemporaries avoided mentioning it. According to Rabelais, the philosophy of his giant Pantagruel, "Pantagruelism", is rooted in "a certain gaiety of mind pickled in the scorn of fortuitous things" (French: "une certaine gaîté d'esprit confite dans le mépris des choses fortuites")."
  • Delicious tags: biology science nature death ants
    "This is one of my favorite things about ants -- the ant death spiral. Actually, it's a circular mill, first described in army ants by Schneirla (1944). A circle of army ants, each one following the ant in front, becomes locked into a circular mill. They will continue to circle each other until they all die. How crazy is that? Sometimes they escape, though. Beebe (1921) described a circular mill he witnessed in Guyana. It measured 1200 feet in circumference and had a 2.5 hour circuit time per ant. The mill persisted for two days, "with ever increasing numbers of dead bodies littering the route as exhaustion took its toll, but eventually a few workers straggled from the trail thus breaking the cycle, and the raid marched off into the forest."
  • Delicious tags: wikipedia games
    "These are games where the rules are intentionally concealed from new players, either because their discovery is part of the game itself, or because the game is a hoax and the rules do not exist. In fiction, the counterpart of the first category are games that supposedly do have a rule set, but that rule set is not disclosed." Includes discovery games, hoax or joke games, and games in works of fiction.
  • Delicious tags: zombies culture science
    "Let's pretend for a moment that zombies are real (as if half of you weren't already daydreaming about that very thing). Have you noticed how most zombie movies take place only after the apocalypse is in full-swing? By the time we join our survivors, the military and government are already wiped out, and none of the streets are safe. There's a reason the movie starts there, and not earlier. It's because the early part, where we go from one zombie to millions, doesn't make any sense. If you let the creeping buzzkill of logic into the zombie party, you realize the zombies would all be re-dead long before you even got a chance to fire up that chainsaw motorcycle you've been working on. Why?"
  • Delicious tags: nature wikipedia india rats
    Mautam (Mizo, ’bamboo death’; also spelt mautaam) is a cyclic ecological phenomenon that occurs every 48 years in the northeastern Indian states of Mizoram and Manipur, which are thirty percent covered by wild bamboo forests, as well as Chin State in Burma, particularly Hakha, Thantlang, Falam, Paletwa and Matupi Townships, creating a widespread famine in those areas … This event is followed invariably by a plague of Black Rats in what is called a rat flood. This occurs as the rats multiply in response to the temporary windfall of seeds and leave the forests to forage on stored grain when the bamboo seeds are exhausted, which in turn causes devastating famine."

Notable links enjoyed today:

  • Delicious tags: television games culture
    "In thirty-eight years, The Price is Right never had a contestant guess the exact value of prizes in the Showcase showdown. Until Terry Kniess outsmarted everyone — and changed everything … He looked into the audience for a moment, leaned into his microphone, and said his bid as though he were reading it from a slip of paper: $23,743. … "Wow," Drew Carey said. "That's a very exact bid. We'll be right back, folks," Carey said. "Don't go away." And then the show just stopped."
  • Delicious tags: args viral marketing games
    "The goal for marketing types in the Internet age is a "viral" ad campaign. You pull off some publicity stunt, there's tons of coverage on the internet, you wind up with millions of eyeballs for virtually no cost. But viral campaigns are all about pushing the envelope. You have to shock people to get their attention, and this is where the potential for disaster lies. Awful, hilarious disaster."
  • Delicious tags: games history culture language
    "I periodically post about “games and names,” or etymologies and explanations of names and words that appear in video games. Over time, I’ve come across various bits of information that I didn’t feel deserved their own post but that might be interesting to readers. I began collecting these bits in what was at one point a short list of odds and ends but which now exists as a bigger-than-planned list of name etymologies, translation oddities, and my own geek theories — with footnotes, no less." Includes the Legend of Zelda, Mario, Donkey Kong, Wario, Sonic the Hedgehog, Street Fighter and other Capcon titles, Final Fantasy, Metroid, Kid Icarus, Castlevania, Earthbound, Chrono Trigger, Secret of Mana, Mega Man, and more.
  • Delicious tags: wikipedia art roman greek history
    The Venus Kallipygos, also known as the Callipygean Venus, all literally meaning "Venus (or Aphrodite) of the beautiful buttocks",[1] is an Ancient Roman marble statue, thought to be a copy of an older Greek original. In an example of anasyrma, it depicts a partially draped woman, raising her light peplos to uncover her hips and buttocks, and looking back and down over her shoulder, perhaps to evaluate them."
  • Delicious tags: games code design
    "Humans have a tendency to anthropomorphize AI opponents. We think the computer is going through a thought process just like a human would do in a similar situation. When we see the ball end up in an advantageous position, we think the computer must have intended that to happen. The effect is magnified here by the computer's ability to pot a ball from any position, so for the computer, all positions are equally advantageous. Hence, it can pot ball after ball, without having to worry about positional play. Because sinking a ball on every single shot would be impossible for a human, the player assumes that the computer is using positional play."
  • Delicious tags: movies mindfuck
    "Mindfuckers aren’t just Dadaism by another name—there has to be some rationale for the mayhem, even if it’s far-fetched (orbiting hallucination-inducing lasers!) or lame (it was all a dream!). And they are not those movies where the audience (and the characters) think they know what’s happening, only to discover in the final moments some key twist that turns everything on its head. (Bruce Willis was balding the whole time?!) ... In Mindfuck Movies you know that Something Is Going On. It’s just not clear what." Covers Spellbound, Rashômon, La Jetée, 2001: A Space Odyssey, Solyaris, Videodrome, The Quiet Earth, Jacob’s Ladder, The Game, Abre los ojos, Cube, Dark City, Memento, Mulholland Dr., Donnie Darko, and Primer. 
  • "Some of the characters we know and love were recycled from other TV shows and commercials Jim Henson worked on, while others were invented by using whatever materials were around." Covers Cookie Monster, Elmo, Telly Monster, Count von Count, Kermit, Swedish Chef, Missy Piggy, Rowlf the Dog, Oscar the Grouch, Gonzo, Statler and Waldorf, Beaker, Fozzier Bear, Bert and Ernie, Grover, Sweetums, Rizzo the Rat, Pepe the King Prawn, and Herry Monster.
  • Delicious tags: wikipedia reference tricks cons
    "Confidence tricks and scams are difficult to classify, because they change often and often contain elements of more than one type. Throughout this list, the perpetrator of the confidence trick is called the “con artist” or simply “artist”, and the intended victim is the “mark”."
  • Delicious tags: puzzles games adventure design
    "In this article, I will be taking a closer look at the various types of adventure game puzzles, how they relate to the gameplay, and even how some of these basic forms relate to other game genres." Creates a classification of self-contained puzzles (interaction, mini-game, and riddle puzzles) and key puzzles (inventory, pattern, and implicit information puzzles).

My Drupal IRC bot.module received a new release today, bringing it to 7.x-1.3:

  • #273116: bot_auth.module added (thanks snufkin, RobLoach).
  • scripts/bot_start.php: the new location of bot_start.php.
  • scripts/bot_check.sh added: restarts the bot if it's not running.
  • #524218: Drush integration added (thanks jonhattan, q0rban, sirkitree).
  • bot_log.channel is now a text column (required for large installations).
  • All incoming messages are forced to UTF-8 (thanks bellHead, nick_vh).
  • #918966: New Net_SmartIRC wrapper; hook_irc_access added (thanks Bevan).
  • All forgotten strlens()s have been changed to drupal_strlen().
  • All forgotten substr()s have been changed to drupal_substr().
  • First release for Drupal 7.x; based on 6.x-1.2 release.
Syndicate content