]> gitweb.factorcode.org Git - factor.git/blob - basis/furnace/actions/actions-docs.factor
76f2ec036a2d3f9592f53b4e058c4811a8218507
[factor.git] / basis / furnace / actions / actions-docs.factor
1 USING: assocs classes help.markup help.syntax io.streams.string
2 http http.server.dispatchers http.server.responses
3 furnace.redirection strings html.forms ;
4 IN: furnace.actions
5
6 HELP: <action>
7 { $values { "action" action } }
8 { $description "Creates a new action." } ;
9
10 HELP: <chloe-content>
11 { $values
12      { "path" "a path" }
13      { "response" response }
14 }
15 { $description "Creates an HTTP response which serves a Chloe template. See " { $link "html.templates.chloe" } "." } ;
16
17 HELP: <page-action>
18 { $values { "page" action } }
19 { $description "Creates a new action which serves a Chloe template when servicing a GET request." } ;
20
21 HELP: action
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."
23 $nl
24 "Action slots are documented in " { $link "furnace.actions.config" } "." } ;
25
26 HELP: new-action
27 { $values
28      { "class" class }
29      { "action" action }
30 }
31 { $description "Constructs a subclass of " { $link action } "." } ;
32
33 HELP: page-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 "template" } " slot. The " { $slot "template" } " slot contains a pair with shape " { $snippet "{ responder name }" } "." } ;
35
36 HELP: validate-integer-id
37 { $description "A utility word which validates an integer parameter named " { $snippet "id" } "." }
38 { $examples
39     { $code
40         "<action>"
41         "    ["
42         "        validate-integer-id"
43         "        \"id\" value <person> select-tuple from-object"
44         "    ] >>init"
45     }
46 } ;
47
48 HELP: validate-params
49 { $values
50      { "validators" "an association list mapping parameter names to validator quotations" }
51 }
52 { $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" } "." }
53 { $examples
54     "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:"
55     { $code
56         """: validate-todo ( -- )
57     {
58         { "summary" [ v-one-line ] }
59         { "priority" [ v-integer 0 v-min-value 10 v-max-value ] }
60         { "description" [ v-required ] }
61     } validate-params ;"""
62     }
63 } ;
64
65 { validate-params validate-values } related-words
66       
67 HELP: validation-failed
68 { $description "Stops processing the current request and takes action depending on the type of the current request:"
69     { $list
70         { "For GET or HEAD requests, the client receives a " { $link <400> } " response." }
71         { "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" } "." }
72     }
73 "This word is called by " { $link validate-params } " and can also be called directly. For more details, see " { $link "furnace.actions.lifecycle" } "." } ;
74
75 ARTICLE: "furnace.actions.page.example" "Furnace page action example"
76 "The " { $vocab-link "webapps.counter" } " vocabulary defines a subclass of " { $link dispatcher } ":"
77 { $code "TUPLE: counter-app < dispatcher ;" }
78 "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,"
79 { $code "{ counter-app \"counter\" } >>template" }
80 "This means the action will serve the Chloe template located at " { $snippet "resource:extra/webapps/counter/counter.xml" } " upon receiving a GET request." ;
81
82 ARTICLE: "furnace.actions.page" "Furnace page actions"
83 "Page actions implement the common case of an action that simply serves a Chloe template in response to a GET request."
84 { $subsections
85     page-action
86     <page-action>
87 }
88 "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."
89 $nl
90 "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."
91 { $subsections "furnace.actions.page.example" } ;
92
93 ARTICLE: "furnace.actions.config" "Furnace action configuration"
94 "Actions have the following slots:"
95 { $table
96   { { $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. A more general facility can be found in the " { $vocab-link "http.server.rewrite" } " vocabulary." } }
97     { { $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." } }
98     { { $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." } }
99     { { $slot "display" } { "A quotation called after the " { $slot "init" } " quotation in a GET request. This quotation must return an HTTP " { $link response } "." } }
100     { { $slot "validate" } { "A quotation called at the beginning of a POST request to validate POST parameters." } }
101     { { $slot "submit" } { "A quotation called after the " { $slot "validate" } " quotation in a POST request. This quotation must return an HTTP " { $link response } "." } }
102 }
103 "At least one of the " { $slot "display" } " and " { $slot "submit" } " slots must be set, otherwise the action will be useless." ;
104
105 ARTICLE: "furnace.actions.validation" "Form validation with actions"
106 "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."
107 $nl
108 "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:"
109 { $subsections validate-params }
110 "The above word expects an association list mapping parameter names to validator quotations; validator quotations can use the words in the " 
111 "Custom validation logic can invoke a word when validation fails; " { $link validate-params } " invokes this word for you:"
112 { $subsections validation-failed }
113 "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." ;
114
115 ARTICLE: "furnace.actions.lifecycle" "Furnace action lifecycle"
116 { $heading "GET request lifecycle" }
117 "A GET request results in the following sequence of events:"
118 { $list
119     { "The " { $snippet "init" } " quotation is called." }
120     { "The " { $snippet "authorize" } " quotation is called." }
121     { "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" } "." }
122     { "The " { $snippet "display" } " quotation is called; it is expected to output an HTTP " { $link response } " on the stack." }
123 }
124 "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."
125 { $heading "HEAD request lifecycle" }
126 "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."
127 { $heading "POST request lifecycle" }
128 "A POST request results in the following sequence of events:"
129 { $list
130     { "The " { $snippet "validate" } " quotation is called." }
131     { "The " { $snippet "authorize" } " quotation is called." }
132     { "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> } "." }
133 }
134 "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" } "." ;
135
136 ARTICLE: "furnace.actions.impl" "Furnace actions implementation"
137 "The following parametrized constructor should be called from constructors for subclasses of " { $link action } ":"
138 { $subsections new-action } ;
139
140 ARTICLE: "furnace.actions" "Furnace actions"
141 "The " { $vocab-link "furnace.actions" } " vocabulary implements a type of responder, called an " { $emphasis "action" } ", which handles the form validation lifecycle."
142 $nl
143 "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)."
144 $nl
145 "The class of actions:"
146 { $subsections action }
147 "Creating a new action:"
148 { $subsections <action> }
149 "Once created, an action needs to be configured; typically the creation and configuration of an action is encapsulated into a single word:"
150 { $subsections "furnace.actions.config" }
151 "Validating forms with actions:"
152 { $subsections "furnace.actions.validation" }
153 "More about the form validation lifecycle:"
154 { $subsections "furnace.actions.lifecycle" }
155 "A convenience class:"
156 { $subsections "furnace.actions.page" }
157 "Low-level features:"
158 { $subsections "furnace.actions.impl" } ;
159
160 ABOUT: "furnace.actions"