-
Notifications
You must be signed in to change notification settings - Fork 921
Description
Environment
- Backpack CRUD: 7.0.22
- Basset: 2.0.2
- DataTables: 2.1.8 (bundled)
- PHP: 8.3
- Laravel: 12
- BASSET_DISK:
s3(CloudFront CDN)
Description
All @bassetBlock-powered button scripts (e.g. deleteEntry, quickButtonAction) fail with Uncaught ReferenceError: deleteEntry is not defined on CRUD list views when Basset is configured with an external disk like S3/CloudFront that produces absolute src URLs.
The show view works fine. The issue is specific to list views where buttons are loaded via the DataTables AJAX response.
Root Cause
The draw.dt handler in datatable_logic.blade.php (line ~740) re-executes <script> tags that DataTables 2.x no longer auto-executes due to replaceChildren(). However, the deduplication check finds the script element currently being iterated on (still in the <td>) before removing it:
document.getElementById(tableId).querySelectorAll('script').forEach(function(script) {
const scriptsToLoad = [];
if (script.src) {
const srcUrl = script.src;
// BUG: This querySelector searches the ENTIRE document, including the
// script we're currently processing (still inside the <td>).
// When the src attribute is absolute, it matches script.src exactly,
// so the script is found and skipped.
if (!document.querySelector(`script[src="${srcUrl}"]`)) {
// ... append to <head> (never reached)
}
// Script is removed from table but was never added to <head>
script.parentNode.removeChild(script);
}
});document.querySelector(script[src="${srcUrl}"]) matches against the src attribute value (not the resolved URL). This means:
-
BASSET_DISK=public(local disk): Thesrcattribute is a relative path (/storage/basset/.../delete-button.js?abc123), butscript.src(DOM property) resolves to an absolute URL (https://myapp.test/storage/basset/.../delete-button.js?abc123). The querySelector doesn't find a match (relative != absolute) → dedup passes → script loads correctly. -
BASSET_DISK=s3(external CDN): Thesrcattribute is already absolute (https://cdn.example.com/basset/.../delete-button.js?abc123), identical toscript.src. The querySelector finds the script currently being iterated → dedup incorrectly concludes it's already loaded → script is removed from the table but never added to<head>.
Steps to Reproduce
- Configure Basset with an external disk that produces absolute URLs:
BASSET_DEV_MODE=false BASSET_DISK=s3 - Run
php artisan basset:cacheto populate assets on S3 - Visit any CRUD list view that has a delete button (e.g.
/admin/entry) - Click the delete button
- Expected: SweetAlert confirmation dialog appears
- Actual:
Uncaught ReferenceError: deleteEntry is not defined
The same issue affects any button that uses @bassetBlock for its JavaScript.
Note: Also reproducible with BASSET_DISK=public by setting BASSET_RELATIVE_PATHS=false (which forces absolute URLs).
Proposed Fix
Move script.parentNode.removeChild(script) before the dedup querySelector check. This removes the script from the DOM first, so the querySelector won't find it:
tableElement.querySelectorAll('script').forEach(function(script) {
// Remove from table FIRST to prevent the dedup querySelector from finding itself
if (script.parentNode) {
script.parentNode.removeChild(script);
}
if (script.src) {
const srcUrl = script.src;
// Now this correctly only finds scripts already loaded in <head>
if (!document.querySelector(`script[src="${srcUrl}"]`)) {
const newScript = document.createElement('script');
Array.from(script.attributes).forEach(attr => {
newScript.setAttribute(attr.name, attr.value);
});
newScript.onerror = function(e) {
console.warn('Error loading script:', srcUrl, e);
};
try {
document.head.appendChild(newScript);
} catch (e) {
console.warn('Error appending external script:', e);
}
}
} else {
const newScript = document.createElement('script');
Array.from(script.attributes).forEach(attr => {
newScript.setAttribute(attr.name, attr.value);
});
newScript.textContent = script.textContent;
try {
document.head.appendChild(newScript);
} catch (e) {
console.warn('Error appending inline script:', e);
}
}
});This also removes the unused scriptsToLoad array (declared inside the forEach callback and never consumed).
Workaround
Publish datatable_logic.blade.php and apply the fix above:
# Copy the view to your project
mkdir -p resources/views/vendor/backpack/crud/components/datatable
cp vendor/backpack/crud/src/resources/views/crud/components/datatable/datatable_logic.blade.php \
resources/views/vendor/backpack/crud/components/datatable/datatable_logic.blade.phpThen edit the published file with the fix.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status