sunet/scheme/httpd/surflets/surflet-input-fields.scm

472 lines
17 KiB
Scheme

;;; SUrflets' input fields
;;; Copyright 2002, 2003 Andreas Bernauer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions to create simple input fields
;; The interface for input-fields does not prescribe what the type of
;; attributes has to be. We choose a record here.
(define-record-type input-field-attributes :input-field-attributes
(make-input-field-attributes default others)
input-field-attributes?
(default input-field-attributes-default set-input-field-attributes-default!)
(others input-field-attributes-others set-input-field-attributes-others!))
;; A simple input-field is a prototype for other input-fields.
;; REPORTED-TYPE is the type of the input-field in HTML, TYPE the
;; internal referenced type and TRANSFORMER the function that
;; translates the HTTP-string of the request into a scheme value.
(define (simple-input-field-maker reported-type type default-pred transformer)
(lambda maybe-further-attributes
(let ((name (generate-input-field-name type)))
(optionals maybe-further-attributes
((default default-pred)
(attributes sxml-attribute?))
(make-input-field name type
transformer
(make-input-field-attributes
(and default `(value ,default))
(sxml-attribute-attributes attributes))
(simple-input-field-maker-html-tree-maker
reported-type))))))
(define (simple-input-field-maker-html-tree-maker reported-type)
(lambda (input-field)
(let ((attributes (input-field-attributes input-field)))
`(input (@ (type ,reported-type)
(name ,(input-field-name input-field))
,(input-field-attributes-default attributes)
,(input-field-attributes-others attributes))))))
(define (make-simple-input-field-default-setter default-pred? wrap?)
(lambda (input-field value)
(if (default-pred? value)
(set-input-field-attributes-default!
(input-field-attributes input-field)
(if wrap? `(value ,value) value))
(error "Default value must be a number or a string or a symbol."
value))
(touch-input-field! input-field)))
(define (string-or-symbol? thing)
(or (string? thing) (symbol? thing)))
(define simple-default? string-or-symbol?)
(define set-simple-input-field-default!
(make-simple-input-field-default-setter simple-default? #t))
;;;;;;;;;;;;;;;;;;;;
;;; Text input field
(define make-text-input-field
(simple-input-field-maker "text" "text"
simple-default? identity))
(define set-text-input-field-value! set-simple-input-field-default!)
;;;;;;;;;;;;;;;;;;;;;;
;;; Number input field
(define (number-input-field-default? value)
(or (number? value)
(simple-default? value)))
(define (number-input-field-transformer string)
(or (string->number string)
(error "wrong type")))
(define make-number-input-field
(simple-input-field-maker "text" "number"
number-input-field-default?
number-input-field-transformer))
(define set-number-input-field-value!
(make-simple-input-field-default-setter number-input-field-default? #t))
;;;;;;;;;;;;;;;;;;;;;;
;;; hidden input-field
;; Little workaraound, as a hidden input-field needs a value. This
;; value is propagated in the slot "default value".
(define make-hidden-input-field
(let ((hidden-input-field-generator
(simple-input-field-maker "hidden" "text"
simple-default? identity)))
(lambda (value . maybe-further-attributes)
(apply hidden-input-field-generator
(cons value maybe-further-attributes)))))
(define set-hidden-input-field-value!
set-simple-input-field-default!)
;;;;;;;;;;;;;;;;;;;;;;;;
;;; Password input field
(define make-password-input-field
(simple-input-field-maker "password" "password"
simple-default? identity))
(define set-password-input-field-value!
set-simple-input-field-default!)
;;; That's it for simple input fields.
;;;;;;;;;;;;;;;;;;;;;;;;
;;; Textarea input field
(define (make-textarea-input-field . maybe-further-attributes)
(let ((name (generate-input-field-name "textarea")))
(optionals maybe-further-attributes
((default-text simple-default?)
(attributes sxml-attribute?))
(make-input-field
name "textarea"
identity
(make-input-field-attributes
(and default-text)
(sxml-attribute-attributes attributes))
make-textarea-input-field-html-tree))))
(define (make-textarea-input-field-html-tree input-field)
(let ((attributes (input-field-attributes input-field)))
`(textarea (@ (type "textarea")
(name ,(input-field-name input-field))
,(input-field-attributes-others attributes))
,(input-field-attributes-default attributes))))
(define set-textarea-input-field-value!
(let ((textarea-default-setter!
(make-simple-input-field-default-setter simple-default? #f)))
(lambda (textarea-input-field value)
(textarea-default-setter! textarea-input-field value))))
;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Selection input field
;; sel-if == select-input-field
;(make-select-input-fields '("this" "that" "those") '(@ ((id "sushi"))))
;(make-select-input-fields '("this" ("that" '(@ (selected))) "those"))
;; dropdown: (size 1)
;;; A selection input field shows a list of options that can be
;;; selected. For this purpose, we introduce an sel-if-option record,
;;; that contains all the information for each option. This is
;;; justified by the fact, that the options list is in HTML seperated,
;;; too.
(define-record-type sel-if-option :sel-if-option
(really-make-sel-if-option tag value selected? attributes)
sel-if-option?
(tag sel-if-option-tag)
(value sel-if-option-value)
(selected? sel-if-option-selected? set-sel-if-option-selected?!)
(attributes sel-if-option-attributes set-sel-if-option-attributes!))
(define (make-sel-if-option tag value selected? attributes)
(if (string? tag)
(really-make-sel-if-option tag value selected?
(sxml-attribute-attributes attributes))
(error "Select-input-field-option's tag must be a string." tag)))
;; Constructor for valued select input-field option.
(define (make-annotated-sel-if-option tag value . maybe-attributes)
(optionals maybe-attributes
((selected? boolean?)
(attributes sxml-attribute?))
(make-sel-if-option tag value selected? attributes)))
;; Constructor for a simple select input-field option (not annotated).
(define (make-simple-sel-if-option tag . maybe-attributes)
(optionals maybe-attributes
((selected? boolean?)
(attributes sxml-attribute?))
(make-sel-if-option tag 'ignored selected? attributes)))
(define-record-discloser :sel-if-option
(lambda (sel-if-option)
(list 'select-input-field-option
(sel-if-option-tag sel-if-option)
(sel-if-option-value sel-if-option)
(sel-if-option-selected? sel-if-option)
(sel-if-option-attributes sel-if-option)
)))
;; Selecting / Unselecting of an option in an select input-field,
;; chosen by tag.
(define (select-sel-if-option! tag sel-if)
(set-select-input-field-option-selected?! tag sel-if #t))
(define (unselect-sel-if-option! tag sel-if)
(set-select-input-field-option-selected?! tag sel-if #f))
(define (set-select-input-field-option-selected?! tag sel-if selected?)
(let ((options (input-field-attributes-default
(input-field-attributes sel-if))))
(if (number? tag)
(set-sel-if-option-selected?! (list-ref options tag)
selected?)
(let lp ((options options))
(if (null? options)
(error "No such option" tag sel-if)
(if (tag=sel-if-option? tag (car options))
(set-sel-if-option-selected?! (car options) selected?)
(lp (cdr options))))))
(touch-input-field! sel-if)))
;; Find sel-if-option in a list by its tag.
(define (tag=sel-if-option? tag sel-if-option)
(string=? tag (sel-if-option-tag sel-if-option)))
(define (find-sel-if-option tag sel-if-options)
(cond ((member/srfi-1 tag sel-if-options tag=sel-if-option?)
=> car)
(else #f)))
(define (find-sel-if-option-value tag sel-if-options)
(cond ((find-sel-if-option tag sel-if-options)
=> sel-if-option-value)
(else #f)))
;; To be compatible with previous versions of MAKE-SELECT-INPUT-FIELD,
;; we accept also a simple list as an option-list. New programs should
;; use sel-if-options-list (easily createable with
;; (map make-simple-sel-if-option option-list))
(define (tolerate-old-sel-if-options sel-if-options)
(if (and (list? sel-if-options)
(every sel-if-option? sel-if-options))
sel-if-options
(map make-simple-sel-if-option sel-if-options)))
(define (make-select-input-field sel-if-options . maybe-further-attributes)
(really-make-select-input-field (tolerate-old-sel-if-options sel-if-options)
#f
maybe-further-attributes))
(define (make-annotated-select-input-field sel-if-options .
maybe-further-attributes)
(really-make-select-input-field sel-if-options #t maybe-further-attributes))
(define (really-make-select-input-field sel-if-options annotated?
maybe-further-attributes)
(let ((real-sel-if-options (tolerate-old-sel-if-options sel-if-options)))
(optionals maybe-further-attributes
((multiple? boolean?)
(attributes sxml-attribute?))
(let ((name (generate-input-field-name "select")))
(if multiple?
(make-multiple-select-input-field name sel-if-options
annotated? attributes)
(make-single-select-input-field name sel-if-options
annotated? attributes))))))
;; internal
(define (make-multiple-select-input-field name sel-if-options
annotated? attributes)
(make-multi-input-field name "mult-select"
(make-sel-if-multiple-transformer name sel-if-options
annotated?)
(make-input-field-attributes
sel-if-options
(list '(multiple)
(sxml-attribute-attributes attributes)))
make-sel-if-html-tree))
;; internal
(define (make-single-select-input-field name sel-if-options
annotated? attributes)
(make-input-field name "select"
(if annotated?
(lambda (tag)
(cond ((find-sel-if-option-value tag sel-if-options)
=> identity)
(else (error "no such option." tag))))
identity)
(make-input-field-attributes
sel-if-options
(sxml-attribute-attributes attributes))
make-sel-if-html-tree))
(define (make-sel-if-multiple-transformer name sel-if-options annotated?)
(lambda (bindings)
(let ((tags (map cdr
(filter (lambda (binding)
(equal? (car binding) name))
bindings))))
(filter-map (if annotated?
(lambda (tag)
(find-sel-if-option-value tag sel-if-options))
(lambda (tag)
(if (find-sel-if-option tag sel-if-options)
tag
#f)))
tags))))
(define (make-sel-if-html-tree sel-if)
(let ((attributes (input-field-attributes sel-if)))
`(select (@ (name ,(input-field-name sel-if))
,(input-field-attributes-others attributes))
#\newline
,@(make-sel-if-options-html-tree
(input-field-attributes-default attributes)))))
(define (make-sel-if-options-html-tree sel-if-options)
(map (lambda (sel-if-option)
`(option (@ ,(and (sel-if-option-selected? sel-if-option) '(selected))
,(sel-if-option-attributes sel-if-option))
,(sel-if-option-tag sel-if-option)))
sel-if-options))
;;;;;;;;;;;;;;;;;;;;;;
;;; radio input-fields
;; Because grouped radio input-fields must use the same name, we
;; cannot just return one radio input-field object, but we must
;; generate several ones with the same name.
(define (make-radio-input-field-group)
(let ((name (generate-input-field-name "radio")))
(lambda (value-string . maybe-further-attributes)
(optionals maybe-further-attributes
((checked? boolean?)
(attributes sxml-attribute?))
(make-input-field name "radio"
identity
(make-input-field-attributes
(if checked? '(checked) #f)
(list `(value ,value-string)
(sxml-attribute-attributes attributes)))
radio-input-field-html-tree-maker)))))
(define (make-annotated-radio-input-field-group)
(let* ((name (generate-input-field-name "radio"))
(value-table (make-integer-table))
(transformer (make-radio-input-field-transformer value-table)))
(lambda (value . maybe-further-attributes)
(optionals maybe-further-attributes
((checked? boolean?)
(attributes sxml-attribute?))
(let ((number (generate-unique-number)))
(table-set! value-table number value)
(make-input-field name "radio"
transformer
(make-input-field-attributes
(if checked? '(checked) #f)
(list`(value ,(number->string number))
(sxml-attribute-attributes attributes)))
radio-input-field-html-tree-maker))))))
(define (make-radio-input-fields values . maybe-further-attributes)
(optionals maybe-further-attributes
((attributes sxml-attribute?))
(let ((radio-gen (make-annotated-radio-input-field-group)))
(map (lambda (value)
(if attributes
(radio-gen value attributes)
(radio-gen value)))
values))))
(define (make-radio-input-field-transformer value-table)
(lambda (tag)
(cond
((string->number tag) =>
(lambda (number)
(let ((value (table-ref value-table number)))
(if value
value
(error "Unknown tag number for radio input-field" tag)))))
(else
(error "Unknown tag number for radio input-field" tag)))))
(define (radio-input-field-html-tree-maker radio-input-field)
(let* ((attributes (input-field-attributes radio-input-field)))
`(input (@ ((type "radio")
(name ,(input-field-name radio-input-field))
,(input-field-attributes-default attributes)
,(input-field-attributes-others attributes))))))
(define (set-input-field-checked?! input-field checked?)
(let ((attributes (input-field-attributes input-field)))
(set-input-field-attributes-default!
attributes
(if checked? '(checked) #f))
(touch-input-field! input-field)))
(define set-radio-input-field-checked?! set-input-field-checked?!)
(define (check-radio-input-field! radio)
(set-radio-input-field-checked?! radio #t))
(define (uncheck-radio-input-field! radio)
(set-radio-input-field-checked?! radio #f))
;;;;;;;;;;;;;;;;;;;;;;;;
;;; checkbox input-field
(define (make-checkbox-input-field . maybe-further-attributes)
(really-make-checkbox-input-field #t
checkbox-transformer
maybe-further-attributes))
(define (make-annotated-checkbox-input-field value . maybe-further-attributes)
(really-make-checkbox-input-field value
(make-checkbox-transformer value)
maybe-further-attributes))
(define (really-make-checkbox-input-field value transformer attributes)
(let ((name (generate-input-field-name "checkbox")))
(optionals attributes
((checked? boolean?)
(attributes sxml-attribute?))
(make-input-field name "checkbox"
transformer
(make-input-field-attributes
(if checked? '(checked) #f)
(sxml-attribute-attributes attributes))
checkbox-input-field-html-tree-maker))))
(define (make-checkbox-transformer value)
(lambda (tag)
(if (string=? tag "on")
value
#f)))
(define checkbox-transformer
(make-checkbox-transformer #t))
(define (checkbox-input-field-html-tree-maker cb-if)
(let ((attributes (input-field-attributes cb-if)))
`(input (@ ((type "checkbox")
(name ,(input-field-name cb-if))
,(input-field-attributes-default attributes)
,(input-field-attributes-others attributes))))))
(define set-checkbox-input-field-checked?! set-input-field-checked?!)
(define (check-checkbox-input-field! checkbox)
(set-checkbox-input-field-checked?! checkbox #t))
(define (uncheck-checkbox-input-field! checkbox)
(set-checkbox-input-field-checked?! checkbox #f))
;;;;;;;;;;;;;;;;;;;;;;
;; button input-fields
(define (make-button type name button-caption attributes)
(make-input-field name type
identity
(make-input-field-attributes
(and button-caption `(value ,button-caption))
(sxml-attribute-attributes attributes))
make-button-html-tree))
(define (make-button-html-tree button-input-field)
(let ((attributes (input-field-attributes button-input-field)))
`(input (@ (type ,(input-field-type button-input-field))
(name ,(input-field-name button-input-field))
,(input-field-attributes-default attributes)
,(input-field-attributes-others attributes)))))
(define (make-submit-button . maybe-further-attributes)
(optionals maybe-further-attributes
((button-caption string?)
(attributes sxml-attribute?))
(make-button "submit" (generate-input-field-name "submit")
button-caption attributes)))
(define (make-reset-button . maybe-further-attributes)
(optionals maybe-further-attributes
((button-caption string?)
(attributes sxml-attribute?))
(make-button "reset" (generate-input-field-name "reset")
button-caption attributes)))
(define (make-image-button image-source . maybe-further-attributes)
(optionals maybe-further-attributes
((attributes sxml-attribute?))
(make-button "image" (generate-input-field-name "imgbtn")
#f `(@ (src ,image-source)
,@(sxml-attribute-attributes attributes)))))
;;EOF