# Add interactive behavior

Use htmx to update part of a page with server-rendered HTML. This example adds a search form that works both with htmx and with ordinary navigation.

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/dev/howto/htmx/index.md.
Add a GET search form to my application using Cicadel's bundled htmx.
Register the script route, include the script once, and render a full page for
ordinary requests and a results fragment for partial requests.
Keep authorization in the handler and test both responses, including escaped input.
```

## Register the script and search routes

Add `apps/search/app.go`. The bundled script is served by your Go application; no CDN is needed.

<!-- example: htmx/apps/search/app.go -->
```go
package search

import (
    "log/slog"
    "net/http"
    "strings"

    "github.com/fauzaanu/cicadel"
    "github.com/fauzaanu/cicadel/htmx"
    "github.com/fauzaanu/cicadel/response"
    "github.com/fauzaanu/cicadel/ui"
    "github.com/fauzaanu/cicadel/urls"
)

func New() cicadel.App {
    return cicadel.App{
        Name: "search",
        Routes: []urls.Route{
            urls.Path("GET", htmx.ScriptPath, "search:script", htmx.Handler().ServeHTTP),
            urls.Path("GET", "/search", "search:index", Search),
        },
    }
}

func Search(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query().Get("q")
    items := []string{}
    for _, title := range []string{"Go notes", "PostgreSQL notes", "UI notes"} {
        if strings.Contains(strings.ToLower(title), strings.ToLower(query)) {
            items = append(items, title)
        }
    }
    err := htmx.HTML(w, r, http.StatusOK,
        ui.Fragment(page(query, items)),
        ui.Fragment(results(items)),
    )
    if err != nil {
        slog.ErrorContext(r.Context(), "render search", "error", err)
        if !ui.Committed(err) {
            response.Error(w, http.StatusInternalServerError)
        }
    }
}
```

For database-backed search, replace the sample list with a query scoped to the current user's permitted rows. Use the same permissions for full and partial requests.

## Include the script and render results

Add `apps/search/page.templ`:

<!-- example: htmx/apps/search/page.templ -->
```templ
package search

import "github.com/fauzaanu/cicadel/htmx"
import "github.com/fauzaanu/cicadel/ui"
import "github.com/fauzaanu/cicadel/ui/layout"

templ page(query string, items []string) {
    @layout.Page.Call(layout.Props{Title: "Search"}, layout.Slots{
        Scripts: htmx.Script(),
        Children: ui.Fragment(searchForm(query, items)),
    })
}

templ searchForm(query string, items []string) {
    <h1>Search notes</h1>
    <form action="/search" method="get" hx-get="/search" hx-target="#results">
        <label for="query">Search</label>
        <input id="query" name="q" type="search" value={ query }/>
        <button type="submit">Search</button>
    </form>
    <section id="results" aria-live="polite">
        @results(items)
    </section>
}

templ results(items []string) {
    if len(items) == 0 {
        <p>No results.</p>
    }
    <ul>
        for _, item := range items {
            <li>{ item }</li>
        }
    </ul>
}
```

`Scripts: htmx.Script()` includes htmx once in the full document. The partial response contains only the results markup. Text inside `{ ... }` is escaped.

## Register the app and build

Import `example.com/myapp/apps/search` in `project/apps.go` and add `search.New()` to the existing list:

```go
func InstalledApps(_ *gorm.DB) ([]cicadel.App, error) {
    return []cicadel.App{
        notes.New(),
        search.New(),
    }, nil
}
```

Keep any other app registrations. Use your own module path in the import.

```sh
go tool cicadel ui build
go tool cicadel ui check --json
go test -race ./...
go run manage.go runserver
```

Open `http://127.0.0.1:8000/search`. You can also check both response forms:

```sh
curl 'http://127.0.0.1:8000/search?q=Go'
curl -H 'HX-Request: true' -H 'HX-Request-Type: partial' \
  'http://127.0.0.1:8000/search?q=Go'
```

The first response is a full HTML document. The second contains `<ul>` results without a document wrapper. `htmx.HTML` also handles the required `Vary` headers and returns a full page for history restoration.

## POST and DELETE requests

The example uses a read-only GET. For writes, keep your app's authentication and CSRF middleware, and include its CSRF token:

```templ
<form action="/notes/delete" method="post">
    @form.CSRF.Call(form.CSRFProps{Token: token}, ui.NoSlots{})
    <button hx-delete="/notes/42" hx-include="closest form" hx-target="#notes">
        Delete note
    </button>
</form>
```

Here `form` is `github.com/fauzaanu/cicadel/ui/form`, and `token` comes from your session's CSRF middleware. Implement both routes if you want the POST fallback. Check object permissions before deleting; htmx headers are not authorization.

Cicadel bundles htmx 4. Use explicit inheritance such as `hx-target:inherited="#results"`, `hx-sync="this:queue all"` for queues, and `hx-status:5xx="swap:none"` when an error response should not replace content. Return useful field errors with status 422.
