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