XSLTools

docs/in-page-updates.html

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