PAD + htmx 🛒 0

Out-of-band updates

One answer, two places: the button that was pressed gets its new state, and the cart badge in the header updates too, swapped by its id with hx-swap-oob.

{fragment}padSession hx-posthx-swap-oob
Live
🎧
Aurora Headphones
€189.00 · 14 in stock
🎶
Pebble Earbuds
€79.00 · 32 in stock
🔊
Boom Speaker
€129.00 · 8 in stock
🎙️
Studio Microphone
€99.00 · sold out
💻
Nimbus Laptop 14
€1,299.00 · 5 in stock
🖥️
Atlas Monitor 27
€349.00 · 11 in stock
🖱️
Glide Mouse
€39.00 · 54 in stock
⌨️
Tactile Keyboard
€119.00 · 21 in stock
Requests htmx made
    Nothing yet - use the example above, and every request it makes shows here with the part PAD answered.

    How it is built

    PAD, on the server htmx, in the browser

    <div class="shop">
      <div id="shelf" class="grid shelf">
      {products}
      {fragment 'item'}
        <div class="card item" id="item-{$id}">
          <div class="item-top"><span class="item-emoji">{$emoji}</span>{if $inCart}<span class="badge is-accent">{$inCart} in cart</span>{/if}</div>
          <strong>{$name}</strong>
          <div class="muted small">{$price | currency('EUR')} &middot; {if $stock}{$stock} in stock{else}sold out{/if}</div>
          <div class="item-buttons">
            <button class="btn-sm" hx-post="?examples/cart&id={$id}&op=add&padFragment=item" hx-target="#item-{$id}" hx-swap="outerHTML">+ Add</button>
            {if $inCart}<button class="btn-sm btn-ghost" hx-post="?examples/cart&id={$id}&op=remove&padFragment=item" hx-target="#item-{$id}" hx-swap="outerHTML">−</button>{/if}
          </div>
        </div>
        {if $posted}
          <span id="cart-count" hx-swap-oob="true">{$cartCount}</span>
          {cartSummary}
        {/if}
      {/fragment}
      {/products}
      </div>
      {cartSummary}
    </div>
    <?php
    
      // The cart is the session's: product id => quantity. A button posts its product - op=add
      // or op=remove - and the answer is the {fragment 'item'} of that product: the card with its
      // new count, then the parts elsewhere on the page that changed too, each marked
      // hx-swap-oob - htmx puts the card back where the button was and the rest where their ids
      // are: the count in the header, the summary beside the shelf. Emptying the cart is an
      // ordinary {form} post that ends in a redirect, the page coming back whole.
    
      $counts   = padSession ( 'htmxCart', [] );
      $products = array_slice ( htmxData ( 'products' ), 0, 8 );
      $posted   = padRequestIs ( 'POST' );
    
      if ( $posted ) {
    
        $id    = (int) padRequest ( 'id', 0 );
        $op    = htmxText ( 'op' );
        $stock = array_column ( $products, 'stock', 'id' );
    
        if ( $op == 'clear' ) {
          padSessionPut ( 'htmxCart', [] );
          padRedirect ();
        }
    
        if ( $op == 'add' and ( $stock [$id] ?? 0 ) > ( $counts [$id] ?? 0 ) )
          $counts [$id] = ( $counts [$id] ?? 0 ) + 1;
        elseif ( $op == 'remove' and isset ( $counts [$id] ) and --$counts [$id] <= 0 )
          unset ( $counts [$id] );
    
        padSessionPut ( 'htmxCart', $counts );
    
        $products = array_values ( array_filter ( $products, fn ( $product ) => $product ['id'] == $id ) );
    
        // A product the shelf does not have - a stale or made-up request - has no card to
        // answer: a 404, which htmx swaps nothing for, where the empty loop rendered no
        // {fragment 'item'} and the answer was a 500, "there is no fragment named 'item'".
    
        if ( ! $products )
          padAbort ( 404, 'There is no such product' );
    
      }
    
      foreach ( $products as $at => $product )
        $products [$at] ['inCart'] = $counts [ $product ['id'] ] ?? 0;
    
      $cart      = htmxCart ( $counts );
      $cartCount = $cart ['count'];
      $lines     = $cart ['lines'];
      $total     = $cart ['total'];
    
    ?>