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;"> '{$application-url}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;">'{$application-url}comments'</span></dt> 142 <dd>The URL which will serve the in-page update requested by this 143 field. We use a special variable called <code>application-url</code> 144 which will need to be provided to the template when generating the Web 145 page. This will produce something like the following:<br /> 146 <pre>http://localhost:8080/comments</pre> 147 So the request for an in-page update will be sent to this 148 generated URL.</dd> 149 <dt><span style="font-weight: bold;">'{template:list-attribute('type-enum', 150 'value')}'</span></dt> 151 <dd>The fields which are going to be used in the processing of the 152 update. Since the presence of the comment field depends on a 153 specific <code>type</code> element and its <code>type-enum</code> 154 elements' <code>value</code> attributes, we specify the names of 155 the fields which yield these values.</dd> 156 <dt><span style="font-weight: bold;">'{template:other-elements(../options)}'</span></dt> 157 <dd>The region which is to be updated. Here, we recall that we 158 defined the region using a special reference to the <code>options</code> 159 element holding <code>comment</code> element. Thus, we use a 160 special value which also refers to that element from the context of 161 the <code>type</code> element.</dd> 162 <dt><span style="font-weight: bold;">'{template:child-attribute('value', 163 template:child-element('comment', 1, 164 template:other-elements(../options)))}'</span></dt> 165 <dd>Even when the types are changed, it may be the case that an 166 exposed comment field does not disappear (for example, if we already 167 have "Personal" selected but select "Important" in addition), and so we 168 need to provide the details of the field which holds the value of the 169 comment text. We find such details by referencing the <code>options</code> 170 element from the <code>type</code> element and stating that we want 171 the <code>value</code> attribute on any <code>comment</code> 172 element that may exist. Note that we cannot reference the <code>comment</code> 173 element directly since it may not exist at first, but then come into 174 being after an update, but not be referenced here in this parameter; 175 therefore, we need to make up the final part of the reference using the 176 special <code>template:child-attribute</code> 177 and <code>template:child-element</code> functions.</dd> 178 <dt><span style="font-weight: bold;">'/structure/item/options'</span></dt> 179 <dd>Finally, we need to provide some context to the application to 180 tell it something about where in the complete form data structure the 181 updated information resides.</dd> 182 </dl> 183 <p>Of course, all this is pretty complicated and at some point in the 184 future, a simplified way of triggering in-page updates will be 185 introduced.</p> 186 <h3>Updating the Web Application</h3> 187 <p>To support both normal requests for Web pages and the special 188 in-page requests, we must make some modifications to the Web 189 application. First, we must introduce some infrastructure to handle the 190 requests for the JavaScript files separately from the requests for 191 pages from our application. Some standard WebStack resources can be 192 used to help with this, and we add some imports at the top of our 193 source file:</p> 194 <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 195 style="font-weight: bold;"># Site map imports.</span><br 196 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 197 style="font-weight: bold;">from WebStack.Resources.ResourceMap import MapResource</span><br 198 style="font-weight: bold;" /><span style="font-weight: bold;">from WebStack.Resources.Static import DirectoryResource</span></pre> 199 <p>Then, we define the resource class as <a href="Web-resource.html">before</a>, 200 but with an additional attribute:</p> 201 <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 202 style="font-weight: bold;"> in_page_resources = {</span><br 203 style="font-weight: bold;" /><span style="font-weight: bold;"> "comments" : ("structure_output_comments.xsl", "comment-node")</span><br 204 style="font-weight: bold;" /><span style="font-weight: bold;"> }</span></pre> 205 <p>This new attribute provides information about the in-page request to 206 retrieve comment regions of the Web form, and it consists of the 207 stylesheet filename that will be generated to produce the page 208 fragments for such comment regions, along with the region marker that 209 we defined above.</p> 210 <p>The <code>respond_to_form</code> method now also includes some 211 additional code:</p> 212 <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 213 style="font-weight: bold;">in_page_resource = self.get_in_page_resource(trans)</span><br 214 style="font-weight: bold;" /><span style="font-weight: bold;"> parameters = form.get_parameters()</span><br /> documents = form.get_documents()<br /></pre> 215 <p>Here, we find out whether an in-page update is requested, along with 216 the raw parameters of the request, some of which will be used later on 217 in the method.</p> 218 <p>The discovery of the form data structure and the addition and 219 removal of elements happens as before, as does the merging of type 220 values and the comment field, if applicable:</p> 221 <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> 222 <p>The significant changes begin when presenting the result of the 223 request processing:</p> 224 <pre> # Start the response.<br /><br /> trans.set_content_type(WebStack.Generic.ContentType("application/xhtml+xml", self.encoding))<br /><br /><span 225 style="font-weight: bold;"> # Define the stylesheet parameters.</span><br 226 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 227 style="font-weight: bold;"> stylesheet_parameters = {}</span><br 228 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 229 style="font-weight: bold;"> # Ensure that an output stylesheet exists.</span><br 230 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 231 style="font-weight: bold;"> if in_page_resource in self.in_page_resources.keys():</span><br 232 style="font-weight: bold;" /><span style="font-weight: bold;"> trans_xsl = self.prepare_fragment("structure", in_page_resource)</span><br 233 style="font-weight: bold;" /><span style="font-weight: bold;"> element_path = parameters.get("element-path", [""])[0]</span><br 234 style="font-weight: bold;" /><span style="font-weight: bold;"> stylesheet_parameters["element-path"] = element_path</span><br 235 style="font-weight: bold;" /><span style="font-weight: bold;"> else:</span><br 236 style="font-weight: bold;" /><span style="font-weight: bold;"> trans_xsl = self.prepare_output("structure")</span></pre> 237 <p>Instead of just obtaining a stylesheet for the <code>structure</code> 238 document, we instead check to see if an in-page update is being 239 requested and, if so, prepare the stylesheet representing the fragment 240 of the Web form to be presented. Additionally, we obtain a special <code>element-path</code> 241 parameter directly from the request parameters; this parameter is added 242 to a collection of parameters that will be used to control the 243 stylesheet when making the final Web page output.</p> 244 <p>Another parameter that will be used in stylesheet processing is 245 the <code>application-url</code> parameter mentioned above. We 246 obtain the address and port of the Web server environment and add the 247 result as a simple URL to the <code>application-url</code> 248 stylesheet parameter. Finally, we send the output to the user but 249 employing the additional stylesheet parameters to configure the result:</p> 250 <pre><span style="font-weight: bold;"> # Add information essential for in-page requests.</span><br 251 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 252 style="font-weight: bold;"> if trans.get_server_port() == "80":</span><br 253 style="font-weight: bold;" /><span style="font-weight: bold;"> stylesheet_parameters["application-url"] = \</span><br 254 style="font-weight: bold;" /><span style="font-weight: bold;"> "http://%s%s" % (trans.get_server_name(), trans.get_path_without_query())</span><br 255 style="font-weight: bold;" /><span style="font-weight: bold;"> else:</span><br 256 style="font-weight: bold;" /><span style="font-weight: bold;"> stylesheet_parameters["application-url"] = \</span><br 257 style="font-weight: bold;" /><span style="font-weight: bold;"> "http://%s:%s%s" % (trans.get_server_name(), trans.get_server_port(), trans.get_path_without_query())</span><br /><br /> # Complete the response.<br /><br /> self.send_output(trans, [trans_xsl], structure<span 258 style="font-weight: bold;">, stylesheet_parameters</span>)</pre> 259 <p>In order to introduce the infrastructure mentioned above which 260 separates requests for Web pages from requests for JavaScript files, we 261 need to provide a more sophisticated implementation of the <code>get_site</code> 262 function:</p> 263 <pre><span style="font-weight: bold;"># Site map initialisation.</span><br 264 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 265 style="font-weight: bold;">def get_site():</span><br 266 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 267 style="font-weight: bold;"> "Return a simple Web site resource."</span><br 268 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 269 style="font-weight: bold;"> # Get the main resource and the directory used by the application.</span><br 270 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 271 style="font-weight: bold;"> very_simple_resource = VerySimpleResource()</span><br 272 style="font-weight: bold;" /><span style="font-weight: bold;"> directory = very_simple_resource.resource_dir</span><br 273 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 274 style="font-weight: bold;"> # Make a simple Web site.</span><br 275 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 276 style="font-weight: bold;"> resource = MapResource({</span><br 277 style="font-weight: bold;" /><span style="font-weight: bold;"> # Static resources:</span><br 278 style="font-weight: bold;" /><span style="font-weight: bold;"> "scripts" : DirectoryResource(os.path.join(directory, "scripts"), {"js" : "text/javascript"}),</span><br 279 style="font-weight: bold;" /><span style="font-weight: bold;"> # Main page and in-page resources:</span><br 280 style="font-weight: bold;" /><span style="font-weight: bold;"> None : very_simple_resource</span><br 281 style="font-weight: bold;" /><span style="font-weight: bold;"> })</span><br 282 style="font-weight: bold;" /><br style="font-weight: bold;" /><span 283 style="font-weight: bold;"> return resource</span></pre> 284 <p>What this does is to create a resource for the application, as 285 before, but then to place the resource into a special WebStack resource 286 which examines the path or URL on the incoming requests and directs 287 such requests according to the following scheme:</p> 288 <ul> 289 <li>If the request mentions something under <code>scripts</code> 290 in its URL, we employ the WebStack <code>DirectoryResource</code> to 291 send the file from the <code>scripts</code> subdirectory of the 292 application's <code>Resources</code> directory.</li> 293 <li>Otherwise, we pass the request on to our application resource in 294 order to produce a Web page for the user.</li> 295 </ul> 296 <p>Thus, when the user's browser asks for a script file, it gets a 297 script file; otherwise it gets a Web page showing either all of the 298 form (if a normal request is received), or a part of the form (if an 299 in-page request is received).</p> 300 </body> 301 </html>