picturepile.html   [plain text]


<?xml version="1.0"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title>Twisted Documentation: PicturePile: a tutorial Woven application</title><link href="../howto/stylesheet.css" type="text/css" rel="stylesheet" /></head><body bgcolor="white"><h1 class="title">PicturePile: a tutorial Woven application</h1><div class="toc"><ol><li><a href="#auto0">Custom Views</a></li><li><a href="#auto1">Simple Input Handling</a></li><li><a href="#auto2">Sessions</a></li><ul><li><a href="#auto3">Controllers</a></li></ul></ol></div><div class="content"><span></span><div class="note"><strong>Note: </strong><p>
The PicturePile tutorial illustrates a simple Woven web application. However, the Woven framework should not be used for new projects. The newer <a href="http://www.divmod.org/Home/Projects/Nevow/">Nevow</a> framework, available as part of the <a href="http://www.divmod.org/Quotient/">Quotient</a> project, is a simpler framework with consistent semantics and better testing and is strongly recommended over Woven.
</p><p>
The tutorial is maintained only for users with an existing Woven codebase.
</p></div><p>To illustrate the basic design of a Woven app, we're going to walk through
building a simple image gallery. Given a directory of images, it will display a
listing of that directory; when a subdirectory or image is clicked on, it will
be displayed.</p><p>To begin, we write an HTML template for the directory index, and save it as directory-listing.html:</p><pre>
&lt;html&gt;
  &lt;head&gt;
    &lt;title model=&quot;title&quot; view=&quot;Text&quot;&gt;Directory listing&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1 model=&quot;title&quot; view=&quot;Text&quot;&gt;&lt;/h1&gt;
    &lt;ul model=&quot;directory&quot; view=&quot;List&quot;&gt;
      &lt;li pattern=&quot;listItem&quot;&gt;&lt;a view=&quot;Anchor&quot; /&gt;&lt;/li&gt;
      &lt;li pattern=&quot;emptyList&quot;&gt;This directory is empty.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre><p>The main things that distinguish a Woven template from standard XHTML are the
