]> gitweb.factorcode.org Git - factor.git/blob - basis/html/templates/chloe/chloe-docs.factor
factor: use new math.ranges syntax in tests and docs
[factor.git] / basis / html / templates / chloe / chloe-docs.factor
1 IN: html.templates.chloe
2 USING: help.markup help.syntax html.components html.forms
3 html.templates html.templates.chloe.syntax
4 html.templates.chloe.compiler html.templates.chloe.components
5 math strings quotations namespaces ;
6 FROM: xml.data => tag ;
7
8 HELP: <chloe>
9 { $values { "path" "a pathname string without the trailing " { $snippet ".xml" } " extension" } { "chloe" chloe } }
10 { $description "Creates a new Chloe template object which can be passed to " { $link call-template } "." } ;
11
12 HELP: required-attr
13 { $values { "tag" tag } { "name" string } { "value" string } }
14 { $description "Extracts an attribute from a tag." }
15 { $errors "Throws an error if the attribute is not specified." } ;
16
17 HELP: optional-attr
18 { $values { "tag" tag } { "name" string } { "value" { $maybe string } } }
19 { $description "Extracts an attribute from a tag." }
20 { $notes "Outputs " { $link f } " if the attribute is not specified." } ;
21
22 HELP: compile-attr
23 { $values { "value" "an attribute value" } }
24 { $description "Compiles code which pushes an attribute value previously extracted by " { $link required-attr } " or " { $link optional-attr } " on the stack. If the attribute value begins with " { $snippet "@" } ", compiles into code which pushes the a form value." } ;
25
26 HELP: CHLOE:
27 { $syntax "CHLOE: name definition... ;" }
28 { $values { "name" "the tag name" } { "definition" { $quotation ( tag -- ) } } }
29 { $description "Defines compilation semantics for the Chloe tag named " { $snippet "tag" } ". The definition body receives a " { $link tag } " on the stack." } ;
30
31 HELP: COMPONENT:
32 { $syntax "COMPONENT: name" }
33 { $description "Defines a Chloe tag named " { $snippet "name" } " rendering the HTML component with class word " { $snippet "name" } ". See " { $link "html.components" } "." } ;
34
35 HELP: reset-cache
36 { $description "Resets the compiled template cache. Chloe automatically recompiles templates when their file changes on disk, however other when redefining Chloe tags or words which they call, the cache may have to be reset manually for the changes to take effect." } ;
37
38 HELP: tag-stack
39 { $var-description "During template compilation, holds the current nesting of XML element names. Can be used from " { $link POSTPONE: CHLOE: } " definitions to make a custom tag behave differently depending on how it is nested." } ;
40
41 HELP: [write]
42 { $values { "string" string } }
43 { $description "Compiles code which writes the string when the template is called." } ;
44
45 HELP: [code]
46 { $values { "quot" quotation } }
47 { $description "Compiles the quotation. It will be called when the template is called." } ;
48
49 HELP: process-children
50 { $values { "tag" tag } { "quot" { $quotation ( compiled-tag -- ) } } }
51 { $description "Compiles the tag. The quotation will be applied to the resulting quotation when the template is called." }
52 { $examples "See " { $link "html.templates.chloe.extend.tags.example" } " for an example which uses this word to implement a custom control flow tag." } ;
53
54 HELP: compile-children>string
55 { $values { "tag" tag } }
56 { $description "Compiles the tag so that the output it generates is written to a string, which is pushed on the stack when the template runs. A subsequent " { $link [code] } " call must be made with a quotation which consumes the string." } ;
57
58 HELP: compile-with-scope
59 { $values { "quot" quotation } }
60 { $description "Calls the quotation and wraps any output it compiles in a " { $link with-scope } " form." } ;
61
62 ARTICLE: "html.templates.chloe.tags.component" "Component Chloe tags"
63 "The following Chloe tags correspond exactly to " { $link "html.components" } ". The " { $snippet "name" } " attribute should be the name of a form value (see " { $link "html.forms.values" } "). Singleton component tags do not allow any other attributes. Tuple component tags map all other attributes to tuple slot values of the component instance."
64 { $table
65     { { $strong "Tag" } { $strong "Component class" } }
66     { { $snippet "t:checkbox" }   { $link checkbox } }
67     { { $snippet "t:choice" }     { $link choice } }
68     { { $snippet "t:code" }       { $link code } }
69     { { $snippet "t:comparison" } { $link comparison } }
70     { { $snippet "t:farkup" }     { $link farkup } }
71     { { $snippet "t:field" }      { $link field } }
72     { { $snippet "t:hidden" }     { $link hidden } }
73     { { $snippet "t:html" }       { $link html } }
74     { { $snippet "t:xml" }        { $link xml } }
75     { { $snippet "t:inspector" }  { $link inspector } }
76     { { $snippet "t:label" }      { $link label } }
77     { { $snippet "t:link" }       { $link link } }
78     { { $snippet "t:password" }   { $link password } }
79     { { $snippet "t:textarea" }   { $link textarea } }
80 } ;
81
82 ARTICLE: "html.templates.chloe.tags.boilerplate" "Boilerplate Chloe tags"
83 "The following Chloe tags interface with the HTML templating " { $link "html.templates.boilerplate" } "."
84 $nl
85 "The tags marked with (*) are only available if the " { $vocab-link "furnace.chloe-tags" } " vocabulary is loaded."
86 { $table
87     { { $snippet "t:title" } "Sets the title. Intended for use in a master template." }
88     { { $snippet "t:write-title" } "Renders the child's title. Intended for use in a child template." }
89     { { $snippet "t:style" } { "Adds CSS markup from the file named by the " { $snippet "t:include" } " attribute. Intended for use in a child template." } }
90     { { $snippet "t:write-style" } "Renders the children's CSS markup. Intended for use in a master template." }
91     { { $snippet "t:script" } { "Adds JS from the file named by the " { $snippet "t:include" } " attribute. Intended for use in a child template." } }
92     { { $snippet "t:write-script" } "Renders the children's JS. Intended for use in a master template." }
93     { { $snippet "t:atom" } { "Adds an Atom feed link. The attributes are the same as the " { $snippet "t:link" } " tag. Intended for use in a child template. (*)" } }
94     { { $snippet "t:write-atom" } "Renders the children's list of Atom feed links. Intended for use in a master template. (*)" }
95     { { $snippet "t:call-next-template" } "Calls the next child template from a master template." }
96 } ;
97
98 ARTICLE: "html.templates.chloe.tags.control" "Control-flow Chloe tags"
99 "While most control flow and logic should be embedded in the web actions themselves and not in the template, Chloe templates do support a minimal amount of control flow."
100 { $table
101     { { $snippet "t:comment" } "All markup within a comment tag is ignored by the compiler." }
102     { { $snippet "t:bind" } { "Renders child content bound to a nested form named by the " { $snippet "t:name" } " attribute. See " { $link with-form } "." } }
103     { { $snippet "t:each" } { "Renders child content once for each element of the sequence in the value named by the " { $snippet "t:name" } " attribute. The sequence element and index are bound to the " { $snippet "value" } " and " { $snippet "index" } " values, respectively. See " { $link with-each-value } "." } }
104     { { $snippet "t:bind-each" } { "Renders child content once for each element of the sequence in the value named by the " { $snippet "t:name" } " attribute. The sequence element's slots are bound to values. See " { $link with-each-object } "." } }
105     { { $snippet "t:even" } { "Only valid inside a " { $snippet "t:each" } " or " { $snippet "t:bind-each" } ". Only renders child content if the " { $snippet "index" } " value is even." } }
106     { { $snippet "t:odd" } "As above, but only if the index value is odd." }
107     { { $snippet "t:if" } { "Renders child content if a boolean condition evaluates to true. The condition value is determined by the " { $snippet "t:code" } " or " { $snippet "t:value" } " attribute, exactly one of which must be specified. The former is a string of the form " { $snippet "vocabulary:word" } " denoting a word to execute with stack effect " { $snippet "( -- ? )" } ". The latter is a value name." } }
108 } ;
109
110 ARTICLE: "html.templates.chloe.tags.form" "Chloe link and form tags"
111 "The following tags are only available if the " { $vocab-link "furnace.chloe-tags" } " vocabulary is loaded."
112 { $table
113     { { $snippet "t:a" } { "Renders a link; extends the standard XHTML " { $snippet "a" } " tag by providing some integration with other web framework features. The following attributes are supported:"
114         { $list
115             { { $snippet "href" } " - a URL. If it begins with " { $snippet "$" } ", then it is interpreted as a responder-relative path." }
116             { { $snippet "rest" } " - a value to add at the end of the URL." }
117             { { $snippet "query" } " - a comma-separated list of value names defined in the current form which are to be passed to the link as query parameters." }
118             { { $snippet "value" } " - a value name holding a URL. If this attribute is specified, it overrides all others." }
119         }
120         "Any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "a" } " tag."
121         $nl
122         "An example:"
123         { $code
124             "<t:a t:href=\"$wiki/view/\""
125             "     t:rest=\"title\""
126             "     class=\"small-link\">"
127             "    View"
128             "</t:a>"
129         }
130         "The above might render as"
131         { $code
132             "<a href=\"http://mysite.org/wiki/view/Factor\""
133             "   class=\"small-link\">"
134             "    View"
135             "</a>"
136         }
137     } }
138     { { $snippet "t:base" } { "Outputs an HTML " { $snippet "<base>" } " tag. The attributes are interpreted in the same manner as the attributes of " { $snippet "t:a" } "." } }
139     { { $snippet "t:form" } {
140         "Renders a form; extends the standard XHTML " { $snippet "form" } " tag by providing some integration with other web framework features, for example by adding hidden fields for authentication credentials and session management allowing those features to work with form submission transparently. The following attributes are supported:"
141         { $list
142             { { $snippet "t:method" } " - just like the " { $snippet "method" } " attribute of an HTML " { $snippet "form" } " tag, this can equal " { $snippet "get" } " or " { $snippet "post" } ". Unlike the HTML tag, the default is " { $snippet "post" } "." }
143             { { $snippet "t:action" } " - a URL. If it begins with " { $snippet "$" } ", then it is interpreted as a responder-relative path." }
144             { { $snippet "t:for" } " - a comma-separated list of form values which are to be inserted in the form as hidden fields. Other than being more concise, this is equivalent to nesting a series of " { $snippet "t:hidden" } " tags inside the form." }
145         }
146         "Any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "form" } " tag."
147     } }
148     { { $snippet "t:button" } {
149         "Shorthand for a form with a single button, whose label is the text child of the " { $snippet "t:button" } " tag. Attributes are processed as with the " { $snippet "t:form" } " tag, with the exception that any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "button" } " tag, rather than the " { $snippet "form" } " tag surrounding it."
150         $nl
151         "An example:"
152         { $code
153             "<t:button t:method=\"POST\""
154             "          t:action=\"$wiki/delete\""
155             "          t:for=\"id\""
156             "          class=\"link-button\">"
157             "    Delete"
158             "</t:button>"
159         }
160     } }
161     { { $snippet "t:validation-errors" } {
162         "Renders validation errors in the current form which are not associated with any field. Such errors are reported by invoking " { $link validation-error } "."
163     } }
164 } ;
165
166 ARTICLE: "html.templates.chloe.tags" "Standard Chloe tags"
167 "A Chloe template is an XML file with a mix of standard XHTML and Chloe tags."
168 $nl
169 "XHTML tags are rendered verbatim, except attribute values which begin with " { $snippet "@" } " are replaced with the corresponding " { $link "html.forms.values" } "."
170 $nl
171 "Chloe tags are defined in the " { $snippet "http://factorcode.org/chloe/1.0" } " namespace; by convention, it is bound with a prefix of " { $snippet "t" } ". The top-level tag must always be the " { $snippet "t:chloe" } " tag. A typical Chloe template looks like so:"
172 { $code
173     "<?xml version=\"1.0\"?>"
174     ""
175     "<t:chloe xmlns:t=\"http://factorcode.org/chloe/1.0\">"
176     "    ..."
177     "</t:chloe>"
178 }
179 { $subsections
180     "html.templates.chloe.tags.component"
181     "html.templates.chloe.tags.boilerplate"
182     "html.templates.chloe.tags.control"
183     "html.templates.chloe.tags.form"
184 } ;
185
186 ARTICLE: "html.templates.chloe.extend" "Extending Chloe"
187 "The " { $vocab-link "html.templates.chloe.syntax" } " and " { $vocab-link "html.templates.chloe.compiler" } " vocabularies contain the heart of the Chloe implementation."
188 $nl
189 "Chloe is implemented as a compiler which converts XML templates into Factor quotations. The template only has to be parsed and compiled once, and not on every HTTP request. This helps improve performance and memory usage."
190 $nl
191 "These vocabularies provide various hooks by which Chloe can be extended. First of all, new " { $link "html.components" } " can be wired in. If further flexibility is needed, entirely new tags can be defined by hooking into the Chloe compiler."
192 { $subsections
193     "html.templates.chloe.extend.components"
194     "html.templates.chloe.extend.tags"
195 } ;
196
197 ARTICLE: "html.templates.chloe.extend.tags" "Extending Chloe with custom tags"
198 "Syntax for defining custom tags:"
199 { $subsections POSTPONE: CHLOE: }
200 "A number of compiler words can be used from the " { $link POSTPONE: CHLOE: } " body to emit compiled template code."
201 $nl
202 "Extracting attributes from the XML tag:"
203 { $subsections
204     required-attr
205     optional-attr
206     compile-attr
207 }
208 "Examining tag nesting:"
209 { $subsections tag-stack }
210 "Generating code for printing strings and calling quotations:"
211 { $subsections
212     [write]
213     [code]
214 }
215 "Generating code from child elements:"
216 { $subsections
217     process-children
218     compile-children>string
219     compile-with-scope
220 }
221 "Examples which illustrate some of the above:"
222 { $subsections "html.templates.chloe.extend.tags.example" } ;
223
224 ARTICLE: "html.templates.chloe.extend.tags.example" "Examples of custom Chloe tags"
225 "As a first example, let's develop a custom Chloe tag which simply renders a random number. The tag will be used as follows:"
226 { $code
227     "<t:random t:min='10' t:max='20' t:generator='system' />"
228 }
229 "The " { $snippet "t:min" } " and " { $snippet "t:max" } " parameters are required, and " { $snippet "t:generator" } ", which can equal one of " { $snippet "default" } ", " { $snippet "system" } " or " { $snippet "secure" } ", is optional, with the default being " { $snippet "default" } "."
230 $nl
231 "Here is the " { $link POSTPONE: USING: } " form that we need for the below code to work:"
232 { $code
233     "USING: combinators kernel math.parser math.ranges random"
234     "html.templates.chloe.compiler html.templates.chloe.syntax ;"
235 }
236 "We write a word which extracts the relevant attributes from an XML tag:"
237 { $code
238     ": random-attrs ( tag -- min max generator )"
239     "    [ \"min\" required-attr string>number ]"
240     "    [ \"max\" required-attr string>number ]"
241     "    [ \"generator\" optional-attr ]"
242     "    tri ;"
243 }
244 "Next, we convert a random generator name into a random generator object:"
245 { $code
246     ": string>random-generator ( string -- generator )"
247     "    {"
248     "        { \"default\" [ random-generator ] }"
249     "        { \"system\" [ system-random-generator ] }"
250     "        { \"secure\" [ secure-random-generator ] }"
251     "    } case ;"
252 }
253 "Finally, we can write our Chloe tag:"
254 { $code
255     "CHLOE: random"
256     "    random-attrs string>random-generator"
257     "    '["
258     "        _ _ _"
259     "        [ [a..b] random present write ]"
260     "        with-random-generator"
261     "    ] [code] ;"
262 }
263 "For the second example, let's develop a Chloe tag which repeatedly renders its child several times, where the number comes from a form value. The tag will be used as follows:"
264 { $code
265     "<t:repeat t:times='n'>Hello world.<br /></t:repeat>"
266 }
267 "This time, we cannot simply extract the " { $snippet "t:times" } " attribute at compile time since its value cannot be determined then. Instead, we execute " { $link compile-attr } " to generate code which pushes the value of that attribute on the stack. We then use " { $link process-children } " to compile child elements as a nested quotation which we apply " { $link times } " to."
268 { $code
269     "CHLOE: repeat"
270     "    [ \"times\" required-attr compile-attr ]"
271     "    [ [ times ] process-children ]"
272     "    bi ;"
273 } ;
274
275 ARTICLE: "html.templates.chloe.extend.components.example" "An example of a custom Chloe component"
276 "As an example, let's develop a custom Chloe component which renders an image stored in a form value. Since the component does not require any configuration, we can define a singleton class:"
277 { $code "SINGLETON: image" }
278 "Now we define a method on the " { $link render* } " generic word which renders the image using " { $link { "xml.syntax" "literals" } } ":"
279 { $code "M: image render* 2drop [XML <img src=<-> /> XML] ;" }
280 "Finally, we can define a Chloe component:"
281 { $code "COMPONENT: image" }
282 "We can use it as follows, assuming the current form has a value named " { $snippet "image" } ":"
283 { $code "<t:image t:name='image' />" } ;
284
285 ARTICLE: "html.templates.chloe.extend.components" "Extending Chloe with custom components"
286 "Custom HTML components implementing the " { $link render* } " word can be wired up with Chloe using the following syntax from " { $vocab-link "html.templates.chloe.components" } ":"
287 { $subsections
288     POSTPONE: COMPONENT:
289     "html.templates.chloe.extend.components.example"
290 } ;
291
292 ARTICLE: "html.templates.chloe" "Chloe templates"
293 "The " { $vocab-link "html.templates.chloe" } " vocabulary implements an XHTML templating engine. Unlike " { $vocab-link "html.templates.fhtml" } ", Chloe templates are always well-formed XML, and no Factor code can be embedded in them, enforcing proper separation of concerns. Chloe templates can be edited using standard XML editing tools; they are less flexible than FHTML, but often simpler as a result."
294 { $subsections
295     <chloe>
296     reset-cache
297     "html.templates.chloe.tags"
298     "html.templates.chloe.extend"
299 } ;
300
301 ABOUT: "html.templates.chloe"