Alpine on a PAD page

Alpine reads attributes, PAD writes them. Both love braces - so give each its own, hand Alpine its state as JSON, and let it talk back to the page that wrote it.

1. The braces

PAD reads every { of a template as the start of a tag; Alpine writes objects with them. Three ways that never collide:

State from PHP

The ^ sigil writes a field as JSON, escaped for the attribute - x-data="{^profile}". No brace of Alpine's in the template, and a quote in a value stays text.

Components in a file

Alpine.data('tabs', ...) in a script of www/, called by name: x-data="tabs({^tabs})" - the file never meets the PAD parser.

A space after the brace

x-data="{ open: false }" written as { open: false } - a brace with a space after it is no PAD tag. It works for a short object; prefer the other two.

2. Load order

The application's scripts first, Alpine last and deferred - a page's components register themselves on alpine:init before Alpine walks the page:

<script src="alpine-app.js"></script>                         <!-- pad.get, pad.post -->
<script defer src="https://unpkg.com/alpinejs@3/dist/cdn.min.js"></script>
...
<script src="examples/search.js"></script>                    <!-- Alpine.data('search', ...) -->

3. Talking back

A page answers JSON when asked - ?page&padFormat=json - with only what its PHP names in $padExpose; the templates do not run for it. A post carries the session's token in a header, read from <meta name="csrf-token" content="{csrf token}">:

const answer = await pad.get('examples/search', { q: this.q });   // the JSON of $padExpose
const saved  = await pad.post('examples/todos', { op: 'add', text }); // X-CSRF-Token sent

4. One set of rules

Keep a form's rules in PHP: padValidate ( $rules ) checks the post, padValidateClient ( $rules ) hands them to the component, {validator} brings the checker, and padValidate.field ( rules.email, value, form ) answers in the server's words.

5. Events from the server

padSse() streams JSON events; an EventSource opened in x-init - in a component that closes it again in destroy() - turns them into state.

Values are text: whatever a visitor typed, the ^ sigil writes it as JSON escaped for the attribute, and x-text puts it in as text. Avoid x-html with data that is not yours.