Components in a file
Tabs, an accordion and a dialog registered with Alpine.data in www/alpine/ - the template calls them by name and hands them PAD's data as the argument: x-data="accordion({^faq})".
{^field}{faq}
Alpine.datax-showx-transition
Live
Accordion - accordion({^faq})
Alpine writes objects with braces, and PAD reads every brace of a template as the start of a tag. Let PAD write the JSON instead.
In www/alpine/examples/components.js - a file the web server sends as it is, so its braces never meet the template parser.
The answers are in the HTML PAD wrote; x-cloak and x-show only fold them away once Alpine runs.
Tabs - tabs({^tabs})
PAD renders the page, with every value escaped for where it stands.
Dialog - dialog()
Rendered by PAD, shown by Alpine
This box was in the page from the start; Alpine only decides when it shows.
Requests to PAD
Nothing yet - use the example above, and every request a component makes to PAD shows here.
How it is built
PAD, on the server Alpine, in the browser
<div class="cols-2">
<section>
<div class="panel-title">Accordion - accordion({open}^faq{close})</div>
<div class="accordion" x-data="accordion({^faq})">
{faq}
<div class="accordion-item" :class="isOpen({current@faq}) && 'is-open'">
<button class="accordion-head" @click="toggle({current@faq})" :aria-expanded="isOpen({current@faq})">{$q}<span aria-hidden="true">+</span></button>
<div class="accordion-body" x-show="isOpen({current@faq})" x-transition>{$a}</div>
</div>
{/faq}
</div>
</section>
<section>
<div class="panel-title">Tabs - tabs({open}^tabs{close})</div>
<div class="tabs" x-data="tabs({^tabs})">
<div class="pills" role="tablist">
<template x-for="tab in list" :key="tab.key">
<button class="pill" role="tab" :class="tab.key === active && 'is-on'" :aria-selected="tab.key === active" @click="active = tab.key" x-text="tab.label"></button>
</template>
</div>
<p class="panel tab-text" x-text="current().text">{$tabs.0.text}</p>
</div>
<div class="panel-title" style="margin-top: 26px">Dialog - dialog()</div>
<div x-data="dialog()">
<button @click="show()">Open the dialog</button>
<div class="backdrop" x-show="open" x-transition.opacity x-cloak @click.self="hide()" @keydown.escape.window="hide()">
<div class="dialog" role="dialog" aria-modal="true" aria-label="A dialog" x-show="open" x-transition>
<h3>Rendered by PAD, shown by Alpine</h3>
<p class="muted">This box was in the page from the start; Alpine only decides when it shows.</p>
<button class="btn-ghost" @click="hide()" x-ref="close">Close</button>
</div>
</div>
</div>
</section>
</div>
<script src="{$padRoot}alpine/examples/components.js"></script><?php
// The data of the components: plain arrays here, a database or a _data file anywhere else.
// The template hands each to its component as the argument - x-data="accordion({^faq})" -
// and renders the same rows as HTML inside it, for a visitor without JavaScript.
$faq = [ [ 'q' => 'Why not write the state in the template?',
'a' => 'Alpine writes objects with braces, and PAD reads every brace of a template as the start of a tag. Let PAD write the JSON instead.' ],
[ 'q' => 'Where do the components live?',
'a' => 'In www/alpine/examples/components.js - a file the web server sends as it is, so its braces never meet the template parser.' ],
[ 'q' => 'Does it work without JavaScript?',
'a' => 'The answers are in the HTML PAD wrote; x-cloak and x-show only fold them away once Alpine runs.' ] ];
$tabs = [ [ 'key' => 'server', 'label' => 'Server', 'text' => 'PAD renders the page, with every value escaped for where it stands.' ],
[ 'key' => 'wire', 'label' => 'Wire', 'text' => 'The state crosses as JSON in an attribute - no second request.' ],
[ 'key' => 'client', 'label' => 'Client', 'text' => 'Alpine reads it and keeps the component live in the browser.' ] ];
?>// Three components, registered before Alpine starts. Each is a function the template calls
// in x-data, with PAD's data as the argument: accordion({^faq}), tabs({^tabs}), dialog().
// This file is sent by the web server as it is - its braces never meet the PAD parser.
document.addEventListener('alpine:init', () => {
// Which question is open - the index PAD's {current@faq} wrote, starting at 1.
Alpine.data('accordion', (items) => ({
items,
open: 1,
isOpen(at) { return this.open === at; },
toggle(at) { this.open = this.open === at ? 0 : at; }
}));
Alpine.data('tabs', (list) => ({
list,
active: list[0].key,
current() { return this.list.find(tab => tab.key === this.active); }
}));
Alpine.data('dialog', () => ({
open: false,
show() {
this.open = true;
this.$nextTick(() => this.$refs.close.focus());
},
hide() { this.open = false; }
}));
});