red-tape/form-arguments/index.html @ 8058918d4932
adopt: Update site.
author |
Steve Losh <steve@stevelosh.com> |
date |
Mon, 23 Dec 2019 15:15:01 -0500 |
parents |
69831b3947d7 |
children |
(none) |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Form Arguments / Red Tape</title>
<link rel="stylesheet" href="../_dmedia/tango.css"/>
<link rel="stylesheet/less" type="text/css" href="../_dmedia/style.less"/>
<script src="../_dmedia/less.js" type="text/javascript">
</script>
</head>
<body class="content">
<div class="wrap">
<header><h1><a href="..">Red Tape</a></h1></header>
<div class="markdown">
<h1 id="form-arguments"><a href="">Form Arguments</a></h1><p>Sometimes a simple form isn't enough. When you need dynamic functionality in
your forms you can use Red Tape's form arguments.</p>
<p>Once you've gone through this document, read the <a href="../rendering/">rendering</a>
guide.</p>
<div class="toc">
<ul>
<li><a href="#the-problem">The Problem</a></li>
<li><a href="#defining-form-arguments">Defining Form Arguments</a></li>
<li><a href="#using-form-arguments">Using Form Arguments</a></li>
<li><a href="#initial-data">Initial Data</a></li>
</ul></div>
<h2 id="the-problem">The Problem</h2>
<p>Let's use a simple example to demonstrate why form arguments are necessary and
how they work. Suppose you have a site that hosts videos. You'd like to have
a "delete video" form to let users delete their videos. A first crack at it
might look like this:</p>
<div class="codehilite"><pre><span class="p">(</span><span class="kd">defn </span><span class="nv">video-exists</span> <span class="p">[</span><span class="nv">video-id</span><span class="p">]</span>
<span class="c1">; Checks that the given video id exists...</span>
<span class="p">)</span>
<span class="p">(</span><span class="nf">defform</span> <span class="nv">delete-video-form</span> <span class="p">{}</span>
<span class="ss">:video-id</span> <span class="p">[</span><span class="nv">video-exists</span><span class="p">])</span>
</pre></div>
<p>Nothing new here. But this form will let <em>anyone</em> delete <em>any</em> video. In real
life you probably only want users to be able to delete their <em>own</em> videos.</p>
<h2 id="defining-form-arguments">Defining Form Arguments</h2>
<p>To define form arguments you can use the <code>:arguments</code> entry in the form options
map, passing a vector of symbols. Let's change our <code>delete-video-form</code> form:</p>
<div class="codehilite"><pre><span class="p">(</span><span class="nf">defform</span> <span class="nv">delete-video-form</span> <span class="p">{</span><span class="ss">:arguments</span> <span class="p">[</span><span class="nv">user</span><span class="p">]}</span>
<span class="ss">:video-id</span> <span class="p">[</span><span class="nv">video-exists</span><span class="p">])</span>
</pre></div>
<p>The form will now take a <code>user</code> argument. So calling the form <em>without</em> data
would be done like this:</p>
<div class="codehilite"><pre><span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">current-user</span> <span class="nv">...</span>
<span class="nv">fresh-delete-form</span> <span class="p">(</span><span class="nf">delete-video-form</span> <span class="nv">current-user</span><span class="p">)])</span>
</pre></div>
<p>And calling it <em>with</em> data would look like this:</p>
<div class="codehilite"><pre><span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">current-user</span> <span class="nv">...</span>
<span class="nv">post-data</span> <span class="nv">...</span>
<span class="nv">form</span> <span class="p">(</span><span class="nf">delete-video-form</span> <span class="nv">current-user</span> <span class="nv">post-data</span><span class="p">)])</span>
</pre></div>
<h2 id="using-form-arguments">Using Form Arguments</h2>
<p>Using form arguments relies on one key idea: your cleaner definitions are
evaluated in a context where the form arguments are bound to the symbols you
specified.</p>
<p>Here's how we could use our <code>user</code> argument:</p>
<div class="codehilite"><pre><span class="p">(</span><span class="kd">defn </span><span class="nv">video-owned-by</span> <span class="p">[</span><span class="nv">user</span> <span class="nv">video-id</span><span class="p">]</span>
<span class="c1">; Check that the given user owns the given video id.</span>
<span class="p">)</span>
<span class="p">(</span><span class="nf">defform</span> <span class="nv">delete-video-form</span> <span class="p">{</span><span class="ss">:arguments</span> <span class="p">[</span><span class="nv">user</span><span class="p">]}</span>
<span class="ss">:video-id</span> <span class="p">[</span><span class="nv">video-exists</span>
<span class="o">#</span><span class="p">(</span><span class="nf">video-owned-by</span> <span class="nv">user</span> <span class="nv">%</span><span class="p">)])</span>
<span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">current-user</span> <span class="nv">...</span>
<span class="nv">post-data</span> <span class="nv">...</span>
<span class="nv">form</span> <span class="p">(</span><span class="nf">delete-video-form</span> <span class="nv">current-user</span> <span class="nv">post-data</span><span class="p">)]</span>
<span class="nv">...</span><span class="p">)</span>
</pre></div>
<p>Notice how the second cleaner in the <code>defform</code> is defined as <code>#(video-owned-by
user %)</code>. <code>user</code> here is the form argument, so the anonymouse function will be
created with the appropriate user each time <code>delete-form</code> is called.</p>
<h2 id="initial-data">Initial Data</h2>
<p>The initial data map is also evaluated in a context where the form arguments are
bound. You can use this to define default values based on the form arguments.</p>
<p>For example: suppose you have a profile page where users can change their email
address and profile. A form for this might look like:</p>
<div class="codehilite"><pre><span class="p">(</span><span class="nf">defform</span> <span class="nv">profile-form</span> <span class="p">{</span><span class="ss">:arguments</span> <span class="p">[</span><span class="nv">user</span><span class="p">]</span>
<span class="ss">:initial</span> <span class="p">{</span><span class="ss">:email</span> <span class="p">(</span><span class="ss">:email</span> <span class="nv">user</span><span class="p">)</span>
<span class="ss">:bio</span> <span class="p">(</span><span class="ss">:bio</span> <span class="nv">user</span><span class="p">)}}</span>
<span class="ss">:email</span> <span class="p">[</span><span class="o">#</span><span class="p">(</span><span class="nf">cleaners/matches</span> <span class="o">#</span><span class="s">"\S+@\S+"</span> <span class="nv">%</span>
<span class="s">"Please enter a valid email address."</span><span class="p">)]</span>
<span class="ss">:bio</span> <span class="p">[])</span>
<span class="p">(</span><span class="nf">profile-form</span> <span class="nv">some-user</span><span class="p">)</span>
<span class="c1">; =></span>
<span class="p">{</span><span class="nv">...</span>
<span class="ss">:results</span> <span class="nv">nil</span>
<span class="ss">:errors</span> <span class="nv">nil</span>
<span class="ss">:data</span> <span class="p">{</span><span class="ss">:email</span> <span class="s">"steve@stevelosh.com"</span>
<span class="ss">:bio</span> <span class="s">"Computers are terrible."</span><span class="p">}}</span>
<span class="p">(</span><span class="nf">profile-form</span> <span class="nv">some-user</span> <span class="p">{</span><span class="ss">:email</span> <span class="s">"this is not an email"</span>
<span class="ss">:bio</span> <span class="s">"I like cats."</span><span class="p">})</span>
<span class="c1">; =></span>
<span class="p">{</span><span class="nv">...</span>
<span class="ss">:results</span> <span class="nv">nil</span>
<span class="ss">:errors</span> <span class="p">{</span><span class="ss">:email</span> <span class="s">"Please enter a valid email address"</span><span class="p">}</span>
<span class="ss">:data</span> <span class="p">{</span><span class="ss">:email</span> <span class="s">"this is not an email"</span>
<span class="ss">:bio</span> <span class="s">"I like cats."</span><span class="p">}}</span>
</pre></div>
<p>Notice how in the fresh form the <code>:data</code> is prepopulated with the user's
information that was pulled from the form argument. Once the user actually
enters some data, the initial data is ignored.</p>
</div>
<footer><p>Made and <a href="http://sjl.bitbucket.org/d/">documented</a> with love by <a href="http://stevelosh.com">Steve
Losh</a>.</p>
<p><br/><a id="rochester-made" href="http://rochestermade.com" title="Rochester Made"><img src="http://rochestermade.com/media/images/rochester-made-dark-on-light.png" alt="Rochester Made" title="Rochester Made"/></a></p></footer>
</div>
</body>
</html>