;;
;;
;; HTML form constructor.
;;
;; Copyright 2007-2013 Ivan Raikov and the Okinawa Institute of
;; Science and Technology.
;;
;;
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; A full copy of the GPL license can be found at
;; .
(module html-form
(html-form form-xsd)
(import scheme chicken data-structures )
(require-extension srfi-1 srfi-13 matchable data-structures )
(define nl (list->string (list #\newline)))
(define lookup-field
(lambda (k lst . rest)
(let-optionals rest ((default #f))
(let loop ((lst lst))
(if (null? lst) default
(let ((elm (car lst)))
(match elm
((s . _) (if (eq? s k) (cdr elm) (loop (cdr lst))))
(else (loop (cdr lst))))))))))
(define (single x) (and x (or (and (list? x) (car x)) x)))
(define (s+ . rest)
(apply string-append (map ->string rest)))
(define special-attrs
`(label rel optional hint label-class hint-class field-class))
(define (special-attr? x) (member x special-attrs))
(define (pid parent id)
(if parent (s+ parent "_" id) (->string id)))
(define (html-form x)
(and (list? x)
(case (car x)
((form-group) (group (cadr x) (cddr x)))
(else (apply field x)))))
(define (group group-name rest)
(let ((children (lookup-field 'children rest))
(label (lookup-field 'label rest)))
`(div (@ (id ,group-name))
(fieldset (@ (class repeat) )
(legend ,(or label group-name))
,(map (lambda (x) (list (html-form x) nl)) (or children (list)))))))
(define (field name default . rest)
(let ((hint (lookup-field 'hint rest))
(label (or (lookup-field 'label rest) name))
(rel (lookup-field 'rel rest))
(optional (lookup-field 'optional rest))
(field-class (or (lookup-field 'field-class rest) 'oneField))
(label-class (or (lookup-field 'label-class rest) 'preField))
(hint-class (or (lookup-field 'hint-class rest) 'inlineLabel)))
`(div (@ ,(if optional `(rel ,optional) `()))
(span (@ (class ,field-class))
(label (@ (for ,name) (class ,label-class)) ,label)
,(if hint `(p (@ (class ,hint-class)) ,hint) `())
,(widget name label rel default (filter (lambda (x) (not (special-attr? (car x)))) rest))))))
(define (widget name label rel default rest)
(or (and (pair? rest)
(filter-map
(lambda (prop)
(case (car prop)
((checkbox) (checkbox name label rel (or (and (pair? (cdr prop)) (cadr prop)) default)))
((textarea) (apply textarea (cons* name label rel default (cdr prop))))
((select) (apply selection (cons* name label rel (cdr prop))))
((button) (apply button (cons* name label rel default (cdr prop))))
((radio) (apply radio (cons* name label rel default (cdr prop))))
(else #f)))
rest))
(text name label rel default)))
(define (checkbox name label rel value . rest)
`((br) (input (@ (type checkbox) (name ,name) (value ,(string-append value ";"))
,(if rel `(rel ,rel) `()))) ,value))
(define (textarea name label rel value . rest)
(let ((rows (single (lookup-field 'rows rest)))
(cols (single (lookup-field 'cols rest)))
(value (if (list? value) value (list value))))
(if (not (and rows cols)) (error 'textarea "missing rows and cols attributes"))
`((textarea (@ (name ,name) (id ,name) (rows ,rows) (cols ,cols) (title ,label)
,(if rel `(rel ,rel) `())) ,nl
,(let ((irows (if (string? rows) (string->number rows) rows))
(icols (if (string? cols) (string->number cols) cols)))
(let rloop ((i irows) (lst value) (ax (list)))
(let cloop ((j icols) (lst lst) (ax ax))
(if (null? lst) (reverse ax)
(if (> j 0) (cloop (- j 1) (cdr lst) (cons " " (cons (car lst) ax)))
(if (> i 1) (rloop (- i 1) lst (cons nl ax))
(reverse (cons nl ax)))))))))
(p))))
(define (selection name label rel value . rest)
`(select (@ (name ,name) (id ,name) (title ,label) ,(if rel `(rel ,rel) `())
,(if (assoc 'multiple rest) `(multiple "yes") `()))
,(map (lambda (x) `((option (@ (value ,x) (label ,x)) ,x) ,nl)) value)))
(define (button name label rel value . rest)
(let ((onclick (lookup-field 'onclick rest)))
`(input (@ (type button) (name ,name) (id ,name) (value ,value) (title ,label)
,(if rel `(rel ,rel) `())
,(if onclick `(script (onclick ,onclick)) `())))))
(define (radio name label rel value . rest)
(map (lambda (x)
(let-values (((rname ropts)
(match x ((name . opts) (values (->string name) opts))
(else (values (->string x) (list))))))
(let ((rel (lookup-field 'rel ropts)))
`( ,rname ": "
(input (@ (type radio) (name ,name) (id ,(pid name rname))
(value ,rname) (title ,rname)
,(if rel `(rel ,rel) `())))))))
rest))
(define (text name label rel value . rest)
`(input (@ (name ,name) (id ,name) (title ,label) (value ,value)
,(if (string? value) `(type "text") `())
,(if rel `(rel ,rel) `()))))
(define (widget-xsd name label rel default rest)
(or (and (pair? rest)
(filter-map
(lambda (prop)
(case (car prop)
((textarea) `((xs:annotation
(xs:documentation
(@ (source "ospi.label")
(xml:lang "en"))
,label))
(xs:simpleType
(xs:restriction
(@ (base "xs:string"))
(xs:maxLength (@ (value "5000")))
))))
((checkbox) `((xs:annotation
(xs:documentation
(@ (source "ospi.label")
(xml:lang "en"))
,label))
(xs:simpleType
(xs:restriction
(@ (base "xs:boolean"))
(xs:enumeration (@ (value "true")))
))))
((radio) (let ((vals (map ->string (cadr prop))))
`((xs:annotation
(xs:documentation
(@ (source "ospi.label")
(xml:lang "en"))
,label))
(xs:simpleType
(xs:restriction
(@ (base "xs:integer"))
. ,(map (lambda (x) `(xs:enumeration (@ (value ,x))))
(list-tabulate (length vals) (lambda (i) i)))
))
)))
((select) (let ((vals (map ->string (cadr prop))))
`((xs:annotation
(xs:documentation
(@ (source "ospi.label")
(xml:lang "en"))
,label))
(xs:simpleType
(xs:restriction
(@ (base "xs:string"))
. ,(map (lambda (x) `(xs:enumeration (@ (value ,x)))) vals)
)))
))
(else #f)))
rest))
`(xs:simpleType
(xs:restriction
(@ (base "xs:string"))
(xs:maxLength (@ (value "100")))
))
))
(define (field-xsd name default . rest)
(let ((hint (lookup-field 'hint rest))
(label (or (lookup-field 'label rest) name))
(rel (lookup-field 'rel rest))
(optional (lookup-field 'optional rest))
(multiple (lookup-field 'multiple rest))
(field-class (or (lookup-field 'field-class rest) 'oneField))
(label-class (or (lookup-field 'label-class rest) 'preField))
(hint-class (or (lookup-field 'hint-class rest) 'inlineLabel)))
`(xs:element
(@ (name ,name)
,(if optional '(minOccurs "0") (if multiple '(minOccurs "1") '(minOccurs "unbounded"))))
,@(widget-xsd name label rel default rest))
))
(define (form-element-xsd x)
(case (car x)
((form-group) (group-xsd (cadr x) (cddr x)))
(else (apply field-xsd x))
))
(define (group-xsd group-name rest)
(let ((children (lookup-field 'children rest))
(label (lookup-field 'label rest)))
`(xs:element
(@ (name ,group-name))
(xs:complexType
(xs:sequence . ,(map form-element-xsd children))
))
))
(define (form-xsd title xs)
(let recur ((xs xs) (ax '()))
(if (null? xs)
`(xs:schema
(@ (xmlns:xs "http://www.w3.org/2001/XMLSchema")
(elementFormDefault "qualified"))
(xs:element (@ (name ,title))
(xs:complexType
(xs:sequence . ,(reverse ax)))
)
)
(let* ((x (car xs))
(ax1 (cons (form-element-xsd x) ax)))
(recur (cdr xs) ax1)
))
))
)