ΛↃ LAMBDACOMBINE

Symbolic Systems Infrastructure

05 · Remove and Append

PATCH-ELEMENTS MODES · REMOVE-ELEMENT · LIST MANAGEMENT
OSIRIS-REx extracting samples from Bennu.
OSIRIS-REx

`patch-elements` has several modes: `:mode :inner` (as we saw in the first chapter) replaces the children of the target. `:mode :append` inserts the new HTML after the existing children, and `:mode :outer` (which we will cover when we introduce «fat morph») replaces the element itself, and is the default

Append mode

Use :mode :append to grow a list. Each call to the endpoint adds one item to the end of the target element:

(d*:patch-elements gen
  (spinneret:with-html-string
    (:li :id item-id "Item content"))
  :selector "#list"
  :mode :append)

remove-element

remove-element takes a CSS selector and removes the matching element from the DOM. It is a convenience wrapper around patch-elements with mode "remove".

(d*:remove-element gen "#item-42")

To pass the target ID from a button click, set a signal before firing the request. The semicolon is required to separate statements in Datastar expressions:

(:button :|data-on:click|
         (format nil "$sel='~A'; ~A" id (d*:sse-get "/remove"))
         "x")

The server reads the sel signal and removes the element:

(let ((sel (d*:read-signal hunchentoot:*request* "sel")))
  (d*:with-sse (gen hunchentoot:*request*)
    (d*:remove-element gen (format nil "#~A" sel))))

Live Demo

Add items to the list. Each item's x button removes it by ID. The IDs are generated server-side.

    Full Source (standalone Hunchentoot)

    ;;;; -*- Mode: LISP; fill-column: 80; coding: utf-8 -*-
    
    ;;;; DEMO-REMOVE.LISP --- remove-element and patch-elements append
    
    ;;;; Copyright (C) 2025, 2026 Frederico Muñoz / ΛↃ lambda combine
    ;;;;
    ;;;; This file is part of datastar-cl, the Common Lisp SDK for Datastar
    ;;;;
    ;;;; License: MIT
    
    ;;; Demonstrates remove-element and patch-elements with :mode :append. The DOM
    ;;; holds a counter; each "Add" appends a new item, each item's "remove" button
    ;;; calls remove-element by ID. No server state in this demo.
    
    (ql:quickload '(:hunchentoot :spinneret :datastar-cl/hunchentoot))
    
    (defpackage #:remove-demo
      (:use #:cl #:hunchentoot)
      (:local-nicknames (:sp :spinneret) (:d* :datastar-cl)))
    (in-package #:remove-demo)
    
    (defparameter *counter* 0)
    
    (hunchentoot:define-easy-handler (index :uri "/") ()
      (setf (hunchentoot:content-type*) "text/html")
      (sp:with-html-string
        (:doctype)
        (:html
         (:head (:script :type "module" :src (d*:datastar-url)))
         (:body
          (:h1 "Remove Element Demo")
          (:p "Add items and remove them individually.")
          (:button :|data-on:click| (d*:sse-get "/add") "Add item")
          (:ol :id "items")))))
    
    (hunchentoot:define-easy-handler (add-endpoint :uri "/add") ()
      (d*:with-sse (gen hunchentoot:*request*)
        (incf *counter*)
        (let ((id (format nil "item-~A" *counter*)))
          (d*:patch-elements gen
                             (sp:with-html-string
                               (:li :id id
                                    "Item " (:strong *counter*)
                                    " "
                                    (:button :|data-on:click|
                                              (format nil "@get('/remove?sel=~A')" id)
                                             "x")))
                             :selector "#items"
                             :mode :append))))
    
    (hunchentoot:define-easy-handler (remove-endpoint :uri "/remove") ()
      (let ((sel (hunchentoot:get-parameter "sel")))
        (when sel
          (d*:with-sse (gen hunchentoot:*request*)
            (d*:remove-element gen (format nil "#~A" sel))))))
    
    (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 8989))
    (format t "~&Server started on http://localhost:8989~%")
    

    (defroute guide-remove (:get :text/html))