Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/lang/en-US/general.php
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,8 @@
'and' => 'and',
'action_source' => 'Action Source',
'search_tip' => 'Searches return a partial match by default. For more specific results, you can use <code>not:value</code> to exclude, <code>is:value</code> for an exact match, <code>is:null</code> for empty values, and <code>is:not_null</code> for non-empty. (In these examples, <code>value</code> is the text you are searching for.)',
'search_load_error_short' => 'Could not load records.',
'search_load_error_help' => 'An API error has occurred. Check your server logs for errors and confirm migrations are up to date.',
'or' => 'or',
'url' => 'URL',
'phone' => 'Phone',
Expand Down
143 changes: 143 additions & 0 deletions resources/views/partials/bootstrap-table.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,88 @@ function resize() {
return default_value;
}

var renderBootstrapTableLoadErrorState = function ($table) {
var $wrapper = $table.closest('.bootstrap-table');

if (!$wrapper.length) {
return;
}

var statusCode = parseInt($table.data('bs-table-load-error-status'), 10);
var statusSuffix = Number.isFinite(statusCode) && statusCode > 0 ? ' (HTTP ' + statusCode + ')' : '';

var errorHtml = '<div class="text-danger">'
+ '<i class="fas fa-triangle-exclamation" aria-hidden="true"></i> '
+ '{{ trans('general.search_load_error_short') }}' + statusSuffix
+ '<div class="text-muted" style="margin-top: 4px;">{{ trans('general.search_load_error_help') }}</div>'
+ '</div>';

var $targets = $wrapper.find('.fixed-table-body .no-records-found td, .fixed-table-body .fixed-table-loading');

if (!$targets.length) {
// On initial load errors, bootstrap-table can insert the no-records row
// after the load-error callback fires. Observe DOM changes briefly and
// render the error state once the row appears, then disconnect.
var existingObserver = $table.data('bs-table-load-error-observer');
if (existingObserver) {
return;
}

var wrapperNode = $wrapper.get(0);
if (!wrapperNode || typeof MutationObserver === 'undefined') {
return;
}

var observer = new MutationObserver(function () {
if ($table.data('bs-table-load-error') !== true) {
observer.disconnect();
$table.removeData('bs-table-load-error-observer');

return;
}

var $newTargets = $wrapper.find('.fixed-table-body .no-records-found td, .fixed-table-body .fixed-table-loading');
if ($newTargets.length) {
observer.disconnect();
$table.removeData('bs-table-load-error-observer');
renderBootstrapTableLoadErrorState($table);
}
});

observer.observe(wrapperNode, { childList: true, subtree: true });
$table.data('bs-table-load-error-observer', observer);

return;
}

var activeObserver = $table.data('bs-table-load-error-observer');
if (activeObserver) {
activeObserver.disconnect();
$table.removeData('bs-table-load-error-observer');
}

$targets.html(errorHtml);
};

var resolveHttpStatusFromLoadErrorArgs = function () {
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) {
continue;
}

if (typeof arg === 'number' && arg > 0) {
return arg;
}

if (typeof arg === 'object' && typeof arg.status === 'number' && arg.status > 0) {
return arg.status;
}
}

return null;
};



var initialAdvancedSearchOperator = getStoredAdvancedSearchOperator() || normalizeAdvancedSearchOperator(data_with_default('advanced-search-operator', defaultAdvancedSearchOperator));
Expand Down Expand Up @@ -607,8 +689,38 @@ classes: 'table table-responsive table-striped snipe-table table-no-bordered',
exportOptions: export_options,
exportTypes: ['xlsx', 'excel', 'csv', 'pdf', 'json', 'xml', 'txt', 'sql', 'doc'],
onLoadSuccess: function () { // possible 'fixme'? this might be for contents, not for headers?
// Clear transport/server error state so genuine empty results show normal messaging.
$(this).data('bs-table-load-error', false);
$(this).removeData('bs-table-load-error-status');
var observer = $(this).data('bs-table-load-error-observer');
if (observer) {
observer.disconnect();
$(this).removeData('bs-table-load-error-observer');
}
$('[data-tooltip="true"]').tooltip(); // Needed to attach tooltips after ajax call
},
onLoadError: function () {
// Mark this table as failed so formatNoMatches can render an explicit error state
// instead of the misleading "no records" empty-state message.
var $table = $(this);
$table.data('bs-table-load-error', true);
var statusCode = resolveHttpStatusFromLoadErrorArgs.apply(null, arguments);
if (statusCode) {
$table.data('bs-table-load-error-status', statusCode);
}

// On first page load, bootstrap-table may already have rendered the default
// no-records row. Force-replace that content immediately after the error.
renderBootstrapTableLoadErrorState($table);
},
onPostBody: function () {
// Keep the error empty-state stable if bootstrap-table re-renders body rows
// while the current table state is still a transport/server error.
var $table = $(this);
if ($table.data('bs-table-load-error') === true) {
renderBootstrapTableLoadErrorState($table);
}
},
onPostHeader: function () {
var lookup = {};
var lookup_initialized = false;
Expand Down Expand Up @@ -666,11 +778,42 @@ classes: 'table table-responsive table-striped snipe-table table-no-bordered',

},
formatNoMatches: function () {
if ($(this).data('bs-table-load-error') === true) {
var statusCode = parseInt($(this).data('bs-table-load-error-status'), 10);
var statusSuffix = Number.isFinite(statusCode) && statusCode > 0 ? ' (HTTP ' + statusCode + ')' : '';
return '<div class="text-danger" style="padding: 8px 0;">'
+ '<i class="fas fa-triangle-exclamation" aria-hidden="true" style="margin-right: 6px;"></i>'
+ '{{ trans('general.search_load_error_short') }}' + statusSuffix
+ '<div class="text-muted" style="margin-top: 4px;">{{ trans('general.search_load_error_help') }}</div>'
+ '</div>';
}

return '{{ trans('table.no_matching_records') }}';
}

});

// Event-driven fallback for initial-load timing/callback-context edge cases.
// In some bootstrap-table flows, onLoadError can race before the empty row exists
// or callback context may not be the raw table element. These events are emitted
// from the table node itself and are reliable across initial load and searches.
var $currentTable = $(this);
$currentTable.off('.snipeLoadState').on('load-success.bs.table.snipeLoadState', function () {
$currentTable.data('bs-table-load-error', false);
$currentTable.removeData('bs-table-load-error-status');
}).on('load-error.bs.table.snipeLoadState', function () {
$currentTable.data('bs-table-load-error', true);
var statusCode = resolveHttpStatusFromLoadErrorArgs.apply(null, Array.prototype.slice.call(arguments, 1));
if (statusCode) {
$currentTable.data('bs-table-load-error-status', statusCode);
}
renderBootstrapTableLoadErrorState($currentTable);
}).on('post-body.bs.table.snipeLoadState', function () {
if ($currentTable.data('bs-table-load-error') === true) {
renderBootstrapTableLoadErrorState($currentTable);
}
});

var bootstrapTableInstance = $(this).data('bootstrap.table');

if (bootstrapTableInstance && typeof bootstrapTableInstance.renderAdvancedSearchTags === 'function') {
Expand Down
Loading