How to write a plugin

Dancer is a DSL for writing web applications. It's focused on providing the obvious which makes the feature set limited on purpose.

For advanced or particular needs, Dancer's DSL can be extended by plugins.

This article will explain the basics and concepts of Dancer plugins and will guide you through the writing of your first plugin: Dancer::Plugin::MobileDevice.

The Basics

Before starting to write our own plugin, let's explain what a plugin can do and how.

A plugin is a package that declares (with the register keyword) a set of keywords that are bound to code refs. When a plugin has performed all of its declarations, it registers itself in Dancer's core with the register_plugin keyword.

Once a Dancer application imports a plugin, all of the keywords declared by the plugin are visible in the application's namespace, just as if they were part of Dancer's core DSL.

A plugin can also do everything a regular Dancer application can do, like declaring a before filter, declaring a couple of route handlers or even running some Perl code at its import time.

The concept of a "mobile" plugin

Now that we know what a Dancer plugin is, and how it works, we have to find something to do. I recently had the idea to implement a plugin which would provide the developer with a facility to detect if the user agent is a mobile device or not.

When thinking of that idea, I realized it would be a perfect subject for an article, because the plugin itself will not be complicated while using an interesting subset of the possibilities.

The idea is to provide the following features:

  • dynamic layout

    We want to serve light HTML content when accessed by a mobile device, so we'll basically set a specific layout whenever a mobile client requests a page. That way, we can forget about the layout and focus on our route handlers, and the plugin will handle that for us.

  • boolean accessor is_mobile_device

    Basically, the plugin should provide an accessor for letting the developer know if the current user agent is a mobile device or not.

  • default token in templates

    We want all of our template calls to be able to know about is_mobile_device, so we want to make it a default token.

OK, I think this feature-set sounds like a good start for Dancer::Plugin::MobileDevice, let's start writing it!

Writing the plugin

Let's start with an empty plugin skeleton:

package Dancer::Plugin::MobileDevice;
use Dancer ':syntax';
use Dancer::Plugin;

register_plugin;

That's it, we have an empty plugin. It does nothing yet, except registering itself to Dancer's core.

We can now add a keyword which will be exported to our caller's namespace (basically, the user's application that uses our plugin).

Adding a keyword: is_mobile_device

We want to provide a helper that returns a boolean value telling if the requesting user agent is a mobile device or not, we'll do that by exporting a new keyword to Dancer's DSL.

This keyword is basically a subroutine bound to a name (Dancer::Plugin takes care of the exporting magic for us, we only have to declare new keywords with register and register the plugin itself when done with register_plugin).

The subroutine bound to our is_mobile_device keyword should do one simple thing: test the user agent string of the incoming request against a pattern of known mobile devices. This is as easy as the following:

register 'is_mobile_device' => sub {
    return request->user_agent =~
        /(iPhone|Android|BlackBerry|Mobile|Palm)/;
};

That's it. The request keyword is Dancer-native and returns a Dancer::Request object representing the current incoming request. It provides among other goodies an accessor to the user agent string (user_agent) and we test it with a regular expression with well-known mobile device strings.

Whenever it's called from a route handler (or a filter), it returns true or false depending on the match.

Dynamic layout

We now want to change the layout whenever a request is served for a mobile device. This can easily be done with a before filter:

before sub {
    var orig_layout => setting('layout');

    if ( is_mobile_device() ) {
        setting layout => 'mobile';
    }
};

A before filter is executed whenever a request is served, before the route handler. This filter takes care of changing the layout setting whenever the is_mobile_device is true.

We don't want the layout setting to remain that way afterwards, we want to restore that setting to its original value after the request is processed. That's why we save it first with the var keyword.

Then, with an after filter, will reset it to its original value:

after sub {
    setting layout => vars->{orig_layout};
};

That way, if a non-mobile client comes just after a mobile one, the layout will be back to its original value.

Default token

Finally, we want our templates to be able to render specific content for mobile devices, so we want to provide all our templates with a is_mobile_device token, whose value will be obviously given by the helper previously defined.

This is easy to do, we just have to write a before_template filter, which will alter the default tokens hash table that is given to any template call.

before_template sub {
    my $tokens = shift;
    $tokens->{'is_mobile_device'} = is_mobile_device();
};

That's it, we can now do this kind of conditions in our views:

<% IF is_mobile_device %>
    some content for mobile devices
<% END %>

Conclusion

Well, here we are. We now have a Dancer plugin that does what we wanted, writing a mobile-aware Dancer app will now be much easier.

As you can imagine, I've written Dancer::Plugin::MobileDevice and uploaded it to CPAN, so if you want to study it a bit more, feel free to grab the tarball. You can also check out the source code from GitHub.

Furthermore, if you want to see this plugin in action, just use your smartphone to access the advent calendar. Indeed, our advent calendar is powered by Dancer and uses Dancer::Plugin::MobileDevice to change the layout dynamically, just as explained in this article.

Author

This article has been written by Alexis Sukrieh for the PerlDancer Advent Calendar 2010.

Copyright

Copyright (C) 2010 by Alexis Sukrieh <sukria@sukria.net>