New validation message concept, t:button tag, fixes

db4
Slava Pestov 2008-05-02 21:18:49 -05:00
parent 42dc4bdc24
commit 117f6dd804
27 changed files with 151 additions and 163 deletions

View File

@ -225,13 +225,13 @@ M: html-stream stream-nl ( stream -- )
: vertical-layout ( list -- )
#! Given a list of HTML components, arrange them vertically.
<table>
<table>
[ <tr> <td> call </td> </tr> ] each
</table> ;
: horizontal-layout ( list -- )
#! Given a list of HTML components, arrange them horizontally.
<table>
<table>
<tr "top" =valign tr> [ <td> call </td> ] each </tr>
</table> ;
@ -246,8 +246,8 @@ M: html-stream stream-nl ( stream -- )
: simple-page ( title quot -- )
#! Call the quotation, with all output going to the
#! body of an html page with the given title.
<html>
<head> <title> swap write </title> </head>
<html>
<head> <title> swap write </title> </head>
<body> call </body>
</html> ;
@ -255,10 +255,13 @@ M: html-stream stream-nl ( stream -- )
#! Call the quotation, with all output going to the
#! body of an html page with the given title. stylesheet-quot
#! is called to generate the required stylesheet.
<html>
<head>
<title> rot write </title>
swap call
</head>
<html>
<head>
<title> rot write </title>
swap call
</head>
<body> call </body>
</html> ;
: render-error ( message -- )
<span "error" =class span> escape-string write </span> ;

View File

@ -30,6 +30,7 @@ IN: http.tests
[ H{ { "a" { "b" "c" } } } ] [ "a=b&a=c" query>assoc ] unit-test
[ "a=3" ] [ { { "a" 3 } } assoc>query ] unit-test
: lf>crlf "\n" split "\r\n" join ;
STRING: read-request-test-1

View File

