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 xml.data strings quotations namespaces ;
8 { $values { "path" "a pathname string without the trailing " { $snippet ".xml" } " extension" } { "chloe" chloe } }
9 { $description "Creates a new Chloe template object which can be passed to " { $link call-template } "." } ;
12 { $values { "tag" tag } { "name" string } { "value" string } }
13 { $description "Extracts an attribute from a tag." }
14 { $errors "Throws an error if the attribute is not specified." } ;
17 { $values { "tag" tag } { "name" string } { "value" { $maybe string } } }
18 { $description "Extracts an attribute from a tag." }
19 { $notes "Outputs " { $link f } " if the attribute is not specified." } ;
22 { $values { "value" "an attribute value" } }
23 { $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." } ;
26 { $syntax "name definition... ;" }
27 { $values { "name" "the tag name" } { "definition" { $quotation "( tag -- )" } } }
28 { $description "Defines compilation semantics for the Chloe tag named " { $snippet "tag" } ". The definition body receives a " { $link tag } " on the stack." } ;
31 { $syntax "COMPONENT: name" }
32 { $description "Defines a Chloe tag named " { $snippet "name" } " rendering the HTML component with class word " { $snippet "name" } ". See " { $link "html.components" } "." } ;
35 { $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." } ;
38 { $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." } ;
41 { $values { "string" string } }
42 { $description "Compiles code which writes the string when the template is called." } ;
45 { $values { "quot" quotation } }
46 { $description "Compiles the quotation. It will be called when the template is called." } ;
48 HELP: process-children
49 { $values { "tag" tag } { "quot" { $quotation "( compiled-tag -- )" } } }
50 { $description "Compiles the tag. The quotation will be applied to the resulting quotation when the template is called." }
51 { $examples "See " { $link "html.templates.chloe.extend.tags.example" } " for an example which uses this word to implement a custom control flow tag." } ;
53 HELP: compile-children>string
54 { $values { "tag" tag } }
55 { $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 HELP: compile-with-scope
58 { $values { "quot" quotation } }
59 { $description "Calls the quotation and wraps any output it compiles in a " { $link with-scope } " form." } ;
61 ARTICLE: "html.templates.chloe.tags.component" "Component Chloe tags"
62 "The following Chloe tags correspond exactly to " { $link "html.components" } ". Singleton component tags do not allow any attributes. Attributes of tuple component tags are mapped to tuple slot values of the component instance."
64 { "Tag" "Component class" }
65 { { $snippet "t:checkbox" } { $link checkbox } }
66 { { $snippet "t:choice" } { $link choice } }
67 { { $snippet "t:code" } { $link code } }
68 { { $snippet "t:comparison" } { $link comparison } }
69 { { $snippet "t:farkup" } { $link farkup } }
70 { { $snippet "t:field" } { $link field } }
71 { { $snippet "t:hidden" } { $link hidden } }
72 { { $snippet "t:html" } { $link html } }
73 { { $snippet "t:inspector" } { $link inspector } }
74 { { $snippet "t:label" } { $link label } }
75 { { $snippet "t:link" } { $link link } }
76 { { $snippet "t:password" } { $link password } }
77 { { $snippet "t:textarea" } { $link textarea } }
80 ARTICLE: "html.templates.chloe.tags.boilerplate" "Boilerplate Chloe tags"
81 "The following Chloe tags interface with the HTML templating " { $link "html.templates.boilerplate" } "."
83 "The tags marked with (*) are only available if the " { $vocab-link "furnace.chloe-tags" } " vocabulary is loaded."
85 { { $snippet "t:title" } "Sets the title from a child template" }
86 { { $snippet "t:write-title" } "Renders the child's title from a master template" }
87 { { $snippet "t:style" } "Adds CSS markup from a child template" }
88 { { $snippet "t:write-style" } "Renders the children's CSS from a master template" }
89 { { $snippet "t:atom" } "Adds an Atom feed link from a child template (*)" }
90 { { $snippet "t:write-atom" } "Renders the children's list of Atom feed links (*)" }
91 { { $snippet "t:call-next-template" } "Calls the child template from a master template" }
94 ARTICLE: "html.templates.chloe.tags.control" "Control-flow Chloe tags"
95 "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."
97 { { $snippet "t:comment" } "All markup within a comment tag is ignored by the compiler." }
98 { { $snippet "t:bind" } { "Renders child content bound to a nested form named by the " { $snippet "t:name" } " attribute. See " { $link with-form } "." } }
99 { { $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 } "." } }
100 { { $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 } "." } }
101 { { $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." } }
102 { { $snippet "t:odd" } "As above, but only if the index value is odd." }
103 { { $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." } }
106 ARTICLE: "html.templates.chloe.tags.form" "Chloe link and form tags"
107 "The following tags are only available if the " { $vocab-link "furnace.chloe-tags" } " vocabulary is loaded."
109 { { $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:"
111 { { $snippet "href" } " - a URL. If it begins with " { $snippet "$" } ", then it is interpreted as a responder-relative path." }
112 { { $snippet "rest" } " - a value to add at the end of the URL." }
113 { { $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." }
114 { { $snippet "value" } " - a value name holding a URL. If this attribute is specified, it overrides all others." }
116 "Any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "a" } " tag."
120 "<t:a t:href=\"$wiki/view/\""
122 " class=\"small-link\">"
126 "The above might render as"
128 "<a href=\"http://mysite.org/wiki/view/Factor\""
129 " class=\"small-link\">"
134 { { $snippet "t:base" } { "Outputs an HTML " { $snippet "<base>" } " tag. The attributes are interpreted in the same manner as the attributes of " { $snippet "t:a" } "." } }
135 { { $snippet "t:form" } {
136 "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:"
138 { { $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" } "." }
139 { { $snippet "t:action" } " - a URL. If it begins with " { $snippet "$" } ", then it is interpreted as a responder-relative path." }
140 { { $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." }
142 "Any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "form" } " tag."
144 { { $snippet "t:button" } {
145 "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."
149 "<t:button t:method=\"POST\""
150 " t:action=\"$wiki/delete\""
152 " class=\"link-button\""
157 { { $snippet "t:validation-errors" } {
158 "Renders validation errors in the current form which are not associated with any field. Such errors are reported by invoking " { $link validation-error } "."
162 ARTICLE: "html.templates.chloe.tags" "Standard Chloe tags"
163 "A Chloe template is an XML file with a mix of standard XHTML and Chloe tags."
165 "XHTML tags are rendered verbatim, except attribute values which begin with " { $snippet "@" } " are replaced with the corresponding " { $link "html.forms.values" } "."
167 "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:"
169 "<?xml version=\"1.0\"?>"
171 "<t:chloe xmlns:t=\"http://factorcode.org/chloe/1.0\">"
175 { $subsection "html.templates.chloe.tags.component" }
176 { $subsection "html.templates.chloe.tags.boilerplate" }
177 { $subsection "html.templates.chloe.tags.control" }
178 { $subsection "html.templates.chloe.tags.form" } ;
180 ARTICLE: "html.templates.chloe.extend" "Extending Chloe"
181 "The " { $vocab-link "html.templates.chloe.syntax" } " and " { $vocab-link "html.templates.chloe.compiler" } " vocabularies contain the heart of the Chloe implementation."
183 "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."
185 "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."
186 { $subsection "html.templates.chloe.extend.components" }
187 { $subsection "html.templates.chloe.extend.tags" } ;
189 ARTICLE: "html.templates.chloe.extend.tags" "Extending Chloe with custom tags"
190 "Syntax for defining custom tags:"
191 { $subsection POSTPONE: CHLOE: }
192 "A number of compiler words can be used from the " { $link POSTPONE: CHLOE: } " body to emit compiled template code."
194 "Extracting attributes from the XML tag:"
195 { $subsection required-attr }
196 { $subsection optional-attr }
197 { $subsection compile-attr }
198 "Examining tag nesting:"
199 { $subsection tag-stack }
200 "Generating code for printing strings and calling quotations:"
201 { $subsection [write] }
202 { $subsection [code] }
203 "Generating code from child elements:"
204 { $subsection process-children }
205 { $subsection compile-children>string }
206 { $subsection compile-with-scope }
207 "Examples which illustrate some of the above:"
208 { $subsection "html.templates.chloe.extend.tags.example" } ;
210 ARTICLE: "html.templates.chloe.extend.tags.example" "Examples of custom Chloe tags"
211 "As a first example, let's develop a custom Chloe tag which simply renders a random number. The tag will be used as follows:"
213 "<t:random t:min='10' t:max='20' t:generator='system' />"
215 "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" } "."
217 "Here is the " { $link POSTPONE: USING: } " form that we need for the below code to work:"
219 "USING: combinators kernel math.parser math.ranges random"
220 "html.templates.chloe.compiler html.templates.chloe.syntax ;"
222 "We write a word which extracts the relevant attributes from an XML tag:"
224 ": random-attrs ( tag -- min max generator )"
225 " [ \"min\" required-attr string>number ]"
226 " [ \"max\" required-attr string>number ]"
227 " [ \"generator\" optional-attr ]"
230 "Next, we convert a random generator name into a random generator object:"
232 ": string>random-generator ( string -- generator )"
234 " { \"default\" [ random-generator ] }"
235 " { \"system\" [ system-random-generator ] }"
236 " { \"secure\" [ secure-random-generator ] }"
239 "Finally, we can write our Chloe tag:"
242 " random-attrs string>random-generator"
245 " [ [a,b] random present write ]"
246 " with-random-generator"
249 "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:"
251 "<t:repeat t:times='n'>Hello world.<br /></t:repeat>"
253 "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."
256 " [ \"times\" required-attr compile-attr ]"
257 " [ [ times ] process-children ]"
261 ARTICLE: "html.templates.chloe.extend.components.example" "An example of a custom Chloe component"
262 "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:"
263 { $code "SINGLETON: image" }
264 "Now we define a method on the " { $link render* } " generic word which renders the image using " { $vocab-link "html.elements" } ":"
265 { $code "M: image render* 2drop <img =src img/> ;" }
266 "Finally, we can define a Chloe component:"
267 { $code "COMPONENT: image" }
268 "We can use it as follows, assuming the current form has a value named " { $snippet "image" } ":"
269 { $code "<t:image t:name='image' />" } ;
271 ARTICLE: "html.templates.chloe.extend.components" "Extending Chloe with custom components"
272 "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" } ":"
273 { $subsection POSTPONE: COMPONENT: }
274 { $subsection "html.templates.chloe.extend.components.example" } ;
276 ARTICLE: "html.templates.chloe" "Chloe templates"
277 "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."
278 { $subsection <chloe> }
279 { $subsection reset-cache }
280 { $subsection "html.templates.chloe.tags" }
281 { $subsection "html.templates.chloe.extend" } ;
283 ABOUT: "html.templates.chloe"