run-what 1.3.0

HTML-first web framework powered by Rust. No JavaScript frameworks, no build steps—just HTML.
<what>
title: "Step 6 — Loops & Conditionals"
active_step: 6
languages: [{"name":"Rust","type":"Systems","year":"2015"},{"name":"Go","type":"Systems","year":"2009"},{"name":"TypeScript","type":"Web","year":"2012"},{"name":"Python","type":"Scripting","year":"1991"},{"name":"Elixir","type":"Functional","year":"2012"}]
categories: [{"name":"Frontend","items":[{"name":"React"},{"name":"Vue"},{"name":"Svelte"}]},{"name":"Backend","items":[{"name":"Rust"},{"name":"Go"},{"name":"Node"}]},{"name":"Database","items":[{"name":"SQLite"},{"name":"Postgres"}]}]
</what>

<h1 class="text-3xl font-bold mb-2" style="color: #111827; letter-spacing: -0.02em;">Step 6 — Loops &amp; Conditionals</h1>
<p class="text-gray-500 text-sm mb-8">Render lists and conditional content without JavaScript.</p>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">The loop tag</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    <code>&lt;loop data="#items#" as="item"&gt;</code> iterates over an array. Nested properties use dot notation: <code>#item.name#</code>.
  </p>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;loop data="#products#" as="product"&gt;
  &lt;div&gt;
    &lt;strong&gt;#product.name#&lt;/strong&gt;
    &lt;span&gt;$#product.price#&lt;/span&gt;
  &lt;/div&gt;
&lt;/loop&gt;</code></pre>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Live loop example</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    Data is fetched via the <code>&lt;what&gt;</code> block and looped over in the template. Here's a hardcoded list of programming languages with their stats:
  </p>

  <div class="card mb-4">
    <div class="card-body" style="padding: 0;">
      <table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">
        <thead>
          <tr style="background: #f9fafb; border-bottom: 1px solid #e5e7eb;">
            <th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Language</th>
            <th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Type</th>
            <th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Year</th>
          </tr>
        </thead>
        <tbody>
          <loop data="#languages#" as="lang">
            <tr style="border-bottom: 1px solid #f3f4f6;">
              <td style="padding: 0.625rem 1rem; font-weight: 500; color: #111827;">#lang.name#</td>
              <td style="padding: 0.625rem 1rem;">
                <if lang.type == "Systems">
                  <span class="badge" style="background: #ede9fe; color: #5b21b6;">#lang.type#</span>
                </if>
                <if lang.type == "Web">
                  <span class="badge" style="background: #dbeafe; color: #1e40af;">#lang.type#</span>
                </if>
                <if lang.type == "Scripting">
                  <span class="badge" style="background: #fef3c7; color: #92400e;">#lang.type#</span>
                </if>
                <if lang.type == "Functional">
                  <span class="badge" style="background: #d1fae5; color: #065f46;">#lang.type#</span>
                </if>
              </td>
              <td style="padding: 0.625rem 1rem; color: #6b7280;">#lang.year#</td>
            </tr>
          </loop>
        </tbody>
      </table>
    </div>
  </div>

  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;loop data="#languages#" as="lang"&gt;
  &lt;tr&gt;
    &lt;td&gt;#lang.name#&lt;/td&gt;
    &lt;td&gt;#lang.type#&lt;/td&gt;
    &lt;td&gt;#lang.year#&lt;/td&gt;
  &lt;/tr&gt;
&lt;/loop&gt;</code></pre>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Conditionals</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    Use <code>&lt;if ...&gt;</code> to show/hide content. <code>&lt;unless&gt;</code> is the negation. <code>&lt;else/&gt;</code> provides the fallback.
  </p>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;if user.authenticated&gt;
  Welcome, #user.full_name#!
  &lt;else/&gt;
  Please log in.
&lt;/if&gt;

&lt;unless items&gt;
  &lt;p&gt;No items found.&lt;/p&gt;
&lt;/unless&gt;

&lt;!-- Comparisons: quote string values, keep numbers bare --&gt;
&lt;if score gt 90&gt;Grade: A&lt;/if&gt;
&lt;if status == "active"&gt;Active&lt;/if&gt;

&lt;!-- Combine conditions with and / or (and binds tighter) --&gt;
&lt;if user.authenticated and user.role == "admin"&gt;Admin tools&lt;/if&gt;
&lt;if role == "admin" or role == "editor"&gt;Staff area&lt;/if&gt;</code></pre>
  <p class="text-gray-600 mb-3" style="line-height: 1.7; margin-top: 0.75rem;">
    Always quote string values (<code>status == "active"</code>) — a bare word on the right side is treated as another variable. There are no parentheses; nest <code>&lt;if&gt;</code> blocks for complex logic.
  </p>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">The <code>cond</code> attribute (legacy, still accepted)</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    The simplified form above is the recommended syntax. Earlier versions used a <code>cond</code> attribute with <code>#...#</code> wrapping — it still works, so older templates keep rendering:
  </p>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;!-- Recommended --&gt;
&lt;if active_step == 2&gt;Step 2!&lt;/if&gt;
&lt;if count gt 0&gt;There are items.&lt;/if&gt;
&lt;unless error&gt;No errors.&lt;/unless&gt;

