Skip to content

Instantly share code, notes, and snippets.

@qya
Created August 7, 2021 09:23
Show Gist options
  • Save qya/9098b8972d90b728f6c22b2bca01b4f0 to your computer and use it in GitHub Desktop.
Save qya/9098b8972d90b728f6c22b2bca01b4f0 to your computer and use it in GitHub Desktop.
Backpack Laravel Datatables Footer

#How to Use Footer Data tables To use these u need change

        $this->crud->setDefaultPageLength(20);
@php
// as it is possible that we can be redirected with persistent table we save the alerts in a variable
// and flush them from session, so we will get them later from localStorage.
$backpack_alerts = \Alert::getMessages();
\Alert::flush();
@endphp
{{-- DATA TABLES SCRIPT --}}
<script type="text/javascript" src="{{ asset('packages/datatables.net/js/jquery.dataTables.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('packages/datatables.net-bs4/js/dataTables.bootstrap4.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('packages/datatables.net-responsive/js/dataTables.responsive.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('packages/datatables.net-responsive-bs4/js/responsive.bootstrap4.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('packages/datatables.net-fixedheader/js/dataTables.fixedHeader.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('packages/datatables.net-fixedheader-bs4/js/fixedHeader.bootstrap4.min.js') }}"></script>
<script>
// here we will check if the cached dataTables paginator length is conformable with current paginator settings.
// datatables caches the ajax responses with pageLength in LocalStorage so when changing this
// settings in controller users get unexpected results. To avoid that we will reset
// the table cache when both lengths don't match.
let $dtCachedInfo = JSON.parse(localStorage.getItem('DataTables_crudTable_/{{$crud->getRoute()}}'))
? JSON.parse(localStorage.getItem('DataTables_crudTable_/{{$crud->getRoute()}}')) : [];
var $dtDefaultPageLength = {{ $crud->getDefaultPageLength() }};
let $dtStoredPageLength = localStorage.getItem('DataTables_crudTable_/{{$crud->getRoute()}}_pageLength');
if(!$dtStoredPageLength && $dtCachedInfo.length !== 0 && $dtCachedInfo.length !== $dtDefaultPageLength) {
localStorage.removeItem('DataTables_crudTable_/{{$crud->getRoute()}}');
}
// in this page we allways pass the alerts to localStorage because we can be redirected with
// persistent table, and this way we guarantee non-duplicate alerts.
$oldAlerts = JSON.parse(localStorage.getItem('backpack_alerts'))
? JSON.parse(localStorage.getItem('backpack_alerts')) : {};
$newAlerts = @json($backpack_alerts);
Object.entries($newAlerts).forEach(([type, messages]) => {
if(typeof $oldAlerts[type] !== 'undefined') {
$oldAlerts[type].push(...messages);
} else {
$oldAlerts[type] = messages;
}
});
// always store the alerts in localStorage for this page
localStorage.setItem('backpack_alerts', JSON.stringify($oldAlerts));
@if ($crud->getPersistentTable())
var saved_list_url = localStorage.getItem('{{ Str::slug($crud->getRoute()) }}_list_url');
//check if saved url has any parameter or is empty after clearing filters.
if (saved_list_url && saved_list_url.indexOf('?') < 1) {
var saved_list_url = false;
} else {
var persistentUrl = saved_list_url+'&persistent-table=true';
}
var arr = window.location.href.split('?');
// check if url has parameters.
if (arr.length > 1 && arr[1] !== '') {
// IT HAS! Check if it is our own persistence redirect.
if (window.location.search.indexOf('persistent-table=true') < 1) {
// IF NOT: we don't want to redirect the user.
saved_list_url = false;
}
}
@if($crud->getPersistentTableDuration())
var saved_list_url_time = localStorage.getItem('{{ Str::slug($crud->getRoute()) }}_list_url_time');
if (saved_list_url_time) {
var $current_date = new Date();
var $saved_time = new Date(parseInt(saved_list_url_time));
$saved_time.setMinutes($saved_time.getMinutes() + {{$crud->getPersistentTableDuration()}});
// if the save time is not expired we force the filter redirection.
if($saved_time > $current_date) {
if (saved_list_url && persistentUrl!=window.location.href) {
window.location.href = persistentUrl;
}
} else {
// persistent table expired, let's not redirect the user
saved_list_url = false;
}
}
@endif
if (saved_list_url && persistentUrl!=window.location.href) {
// finally redirect the user.
window.location.href = persistentUrl;
}
@endif
var crud = {
exportButtons: JSON.parse('{!! json_encode($crud->get('list.export_buttons')) !!}'),
functionsToRunOnDataTablesDrawEvent: [],
addFunctionToDataTablesDrawEventQueue: function (functionName) {
if (this.functionsToRunOnDataTablesDrawEvent.indexOf(functionName) == -1) {
this.functionsToRunOnDataTablesDrawEvent.push(functionName);
}
},
responsiveToggle: function(dt) {
$(dt.table().header()).find('th').toggleClass('all');
dt.responsive.rebuild();
dt.responsive.recalc();
},
executeFunctionByName: function(str, args) {
var arr = str.split('.');
var fn = window[ arr[0] ];
for (var i = 1; i < arr.length; i++)
{ fn = fn[ arr[i] ]; }
fn.apply(window, args);
},
updateUrl : function (new_url) {
url_start = "{{ url($crud->route) }}";
url_end = new_url.replace(url_start, '');
url_end = url_end.replace('/search', '');
new_url = url_start + url_end;
window.history.pushState({}, '', new_url);
localStorage.setItem('{{ Str::slug($crud->getRoute()) }}_list_url', new_url);
},
dataTableConfiguration: {
@if ($crud->getResponsiveTable())
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal( {
header: function ( row ) {
// show the content of the first column
// as the modal header
// var data = row.data();
// return data[0];
return '';
}
} ),
renderer: function ( api, rowIdx, columns ) {
var data = $.map( columns, function ( col, i ) {
var columnHeading = crud.table.columns().header()[col.columnIndex];
// hide columns that have VisibleInModal false
if ($(columnHeading).attr('data-visible-in-modal') == 'false') {
return '';
}
return '<tr data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
'<td style="vertical-align:top; border:none;"><strong>'+col.title.trim()+':'+'<strong></td> '+
'<td style="padding-left:10px;padding-bottom:10px; border:none;">'+col.data+'</td>'+
'</tr>';
} ).join('');
return data ?
$('<table class="table table-striped mb-0">').append( '<tbody>' + data + '</tbody>' ) :
false;
},
}
},
fixedHeader: true,
@else
responsive: false,
scrollX: true,
@endif
@if($crud->getDefaultPageLength() == 20)
/*
sum table on footer
*/
footerCallback: function ( row, data, start, end, display ) {
var api = this.api(), data;
var intVal = function ( i ) {
return typeof i === 'string' ?
$(i).text().replace('x','').replace('IDR','').replace(/[\$,]/g, '')*1 :
typeof i === 'number' ?
i : 0;
};
var intMoney = function(i){
if(isNaN(i)) return '';
return typeof i === 'number' ? i.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,') :'';
}
@php $x = 0; @endphp
@foreach ($crud->columns() as $column)
@if ($column['label'] == 'No Rekening')
// Nothing
@else
var total{{$x}} = api.column({{$x}}).data().reduce(function(a, b) {
return intVal(a) + intVal(b);
}, 0);
@if($x == 0)
$( api.column( 0 ).footer() ).html('Total');
@else
$( api.column({{$x}}).footer() ).html(intMoney(total{{$x}}));
@endif
@endif
@php $x++; @endphp
@endforeach
},
@endif
@if ($crud->getPersistentTable())
stateSave: true,
/*
if developer forced field into table 'visibleInTable => true' we make sure when saving datatables state
that it reflects the developer decision.
*/
stateSaveParams: function(settings, data) {
localStorage.setItem('{{ Str::slug($crud->getRoute()) }}_list_url_time', data.time);
data.columns.forEach(function(item, index) {
var columnHeading = crud.table.columns().header()[index];
if ($(columnHeading).attr('data-visible-in-table') == 'true') {
return item.visible = true;
}
});
},
@if($crud->getPersistentTableDuration())
stateLoadParams: function(settings, data) {
var $saved_time = new Date(data.time);
var $current_date = new Date();
$saved_time.setMinutes($saved_time.getMinutes() + {{$crud->getPersistentTableDuration()}});
//if the save time as expired we force datatabled to clear localStorage
if($saved_time < $current_date) {
if (localStorage.getItem('{{ Str::slug($crud->getRoute())}}_list_url')) {
localStorage.removeItem('{{ Str::slug($crud->getRoute()) }}_list_url');
}
if (localStorage.getItem('{{ Str::slug($crud->getRoute())}}_list_url_time')) {
localStorage.removeItem('{{ Str::slug($crud->getRoute()) }}_list_url_time');
}
return false;
}
},
@endif
@endif
autoWidth: false,
pageLength: $dtDefaultPageLength,
lengthMenu: @json($crud->getPageLengthMenu()),
/* Disable initial sort */
aaSorting: [],
language: {
"emptyTable": "{{ trans('backpack::crud.emptyTable') }}",
"info": "{{ trans('backpack::crud.info') }}",
"infoEmpty": "{{ trans('backpack::crud.infoEmpty') }}",
"infoFiltered": "{{ trans('backpack::crud.infoFiltered') }}",
"infoPostFix": "{{ trans('backpack::crud.infoPostFix') }}",
"thousands": "{{ trans('backpack::crud.thousands') }}",
"lengthMenu": "{{ trans('backpack::crud.lengthMenu') }}",
"loadingRecords": "{{ trans('backpack::crud.loadingRecords') }}",
"processing": "<img src='{{ asset('packages/backpack/crud/img/ajax-loader.gif') }}' alt='{{ trans('backpack::crud.processing') }}'>",
"search": "_INPUT_",
"searchPlaceholder": "{{ trans('backpack::crud.search') }}...",
"zeroRecords": "{{ trans('backpack::crud.zeroRecords') }}",
"paginate": {
"first": "{{ trans('backpack::crud.paginate.first') }}",
"last": "{{ trans('backpack::crud.paginate.last') }}",
"next": ">",
"previous": "<"
},
"aria": {
"sortAscending": "{{ trans('backpack::crud.aria.sortAscending') }}",
"sortDescending": "{{ trans('backpack::crud.aria.sortDescending') }}"
},
"buttons": {
"copy": "{{ trans('backpack::crud.export.copy') }}",
"excel": "{{ trans('backpack::crud.export.excel') }}",
"csv": "{{ trans('backpack::crud.export.csv') }}",
"pdf": "{{ trans('backpack::crud.export.pdf') }}",
"print": "{{ trans('backpack::crud.export.print') }}",
"colvis": "{{ trans('backpack::crud.export.column_visibility') }}"
},
},
processing: true,
serverSide: true,
searching: @json($crud->getOperationSetting('searchableTable') ?? true),
ajax: {
"url": "{!! url($crud->route.'/search').'?'.Request::getQueryString() !!}",
"type": "POST"
},
dom:
"<'row hidden'<'col-sm-6'i><'col-sm-6 d-print-none'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row mt-2 d-print-none '<'col-sm-12 col-md-4'l><'col-sm-0 col-md-4 text-center'B><'col-sm-12 col-md-4 'p>>",
}
}
</script>
@include('crud::inc.export_buttons')
<script type="text/javascript">
jQuery(document).ready(function($) {
crud.table = $("#crudTable").DataTable(crud.dataTableConfiguration);
// move search bar
$("#crudTable_filter").appendTo($('#datatable_search_stack' ));
$("#crudTable_filter input").removeClass('form-control-sm');
// move "showing x out of y" info to header
@if($crud->getSubheading())
$('#crudTable_info').hide();
@else
$("#datatable_info_stack").html($('#crudTable_info')).css('display','inline-flex').addClass('animated fadeIn');
@endif
@if($crud->getOperationSetting('resetButton') ?? true)
// create the reset button
var crudTableResetButton = '<a href="{{url($crud->route)}}" class="ml-1" id="crudTable_reset_button">{{ trans('backpack::crud.reset') }}</a>';
$('#datatable_info_stack').append(crudTableResetButton);
// when clicking in reset button we clear the localStorage for datatables.
$('#crudTable_reset_button').on('click', function() {
//clear the filters
if (localStorage.getItem('{{ Str::slug($crud->getRoute())}}_list_url')) {
localStorage.removeItem('{{ Str::slug($crud->getRoute()) }}_list_url');
}
if (localStorage.getItem('{{ Str::slug($crud->getRoute())}}_list_url_time')) {
localStorage.removeItem('{{ Str::slug($crud->getRoute()) }}_list_url_time');
}
//clear the table sorting/ordering/visibility
if(localStorage.getItem('DataTables_crudTable_/{{ $crud->getRoute() }}')) {
localStorage.removeItem('DataTables_crudTable_/{{ $crud->getRoute() }}');
}
});
@endif
// move the bottom buttons before pagination
$("#bottom_buttons").insertBefore($('#crudTable_wrapper .row:last-child' ));
// override ajax error message
$.fn.dataTable.ext.errMode = 'none';
$('#crudTable').on('error.dt', function(e, settings, techNote, message) {
new Noty({
type: "error",
text: "<strong>{{ trans('backpack::crud.ajax_error_title') }}</strong><br>{{ trans('backpack::crud.ajax_error_text') }}"
}).show();
});
// when changing page length in datatables, save it into localStorage
// so in next requests we know if the length changed by user
// or by developer in the controller.
$('#crudTable').on( 'length.dt', function ( e, settings, len ) {
localStorage.setItem('DataTables_crudTable_/{{$crud->getRoute()}}_pageLength', len);
});
// make sure AJAX requests include XSRF token
$.ajaxPrefilter(function(options, originalOptions, xhr) {
var token = $('meta[name="csrf_token"]').attr('content');
if (token) {
return xhr.setRequestHeader('X-XSRF-TOKEN', token);
}
});
// on DataTable draw event run all functions in the queue
// (eg. delete and details_row buttons add functions to this queue)
$('#crudTable').on( 'draw.dt', function () {
crud.functionsToRunOnDataTablesDrawEvent.forEach(function(functionName) {
crud.executeFunctionByName(functionName);
});
} ).dataTable();
@if ($crud->getResponsiveTable())
// when columns are hidden by reponsive plugin,
// the table should have the has-hidden-columns class
crud.table.on( 'responsive-resize', function ( e, datatable, columns ) {
if (crud.table.responsive.hasHidden()) {
$("#crudTable").removeClass('has-hidden-columns').addClass('has-hidden-columns');
} else {
$("#crudTable").removeClass('has-hidden-columns');
}
} );
@else
// make sure the column headings have the same width as the actual columns
// after the user manually resizes the window
var resizeTimer;
function resizeCrudTableColumnWidths() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
// Run code here, resizing has "stopped"
crud.table.columns.adjust();
}, 250);
}
$(window).on('resize', function(e) {
resizeCrudTableColumnWidths();
});
$('.sidebar-toggler').click(function() {
resizeCrudTableColumnWidths();
});
@endif
});
</script>
@include('crud::inc.details_row_logic')
@extends(backpack_view('blank'))
@php
$defaultBreadcrumbs = [
trans('backpack::crud.admin') => url(config('backpack.base.route_prefix'), 'dashboard'),
$crud->entity_name_plural => url($crud->route),
trans('backpack::crud.list') => false,
];
// if breadcrumbs aren't defined in the CrudController, use the default breadcrumbs
$breadcrumbs = $breadcrumbs ?? $defaultBreadcrumbs;
@endphp
@section('header')
<div class="container-fluid">
<h2>
<span class="text-capitalize">{!! $crud->getHeading() ?? $crud->entity_name_plural !!}</span>
<small id="datatable_info_stack">{!! $crud->getSubheading() ?? '' !!}</small>
</h2>
</div>
@endsection
@section('content')
<!-- Default box -->
<div class="row">
<!-- THE ACTUAL CONTENT -->
<div class="{{ $crud->getListContentClass() }}">
<div class="row mb-0">
<div class="col-sm-6">
@if ( $crud->buttons()->where('stack', 'top')->count() || $crud->exportButtons())
<div class="d-print-none {{ $crud->hasAccess('create')?'with-border':'' }}">
@include('crud::inc.button_stack', ['stack' => 'top'])
</div>
@endif
</div>
<div class="col-sm-6">
<div id="datatable_search_stack" class="mt-sm-0 mt-2 d-print-none"></div>
</div>
</div>
{{-- Backpack List Filters --}}
@if ($crud->filtersEnabled())
@include('crud::inc.filters_navbar')
@endif
<table id="crudTable" class="bg-white table table-striped table-hover nowrap rounded shadow-xs border-xs mt-2" cellspacing="0">
<thead>
<tr>
{{-- Table columns --}}
@foreach ($crud->columns() as $column)
<th
data-orderable="{{ var_export($column['orderable'], true) }}"
data-priority="{{ $column['priority'] }}"
{{--
data-visible-in-table => if developer forced field in table with 'visibleInTable => true'
data-visible => regular visibility of the field
data-can-be-visible-in-table => prevents the column to be loaded into the table (export-only)
data-visible-in-modal => if column apears on responsive modal
data-visible-in-export => if this field is exportable
data-force-export => force export even if field are hidden
--}}
{{-- If it is an export field only, we are done. --}}
@if(isset($column['exportOnlyField']) && $column['exportOnlyField'] === true)
data-visible="false"
data-visible-in-table="false"
data-can-be-visible-in-table="false"
data-visible-in-modal="false"
data-visible-in-export="true"
data-force-export="true"
@else
data-visible-in-table="{{var_export($column['visibleInTable'] ?? false)}}"
data-visible="{{var_export($column['visibleInTable'] ?? true)}}"
data-can-be-visible-in-table="true"
data-visible-in-modal="{{var_export($column['visibleInModal'] ?? true)}}"
@if(isset($column['visibleInExport']))
@if($column['visibleInExport'] === false)
data-visible-in-export="false"
data-force-export="false"
@else
data-visible-in-export="true"
data-force-export="true"
@endif
@else
data-visible-in-export="true"
data-force-export="false"
@endif
@endif
>
{!! $column['label'] !!}
</th>
@endforeach
@if ( $crud->buttons()->where('stack', 'line')->count() )
<th data-orderable="false"
data-priority="{{ $crud->getActionsColumnPriority() }}"
data-visible-in-export="false"
>{{ trans('backpack::crud.actions') }}</th>
@endif
</tr>
</thead>
<tbody>
</tbody>
@if($crud->getDefaultPageLength() == 20)
<tfoot>
<tr>
@foreach ($crud->columns() as $column)
<th></th>
@endforeach
@if ( $crud->buttons()->where('stack', 'line')->count() )
<th></th>
@endif
</tr>
</tfoot>
@else
<tfoot>
<tr>
@foreach ($crud->columns() as $column)
<th>{!! $column['label'] !!}</th>
@endforeach
@if ( $crud->buttons()->where('stack', 'line')->count() )
<th>{{ trans('backpack::crud.actions') }}</th>
@endif
</tr>
</tfoot>
@endif
</table>
@if ( $crud->buttons()->where('stack', 'bottom')->count() )
<div id="bottom_buttons" class="d-print-none text-center text-sm-left">
@include('crud::inc.button_stack', ['stack' => 'bottom'])
<div id="datatable_button_stack" class="float-right text-right hidden-xs"></div>
</div>
@endif
</div>
</div>
@endsection
@section('after_styles')
<!-- DATA TABLES -->
<link rel="stylesheet" type="text/css" href="{{ asset('packages/datatables.net-bs4/css/dataTables.bootstrap4.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ asset('packages/datatables.net-fixedheader-bs4/css/fixedHeader.bootstrap4.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ asset('packages/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css') }}">
<link rel="stylesheet" href="{{ asset('packages/backpack/crud/css/crud.css').'?v='.config('backpack.base.cachebusting_string') }}">
<link rel="stylesheet" href="{{ asset('packages/backpack/crud/css/form.css').'?v='.config('backpack.base.cachebusting_string') }}">
<link rel="stylesheet" href="{{ asset('packages/backpack/crud/css/list.css').'?v='.config('backpack.base.cachebusting_string') }}">
<!-- CRUD LIST CONTENT - crud_list_styles stack -->
@stack('crud_list_styles')
@endsection
@section('after_scripts')
@include('crud::inc.datatables_logic')
<script src="{{ asset('packages/backpack/crud/js/crud.js').'?v='.config('backpack.base.cachebusting_string') }}"></script>
<script src="{{ asset('packages/backpack/crud/js/form.js').'?v='.config('backpack.base.cachebusting_string') }}"></script>
<script src="{{ asset('packages/backpack/crud/js/list.js').'?v='.config('backpack.base.cachebusting_string') }}"></script>
<!-- CRUD LIST CONTENT - crud_list_scripts stack -->
@stack('crud_list_scripts')
@endsection
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment