First Stab at Widgets

July 1, 2010

Michael Snoyman

Having finished a bunch of minor maintenance on Yesod (more consistent API names, that kind of stuff), I've begun to focus in on the big new feature brewing for Yesod 0.4.0: widgets. I'm really not a big fan of designing awesome technology out in the middle of nowhere; instead, I like to think of real-world problems I want to solve, and then build the tools around it. So let's start with a motivating example: forms.

We all know how annoying it is to deal with web forms. Back in the good old days of raw CGI, you'd have to write your form HTML, write parse code separately, probably write another version of the HTML to create sticky fields, etc. We've come much farther than that, especially with libraries like formlet. However, there's still one piece of the puzzle which is tedious to set up: the Javascript and CSS.

For example, I want to have a day field. Of course, like any sane person, I use YYYY-MM-DD format. But I live in a country where the standard format is DD/MM/YYYY, and many of the user's of my sites live in a country where the standard is MM/DD/YYYY. So ideally I'd like to use Javascript to both do client-side checking and provide a nice date picker. I also would like error messages to be displayed in some shade of red.

So with Yesod 0.3.0 I would need to include the necessary Javascrip libraries in my template, include the datepicker CSS file in the template, write a local Javascript file to initiate the datepicker and the validation code, and write a local stylesheet for styling the error messages. Now that's tedious.

Instead, we're going to create composable widgets. They keep track of not just HTML to be placed in the body of a document, but also scripts and stylesheets to load, extra HTML to throw in the head, CSS for <style> tags, and the title of the page. It also should supply me with unique identifiers to use.

I've implemented this as a monad transformer stack; you can see the implementation on Github. The Yesod.Widget module also contains a bunch of functions for creating widgets. For example, addStylesheet adds a stylesheet, addBody adds some content to the body of the page, etc. There's also a cool function applyLayoutW which is able to display a widget with the default page layout.

Since a widget is just a monad stack, it composes very easily. There's also a Monoid instance to make life simpler. The trickiest part of it right now is when you want to wrap the body HTML code up somehow (for example, sticking everything in a div id=wrapper tag). For this, check out the wrapWidget and extractBody functions in the Yesod.Widget module.

Forms

The next big step is converting Yesod.Form over to use widgets. The original inspiration for this module is formlets; however, I'm veering farther and farther from the API, as my goals are quite different than the formlets API. As an example, formlets does not support inline error messages; all validation messages must be shown together. I don't find that an acceptable tradeoff.

The API is in flux right now, so I don't want to spend too much time talking about it. However, here's a high-level approach: there is a GForm monad which takes an "xml" type parameter. There are then two type synonyms: Form sets that xml type parameter to be a widget. This is what you'll usually want to embed in a page.

The other type synonym is FormField, which instead sets that xml type to FieldInfo. A FieldInfo contains information on the label, error messages, and various other things in a field. Of course, you can't display this directly; there will be various helper functions to convert a FieldInfo into a widget. The reason to split this up is so users have more control of how to layout their forms. For example, there's a function currently to display a FieldInfo as a row in a table; we could also provide a list and paragraph version.

And I still intend to have all this tie in with some typeclass for automatic creation of forms for PersistEntity instances. Just imagine: you could declare your entity structure once, and Yesod will create a database schema, generate SQL code, and create HTML forms for you automatically. I have some examples of this actually working with various versions of Yesod, and most likely the release of Yesod 0.4.0 will be accompanied by a screencast showing how to use all these features together. Most likely it will be the inevitable blog example ;).