Outdated egg!
This is an egg for CHICKEN 4, the unsupported old release. You're almost certainly looking for the CHICKEN 5 version of this egg, if it exists.
If it does not exist, there may be equivalent functionality provided by another egg; have a look at the egg index. Otherwise, please consider porting this egg to the current version of CHICKEN.
waffle
Description
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:
- http://www.snell-pym.org.uk/archives/2006/12/17/the-implementation-of-web-applications
- http://www.snell-pym.org.uk/archives/2007/05/18/a-design-for-a-scheme-web-application-framework/
- http://www.snell-pym.org.uk/archives/2007/06/17/another-thing-i-hate-about-web-application-frameworks/
- http://pagesperso-systeme.lip6.fr/Christian.Queinnec/PDF/www.pdf
Source Code
https://bitbucket.org/andyjpb/waffle
Author
Requirements
API
add-widget
[procedure] (add-widget widget-name definition)Makes the widget described by definition available as widget-name.
(add-widget 'say-hello `((markup . (div "Hello " ,user)) (attributes . ((user #f)))))
load-widget
[procedure] (load-widget widget-name filename)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
[procedure] (load-widgets-from-directory dir extension #!optional prefix)Loads a bunch of widgets from the specified dir.
For example,
(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
[procedure] (waffle-sxml->html sxml)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"))))
widget-rules
[parameter] widget-rulesThis 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.
HTML Forms and the name attribute.
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.
Tutorial Video
I've deployed this code in production at Knodium and at https://registers.app/
Here's a video that show's the Knodium interface that was built entirely with waffle https://www.youtube.com/watch?v=gOPuWi-dbQg
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.
License
Copyright (C) 2012, Andy Bennett All rights reserved. LGPL-2.1 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
Version History
- 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.