WAFFLE - Widgets and Forms For Lisp Enthusiasts
WAFFLE is a toolkit for building HTML and other XML based pages through composition of discrete, user definable, widgets. Widgets comprise markup specified in SXML as well as a set of attributes which are rendered into the widget.
WAFFLE handles the composition of multiple widgets of the same type containing HTML Form elements. If a widget is given a 'name' attribute then the value of this attribute propagates to child widgets and HTML Form elements. The values does not propagate to any other type of element. Name attributes in other types of element are ignored.
The value propagates by being prepended to the name attribute of the child widgets or form elements.
This allows multiple widgets containing form elements to be composed on the same page without their names clashing.
WAFFLE is based on ideas in the following papers and essays:
- add-widget widget-name definitionprocedure
Makes the widget described by definition available as widget-name.
(add-widget 'say-hello `((markup . (div "Hello " ,user)) (attributes . ((user #f)))))
- load-widget widget-name filenameprocedure
Loads a widget from the specified filename and makes it available as widget-name.
The files are similar to scheme source files and have markup and attribute sections thus:
(markup . `(*TOP* ,@(if user `(div (img (@ (class user-avatar) (src "/face.svg"))) (span ,user) (a (@ (href "/logout")) "Log out")) `(a (@ (href "/login")) "Log in")) )) (attributes . ( (user #f) ))
The cdr of markup should evaluate to some sxml. You can use R5RS scheme and the variables listed in attributes to generate it. When the widget is called, if an attribute is specified then it is bound to that value otherwise it is bound to the default value specified in the attributes list.
If the above code was placed in the user-menu.widget.scm file you could load it thus:
(use waffle) (load-widget 'user-menu "user-menu.widget.scm")
- load-widgets-from-directory dir extension #!optional prefixprocedure
Loads a bunch of widgets from the specified dir.
(use waffle) (load-widgets-from-directory "./widgets" ".widget.scm")
...will load the files *.widget.scm. They will be automatically assigned widget names based on their filename. x.widget.scm will be named as if it has been loaded thus (load-widget 'x "x.widget.scm").
- waffle-sxml->html sxmlprocedure
Render the widget tree in sxml down to HTML on (current-output-port).
(use waffle) (load-widgets-from-directory "./widgets" ".widget.scm") (waffle-sxml->html `(html (body (user-menu "Andy") (say-hello "Andy"))))
This is where the widgets end up after they've been added or loaded. If you want to use two or more distinct widget sets in the same process, you should parameterize this around your render pipeline.
waffle has knowledge of HTML Forms and makes it easy to compose widgets to make complex Forms.
If a widget is given a name attribute then its value will propagate down dynamically to any HTML Form elements that end up in the sub-tree.
For example, you might have some widgets that make up an invoice editor thus:
(add-widget 'invoice-line `((markup . (div (input (@ (name "line-item"))) (input (@ (name "cost"))))) (attributes . (())))) (add-widget 'invoice `((markup . (div (h1 "Invoice" ,invoice-id) ,@contents (input (@ (type "submit") (name "ok"))))) (attributes . ((invoice-id "0")))))
...and you might want to compose them thus:
`(invoice (@ (invoice-id "10000433")) (invoice-line (@ (name "line1"))) (invoice-line (@ (name "line2"))) (invoice-line (@ (name "line3"))))
The inputs in the DOM will end up with the following names:
line1/line-item line1/cost line2/line-item line2/cost line3/line-item line3/cost ok
This allows your rendering code to compose Form elements in a straightforward way and for the form handling code to work out the structure of the Form that was submitted.
I've deployed this code in production at Knodium and at https://registers.app/
You can find a talk I did about how we used it at Knodium here https://media.ccc.de/v/c116_lisp_-_2013-08-25_11:15_-_building_knodium_com_with_scheme_-_andy_bennett_-_1281#video
The microphone sound starts properly a few minutes in! This first part of the talk shows you the site and webapp we generated with waffle and then I get into some examples and code.
As well as the websites, I've also used it for generating both the text and HTML parts for eMails from the same waffle message template and a complementary pair of widget sets.
Copyright (C) 2012, Andy Bennett All rights reserved. LGPL-2.1 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
- 0.1, (2019/01/16) : I'm finally putting it into the CHICKEN coop! It's been available informally for ages but now I've done the tidying up work to be able to make it more widely available.