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 use
s 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>