red-tape/form-arguments/index.html @ 15d3b832fdc5 default tip

cl-digraph: Update site.
author Steve Losh <steve@stevelosh.com>
date Wed, 21 Jun 2023 15:21:12 -0400
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">; =&gt;</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">; =&gt;</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>