XSLTools

docs/in-page-updates.html

389:c78d5648e820
2005-11-08 paulb [project @ 2005-11-08 19:01:58 by paulb] Added scripts (although they are not yet used).
     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">     2 <html xmlns="http://www.w3.org/1999/xhtml"><head>     3   <meta content="text/html;charset=ISO-8859-1" http-equiv="Content-Type" />     4        5   <title>Creating Applications: In-Page Updates</title><meta name="generator" content="amaya 8.1a, see http://www.w3.org/Amaya/" />     6   <link href="styles.css" rel="stylesheet" type="text/css" /></head>     7 <body>     8 <h1>Creating Applications: In-Page Updates</h1>     9 <p>One fashionable avenue in Web application design has been that of    10 updating Web pages in applications without having to refresh the entire    11 page every time an action is performed. Together with some JavaScript    12 support in the browser, XSLForms also provides some functionality for    13 such "in-page" or "live" updates.</p>    14 <p>Consider the addition of a comment field to our application. Here is    15 how the HTML code might look:</p>    16 <pre>&lt;div template:element="item"&gt;<br />  &lt;p&gt;<br />    Some item: &lt;input template:attribute-field="value" name="..." type="text" value="..." /&gt;<br />    &lt;input name="..." template:selector-field="remove" type="submit" value="Remove" /&gt;<br />  &lt;/p&gt;<br />  &lt;p&gt;<br />    Item type:<br />    &lt;select template:multiple-choice-list-field="type,type-enum,value" name="..." multiple="multiple"&gt;<br />      &lt;option template:multiple-choice-list-value="type-enum,value,selected" value="..." /&gt;<br />    &lt;/select&gt;<br />  &lt;/p&gt;<br />  <span style="font-weight: bold;">&lt;p template:element="options"&gt;</span><br style="font-weight: bold;" /><span style="font-weight: bold;">    &lt;span </span><span style="font-weight: bold;">template:element="comment"&gt;</span><span style="font-weight: bold;">Comment:</span><br style="font-weight: bold;" /><span style="font-weight: bold;">      &lt;textarea template:attribute-area="value,insert" name="..." cols="40" rows="3"&gt;</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        Some comment</span><br style="font-weight: bold;" /><span style="font-weight: bold;">      &lt;/textarea&gt;<br />    &lt;/span&gt;<br style="font-weight: bold;" /></span><span style="font-weight: bold;">  &lt;/p&gt;</span><br />  &lt;p&gt;<br />    Itself containing more items:<br />  &lt;/p&gt;<br />  &lt;p template:element="subitem"&gt;<br />    Sub-item: &lt;input template:attribute-field="subvalue" name="..." type="text" value="..." /&gt;<br />    &lt;input name="..." template:selector-field="remove2" type="submit" value="Remove" /&gt;<br />  &lt;/p&gt;<br />  &lt;p&gt;<br />    &lt;input name="..." template:selector-field="add2,subitem" type="submit" value="Add subitem" /&gt;<br />  &lt;/p&gt;<br />&lt;/div&gt;</pre>    17 <p>Here, a&nbsp;<code>textarea</code> element has been added with a special&nbsp;<code>template:attribute-area</code> annotation being used to state that the contents of the element are to be mapped to the <code>value</code>&nbsp;attribute, and that the attribute contents are to be inserted inside the&nbsp;<code>textarea</code> element (replacing the <code>Some Comment</code> text).</p><p>The newly-added&nbsp;<code>textarea</code> field might actually&nbsp;be    18 presented in the application in its current state, despite the&nbsp;lack of any <code>options</code> or <code>comment</code>    19 elements    20 manipulated by the    21 application, due to the document initialisation mechanism employed by    22 the application. However, what would be more interesting is the    23 possibility of only showing the comment field if something else in the    24 document had a certain value or state.</p>    25 <p>Let us imagine that if the type of an item was set to "Personal",    26 the comment field would appear and permit the recording of some text    27 for that item. One approach that would make this possible&nbsp;is to    28 add a transformation which checks the type values set for each of the    29 items and removes the <code>options</code> and <code>comment</code> elements for items which do not qualify. In the Web resource, we&nbsp;make the following    30 changes:</p>    31 <pre>    transform_resources = {<br />        "comments" : ["structure_comments.xsl"]<br />        }</pre>    32 <p>What this does is to state that when we carry out the <code>comments</code>    33 transformation, the specified stylesheet is employed, filtering out the    34 comments for non-qualifying items and preserving them for qualifying    35 items.</p><p>Further down in the code, we add a transformation:</p><pre>        # After the document initialisation...<br /><br />        # Add the comments.<br /><br />        comments_xsl_list = self.prepare_transform("comments")<br />        structure = self.get_result(comments_xsl_list, structure)</pre>    36 <p>This new stylesheet works according to the following principles:</p>    37 <ol>    38   <li>Descend into the form data structure, copying all elements,    39 attributes and text that the stylesheet is not programmed to recognise.</li>    40   <li>When encountering an&nbsp;<code>item</code> element (which the    41 stylesheet is programmed to recognise), do the following:<br />    42     <ol>    43       <li>Copy the&nbsp;element "skeleton" and its attributes so that    44 the&nbsp;<code>value</code> attribute is retained.</li>    45       <li>Produce a new <code>options</code> element and process it.</li>    46     </ol>    47   </li>    48   <li>When processing a new <code>options</code> element, do the    49 following:<br />    50     <ol>    51       <li>Inside this new <code>options</code> element, investigate    52 the values associated with the&nbsp;<code>type</code> element.</li>    53       <li>If any of the selected type values is "Personal", make a new <code>comment</code>    54 element, then&nbsp;add&nbsp;any attributes that may be found on    55 existing <code>comment</code> elements within the current <code>type</code>    56 element.</li>    57     </ol>    58   </li>    59 </ol>    60 <p>Since this stylesheet is used after the document initialisation,    61 we may (and even must) take advantage of the results of that activity, including noting that selected values on <code>type-enum</code>    62 elements are marked with the <code>value-is-set</code> attribute.</p>    63 <p>The stylesheet source code can be found in&nbsp;<code>examples/Common/VerySimple/Resources/structure_comments.xsl</code>.</p>    64 <h2>Limitations and Enhancements</h2>    65 <p>Whilst the above modifications adds a comment field for each item    66 with a type of "Personal", and whilst the comment field will appear and    67 disappear for items as their type changes, such updates only take place    68 when items and subitems are added and removed. We could add an update    69 button to the page which performs an explicit refresh of the page    70 without adding or removing anything, and for the sake of usability, we    71 probably should add such a button (just below the&nbsp;<code>Add item</code>    72 button):</p>    73 <pre>&lt;p&gt;<br />  &lt;input name="update" type="submit" value="Update" /&gt;<br />&lt;/p&gt;</pre>    74 <p>However, we could also add an in-page update to make each comments    75 field appear and disappear as soon as we have changed the type of an    76 item.</p>    77 <h3>Template Changes</h3>    78 <p>We must first define a region of the template where a comment fields    79 can be added and removed, regardless of whether such a field existed    80 there before. The above template code needs modifying slightly to    81 permit this:</p>    82 <pre>  <span style="font-weight: bold;">&lt;p template:element="options" template:id="comment-node" id="{template:this-element()}"&gt;</span><br style="font-weight: bold;" /><span style="font-weight: bold;">    &lt;span template:element="comment"&gt;</span>Comment:<br />      &lt;textarea template:attribute-area="value,insert" name="..." cols="40" rows="3"&gt;<br />        Some comment<br />      &lt;/textarea&gt;<br />    <span style="font-weight: bold;">&lt;/span&gt;</span><br />  &lt;/p&gt;</pre>    83 <p>Here, we have added this region definition to the paragraph    84 surrounding the comment field, annotating the paragraph with the    85 following attributes:</p>    86 <ul>    87   <li>The&nbsp;<code>template:id</code> attribute is used to define a    88 template fragment used only to prepare the updated part of the Web    89 page. Here we define the fragment or region as being just this    90 paragraph.</li>    91   <li>The standard HTML&nbsp;<code>id</code> attribute is used to    92 define which part of the active Web page will be replaced when    93 performing an in-page update. This attribute needs to have a unique    94 value, but the easiest basis for such a value is a selector-style    95 reference to the <code>options</code> element within which the&nbsp;<code>comment</code>    96 element resides.</li>    97 </ul>    98 <p>Another change has been to put the&nbsp;<code>template:element</code>    99 annotation inside the above fragment or region annotations. Had we not   100 done this, the lack of a&nbsp;<code>comment</code> element in the form   101 data could have prevented the&nbsp;<code>id</code> attribute from   102 appearing in the Web page, this preventing any hope of an in-page   103 update since there would be no way of knowing where such an update   104 should be applied.</p>   105 <h3>Adding JavaScript</h3>   106 <p>Since we rely on JavaScript support in the browser, the following   107 references to scripts must also be added to the template, as shown in   108 the following excerpt:</p>   109 <pre>&lt;head&gt;<br />  &lt;title&gt;Example&lt;/title&gt;<br />  <span style="font-weight: bold;">&lt;script type="text/javascript" src="scripts/sarissa.js"&gt; &lt;/script&gt;</span><br style="font-weight: bold;" /><span style="font-weight: bold;">  &lt;script type="text/javascript" src="scripts/XSLForms.js"&gt; &lt;/script&gt;</span><br />&lt;/head&gt;</pre>   110 <p>These special script files can be found in&nbsp;<code>examples/Common/VerySimple/Resources/scripts</code>.</p>   111 <p>Now we can concentrate on adding the event which triggers an in-page   112 update. Since it is the type values that cause each comment field to be   113 added or removed, we add an event attribute on the form field   114 responsible for displaying the type values:</p>   115 <pre>  &lt;p&gt;<br />    Item type:<br />    &lt;select template:multiple-choice-list-field="type,type-enum,value" name="..." multiple="multiple"<br />      <span style="font-weight: bold;">onchange="requestUpdate(</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        'comments',</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        '{template:list-attribute('type-enum', 'value')}',</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        '{template:other-elements(../options)}',</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        '{template:child-attribute('value', template:child-element('comment', 1, template:other-elements(../options)))}',</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        '/structure/item/options')"</span>&gt;<br />      &lt;option template:multiple-choice-list-value="type-enum,value,selected" value="..." /&gt;<br />    &lt;/select&gt;<br />  &lt;/p&gt;</pre>   116 <p>This complicated string calls a special update request JavaScript   117 function which triggers the in-page update, and it specifies the   118 following things:</p>   119 <dl>   120   <dt><span style="font-weight: bold;">'comments'</span></dt>   121   <dd>The URL which will serve the in-page update requested by this   122 field. Since the value stated is a relative reference to a resource, it   123 will produce something like the following:<br />   124     <pre>http://localhost:8080/comments</pre>   125 So the request for an in-page update will be sent to this   126 generated URL.</dd>   127   <dt><span style="font-weight: bold;">'{template:list-attribute('type-enum',   128 'value')}'</span></dt>   129   <dd>The fields which are going to be used in the processing of the   130 update. Since the presence of the comment field depends on a   131 specific&nbsp;<code>type</code> element and its&nbsp;<code>type-enum</code>   132 elements'&nbsp;<code>value</code> attributes, we specify the names of   133 the fields which yield these values.</dd>   134   <dt><span style="font-weight: bold;">'{template:other-elements(../options)}'</span></dt>   135   <dd>The region which is to be updated. Here, we recall that we   136 defined the region using a special reference to the <code>options</code>   137 element holding&nbsp;<code>comment</code> element. Thus, we use a   138 special value which also refers to that element from the context of   139 the&nbsp;<code>type</code> element.</dd>   140   <dt><span style="font-weight: bold;">'{template:child-attribute('value',   141 template:child-element('comment', 1,   142 template:other-elements(../options)))}'</span></dt>   143   <dd>Even when the types are changed, it may be the case that an   144 exposed comment field does not disappear (for example, if we already   145 have "Personal" selected but select "Important" in addition), and so we   146 need to provide the details of the field which holds the value of the   147 comment text. We find such details by referencing the <code>options</code>   148 element from the <code>type</code> element and stating that we want   149 the <code>value</code> attribute on any <code>comment</code>   150 element that may exist. Note that we cannot reference the <code>comment</code>   151 element directly since it may not exist at first, but then come into   152 being after an update, but not be referenced here in this parameter;   153 therefore, we need to make up the final part of the reference using the   154 special <code>template:child-attribute</code>   155 and <code>template:child-element</code> functions.</dd>   156   <dt><span style="font-weight: bold;">'/structure/item/options'</span></dt>   157   <dd>Finally, we need to provide some context to the application to   158 tell it something about where in the complete form data structure the   159 updated information resides.</dd>   160 </dl>   161 <p>Of course, all this is pretty complicated and at some point in the   162 future, a simplified way of triggering in-page updates will be   163 introduced.</p>   164 <h3>Updating the Web Application</h3>   165 <p>To support both normal requests for Web pages and the special   166 in-page requests, we must make some modifications to the Web   167 application. First, we must introduce some infrastructure to handle the   168 requests for the JavaScript files separately from the requests for   169 pages from our application. Some standard WebStack resources can be   170 used to help with this, and we add some imports at the top of our   171 source file:</p>   172 <pre>#!/usr/bin/env python<br /><br />"A very simple example application."<br /><br />import WebStack.Generic<br />import XSLForms.Resources.WebResources<br />import XSLForms.Utils<br />import os<br /><br /><span style="font-weight: bold;"># Site map imports.</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">from WebStack.Resources.ResourceMap import MapResource</span><br style="font-weight: bold;" /><span style="font-weight: bold;">from WebStack.Resources.Static import DirectoryResource</span></pre>   173 <p>Then, we define the resource class as <a href="Web-resource.html">before</a>,   174 but with an additional attribute:</p>   175 <pre># Resource classes.<br /><br />class VerySimpleResource(XSLForms.Resources.WebResources.XSLFormsResource):<br /><br />    "A very simple resource providing a hierarchy of editable fields."<br /><br />    resource_dir = os.path.join(os.path.split(__file__)[0], "Resources")<br />    encoding = "utf-8"<br />    template_resources = {<br />        "structure" : ("structure_multivalue_template.xhtml", "structure_output.xsl")<br />        }<br />    init_resources = {<br />        "structure" : ("structure_multivalue_template.xhtml", "structure_input.xsl")<br />        }<br />    transform_resources = {<br />        "comments" : ["structure_comments.xsl"]<br />        }<br />    document_resources = {<br />        "types" : "structure_types.xml"<br />        }<br /><span style="font-weight: bold;">    in_page_resources = {</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        "comments" : ("structure_output_comments.xsl", "comment-node")</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        }</span></pre>   176 <p>This new attribute provides information about the in-page request to   177 retrieve comment regions of the Web form, and it consists of the   178 stylesheet filename that will be generated to produce the page   179 fragments for such comment regions, along with the region marker that   180 we defined above.</p>   181 <p>The&nbsp;<code>respond_to_form</code> method now also includes some   182 additional code:</p>   183 <pre>    def respond_to_form(self, trans, form):<br /><br />        """<br />        Respond to a request having the given transaction 'trans' and the given<br />        'form' information.<br />        """<br /><br />        <span style="font-weight: bold;">in_page_resource = self.get_in_page_resource(trans)</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        parameters = form.get_parameters()</span><br />        documents = form.get_documents()<br /></pre>   184 <p>Here, we find out whether an in-page update is requested, along with   185 the raw parameters of the request, some of which will be used later on   186 in the method.</p>   187 <p>The discovery of the form data structure and the addition and   188 removal of elements happens as before, as does the merging of type   189 values and the comment field, if applicable:</p>   190 <pre>        # Ensure the presence of a document.<br /><br />        if documents.has_key("structure"):<br />            structure = documents["structure"]<br />        else:<br />            structure = form.new_instance("structure")<br /><br />        # Add and remove elements according to the selectors found.<br /><br />        selectors = form.get_selectors()<br />        XSLForms.Utils.remove_elements(selectors.get("remove2"))<br />        XSLForms.Utils.add_elements(selectors.get("add2"), "subitem")<br />        XSLForms.Utils.remove_elements(selectors.get("remove"))<br />        XSLForms.Utils.add_elements(selectors.get("add"), "item")<br /><br />        # Initialise the document, adding enumerations/ranges.<br /><br />        structure_xsl = self.prepare_initialiser("structure")<br />        types_xml = self.prepare_document("types")<br />        structure = self.get_result([structure_xsl], structure, references={"type" : types_xml})<br /><br />        # Add the comments.<br /><br />        comments_xsl_list = self.prepare_transform("comments")<br />        structure = self.get_result(comments_xsl_list, structure)<br /></pre>   191 <p>The significant changes begin when presenting the result of the   192 request processing:</p>   193 <pre>        # Start the response.<br /><br />        trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding))<br /><br /><span style="font-weight: bold;"></span><span style="font-weight: bold;">        # Ensure that an output stylesheet exists.</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">        if in_page_resource in self.in_page_resources.keys():</span><br style="font-weight: bold;" /><span style="font-weight: bold;">            trans_xsl = self.prepare_fragment("structure", in_page_resource)</span><span style="font-weight: bold;"></span><br style="font-weight: bold;" /><span style="font-weight: bold;">            stylesheet_parameters = self.prepare_parameters(parameters)</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        else:</span><br style="font-weight: bold;" /><span style="font-weight: bold;">            trans_xsl = self.prepare_output("structure")<br /></span>    <span style="font-weight: bold;"></span><span style="font-weight: bold;">        stylesheet_parameters = {}</span></pre>   194 <p>Instead of just obtaining a stylesheet for the&nbsp;<code>structure</code>   195 document, we instead check to see if an in-page update is being   196 requested and, if so, prepare the stylesheet representing the fragment   197 of the Web form to be presented. Additionally, we obtain special stylesheet parameters using the raw request parameters; this introduces information that will be used to control the   198 stylesheet when making the final Web page output.</p>   199 <p>Finally, we send the output to the user but employing the additional   200 stylesheet parameters to configure the result:</p>   201 <pre><span style="font-weight: bold;">        # Complete the response.<br /><br />        self.send_output(trans, [trans_xsl], structure<span style="font-weight: bold;">, stylesheet_parameters</span>)</span></pre>   202 <p>In order to introduce the infrastructure mentioned above which   203 separates requests for Web pages from requests for JavaScript files, we   204 need to provide a more sophisticated implementation of the&nbsp;<code>get_site</code>   205 function:</p>   206 <pre><span style="font-weight: bold;"># Site map initialisation.</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">def get_site():</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">    "Return a simple Web site resource."</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">    # Get the main resource and the directory used by the application.</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">    very_simple_resource = VerySimpleResource()</span><br style="font-weight: bold;" /><span style="font-weight: bold;">    directory = very_simple_resource.resource_dir</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">    # Make a simple Web site.</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">    resource = MapResource({</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        # Static resources:</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        "scripts" : DirectoryResource(os.path.join(directory, "scripts"), {"js" : "text/javascript"}),</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        # Main page and in-page resources:</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        None : very_simple_resource</span><br style="font-weight: bold;" /><span style="font-weight: bold;">        })</span><br style="font-weight: bold;" /><br style="font-weight: bold;" /><span style="font-weight: bold;">    return resource</span></pre>   207 <p>What this does is to create a resource for the application, as   208 before, but then to place the resource into a special WebStack resource   209 which examines the path or URL on the incoming requests and directs   210 such requests according to the following scheme:</p>   211 <ul>   212   <li>If the request mentions something under&nbsp;<code>scripts</code>   213 in its URL, we employ the WebStack <code>DirectoryResource</code> to   214 send the file from the&nbsp;<code>scripts</code> subdirectory of the   215 application's <code>Resources</code> directory.</li>   216   <li>Otherwise, we pass the request on to our application resource in   217 order to produce a Web page for the user.</li>   218 </ul>   219 <p>Thus, when the user's browser asks for a script file, it gets a   220 script file; otherwise it gets a Web page showing either all of the   221 form (if a normal request is received), or a part of the form (if an   222 in-page request is received).</p>   223 </body></html>