;;; SUrflets' input fields ;;; Copyright 2002, 2003 Andreas Bernauer ;;; With additions from Eric Knauel (2003) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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?) (rows number?) (cols number?) (readonly symbol?) (attributes sxml-attribute?)) (let ((extra-attributes '())) (if (eq? readonly 'readonly) (set! extra-attributes (cons '(readonly) extra-attributes))) (if cols (set! extra-attributes (cons `(cols ,cols) extra-attributes))) (if rows (set! extra-attributes (cons `(rows ,rows) extra-attributes))) (make-input-field name "textarea" identity (make-input-field-attributes (and default-text) (cons extra-attributes (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 seperated in HTML, ;;; too. The TAG is the string that is displayed in the website, the ;;; VALUE is the value that is returned by input-field-value, if this ;;; option was selected. TAG is assumed to be unique by some functions ;;; (e.g. select and unselect) SELECTED? tells us, if this option is ;;; preselected. (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 tag 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) ; is tag an index? (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) ;; MEMBER/SRFI-1 returns the sublist that starts with the ;; searched element. (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))) (define (add-sel-if-option! sel-if sel-if-option) (let ((attributes (input-field-attributes sel-if))) (set-input-field-attributes-default! attributes (cons sel-if-option (input-field-attributes-default attributes))) (touch-input-field! sel-if))) (define (delete-sel-if-option! sel-if sel-if-option) (let* ((attributes (input-field-attributes sel-if)) (sel-if-options (input-field-attributes-default attributes))) (if (sel-if-option? sel-if-option) (set-input-field-attributes-default! attributes (delete sel-if-option sel-if-options)) (let ((tag sel-if-option)) (set-input-field-attributes-default! attributes (delete tag sel-if-options tag=sel-if-option?)))) (touch-input-field! sel-if))) ;; 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) maybe-further-attributes)) (define (make-annotated-select-input-field sel-if-options . maybe-further-attributes) (really-make-select-input-field sel-if-options maybe-further-attributes)) (define (really-make-select-input-field sel-if-options 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 attributes) (make-single-select-input-field name sel-if-options attributes)))))) ;; internal (define (make-multiple-select-input-field name sel-if-options attributes) (make-multi-input-field name "mult-select" sel-if-multiple-transformer (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 attributes) (make-input-field name "select" (lambda (tag) (cond ((find-sel-if-option-value tag sel-if-options) => identity) (else (error "no such option." tag)))) (make-input-field-attributes sel-if-options (sxml-attribute-attributes attributes)) make-sel-if-html-tree)) (define (sel-if-multiple-transformer input-field bindings) (let ((name (input-field-name input-field)) (sel-if-options (input-field-attributes-default (input-field-attributes input-field)))) (let* ((my-bindings (filter (lambda (binding) (equal? (car binding) name)) bindings)) (tags (map cdr my-bindings))) (filter-map (lambda (tag) (find-sel-if-option-value tag sel-if-options)) 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))))) ;; Image buttons cannot be simple buttons, as the browser does not ;; send their simple name, but the coordinates where the user clicked ;; into. Thanks to Eric Knauel for reporting this bug. (define (make-image-button image-source . maybe-further-attributes) (optionals maybe-further-attributes ((attributes sxml-attribute?)) (make-multi-input-field (generate-input-field-name "imgbtn") "image" image-button-transformer (make-input-field-attributes `(src ,image-source) (sxml-attribute-attributes attributes)) make-button-html-tree))) ;; The following two functions are from Eric Knauel's fix for the ;; image-button bug: (define (image-button-transformer image-button bindings) (let ((x (find-image-button-coordinate image-button bindings ".x")) (y (find-image-button-coordinate image-button bindings ".y"))) (let ((x-number (string->number x)) (y-number (string->number y))) (and x y (if (and x-number y-number) (cons x-number y-number) (error "Image button coordinates aren't numbers. " x y)))))) (define (find-image-button-coordinate image-button bindings suffix) (let* ((name (input-field-name image-button))) (cond ((assoc (string-append name suffix) bindings) => (lambda (pair) (cdr pair))) (else #f)))) ;;EOF