! Copyright (C) 2008 Slava Pestov ! See http://factorcode.org/license.txt for BSD license. USING: accessors namespaces kernel io math.parser assocs classes words classes.tuple arrays sequences splitting mirrors hashtables fry locals combinators continuations math calendar.format html html.elements xml.entities http.server.validators ; IN: http.server.components ! Renderer protocol GENERIC: render-summary* ( value renderer -- ) GENERIC: render-view* ( value renderer -- ) GENERIC: render-edit* ( value id renderer -- ) M: object render-summary* render-view* ; TUPLE: field type ; C: field M: field render-view* drop escape-string write ; M: field render-edit* > =type =name =value input/> ; TUPLE: hidden < field ; : hidden ( -- renderer ) T{ hidden f "hidden" } ; inline ! Component protocol SYMBOL: components TUPLE: component id required default renderer ; : component ( name -- component ) dup components get at [ ] [ "No such component: " prepend throw ] ?if ; GENERIC: init ( component -- component ) M: component init ; GENERIC: validate* ( value component -- result ) GENERIC: component-string ( value component -- string ) SYMBOL: values : value values get at ; : set-value values get set-at ; : blank-values H{ } clone values set ; : from-tuple values set ; : values-tuple values get mirror-object ; : render-view-or-summary ( component -- value renderer ) [ id>> value ] [ component-string ] [ renderer>> ] tri ; : render-view ( component -- ) render-view-or-summary render-view* ; : render-summary ( component -- ) render-view-or-summary render-summary* ; > ] [ renderer>> ] bi render-edit* ; : render-edit-error ( component -- ) [ id>> value ] keep [ [ value>> ] dip render-edit-string ] [ drop reason>> render-error ] 2bi ; : value-or-default ( component -- value ) [ id>> value ] [ default>> ] bi or ; : render-edit-value ( component -- ) [ value-or-default ] [ component-string ] [ render-edit-string ] tri ; PRIVATE> : render-edit ( component -- ) dup id>> value validation-error? [ render-edit-error ] [ render-edit-value ] if ; : validate ( value component -- result ) '[ , over empty? [ [ default>> [ v-default ] when* ] [ required>> [ v-required ] when ] bi ] [ validate* ] if ] with-validator ; : new-component ( id class renderer -- component ) swap new swap >>renderer swap >>id init ; inline ! String input fields TUPLE: string < component one-line min-length max-length ; : new-string ( id class -- component ) "text" new-component t >>one-line ; inline : ( id -- component ) string new-string ; M: string validate* [ one-line>> [ v-one-line ] when ] [ min-length>> [ v-min-length ] when* ] [ max-length>> [ v-max-length ] when* ] tri ; M: string component-string drop ; ! Username fields TUPLE: username < string ; M: username init 2 >>min-length 20 >>max-length ; : ( id -- component ) username new-string ; M: username validate* call-next-method v-one-word ; ! E-mail fields TUPLE: email < string ; : ( id -- component ) email new-string 5 >>min-length 60 >>max-length ; M: email validate* call-next-method dup empty? [ v-email ] unless ; ! URL fields TUPLE: url < string ; : ( id -- component ) url new-string 5 >>min-length 60 >>max-length ; M: url validate* call-next-method dup empty? [ v-url ] unless ; ! Don't send passwords back to the user TUPLE: password-renderer < field ; : password-renderer T{ password-renderer f "password" } ; : blank-password >r >r drop "" r> r> ; M: password-renderer render-edit* blank-password call-next-method ; ! Password fields TUPLE: password < string ; M: password init 6 >>min-length 60 >>max-length ; : ( id -- component ) password new-string password-renderer >>renderer ; M: password validate* call-next-method v-one-word ; ! Number fields TUPLE: number < string min-value max-value ; : ( id -- component ) number new-string ; M: number validate* [ v-number ] [ [ min-value>> [ v-min-value ] when* ] [ max-value>> [ v-max-value ] when* ] bi ] bi* ; M: number component-string drop dup [ number>string ] when ; ! Integer fields TUPLE: integer < number ; : ( id -- component ) integer new-string ; M: integer validate* call-next-method v-integer ; ! Simple captchas TUPLE: captcha < string ; : ( id -- component ) captcha new-string ; M: captcha validate* drop v-captcha ; ! Text areas TUPLE: text-renderer rows cols ; : new-text-renderer ( class -- renderer ) new 60 >>cols 20 >>rows ; : ( -- renderer ) text-renderer new-text-renderer ; M: text-renderer render-view* drop escape-string write ; M: text-renderer render-edit* ; TUPLE: text < string ; : new-text ( id class -- component ) new-string f >>one-line >>renderer ; : ( id -- component ) text new-text ; ! HTML text component TUPLE: html-text-renderer < text-renderer ; : ( -- renderer ) html-text-renderer new-text-renderer ; M: html-text-renderer render-view* drop escape-string write ; TUPLE: html-text < text ; : ( id -- component ) html-text new-text >>renderer ; ! Date component TUPLE: date < string ; : ( id -- component ) date new-string ; M: date component-string drop timestamp>string ; ! Link components GENERIC: link-title ( obj -- string ) GENERIC: link-href ( obj -- url ) SINGLETON: link-renderer M: link-renderer render-view* drop link-title escape-string write ; TUPLE: link < string ; : ( id -- component ) link new-string link-renderer >>renderer ; ! List components SYMBOL: +plain+ SYMBOL: +ordered+ SYMBOL: +unordered+ TUPLE: list-renderer component type ; C: list-renderer : render-plain-list ( seq component quot -- ) '[ , component>> renderer>> @ ] each ; inline : render-li-list ( seq component quot -- ) '[
  • @
  • ] render-plain-list ; inline : render-ordered-list ( seq quot component -- )
      render-li-list
    ; inline : render-unordered-list ( seq quot component -- )
      render-li-list
    ; inline : render-list ( value renderer quot -- ) over type>> { { +plain+ [ render-plain-list ] } { +ordered+ [ render-ordered-list ] } { +unordered+ [ render-unordered-list ] } } case ; inline M: list-renderer render-view* [ render-view* ] render-list ; M: list-renderer render-summary* [ render-summary* ] render-list ; TUPLE: list < component ; : ( id component type -- list ) list swap new-component ; M: list component-string drop ; ! Choice TUPLE: choice-renderer choices ; C: choice-renderer M: choice-renderer render-view* drop escape-string write ; : render-option ( text selected? -- ) ; : render-options ( options selected -- ) '[ dup , member? render-option ] each ; M: choice-renderer render-edit* ; TUPLE: choice < string ; : ( id choices -- component ) swap choice new-string swap >>renderer ; ! Menu TUPLE: menu-renderer choices size ; : ( choices -- renderer ) 5 menu-renderer boa ; M:: menu-renderer render-edit* ( value id renderer -- ) ; TUPLE: menu < string ; : ( id choices -- component ) swap menu new-string swap >>renderer ; ! Checkboxes TUPLE: checkbox-renderer label ; C: checkbox-renderer M: checkbox-renderer render-edit* label>> escape-string write ; TUPLE: checkbox < string ; : ( id label -- component ) checkbox swap new-component ;