chickadee » waffle

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:

Source Code

https://bitbucket.org/andyjpb/waffle

Author

Andy Bennett

Requirements

API

add-widget

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

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

load-widgets-from-directory dir extension #!optional prefixprocedure

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

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"))))

widget-rules

widget-rulesparameter

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.

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

Contents »