&lt;!-- Legacy long-hand equivalent (still accepted): --&gt;
&lt;if cond="#active_step# == 2"&gt;Step 2!&lt;/if&gt;
&lt;if cond="#count# &gt; 0"&gt;There are items.&lt;/if&gt;
&lt;unless cond="#error#"&gt;No errors.&lt;/unless&gt;</code></pre>

  <p class="text-gray-600 mb-3" style="line-height: 1.7; margin-top: 0.75rem;">
    In the simplified form, use keyword operators — a literal <code>&gt;</code> would close the HTML tag:
  </p>
  <div class="card mb-4">
    <div class="card-body">
      <table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">
        <thead>
          <tr style="border-bottom: 1px solid #e5e7eb;">
            <th style="text-align: left; padding: 0.5rem 0.75rem; color: #6b7280; font-weight: 600;">Keyword</th>
            <th style="text-align: left; padding: 0.5rem 0.75rem; color: #6b7280; font-weight: 600;">Equivalent</th>
            <th style="text-align: left; padding: 0.5rem 0.75rem; color: #6b7280; font-weight: 600;">Meaning</th>
          </tr>
        </thead>
        <tbody>
          <tr style="border-bottom: 1px solid #f3f4f6;">
            <td style="padding: 0.5rem 0.75rem;"><code>gt</code></td>
            <td style="padding: 0.5rem 0.75rem;"><code>&gt;</code></td>
            <td style="padding: 0.5rem 0.75rem; color: #6b7280;">Greater than</td>
          </tr>
          <tr style="border-bottom: 1px solid #f3f4f6;">
            <td style="padding: 0.5rem 0.75rem;"><code>lt</code></td>
            <td style="padding: 0.5rem 0.75rem;"><code>&lt;</code></td>
            <td style="padding: 0.5rem 0.75rem; color: #6b7280;">Less than</td>
          </tr>
          <tr style="border-bottom: 1px solid #f3f4f6;">
            <td style="padding: 0.5rem 0.75rem;"><code>gte</code></td>
            <td style="padding: 0.5rem 0.75rem;"><code>&gt;=</code></td>
            <td style="padding: 0.5rem 0.75rem; color: #6b7280;">Greater than or equal</td>
          </tr>
          <tr>
            <td style="padding: 0.5rem 0.75rem;"><code>lte</code></td>
            <td style="padding: 0.5rem 0.75rem;"><code>&lt;=</code></td>
            <td style="padding: 0.5rem 0.75rem; color: #6b7280;">Less than or equal</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Live conditional demo</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    Toggle a session flag, then refresh the page &mdash; conditionals are evaluated on the
    server each time a page renders, so the switch shows up on the next render.
  </p>
  <div class="card mb-4">
    <div class="card-body">
      <if session.tut_mode == "pro">
        <div class="alert" style="background: #ede9fe; border: 1px solid #c4b5fd; color: #5b21b6; border-radius: 0.5rem; padding: 0.75rem 1rem; margin-bottom: 1rem;">
          Pro mode is ON. You see advanced options.
        </div>
      </if>
      <unless session.tut_mode == "pro">
        <div class="alert" style="background: #f3f4f6; border: 1px solid #e5e7eb; color: #374151; border-radius: 0.5rem; padding: 0.75rem 1rem; margin-bottom: 1rem;">
          Standard mode. Click the button to switch.
        </div>
      </unless>
      <div class="flex gap-2">
        <button class="btn btn-outline btn-sm" w-set="session.tut_mode = pro">Enable Pro</button>
        <button class="btn btn-outline btn-sm" w-set="session.tut_mode = standard">Standard</button>
      </div>
    </div>
  </div>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Nested loops</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    Loops can be nested. Each loop has its own scope — use distinct <code>as</code> names. Here's a live example with categories and their items:
  </p>

  <div class="card mb-4">
    <div class="card-body">
      <loop data="#categories#" as="category">
        <div style="margin-bottom: 0.75rem;">
          <div class="text-sm font-semibold text-gray-900 mb-1">#category.name#</div>
          <div style="display: flex; gap: 0.375rem; flex-wrap: wrap;">
            <loop data="#category.items#" as="item">
              <span class="badge" style="background: #eef2ff; color: #4338ca;">#item.name#</span>
            </loop>
          </div>
        </div>
      </loop>
    </div>
  </div>

  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;loop data="#categories#" as="category"&gt;
  &lt;h3&gt;#category.name#&lt;/h3&gt;
  &lt;loop data="#category.items#" as="item"&gt;
    &lt;span&gt;#item.name#&lt;/span&gt;
  &lt;/loop&gt;
&lt;/loop&gt;</code></pre>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Loop with empty state</h2>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;unless items&gt;
  &lt;p class="text-gray-500"&gt;No items yet.&lt;/p&gt;
&lt;/unless&gt;

&lt;loop data="#items#" as="item"&gt;
  &lt;div&gt;#item.name#&lt;/div&gt;
&lt;/loop&gt;</code></pre>
</section>

<div class="card" style="border-color: #bbf7d0; background: #f0fdf4;">
  <div class="card-body">
    <div class="text-sm font-semibold mb-2" style="color: #14532d;">What you learned</div>
    <ul class="text-sm space-y-1" style="color: #166534; padding-left: 1.25rem; list-style: disc;">
      <li><code>&lt;loop data="#items#" as="item"&gt;</code> iterates over arrays</li>
      <li>Nested data access: <code>#item.name#</code>, <code>#item.meta.created_at#</code></li>
      <li><code>&lt;if var&gt;</code>, <code>&lt;else/&gt;</code>, <code>&lt;unless var&gt;</code></li>
      <li>Simplified: <code>&lt;if count gt 0&gt;</code> — no <code>cond</code> or <code>#...#</code> needed</li>
      <li>Keyword operators: <code>gt</code>, <code>lt</code>, <code>gte</code>, <code>lte</code></li>
      <li>Nested loops: use distinct <code>as</code> names for each level</li>
      <li>Loops and conditionals compose — conditionals work inside loops</li>
    </ul>
  </div>
</div>