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><div template:element="item"><br /> <p><br /> Some item: <input template:attribute="value" name="{template:this-attribute()}" type="text" value="{$this-value}" /><br /> <input name="remove={template:this-element()}" type="submit" value="Remove" /><br /> </p><br /> <p><br /> Item type:<br /> <select template:element="type" name="{template:list-attribute('type-enum', 'value')}" multiple="multiple"><br /> <option template:element="type-enum" template:expr="@value-is-set" template:expr-attr="selected"<br /> template:value="@value" value="{@value}" /><br /> </select><br /> </p><br /> <span 20 style="font-weight: bold;"><p template:element="options"></span><br 21 style="font-weight: bold;" /><span style="font-weight: bold;"> <span </span><span 22 style="font-weight: bold;">template:element="comment"></span><span 23 style="font-weight: bold;">Comment:</span><br 24 style="font-weight: bold;" /><span style="font-weight: bold;"> <textarea template:attribute="value" name="{template:this-attribute()}" cols="40" rows="3"></span><br 25 style="font-weight: bold;" /><span style="font-weight: bold;"> <span template:value="$this-value" template:effect="replace">Some comment</span></span><br 26 style="font-weight: bold;" /><span style="font-weight: bold;"> </textarea><br /> </span><br 27 style="font-weight: bold;" /></span><span style="font-weight: bold;"> </p></span><br /> <p><br /> Itself containing more items:<br /> </p><br /> <p template:element="subitem"><br /> Sub-item: <input template:attribute="subvalue" name="{template:this-attribute()}" type="text" value="{$this-value}" /><br /> <input name="remove2={template:this-element()}" type="submit" value="Remove" /><br /> </p><br /> <p><br /> <input name="add2={template:this-element()}" type="submit" value="Add subitem" /><br /> </p><br /></div></pre> 28 <p>The newly-added <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 additional work to add such <code>options</code> 34 and <code>comment</code> 35 elements 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 <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 <code>item</code> element (which the 51 stylesheet is programmed to recognise), do the following:<br /> 52 <ol> 53 <li>Copy the element "skeleton" and its attributes so that 54 the <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 <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 add 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 <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 <code>Add item</code> 83 button):</p> 84 <pre><p><br /> <input name="update" type="submit" value="Update" /><br /></p></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;"><p template:element="options" template:id="comment-node" id="{template:this-element()}"></span><br 94 style="font-weight: bold;" /><span style="font-weight: bold;"> <span template:element="comment"></span>Comment:<br /> <textarea template:attribute="value" name="{template:this-attribute()}" cols="40" rows="3"><br /> <span template:value="$this-value" template:effect="replace">Some comment</span><br /> </textarea><br /> <span 95 style="font-weight: bold;"></span></span><br /> </p></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 <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 <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 <code>comment</code> 109 element resides.</li> 110 </ul> 111 <p>Another change has been to put the <code>template:element</code> 112 annotation inside the above fragment or region annotations. Had we not 113 done this, the lack of a <code>comment</code> element in the form 114 data could have prevented the <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><head><br /> <title>Example</title><br /> <span 123 style="font-weight: bold;"><script type="text/javascript" src="scripts/sarissa.js"> </script></span><br 124 style="font-weight: bold;" /><span style="font-weight: bold;"> <script type="text/javascript" src="scripts/XSLForms.js"> </script></span><br /></head></pre> 125 <p>These special script files can be found in <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> <p><br /> Item type:<br /> <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>><br /> <option template:element="type-enum" template:expr="@value-is-set" template:expr-attr="selected"<br /> template:value="@value" value="{@value}" /><br /> </select><br /> </p></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 <code>type</code> element and its <code>type-enum</code> 153 elements' <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 <code>comment</code> element. Thus, we use a 159 special value which also refers to that element from the context of 160 the <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 <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 <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 <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 <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 <code>scripts</code> 278 in its URL, we employ the WebStack <code>DirectoryResource</code> to 279 send the file from the <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>