1 USING: assocs classes help.markup help.syntax io.streams.string
2 http http.server.dispatchers http.server.responses
3 furnace.redirection strings multiline ;
7 { $values { "action" action } }
8 { $description "Creates a new action." } ;
12 { "path" "a pathname string" }
13 { "response" response }
15 { $description "Creates an HTTP response which serves a Chloe template. See " { $link "html.templates.chloe" } "." } ;
18 { $values { "page" action } }
19 { $description "Creates a new action which serves a Chloe template when servicing a GET request." } ;
22 { $class-description "The class of Furnace actions. New instances are created with " { $link <action> } ". New instances of subclasses can be created with " { $link new-action } ". The " { $link page-action } " class is a useful subclass."
24 "Action slots are documented in " { $link "furnace.actions.config" } "." } ;
31 { $description "Constructs a subclass of " { $link action } "." } ;
34 { $class-description "The class of Chloe page actions. These are actions whose " { $slot "display" } " slot is pre-set to serve the Chloe template stored in the " { $slot "page" } " slot." } ;
41 { $description "Outputs the value of a query parameter (if the current request is a GET or HEAD request) or a POST parameter (if the current request is a POST request)." }
42 { $notes "Instead of using this word, it is better to use " { $link validate-params } " and then access parameters via " { $link "html.forms.values" } " words." } ;
45 { $var-description "A variable holding an assoc of query parameters (if the current request is a GET or HEAD request) or POST parameters (if the current request is a POST request)." }
46 { $notes "Instead of using this word, it is better to use " { $link validate-params } " and then access parameters via " { $link "html.forms.values" } " words." } ;
48 HELP: validate-integer-id
49 { $description "A utility word which validates an integer parameter named " { $snippet "id" } "." }
54 " validate-integer-id"
55 " \"id\" value <person> select-tuple from-object"
62 { "validators" "an association list mapping parameter names to validator quotations" }
64 { $description "Validates query or POST parameters, depending on the request type, and stores them in " { $link "html.forms.values" } ". The validator quotations can execute " { $link "validators" } "." }
66 "A simple validator from " { $vocab-link "webapps.todo" } "; this word is invoked from the " { $slot "validate" } " quotation of action for editing a todo list item:"
68 <" : validate-todo ( -- )
70 { "summary" [ v-one-line ] }
71 { "priority" [ v-integer 0 v-min-value 10 v-max-value ] }
72 { "description" [ v-required ] }
77 HELP: validation-failed
78 { $description "Stops processing the current request and takes action depending on the type of the current request:"
80 { "For GET or HEAD requests, the client receives a " { $link <400> } " response." }
81 { "For POST requests, the client is sent back to the page containing the form submission, with current form values and validation errors passed in a " { $link "furnace.conversations" } "." }
83 "This word is called by " { $link validate-params } " and can also be called directly. For more details, see " { $link "furnace.actions.lifecycle" } "." } ;
85 ARTICLE: "furnace.actions.page.example" "Furnace page action example"
86 "The " { $vocab-link "webapps.counter" } " vocabulary defines a subclass of " { $link dispatcher } ":"
87 { $code "TUPLE: counter-app < dispatcher ;" }
88 "The " { $snippet "<counter-app>" } " constructor word creates a new instance of the " { $snippet "counter-app" } " class, and adds a " { $link page-action } " instance to the dispatcher. This " { $link page-action } " has its " { $slot "template" } " slot set as follows,"
89 { $code "{ counter-app \"counter\" } >>template" }
90 "This means the action will serve the Chloe template located at " { $snippet "resource:extra/webapps/counter/counter.xml" } " upon receiving a GET request." ;
92 ARTICLE: "furnace.actions.page" "Furnace page actions"
93 "Page actions implement the common case of an action that simply serves a Chloe template in response to a GET request."
94 { $subsection page-action }
95 { $subsection <page-action> }
96 "When using a page action, instead of setting the " { $slot "display" } " slot, the " { $slot "template" } " slot is set instead. The " { $slot "init" } ", " { $slot "authorize" } ", " { $slot "validate" } " and " { $slot "submit" } " slots can still be set as usual."
98 "The " { $slot "template" } " slot of a " { $link page-action } " contains a pair with shape " { $snippet "{ responder name }" } ", where " { $snippet "responder" } " is a responder class, usually a subclass of " { $link dispatcher } ", and " { $snippet "name" } " is the name of a template file, without the " { $snippet ".xml" } " extension, relative to the directory containing the responder's vocabulary source file."
99 { $subsection "furnace.actions.page.example" } ;
101 ARTICLE: "furnace.actions.config" "Furnace action configuration"
102 "Actions have the following slots:"
104 { { $slot "rest" } { "A parameter name to map the rest of the URL, after the action name, to. If this is not set, then navigating to a URL where the action is not the last path component will return to the client with an error." } }
105 { { $slot "init" } { "A quotation called at the beginning of a GET or HEAD request. Typically this quotation configures " { $link "html.forms" } " and parses query parameters." } }
106 { { $slot "authorize" } { "A quotation called at the beginning of a GET, HEAD or POST request. In GET requests, it is called after the " { $slot "init" } " quotation; in POST requests, it is called after the " { $slot "validate" } " quotation. By convention, this quotation performs custom authorization checks which depend on query parameters or POST parameters." } }
107 { { $slot "display" } { "A quotation called after the " { $slot "init" } " quotation in a GET request. This quotation must return an HTTP " { $link response } "." } }
108 { { $slot "validate" } { "A quotation called at the beginning of a POST request to validate POST parameters." } }
109 { { $slot "submit" } { "A quotation called after the " { $slot "validate" } " quotation in a POST request. This quotation must return an HTTP " { $link response } "." } }
111 "At least one of the " { $slot "display" } " and " { $slot "submit" } " slots must be set, otherwise the action will be useless." ;
113 ARTICLE: "furnace.actions.validation" "Form validation with actions"
114 "The action code is set up so that the " { $slot "init" } " quotation can validate query parameters, and the " { $slot "validate" } " quotation can validate POST parameters."
116 "A word to validate parameters and make them available as HTML form values (see " { $link "html.forms.values" } "); typically this word is invoked from the " { $slot "init" } " and " { $slot "validate" } " quotations:"
117 { $subsection validate-params }
118 "The above word expects an association list mapping parameter names to validator quotations; validator quotations can use the words in the "
119 "Custom validation logic can invoke a word when validation fails; " { $link validate-params } " invokes this word for you:"
120 { $subsection validation-failed }
121 "If validation fails, no more action code is executed, and the client is redirected back to the originating page, where validation errors can be displayed. Note that validation errors are rendered automatically by the " { $link "html.components" } " words, and in particular, " { $link "html.templates.chloe" } " use these words." ;
123 ARTICLE: "furnace.actions.lifecycle" "Furnace action lifecycle"
124 { $heading "GET request lifecycle" }
125 "A GET request results in the following sequence of events:"
127 { "The " { $snippet "init" } " quotation is called." }
128 { "The " { $snippet "authorize" } " quotation is called." }
129 { "If the GET request was generated as a result of form validation failing during a POST, then the form values entered by the user, along with validation errors, are stored in " { $link "html.forms.values" } "." }
130 { "The " { $snippet "display" } " quotation is called; it is expected to output an HTTP " { $link response } " on the stack." }
132 "Any one of the above steps can perform validation; if " { $link validation-failed } " is called during a GET request, the client receives a " { $link <400> } " error."
133 { $heading "HEAD request lifecycle" }
134 "A HEAD request proceeds exactly like a GET request. The only difference is that the " { $slot "body" } " slot of the " { $link response } " object is never rendered."
135 { $heading "POST request lifecycle" }
136 "A POST request results in the following sequence of events:"
138 { "The " { $snippet "validate" } " quotation is called." }
139 { "The " { $snippet "authorize" } " quotation is called." }
140 { "The " { $snippet "submit" } " quotation is called; it is expected to output an HTTP " { $link response } " on the stack. By convention, this response should be a " { $link <redirect> } "." }
142 "Any one of the above steps can perform validation; if " { $link validation-failed } " is called during a POST request, the client is sent back to the page containing the form submission, with current form values and validation errors passed in a " { $link "furnace.conversations" } "." ;
144 ARTICLE: "furnace.actions.impl" "Furnace actions implementation"
145 "The following words are used by the action implementation and there is rarely any reason to call them directly:"
146 { $subsection new-action }
147 { $subsection param }
148 { $subsection params } ;
150 ARTICLE: "furnace.actions" "Furnace actions"
151 "The " { $vocab-link "furnace.actions" } " vocabulary implements a type of responder, called an " { $emphasis "action" } ", which handles the form validation lifecycle."
153 "Other than form validation capability, actions are also often simpler to use than implementing new responders directly, since creating a new class is not required, and the action dispatches on the request type (GET, HEAD, or POST)."
155 "The class of actions:"
156 { $subsection action }
157 "Creating a new action:"
158 { $subsection <action> }
159 "Once created, an action needs to be configured; typically the creation and configuration of an action is encapsulated into a single word:"
160 { $subsection "furnace.actions.config" }
161 "Validating forms with actions:"
162 { $subsection "furnace.actions.validation" }
163 "More about the form validation lifecycle:"
164 { $subsection "furnace.actions.lifecycle" }
165 "A convenience class:"
166 { $subsection "furnace.actions.page" }
167 "Low-level features:"
168 { $subsection "furnace.actions.impl" } ;
170 ABOUT: "furnace.actions"