Decoupled Frontends & CORS
Mounting an Xcrud widget - or just its toast/spinner helpers - from a frontend that isn't a PHP page at all.
window.Xcrud.mount()
Every Xcrud widget is, client-side, a plain JavaScript object. core/xcrud.js exposes a global helper that constructs one directly against a mount point, without any PHP render() call involved:
<div id="payments-grid"></div>
<script src="core/xcrud.js"></script>
<script>
window.Xcrud.mount('payments-grid', {
route: 'payments',
api: 'https://api.example.com/core/ajax_crud.php'
});
</script>
This is the shape a fully static frontend (a plain HTML file, a separate SPA) uses to embed a grid that talks to an xCRUD Nova Pro backend it doesn't share a PHP runtime with. The mount point still needs the matching theme's assets loaded first - see Xcrud::uiAssets() in the Configuration Reference.
Widget-less helpers
The same global also exposes the UI-feedback helpers every mounted grid already uses internally, so a page with no grid at all - just a button, say - can reuse the exact same look:
| Function | Description |
|---|---|
Xcrud.toast(message, type) | Shows the same toast notification a Save/Delete action or a hook's own ->toast() call would. |
Xcrud.spinnerHtml(ui) | Returns the theme's spinner markup as an HTML string. ui defaults to XcrudConfig::$ui's own default ('tailwind') when omitted. |
Xcrud.setButtonLoading(btn, isLoading, loadingLabel, ui) | Toggles a button between its normal and loading state (disabled, spinner, optional label swap) - the same treatment every Save/Delete button uses while a request is in flight. |
<button id="run-report">Generate Report</button>
<script>
var btn = document.getElementById('run-report');
btn.addEventListener('click', function () {
window.Xcrud.setButtonLoading(btn, true, 'Processing…');
fetch('core/demo_async.php', { method: 'POST' })
.then(function (res) { return res.json(); })
.then(function (json) {
window.Xcrud.setButtonLoading(btn, false);
window.Xcrud.toast(json.message || 'Done.', json.success ? 'success' : 'error');
});
});
</script>
This is exactly the pattern pages/async-demo.php uses - a page with no $xcrud widget at all, loading Xcrud::uiAssets() and core/xcrud.js directly to get these globals.
Server-side config for cross-origin use
When the frontend and backend are served from different origins, several XcrudConfig settings need to move away from their same-origin defaults:
| Property | Type | Description |
|---|---|---|
$allowedOrigins | array | The CORS whitelist - origins allowed to call ajax_crud.php cross-domain. A frontend origin missing from this list is the most common cause of a browser-side "network error" even though the backend responded fine. |
$crossOriginSessionCookie | bool | When true, switches the login session cookie to SameSite=None; Secure so it survives a cross-origin fetch(..., {credentials: 'include'}) call. Needed only when $loginAuthentication is also on and the frontend is on a different origin. Requires a genuinely secure context (real HTTPS, or both sides on localhost/127.0.0.1). |
$forceApiUrl | string | Forces every widget on every page to call this absolute URL for the API, instead of the relative core/ajax_crud.php default - needed when the HTML is served from a different origin than the API. |
$forceUploadUrl | string | Same idea for the upload endpoint. |
$forceGalleryUrl | string | Same idea for the gallery endpoint. |
$forceMediaUrl | string | Same idea for images_handler.php, the signed endpoint every uploaded file is actually served through. |
public static array $allowedOrigins = ['https://app.example.com'];
public static bool $crossOriginSessionCookie = true;
public static string $forceApiUrl = 'https://api.example.com/core/ajax_crud.php';
public static string $forceUploadUrl = 'https://api.example.com/core/upload.php';
public static string $forceMediaUrl = 'https://api.example.com/core/images_handler.php';