@ -143,7 +143,7 @@ IN: http
: assoc>query ( hash -- str )
[
{
{ [ dup number? ] [ number>string ] }
{ [ dup number? ] [ number>string 1array ] }
{ [ dup string? ] [ 1array ] }
{ [ dup sequence? ] [ ] }
} cond

View File

@ -2,13 +2,20 @@
! See http://factorcode.org/license.txt for BSD license.
USING: accessors sequences kernel assocs combinators
http.server http.server.validators http hashtables namespaces
fry continuations locals ;
fry continuations locals boxes xml.entities html.elements io ;
IN: http.server.actions
SYMBOL: +path+
SYMBOL: params
SYMBOL: validation-message
: render-validation-message ( -- )
validation-message get value>> [
<span "error" =class span>
escape-string write
</span>
] when* ;
TUPLE: action init display submit get-params post-params ;
: <action>
@ -37,11 +44,16 @@ TUPLE: action init display submit get-params post-params ;
: validation-failed ( -- * )
action get display>> call exit-with ;
: validation-failed-with ( string -- * )
validation-message get >box
validation-failed ;
M: action call-responder* ( path action -- response )
'[
, [ CHAR: / = ] right-trim empty? [
, action set
request get
<box> validation-message set
[ request-params params set ]
[
method>> {

View File

@ -2,8 +2,6 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<t:style t:include="resource:extra/http/server/auth/admin/admin.css" />
<div class="navbar">
<t:a t:href="$user-admin">List Users</t:a>
| <t:a t:href="$user-admin/new">Add User</t:a>
@ -12,9 +10,7 @@
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if>
<t:form t:action="$login/logout" t:flow="begin" class="inline">
| <button type="submit" class="link-button link">Logout</button>
</t:form>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
</div>
<h1><t:write-title /></h1>

View File

@ -4,9 +4,7 @@
<t:title>Edit User</t:title>
<t:form t:action="$user-admin/edit">
<t:edit t:component="username" />
<t:form t:action="$user-admin/edit" t:for="username">
<table>
@ -49,17 +47,10 @@
<p>
<button type="submit" class="link-button link">Update</button>
<t:if t:var="http.server.auth.login:password-mismatch?">
<t:error>passwords do not match</t:error>
</t:if>
<t:validation-message />
</p>
</t:form>
<t:form t:action="$user-admin/delete">
<t:edit t:component="username" />
<button type="submit" class="link-button link">Delete</button>
</t:form>
<t:button t:action="$user-admin/delete" t:for="username" class="link-button link">Delete</t:button>
</t:chloe>

View File

@ -42,14 +42,7 @@
<p>
<button type="submit" class="link-button link">Create</button>
<t:if t:var="http.server.auth.login:user-exists?">
<t:error>username taken</t:error>
</t:if>
<t:if t:var="http.server.auth.login:password-mismatch?">
<t:error>passwords do not match</t:error>
</t:if>
<t:validation-message />
</p>
</t:form>

View File

@ -62,14 +62,7 @@
<p>
<input type="submit" value="Update" />
<t:if t:var="http.server.auth.login:login-failed?">
<t:error>invalid password</t:error>
</t:if>
<t:if t:var="http.server.auth.login:password-mismatch?">
<t:error>passwords do not match</t:error>
</t:if>
<t:validation-message />
</p>
</t:form>

View File

@ -30,8 +30,6 @@ http.server.validators ;
IN: http.server.auth.login
QUALIFIED: smtp
SYMBOL: login-failed?
TUPLE: login < dispatcher users checksum ;
: users ( -- provider )
@ -82,6 +80,8 @@ M: user-saver dispose
username>> set-uid
"$login" end-flow ;
: login-failed "invalid username or password" validation-failed-with ;
:: <login-action> ( -- action )
[let | form [ <login-form> ] |
<action>
@ -94,12 +94,8 @@ M: user-saver dispose
form validate-form
"password" value "username" value check-login [
successful-login
] [
login-failed? on
validation-failed
] if*
"password" value "username" value check-login
[ successful-login ] [ login-failed ] if*
] >>submit
] ;
@ -121,14 +117,13 @@ M: user-saver dispose
"email" <email> add-field
"captcha" <captcha> add-field ;
SYMBOL: password-mismatch?
SYMBOL: user-exists?
: password-mismatch "passwords do not match" validation-failed-with ;
: user-exists "username taken" validation-failed-with ;
: same-password-twice ( -- )
"new-password" value "verify-password" value = [
password-mismatch? on
validation-failed
] unless ;
"new-password" value "verify-password" value =
[ password-mismatch ] unless ;
:: <register-action> ( -- action )
[let | form [ <register-form> ] |
@ -150,10 +145,7 @@ SYMBOL: user-exists?
"email" value >>email
H{ } clone >>profile
users new-user [
user-exists? on
validation-failed
] unless*
users new-user [ user-exists ] unless*
successful-login
@ -201,7 +193,7 @@ SYMBOL: user-exists?
same-password-twice
"password" value uid check-login
[ login-failed? on validation-failed ] unless
[ login-failed ] unless
"new-password" value >>encoded-password
] unless

View File

@ -23,10 +23,8 @@
<p>
<input type="submit" value="Log in" />
<t:validation-message />
<t:if t:var="http.server.auth.login:login-failed?">
<t:error>invalid username or password</t:error>
</t:if>
</p>
</t:form>

View File

@ -32,10 +32,7 @@
<p>
<input type="submit" value="Set password" />
<t:if t:var="http.server.auth.login:password-mismatch?">
<t:error>passwords do not match</t:error>
</t:if>
<t:validation-message />
</p>
</t:form>

View File

@ -63,14 +63,7 @@
<p>
<input type="submit" value="Register" />
<t:if t:var="http.server.auth.login:user-exists?">
<t:error>username taken</t:error>
</t:if>
<t:if t:var="http.server.auth.login:password-mismatch?">
<t:error>passwords do not match</t:error>
</t:if>
<t:validation-message />
</p>

View File

@ -1,7 +1,7 @@
! Copyright (C) 2008 Slava Pestov
! See http://factorcode.org/license.txt for BSD license.
USING: splitting kernel io sequences xmode.code2html accessors
http.server.components xml.entities ;
http.server.components html xml.entities ;
IN: http.server.components.code
TUPLE: code-renderer < text-renderer mode ;
@ -11,7 +11,9 @@ TUPLE: code-renderer < text-renderer mode ;
swap >>mode ;
M: code-renderer render-view*
[ string-lines ] [ mode>> value ] bi* htmlize-lines ;
[
[ string-lines ] [ mode>> value ] bi* htmlize-lines
] with-html-stream ;
: <code> ( id mode -- component )
swap <text>

View File

@ -3,7 +3,7 @@
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.elements xml.entities
calendar.format html html.elements xml.entities
http.server.validators ;
IN: http.server.components
@ -24,9 +24,6 @@ M: field render-view*
M: field render-edit*
<input type>> =type =name =value input/> ;
: render-error ( message -- )
<span "error" =class span> escape-string write </span> ;
TUPLE: hidden < field ;
: hidden ( -- renderer ) T{ hidden f "hidden" } ; inline

View File

@ -1,10 +1,14 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors kernel sequences combinators kernel namespaces
classes.tuple assocs splitting words arrays memoize
io io.files io.encodings.utf8 html.elements unicode.case
tuple-syntax xml xml.data xml.writer xml.utilities
io io.files io.encodings.utf8 io.streams.string
unicode.case tuple-syntax html html.elements
multiline xml xml.data xml.writer xml.utilities
http.server
http.server.auth
http.server.flows
http.server.actions
http.server.components
http.server.sessions
http.server.templating
@ -21,7 +25,10 @@ DEFER: process-template
: chloe-ns "http://factorcode.org/chloe/1.0" ; inline
: filter-chloe-attrs ( assoc -- assoc' )
: chloe-attrs-only ( assoc -- assoc' )
[ drop name-url chloe-ns = ] assoc-filter ;
: non-chloe-attrs-only ( assoc -- assoc' )
[ drop name-url chloe-ns = not ] assoc-filter ;
: chloe-tag? ( tag -- ? )
@ -45,6 +52,12 @@ MEMO: chloe-name ( string -- name )
: optional-attr ( tag name -- value )
chloe-name swap at ;
: children>string ( tag -- string )
[ [ process-template ] each ] with-string-writer ;
: title-tag ( tag -- )
children>string set-title ;
: write-title-tag ( tag -- )
drop
"head" tags get member? "title" tags get member? not and
@ -131,16 +144,20 @@ MEMO: chloe-name ( string -- name )
: form-start-tag ( tag -- )
[
<form
"POST" =method
{
[ flow-attr ]
[ session-attr ]
[ "action" required-attr resolve-base-path =action ]
[ tag-attrs filter-chloe-attrs print-attrs ]
} cleave
form>
hidden-form-field
[
<form
"POST" =method
{
[ flow-attr ]
[ session-attr ]
[ "action" required-attr resolve-base-path =action ]
[ tag-attrs non-chloe-attrs-only print-attrs ]
} cleave
form>
] [
hidden-form-field
"for" optional-attr [ component render-edit ] when*
] bi
] with-scope ;
: form-tag ( tag -- )
@ -149,6 +166,26 @@ MEMO: chloe-name ( string -- name )
[ drop </form> ]
tri ;
DEFER: process-chloe-tag
STRING: button-tag-markup
<t:form class="inline" xmlns:t="http://factorcode.org/chloe/1.0">
<button type="submit"></button>
</t:form>
;
: add-tag-attrs ( attrs tag -- )
tag-attrs swap update ;
: button-tag ( tag -- )
button-tag-markup string>xml delegate
{
[ >r tag-attrs chloe-attrs-only r> add-tag-attrs ]
[ >r tag-attrs non-chloe-attrs-only r> "button" tag-named add-tag-attrs ]
[ >r children>string 1array r> "button" tag-named set-tag-children ]
[ nip ]
} 2cleave process-chloe-tag ;
: attr>word ( value -- word/f )
dup ":" split1 swap lookup
[ ] [ "No such word: " swap append throw ] ?if ;
@ -159,23 +196,25 @@ MEMO: chloe-name ( string -- name )
] unless ;
: if-satisfied? ( tag -- ? )
t swap
{
[ "code" optional-attr [ attr>word execute ] [ t ] if* ]
[ "var" optional-attr [ attr>var get ] [ t ] if* ]
[ "svar" optional-attr [ attr>var sget ] [ t ] if* ]
[ "uvar" optional-attr [ attr>var uget ] [ t ] if* ]
} cleave 4array [ ] all? ;
[ "code" optional-attr [ attr>word execute and ] when* ]
[ "var" optional-attr [ attr>var get and ] when* ]
[ "svar" optional-attr [ attr>var sget and ] when* ]
[ "uvar" optional-attr [ attr>var uget and ] when* ]
[ "value" optional-attr [ value and ] when* ]
} cleave ;
: if-tag ( tag -- )
dup if-satisfied? [ process-tag-children ] [ drop ] if ;
: error-tag ( tag -- )
: error-message-tag ( tag -- )
children>string render-error ;
: process-chloe-tag ( tag -- )
dup name-tag {
{ "chloe" [ [ process-template ] each ] }
{ "title" [ children>string set-title ] }
{ "title" [ title-tag ] }
{ "write-title" [ write-title-tag ] }
{ "style" [ style-tag ] }
{ "write-style" [ write-style-tag ] }
@ -186,7 +225,9 @@ MEMO: chloe-name ( string -- name )
{ "summary" [ summary-tag ] }
{ "a" [ a-tag ] }
{ "form" [ form-tag ] }
{ "error" [ error-tag ] }
{ "button" [ button-tag ] }
{ "error-message" [ error-message-tag ] }
{ "validation-message" [ drop render-validation-message ] }
{ "if" [ if-tag ] }
{ "comment" [ drop ] }
{ "call-next-template" [ drop call-next-template ] }

View File

@ -43,6 +43,13 @@ a:hover, .link:hover {
border: 1px dashed #ccc;
background-color: #f5f5f5;
padding: 5px;
font-size: 150%;
color: #000000;
color: #000;
}
.description p:first-child {
margin-top: 0px;
}
.description p:last-child {
margin-bottom: 0px;
}

View File

@ -10,14 +10,8 @@
<tr><th class="field-label">Date: </th><td><t:view t:component="date" /></td></tr>
</table>
<div class="description">
<t:view t:component="contents" />
</div>
<pre class="description"><t:view t:component="contents" /></pre>
<t:form t:action="$pastebin/delete-annotation" class="inline">
<t:edit t:component="id" />
<t:edit t:component="aid" />
<button class="link-button link">Delete Annotation</button>
</t:form>
<t:button t:action="$pastebin/delete-annotation" t:for="aid" class="link-button link">Delete Annotation</t:button>
</t:chloe>

View File

@ -4,8 +4,7 @@
<t:title>New Annotation</t:title>
<t:form t:action="$pastebin/annotate">
<t:edit t:component="id" />
<t:form t:action="$pastebin/annotate" t:for="id">
<table>
<tr><th class="field-label">Summary: </th><td><t:edit t:component="summary" /></td></tr>

View File

@ -3,7 +3,7 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<tr>
<td><t:a t:href="view-paste" query="id"><t:view t:component="summary" /></t:a></td>
<td><t:a t:href="$pastebin/view-paste" t:query="id"><t:view t:component="summary" /></t:a></td>
<td><t:view t:component="author" /></td>
<td><t:view t:component="date" /></td>
</tr>

View File

@ -2,9 +2,7 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<t:title>Pastebin</t:title>
<h2>Paste: <t:view t:component="summary" /></h2>
<t:title>Paste: <t:view t:component="summary" /></t:title>
<table>
<tr><th class="field-label">Author: </th><td><t:view t:component="author" /></td></tr>
@ -14,10 +12,7 @@
<pre class="description"><t:view t:component="contents" /></pre>
<t:form t:action="$pastebin/delete-paste" class="inline">
<t:edit t:component="id" />
<button class="link-button link">Delete Paste</button>
</t:form>
<t:button t:action="$pastebin/delete-paste" t:for="id" class="link-button link">Delete Paste</t:button>
|
<t:a t:href="$pastebin/annotate" t:query="id">Annotate</t:a>

View File

@ -207,12 +207,11 @@ annotation "ANNOTATION"
:: <delete-annotation-action> ( ctor next -- action )
<action>
{ { "id" [ v-number ] } { "aid" [ v-number ] } } >>post-params
{ { "aid" [ v-number ] } } >>post-params
[
"id" get "aid" get ctor call delete-tuples
"id" get next <id-redirect>
f "aid" get ctor call select-tuple
[ delete-tuples ] [ id>> next <id-redirect> ] bi
] >>submit ;
:: <new-paste-action> ( form ctor next -- action )
@ -247,7 +246,7 @@ can-delete-pastes? define-capability
<feed-action> "feed.xml" add-responder
<paste-form> [ <paste> ] <view-paste-action> "view-paste" add-responder
[ <paste> ] "$pastebin/list" <delete-paste-action> { can-delete-pastes? } <protected> "delete-paste" add-responder
[ <annotation> ] "$pastebin/view-paste" { can-delete-pastes? } <delete-annotation-action> <protected> "delete-annotation" add-responder
[ <annotation> ] "$pastebin/view-paste" <delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder
<paste-form> [ <paste> ] <view-paste-action> "$pastebin/view-paste" add-responder
<new-paste-form> [ <paste> now >>date ] "$pastebin/view-paste" <new-paste-action> "new-paste" add-responder
<new-annotation-form> [ <annotation> now >>date ] "$pastebin/view-paste" <annotate-action> "annotate" add-responder

View File

@ -11,15 +11,13 @@
| <t:a t:href="$pastebin/new-paste">New Paste</t:a>
| <t:a t:href="$pastebin/feed.xml">Atom Feed</t:a>
<t:if t:var="http.server.auth:logged-in-user">
<t:if t:code="http.server.sessions:uid">
<t:if t:code="http.server.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if>
<t:form t:action="$login/logout" t:flow="begin" class="inline">
| <button type="submit" class="link-button link">Logout</button>
</t:form>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
</t:if>

View File

@ -4,9 +4,7 @@
<t:title>Edit Blog</t:title>
<t:form t:action="$planet-factor/admin/edit-blog">
<t:edit t:component="id" />
<t:form t:action="$planet-factor/admin/edit-blog" t:for="id">
<table>
@ -31,8 +29,5 @@
</t:form>
<t:form t:action="$planet-factor/admin/delete-blog" class="inline">
<t:edit t:component="id" />
<button type="submit" class="link-button link">Delete</button>
</t:form>
<t:button t:action="$planet-factor/admin/delete-blog" t:for="id" class="link-button link">Delete</t:button>
</t:chloe>

View File

@ -9,14 +9,12 @@
| <t:a t:href="$planet-factor/feed.xml">Atom Feed</t:a>
| <t:a t:href="$planet-factor/admin">Admin</t:a>
<t:if t:var="http.server.auth:logged-in-user">
<t:if t:code="http.server.sessions:uid">
<t:if t:code="http.server.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if>
<t:form t:action="$login/logout" t:flow="begin" class="inline">
| <button type="submit" class="link-button link">Logout</button>
</t:form>
| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
</t:if>
</div>

View File

@ -4,9 +4,7 @@
<t:title>Edit Item</t:title>
<t:form t:action="$todo-list/edit">
<t:edit t:component="id" />
<t:form t:action="$todo-list/edit" t:for="id">
<table>
<tr><th class="field-label">Summary: </th><td><t:edit t:component="summary" /></td></tr>
<tr><th class="field-label">Priority: </th><td><t:edit t:component="priority" /></td></tr>
@ -16,11 +14,12 @@
<input type="SUBMIT" value="Done" />
</t:form>
<t:a t:href="$todo-list/view" t:query="id">View</t:a>
|
<t:form t:action="$todo-list/delete" t:class="inline">
<t:edit t:component="id" />
<button type="submit" class="link-button link">Delete</button>
</t:form>
<t:if t:value="id">
<t:a t:href="$todo-list/view" t:query="id">View</t:a>
|
<t:button t:action="$todo-list/delete" t:for="id" class="link-button link">Delete</t:button>
</t:if>
</t:chloe>

View File

@ -12,9 +12,7 @@
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if>
<t:form t:action="$login/logout" t:flow="begin" class="inline">
| <button type="submit" class="link-button link">Logout</button>
</t:form>
<t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
</div>
<h1><t:write-title /></h1>

View File

@ -15,9 +15,6 @@
<t:a t:href="$todo-list/edit" t:query="id">Edit</t:a>
|
<t:form t:action="$todo-list/delete" class="inline">
<t:edit t:component="id" />
<button class="link-button link">Delete</button>
</t:form>
<t:button t:action="$todo-list/delete" t:for="id" class="link-button link">Delete</t:button>
</t:chloe>