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