Sliders for Yesod

October 10, 2012

Michael Snoyman recently had a blog post about composability in the Yesod web framework for Haskell, where he responded to comments about it being difficult to build reusable components for Yesod by, well, building one. I’m pretty much a newbie with Yesod, but the composability aspect of things had never looked too difficult to me, so I thought I’d also have a go at the same kind of exercise.

I decided to implement a slider form field, using one of the nicest jQuery-based slider widgets I’ve found. All the code for this example is available as a Gist. I’ll follow the usual convention here of putting more or less all of the code in the blog post too.

Sliders

The idea here is to hide some of the details of embedding components into web pages so that a user can write a few lines of Haskell instead of a mess of HTML, CSS, JavaScript plus whatever server-side code is needed to talk to all of that.

We’re going to put everything we need into a single Haskell module called JqSlider (file JqSlider.hs).

Setup

We re-export a couple of things (YesodJquery and Default) for convenience.

We have the usual pile of imports:

And now we get into things properly. The sliders in the plugin we’re using support both single value sliders and range sliders, so we define a type synonym for the result of a slider field:

This isn’t the prettiest way to do things (we should probably have separate field types for single value sliders and range sliders) but it’ll do for now.

We need to be able to access the CSS and JavaScript files that implement the sliders so, as is done in Yesod.Jquery, we implement a type class to record where these things are. Any application wanting to make use of this component should declare its foundation data type to be an instance of YesodJqSlider:

Settings

Much of the work we have to do turns out to be marshalling values between Haskell and JavaScript for setting the many options of the sliders. A simple case is a way of defining the locale-dependent formatting of numeric values:

Here, the marshalling is done by Aeson’s default derivation of a ToJSON instance:

The overall slider settings are a bit more complicated, and it’s nice to have a more “Haskelly” view of things, which means we need to write a custom ToJSON instance:

This feels a little clunky, but it’s easy to use and the implementation details are hidden away.

A slider field

Finally, we get to the definition of a slider field. This is essentially a double field with a prettier user interface (apart from the possibility of having two return values, for a range slider). We parse the return value appropriately (it should either be a single real number, or a pair of real numbers separated by a semicolon).

To render the field, we just produce a HTML INPUT element of the appropriate type and add in a bit of JavaScript to transform the input into a slider at document load time.

(The ugliness in showVal with these repeated calls to either is another indication that we should really have separate jqSliderField and jqRangeSliderField field types.)

A usage example

So, how do we use it?

File layout

One slight ugliness we need to deal with up front is file layout. We need access to the CSS and JavaScript files for the slider plugin (which we can point to using the methods in our YesodJqSlider instance), but we also need access to the images used for theming the sliders. Unfortunately, as is pretty common, the paths to the images are hard-coded in the CSS files. For the moment, that means that if the CSS is in .../css/jquery.slider.min.css, then the image files must be in .../img. I can imagine some sorts of solution to this problem using Yesod’s templating system, but that would make it tricky to integrate directly with the original plugin, since we would have to transform the original CSS.

Anyway, modulo this minor problem, we can now use slider fields directly from Haskell with little or no pain (file jqslider-example.hs).

Setup

The usual imports are followed by our foundation data type definition. Here, we’re going to need to serve up some static resources (CSS, JavaScript, image files) as well as the pages defined by our handlers, so we need to include a static subsite.

Routes are simple: just a home page with some sliders, plus the static subsite.

Handlers

The handlers are also pretty simple: generate a form using the sliderForm function, then either just lay it out or process the results (we have three sliders in the form and just add a message with their values).

The page layout (in jqslider-example.hamlet) is straightforward:

Form definition

Constructing the sliders is basically a matter of choosing settings. Note that the result value for a slider form field is a JqSliderVal, since we need to represent both single values and ranges.

Type class instances

We need to set up our static site and implement some type class instances, and then we’re ready to go.

Off we go…

The main program only has one wrinkle, in that we need to set up the static site in our foundation object.

And here’s what it looks like:

Screenshot

Conclusions

I think that wasn’t too bad. I like the way that it’s possible to bundle up the different parts of a component like this, and then to hide the complexity from the user – producing a form with sliders looks pretty much the same as the basic form examples in the Yesod book.