Headless and HATEOAS
HTML Forms
By default, you would use HTML forms like this to send data to the Kirby Kart plugin routes.
<?php
/** @var ProductPage $page */
$product ??= $page;
if ($product->inStock()) { ?>
<form method="POST" action="<?= $product->add() ?>">
<input type="hidden" name="redirect" value="<?= $redirect ?? $page->url() ?>">
<button type="submit" onclick="this.disabled=true;this.form.submit();">Add to cart</button>
</form>
<?php } else { ?>
<p><mark>Out of stock</mark></p>
<?php }
Headless
To use the same routes but for a headless setup when using a modern Javascript framework like Next.js, Vue or Solid to build your website you need to adjust it a bit. Given both the Content-Type
and X-CSRF-TOKEN
the Kirby Kart plugins router will respond with JSON instead of redirecting (like it would in the HTML form example).
You can force the Kirby Kart router to always respond with JSON in setting the bnomei.kart.router.mode = json
in the config.
import { createSignal } from "solid-js";
import { csrfToken } from "./store"; // Import global CSRF token
const CartToggleButton = (props) => {
const [inCart, setInCart] = createSignal(props.product.inCart); // Initial state
const toggleCart = async () => {
const url = inCart()
? `/kart/cart-remove?product=${props.product.uuid.id}`
: `/kart/cart-add?product=${props.product.uuid.id}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": csrfToken(),
},
});
if (response.ok) {
setInCart(!inCart()); // Toggle state on success
}
};
return props.product.inStock ? (
<button onClick={toggleCart}>
{inCart() ? "Remove from cart" : "Add to cart"}
</button>
) : (
<p><mark>Out of stock</mark></p>
);
};
export default CartToggleButton;
TIP: You can use the kart/csrf
-endpoint to retrieve the current CSRF session token.
TIP: The Kirby Kart plugin supports KQL in exposing the most common props by default.
TIP: Instead of relying on the default behaviour of the router you can also use snippets to customize the JSON output. See HATEOAS example below for more details.
HATEOAS
If you want your website to swap HTML instead of making a form request and reloading then Htmx or Datastar are great choices. The Kirby Kart router will automatically detect the HX-Request
-header sent by Htmx and respond with a matching snippet (see below).
While you could force the Kirby Kart router to always respond with HTML in setting the bnomei.kart.router.mode = html
in the config you might not want that for every request. It might be easier to send a fake HX-Request
header for Datastar instead.
The bnomei.kart.router.snippets
config setting provides a default mapping for snippets. Only the snippets listed in the array will be called and you need to overwrite this setting to add your own.
'bnomei.kart.router.snippets' => [
// define the snippets that are allowed to be called
'kart/cart-add',
'kart/cart-buy',
'kart/cart-later', // dummy
'kart/cart-remove', // dummy
'kart/captcha',
'kart/login',
'kart/login-magic',
'kart/signup-magic',
'kart/wish-or-forget',
'kart/wishlist-add' => 'kart/wish-or-forget.htmx', // htmx
'kart/wishlist-now', // dummy
'kart/wishlist-remove' => 'kart/wish-or-forget.htmx', // htmx
// overwrite to change or set your own
],
For all plain values, the router will respond with the HTML code generated by the snippet. So the route kart/cart-add
will render the snippet site/snippets/kart/card-add.php
.
Note the two mappings that have the Htmx comment. Here the route is mapped to a snippet with a different name to allow the dynamic behaviour.
kart/wishlist-add
outputs site/snippets/kart/wish-or-forget.htmx.php
and the route kart/wishlist-route
outputs site/snippets/kart/wish-or-forget.htmx.php
as well.
This works since the snippet is aware of the state of the product in the wishlist.
<?php
$product ??= $page;
?>
<?php if (! kart()->wishlist()->has($product)) { ?>
<button hx-post="<?= $product->wish() ?>"
hx-disabled-elt="this"
hx-swap="outerHTML"
title="add to wishlist">Add to wishlist</button>
<?php } else { ?>
<button hx-post="<?= $product->forget() ?>"
hx-disabled-elt="this"
hx-swap="outerHTML"
title="remove from wishlist">Remove from wishlist</button>
<?php }