<code>model</code>, <code>view</code>, and <code>pattern</code> attributes on
tags. Predictably, <code>model</code> and <code>view</code> specify which model
and view will be chosen to fill the corresponding node. The <code>pattern</code>
attribute is used with views that have multiple parts, such as List. This
example uses two patterns <code base="twisted.web.woven.widgets">List</code>
provides; <code>listItem</code> marks the node that will be used as the template
for each item in the list, and <code>emptyList</code> marks the node displayed
when the list has no items.</p><p>Next, we create a <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.page.Page.html" title="twisted.web.woven.page.Page">Page</a></code> that will display the directory listing, filling the template
above (after a few imports):</p><pre class="python">
<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span><span class="py-src-op">,</span> <span class="py-src-variable">internet</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span><span class="py-src-op">.</span><span class="py-src-variable">woven</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">page</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">microdom</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">class</span> <span class="py-src-identifier">DirectoryListing</span><span class="py-src-op">(</span><span class="py-src-parameter">page</span><span class="py-src-op">.</span><span class="py-src-parameter">Page</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-indent">    </span><span class="py-src-variable">templateFile</span> <span class="py-src-op">=</span> <span class="py-src-string">&quot;directory-listing.xhtml&quot;</span><span class="py-src-newline">
</span>    <span class="py-src-variable">templateDirectory</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">split</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">abspath</span><span class="py-src-op">(</span><span class="py-src-variable">__file__</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-keyword">def</span> <span class="py-src-identifier">initialize</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-op">*</span><span class="py-src-parameter">args</span><span class="py-src-op">,</span> <span class="py-src-op">**</span><span class="py-src-parameter">kwargs</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span> <span class="py-src-op">=</span> <span class="py-src-variable">kwargs</span><span class="py-src-op">[</span><span class="py-src-string">'directory'</span><span class="py-src-op">]</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wmfactory_title</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-string">&quot;&quot;&quot;Model factory for the title.

        This method will be called to create the model to use when
        'model=&quot;title&quot;' is found in the template.
        &quot;&quot;&quot;</span>           <span class="py-src-newline">
</span>        <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wmfactory_directory</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-string">&quot;&quot;&quot;Model factory for the directory.

        This method will be called to create the model to use when
        'model=&quot;directory&quot;' is found in the template.
        &quot;&quot;&quot;</span>           <span class="py-src-newline">
</span>        <span class="py-src-variable">files</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">listdir</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">xrange</span><span class="py-src-op">(</span><span class="py-src-variable">len</span><span class="py-src-op">(</span><span class="py-src-variable">files</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">files</span><span class="py-src-op">[</span><span class="py-src-variable">i</span><span class="py-src-op">]</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">                </span><span class="py-src-variable">files</span><span class="py-src-op">[</span><span class="py-src-variable">i</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">files</span><span class="py-src-op">[</span><span class="py-src-variable">i</span><span class="py-src-op">]</span> <span class="py-src-op">+</span> <span class="py-src-string">'/'</span><span class="py-src-newline">
</span>        <span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">return</span> <span class="py-src-variable">files</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">getDynamicChild</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">name</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span>        <span class="py-src-comment"># Protect against malicious URLs like '..'
</span><span class="py-src-indent">        </span><span class="py-src-keyword">if</span> <span class="py-src-variable">static</span><span class="py-src-op">.</span><span class="py-src-variable">isDangerous</span><span class="py-src-op">(</span><span class="py-src-variable">name</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-keyword">return</span> <span class="py-src-variable">static</span><span class="py-src-op">.</span><span class="py-src-variable">dangerousPathError</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>        <span class="py-src-comment"># Return a DirectoryListing or an ImageDisplay resource, depending on
</span>        <span class="py-src-comment"># whether the path corresponds to a directory or to a file
</span>        <span class="py-src-dedent"></span><span class="py-src-variable">path</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">name</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">exists</span><span class="py-src-op">(</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">                </span><span class="py-src-keyword">return</span> <span class="py-src-variable">DirectoryListing</span><span class="py-src-op">(</span><span class="py-src-variable">directory</span><span class="py-src-op">=</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>            <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">                </span><span class="py-src-keyword">return</span> <span class="py-src-variable">ImageDisplay</span><span class="py-src-op">(</span><span class="py-src-variable">image</span><span class="py-src-op">=</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Due to the somewhat complex inheritance hierarchy in Woven's internals, a lot
of processing is done in the <code>__init__</code> method for <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.page.Page.html" title="twisted.web.woven.page.Page">Page</a></code>. Therefore, a separate
<code>initialize</code> method is provided so that one can easily access keyword
args without having to disturb the internal setup; it is called with the same
args that <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.page.Page.__init__.html" title="twisted.web.woven.page.Page.__init__">Page.__init__</a></code>
receives.</p><p>The <code>templateFile</code> attribute tells the Page what file to load the
template from; in this case, we will store the templates in the same directory
as the Python module. The <code>wmfactory</code> (short for Woven Model Factory)
methods return objects to be used as models; In this case,
<code>wmfactory_title</code> will return a string, the directory's name, and
<code>wmfactory_directory</code> will return a list of strings, the directory's
content.</p><p>Upon rendering, Woven will scan the template's DOM tree for nodes to fill;
when it encounters one, it gets the model (in this case by calling methods on
the Page prefixed with <code>wmfactory_</code>), then creates a view for that
model; this page uses standard widgets for its models and so contains no custom
view code. The view fills the DOM node with the appropriate data. Here, the view
for <code>title</code> is <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.widgets.Text.html" title="twisted.web.woven.widgets.Text">Text</a></code>, and so will merely insert the
string.  The view for <code>directory</code> is <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.widgets.List.html" title="twisted.web.woven.widgets.List">List</a></code>, and so each element of the list
will be formatted within the '&lt;ul&gt;'. Since the view for list items is
Anchor, each item in the list will be formatted as an <code>&lt;a&gt;</code>
tag.</p><p>So, for a directory <q>Images</q> containing <q>foo.jpeg</q>,
<q>baz.png</q>, and a directory <q>MoreImages</q>, the rendered page will look
like this:</p><pre>
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;/Users/ashort/Pictures&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;/Users/ashort/Pictures&lt;/h1&gt;
    &lt;ul&gt;
      &lt;li&gt;
        &lt;a href=&quot;foo.jpeg&quot;&gt;foo.jpeg&lt;/a&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;a href=&quot;baz.png&quot;&gt;baz.png&lt;/a&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;a href=&quot;MoreImages/&quot;&gt;MoreImages/&lt;/a&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre><p>As you can see, the nodes marked with <code>model</code> and
<code>view</code> are replaced with the data from their models, as formatted by
their views. In particular, the List view repeated the node marked with the
<code>listItem</code> pattern for each item in the list.</p><p>For displaying the actual images, we use this template, which we save as image-display.html:</p><pre>
&lt;html&gt;
  &lt;head&gt;
    &lt;title model=&quot;image&quot; view=&quot;Text&quot;&gt;Filename&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;img src=&quot;preview&quot; /&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre>

And here is the definition of <code>ImageDisplay</code>:

<pre class="python">
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">class</span> <span class="py-src-identifier">ImageDisplay</span><span class="py-src-op">(</span><span class="py-src-parameter">page</span><span class="py-src-op">.</span><span class="py-src-parameter">Page</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-indent">    </span><span class="py-src-variable">templateFile</span><span class="py-src-op">=</span><span class="py-src-string">&quot;image-display.xhtml&quot;</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-keyword">def</span> <span class="py-src-identifier">initialize</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-op">*</span><span class="py-src-parameter">args</span><span class="py-src-op">,</span> <span class="py-src-op">**</span><span class="py-src-parameter">kwargs</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">image</span> <span class="py-src-op">=</span> <span class="py-src-variable">kwargs</span><span class="py-src-op">[</span><span class="py-src-string">'image'</span><span class="py-src-op">]</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wmfactory_image</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-keyword">return</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">image</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wchild_preview</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-keyword">return</span> <span class="py-src-variable">static</span><span class="py-src-op">.</span><span class="py-src-variable">File</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">image</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Instead of using <code>getDynamicChild</code>, this class uses a
<code>wchild_</code> method to return the image data when the
<code>preview</code> child is requested. <code>getDynamicChild</code> is only
called if there are no <code>wchild_</code> methods available to handle the
requested URL.</p><p>Finally, we create a webserver set to start with a directory listing, and
connect it to a port. We will tell this Site to serve a DirectoryListing of a
directory named <q>Pictures</q> in our home directory:</p><pre class="python">
<span class="py-src-variable">rootDirectory</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">expanduser</span><span class="py-src-op">(</span><span class="py-src-string">&quot;~/Pictures&quot;</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-variable">site</span> <span class="py-src-op">=</span> <span class="py-src-variable">server</span><span class="py-src-op">.</span><span class="py-src-variable">Site</span><span class="py-src-op">(</span><span class="py-src-variable">DirectoryListing</span><span class="py-src-op">(</span><span class="py-src-variable">directory</span><span class="py-src-op">=</span><span class="py-src-variable">rootDirectory</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-variable">application</span> <span class="py-src-op">=</span> <span class="py-src-variable">service</span><span class="py-src-op">.</span><span class="py-src-variable">Application</span><span class="py-src-op">(</span><span class="py-src-string">&quot;ImagePool&quot;</span><span class="py-src-op">)</span> <span class="py-src-newline">
</span><span class="py-src-variable">parent</span> <span class="py-src-op">=</span> <span class="py-src-variable">service</span><span class="py-src-op">.</span><span class="py-src-variable">IServiceCollection</span><span class="py-src-op">(</span><span class="py-src-variable">application</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-variable">internet</span><span class="py-src-op">.</span><span class="py-src-variable">TCPServer</span><span class="py-src-op">(</span><span class="py-src-number">8088</span><span class="py-src-op">,</span> <span class="py-src-variable">site</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">setServiceParent</span><span class="py-src-op">(</span><span class="py-src-variable">parent</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-endmarker"></span></pre><p>And then start the server by running the following command-line:
<code class="shell">twistd -noy picturepile.py</code>.</p><h2>Custom Views<a name="auto0"></a></h2><p>Now, let's add thumbnails to our directory listing. We begin by
changing the view for the links to <q>thumbnail</q>:</p><pre>
&lt;html&gt;
  &lt;head&gt;
    &lt;title model=&quot;title&quot; view=&quot;Text&quot;&gt;Directory listing&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1 model=&quot;title&quot; view=&quot;Text&quot;&gt;&lt;/h1&gt;
    &lt;ul model=&quot;directory&quot; view=&quot;List&quot;&gt;
      &lt;li pattern=&quot;listItem&quot;&gt;&lt;a view=&quot;thumbnail&quot; /&gt;&lt;/li&gt;
      &lt;li pattern=&quot;emptyList&quot;&gt;This directory is empty.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre><p>Woven doesn't include a standard <q>thumbnail</q> widget, so we'll have
to write the code for this view ourselves. (Standard widgets are named with
initial capital letters; by convention, custom views are named like methods,
with initial lowercase letters.)</p><p>The simplest way to do it is with a <code>wvupdate_</code> (short for Woven
View Update) method on our DirectoryListing class:</p><pre class="python">
<span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_thumbnail</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">a</span> <span class="py-src-op">=</span> <span class="py-src-variable">microdom</span><span class="py-src-op">.</span><span class="py-src-variable">lmx</span><span class="py-src-op">(</span><span class="py-src-variable">node</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">a</span><span class="py-src-op">[</span><span class="py-src-string">'href'</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline">
</span>        <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">img</span><span class="py-src-op">(</span><span class="py-src-variable">src</span><span class="py-src-op">=</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">+</span><span class="py-src-string">'/preview'</span><span class="py-src-op">)</span><span class="py-src-op">,</span><span class="py-src-variable">width</span><span class="py-src-op">=</span><span class="py-src-string">'200'</span><span class="py-src-op">,</span><span class="py-src-variable">height</span><span class="py-src-op">=</span><span class="py-src-string">'200'</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>When the <code>thumbnail</code> view is requested, this method is called with
the HTTP request, the DOM node marked with this view, and the data from the
associated model (in this case, the name of the image or directory). With this
approach, we can now modify the DOM as necessary. First, we wrap the node in
<code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.microdom.lmx.html" title="twisted.web.microdom.lmx">lmx</a></code>, a class provided by
Twisted's DOM implementation that provides convenient syntax for modifying DOM
nodes; attributes can be treated as dictionary keys, and the <code>text</code>
and <code>add</code> methods provide for adding text to the node and adding
children, respectively.  If this item is a directory, a textual link is
displayed; else, it produces an <code>IMG</code> tag of fixed size.</p><h2>Simple Input Handling<a name="auto1"></a></h2><p>Limiting thumbnails to a single size is rather inflexible; our app would
be nicer if one could adjust it. Let's add a list of thumbnail sizes to the
directory listing. Again, we start with the template:</p><div class="html-listing"><pre class="htmlsource">

    &lt;html&gt;
    &lt;head&gt;
      &lt;title model=&quot;title&quot; view=&quot;Text&quot;&gt;Directory listing&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;h1 model=&quot;title&quot; view=&quot;Text&quot;&gt;&lt;/h1&gt;
      &lt;form action=&quot;&quot;&gt;
        Thumbnail size:
        &lt;select name=&quot;thumbnailSize&quot; onChange=&quot;submit()&quot; view=&quot;adjuster&quot;&gt;
          &lt;option value=&quot;400&quot;&gt;400x400&lt;/option&gt;
          &lt;option value=&quot;200&quot;&gt;200x200&lt;/option&gt;
          &lt;option value=&quot;100&quot;&gt;100x100&lt;/option&gt;
          &lt;option value=&quot;50&quot;&gt;50x50&lt;/option&gt;
        &lt;/select&gt;
      &lt;/form&gt;
      &lt;ul model=&quot;directory&quot; view=&quot;List&quot;&gt;
        &lt;li pattern=&quot;listItem&quot;&gt;&lt;a view=&quot;thumbnail&quot; /&gt;&lt;/li&gt;
        &lt;li pattern=&quot;emptyList&quot;&gt;This directory is empty.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/body&gt;
  &lt;/html&gt; </pre><div class="caption">Source listing - <a href="listings/PicturePile/directory-listing3.html"><span class="filename">listings/PicturePile/directory-listing3.html</span></a></div></div><p>This time, we add a form with a list of thumbnail sizes named
<code>thumbnailSize</code>: we want the form to reflect the selected option, so
we place an <code>adjuster</code> view on the <code>select</code> tag that looks
for the right <code>option</code> tag and puts <code>selected=1</code> on it
(the default size being 200):</p><pre class="python">
<span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_adjuster</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">widget</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">args</span><span class="py-src-op">.</span><span class="py-src-variable">get</span><span class="py-src-op">(</span><span class="py-src-string">'thumbnailSize'</span><span class="py-src-op">,</span><span class="py-src-op">(</span><span class="py-src-string">'200'</span><span class="py-src-op">,</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-newline">
</span>        <span class="py-src-variable">domhelpers</span><span class="py-src-op">.</span><span class="py-src-variable">locateNodes</span><span class="py-src-op">(</span><span class="py-src-variable">widget</span><span class="py-src-op">.</span><span class="py-src-variable">node</span><span class="py-src-op">.</span><span class="py-src-variable">childNodes</span><span class="py-src-op">,</span> <span class="py-src-nl">
</span>                               <span class="py-src-string">'value'</span><span class="py-src-op">,</span> <span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-op">.</span><span class="py-src-variable">setAttribute</span><span class="py-src-op">(</span><span class="py-src-string">'selected'</span><span class="py-src-op">,</span> <span class="py-src-string">'1'</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p><code>request.args</code> is a dictionary, mapping argument names to lists of
values (since multiple HTTP arguments are possible). In this case, we only care
about the first argument named <code>thumbnailSize</code>.
<code>domhelpers.locateNodes</code> is a helper function which, given a list of
DOM nodes, a key, and a value, will search each tree and return all nodes that
have the requested key-value pair.</p><p>Next, we modify the <code>thumbnail</code> view to look at the arguments from
the HTTP request and use that as the size for the images:</p><pre class="python">
<span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_thumbnail</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">args</span><span class="py-src-op">.</span><span class="py-src-variable">get</span><span class="py-src-op">(</span><span class="py-src-string">'thumbnailSize'</span><span class="py-src-op">,</span><span class="py-src-op">(</span><span class="py-src-string">'200'</span><span class="py-src-op">,</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-newline">
</span>        <span class="py-src-variable">a</span> <span class="py-src-op">=</span> <span class="py-src-variable">microdom</span><span class="py-src-op">.</span><span class="py-src-variable">lmx</span><span class="py-src-op">(</span><span class="py-src-variable">node</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">a</span><span class="py-src-op">[</span><span class="py-src-string">'href'</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline">
</span>        <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">img</span><span class="py-src-op">(</span><span class="py-src-variable">src</span><span class="py-src-op">=</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">+</span><span class="py-src-string">'/preview'</span><span class="py-src-op">)</span><span class="py-src-op">,</span><span class="py-src-variable">width</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">,</span><span class="py-src-variable">height</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><h2>Sessions<a name="auto2"></a></h2><p>A disadvantage to the approach taken in the previous section is that
subdirectories do receive the same thumbnail sizing as their parents; also,
reloading the page sets it back to the default size of 200x200. To remedy
this, we need a way to store data that lasts longer than a single page
render. Fortunately, twisted.web provides this in the form of a Session
object. Since only one Session exists per user for all applications on the
server, the Session object is Componentized, and each application adds
adapters to contain their own state and behaviour, as explained in the <a href="components.html">Components</a> documentation. So, we start with an
interface, and a class that implements it, and registration of our class upon
Session:</p><pre class="python">
<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPreferences</span><span class="py-src-op">(</span><span class="py-src-parameter">components</span><span class="py-src-op">.</span><span class="py-src-parameter">Interface</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-keyword">pass</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-dedent"></span><span class="py-src-keyword">class</span> <span class="py-src-identifier">Preferences</span><span class="py-src-op">(</span><span class="py-src-parameter">components</span><span class="py-src-op">.</span><span class="py-src-parameter">Adapter</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-variable">__implements__</span> <span class="py-src-op">=</span> <span class="py-src-variable">IPreferences</span><span class="py-src-newline">
</span>    <span class="py-src-nl">
</span><span class="py-src-dedent"></span><span class="py-src-variable">components</span><span class="py-src-op">.</span><span class="py-src-variable">registerAdapter</span><span class="py-src-op">(</span><span class="py-src-variable">Preferences</span><span class="py-src-op">,</span> <span class="py-src-variable">server</span><span class="py-src-op">.</span><span class="py-src-variable">Session</span><span class="py-src-op">,</span> <span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-endmarker"></span></pre><p>We're just going to store data on this class, so no methods are defined.</p><p>Next, we change our view methods, <code>wvupdate_thumbnail</code> and
<code>wvupdate_adjuster</code>, to retrieve their size data from the Preferences
object stored on the Session, instead of the HTTP request:</p><pre class="python">
<span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_thumbnail</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">prefs</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">getSession</span><span class="py-src-op">(</span><span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">getattr</span><span class="py-src-op">(</span><span class="py-src-variable">prefs</span><span class="py-src-op">,</span> <span class="py-src-string">'size'</span><span class="py-src-op">,</span><span class="py-src-string">'200'</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">a</span> <span class="py-src-op">=</span> <span class="py-src-variable">microdom</span><span class="py-src-op">.</span><span class="py-src-variable">lmx</span><span class="py-src-op">(</span><span class="py-src-variable">node</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">a</span><span class="py-src-op">[</span><span class="py-src-string">'href'</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline">
</span>        <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">            </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">img</span><span class="py-src-op">(</span><span class="py-src-variable">src</span><span class="py-src-op">=</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">+</span><span class="py-src-string">'/preview'</span><span class="py-src-op">)</span><span class="py-src-op">,</span><span class="py-src-variable">width</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">,</span><span class="py-src-variable">height</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_adjuster</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">widget</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">prefs</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">getSession</span><span class="py-src-op">(</span><span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">getattr</span><span class="py-src-op">(</span><span class="py-src-variable">prefs</span><span class="py-src-op">,</span> <span class="py-src-string">'size'</span><span class="py-src-op">,</span><span class="py-src-string">'200'</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">domhelpers</span><span class="py-src-op">.</span><span class="py-src-variable">locateNodes</span><span class="py-src-op">(</span><span class="py-src-variable">widget</span><span class="py-src-op">.</span><span class="py-src-variable">node</span><span class="py-src-op">.</span><span class="py-src-variable">childNodes</span><span class="py-src-op">,</span> <span class="py-src-nl">
</span>                               <span class="py-src-string">'value'</span><span class="py-src-op">,</span> <span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-op">.</span><span class="py-src-variable">setAttribute</span><span class="py-src-op">(</span><span class="py-src-string">'selected'</span><span class="py-src-op">,</span> <span class="py-src-string">'1'</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><h3>Controllers<a name="auto3"></a></h3><p>Now we turn to the question of how the data gets into the session in the
first place. While it is possible to to place it there from within the
<code>wvupdate_</code> methods, since they both have access to the HTTP request,
it is desirable at times to separate out input handling, which is what
controllers are for. So, we add a <code>wcfactory_</code> (short for Woven
Controller Factory) method to DirectoryListing:</p><pre class="python">
<span class="py-src-keyword">def</span> <span class="py-src-identifier">wcfactory_adjuster</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">model</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-keyword">return</span> <span class="py-src-variable">ImageSizer</span><span class="py-src-op">(</span><span class="py-src-variable">model</span><span class="py-src-op">,</span> <span class="py-src-variable">name</span><span class="py-src-op">=</span><span class="py-src-string">'thumbnailSize'</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>ImageSizer is a controller. It checks the input for validity (in this case,
since it subclasses <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.input.Anything.html" title="twisted.web.woven.input.Anything">Anything</a></code>, it merely ensures the input is
non-empty) and calls <code>handleValid</code> if the check succeeds; in this
case, we retrieve the Preferences component from the session, and store the size
received from the form upon it:</p><pre class="python">
<span class="py-src-keyword">class</span> <span class="py-src-identifier">ImageSizer</span><span class="py-src-op">(</span><span class="py-src-parameter">input</span><span class="py-src-op">.</span><span class="py-src-parameter">Anything</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-keyword">def</span> <span class="py-src-identifier">handleValid</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">prefs</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">getSession</span><span class="py-src-op">(</span><span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">prefs</span><span class="py-src-op">.</span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Finally, we must modify the template to use our new controller. Since we are
concerned with the input from the <code>&lt;select&gt;</code> element of the
form, we place the controller upon it:</p><div class="html-listing"><pre class="htmlsource">

    &lt;html&gt;
    &lt;head&gt;
      &lt;title model=&quot;title&quot; view=&quot;Text&quot;&gt;Directory listing&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;h1 model=&quot;title&quot; view=&quot;Text&quot;&gt;&lt;/h1&gt;
      &lt;form action=&quot;&quot;&gt;
        Thumbnail size:
        &lt;select name=&quot;thumbnailSize&quot; onChange=&quot;submit()&quot; view=&quot;adjuster&quot;
          controller=&quot;adjuster&quot;&gt;
          &lt;option value=&quot;400&quot;&gt;400x400&lt;/option&gt;
          &lt;option value=&quot;200&quot;&gt;200x200&lt;/option&gt;
          &lt;option value=&quot;100&quot;&gt;100x100&lt;/option&gt;
          &lt;option value=&quot;50&quot;&gt;50x50&lt;/option&gt;
        &lt;/select&gt;
      &lt;/form&gt;
      &lt;ul model=&quot;directory&quot; view=&quot;List&quot;&gt;
        &lt;li pattern=&quot;listItem&quot;&gt;&lt;a view=&quot;thumbnail&quot; /&gt;&lt;/li&gt;
        &lt;li pattern=&quot;emptyList&quot;&gt;This directory is empty.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/body&gt;
  &lt;/html&gt; 
</pre><div class="caption">Source listing - <a href="listings/PicturePile/directory-listing4.html"><span class="filename">listings/PicturePile/directory-listing4.html</span></a></div></div><p>Now, the selected size will be remembered across subdirectories and
page reloads.</p></div><p><a href="../howto/index.html">Index</a></p><span class="version">Version: 1.3.0</span></body></html>