I have been fighting with this code all day and cannot figure out what I am doing wrong.
The page is supposed to load a datatable with fire hydrant data. There are two dynamic columns. One of the dynamic columns displays a line of test based on data in a related table. The other builds a button that links to the next inspection form.
This is my blade page
{{--
Template Name: Etiam posuere venenatis enim,
Version: 1.0
Last Updated: 04/07/2024 14:39
Author: Philip Hayes
url:
Description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec velit orci, consequat quis lobortis ac, tincidunt at est.
--}}
@php
$pageTitle = 'Hydrant Inspections';
@endphp
@extends('layouts.master')
@section('title', $pageTitle)
@section('headerStyle')
<link rel="stylesheet" media="screen, print"
href="{{ URL::asset('css/datagrid/datatables/datatables.bundle.css') }}">
<style>
/* Center the button in the table cell */
.center-align {
text-align: center;
}
</style>
@stop
@section('content')
<main id="js-page-content" role="main" class="page-content">
@component('common-components.breadcrumb')
@slot('item1')
Welcome
@endslot
@slot('item2')
Welcome to FireOps
@endslot
@endcomponent
<div class="subheader">
<h1 class="subheader-title">
<i class="subheader-icon fal fa-window"></i> {{ $pageTitle }}
{{-- Make this block of code visible if page_help is set to true --}}
@if ($page_help)
<small>
This is the holding spot for page help. Page help is a paragraph describing what the page is
used
for and how to use it.
</small>
@endif
{{-- End block of code --}}
</h1>
</div>
{{-- <div class="mb-2 text-right">--}}
{{-- <a href="{{ route('sfd.hydrant.inspection.map') }}">--}}
{{-- <button type="button" class="btn btn-primary waves-effect waves-themed">Hydrant Inspection Map</button>--}}
{{-- </a>--}}
{{-- </div>--}}
<div class="row mb-2">
<div class="col-lg-3 text-center">
<label for="station_id">Station</label>
<select class="select2 shadow-1 form-control @error('station_id') is-invalid @enderror"
id="station_id" name="station_id"> <!-- Correct name and ID -->
<option value="">Station Filter...</option>
@foreach ($stations as $station)
<option value="{{ $station->id }}">
{{ $station->name }}
</option>
@endforeach
</select>
</div>
<div class="col-lg-3 text-center">
<label for="shift_id">Shift</label>
<select class="select2 shadow-1 form-control @error('shift_id') is-invalid @enderror"
id="shift_id" name="shift_id"> <!-- Correct name and ID -->
<option value="">Shift Filter...</option>
@foreach ($shifts as $shift)
<option value="{{ $shift->id }}">
{{ $shift->name }}
</option>
@endforeach
</select>
</div>
<div class="col-lg-3 text-center">
<label for="next_inspection">Next Inspection</label>
<select class="select2 shadow-1 form-control @error('next_inspection') is-invalid @enderror"
id="next_inspection" name="next_inspection"> <!-- Correct name and ID -->
<option value="">Inspection Filter...</option>
<option value="1">First Inspection</option>
<option value="2">Second Inspection</option>
<option value="3">Third Inspection</option>
</select>
</div>
<div class="col-lg-3 text-center">
</div>
</div>
<!-- Updated 'Clear Filters' link with a JavaScript trigger -->
<div class="row mb-2">
<div class="col-lg-12 text-center">
<a href="javascript:void(0);" class="clear-filters">Clear Filters</a>
</div>
</div>
<table id="dt-basic-example" class="table table-bordered table-hover table-striped w-100">
<thead>
<tr>
<th>ID</th>
<th>Hydrant ID</th>
<th>Location</th>
<th>Station</th>
<th>Shift</th>
<th></th>
</tr>
</thead>
</table>
</main>
@stop
@section('footerScript')
<script src="{{ URL::asset('js/datagrid/datatables/datatables.bundle.js') }}"></script>
<script>
$(document).ready(function () {
// Combine the two document ready blocks here...
// Initialize Select2
$('#station_id').select2();
$('#shift_id').select2();
$('#next_inspection').select2();
// Function to update query parameters and reload the page
function updateQueryParam(param, value) {
var url = new URL(window.location.href);
url.searchParams.set(param, value);
window.location.href = url.href; // Reload the page with updated URL
}
// Listen for changes on the select2 station_id element
$('#station_id').on('change', function () {
var value = $(this).val();
if (value) {
updateQueryParam('station_id', value);
}
});
// Listen for changes on the select2 shift_id element
$('#shift_id').on('change', function () {
var value = $(this).val();
if (value) {
updateQueryParam('shift_id', value);
}
});
// Listen for changes on the select2 inspection element
$('#next_inspection').on('change', function () {
var value = $(this).val();
if (value) {
updateQueryParam('inspection', value);
}
});
// Function to set a cookie (plain JavaScript)
function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
// Function to get a cookie (plain JavaScript)
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
// Function to clear cookies
function clearCookies() {
setCookie('hydrant_station_id', '', -1);
setCookie('hydrant_shift_id', '', -1);
setCookie('hydrant_next_inspection', '', -1);
setCookie('datatable_page', '', -1);
setCookie('datatable_search', '', -1);
setCookie('datatable_length', '', -1);
}
// Retrieve the cookies and apply them to filters on page load
var savedStationId = getCookie('hydrant_station_id');
var savedShiftId = getCookie('hydrant_shift_id');
var savedNextInspection = getCookie('hydrant_next_inspection');
var savedPage = getCookie('datatable_page') ? parseInt(getCookie('datatable_page')) : 0;
var savedSearch = getCookie('datatable_search');
var savedLength = getCookie('datatable_length') ? parseInt(getCookie('datatable_length')) : 10;
if (savedStationId) {
$('#station_id').val(savedStationId).trigger('change.select2');
}
if (savedShiftId) {
$('#shift_id').val(savedShiftId).trigger('change.select2');
}
if (savedNextInspection) {
$('#next_inspection').val(savedNextInspection).trigger('change.select2');
}
// Initialize DataTable and apply filters, search criteria, pagination, and entries per page
var table = $('#dt-basic-example').DataTable({
processing: true,
serverSide: true,
responsive: true,
pageLength: savedLength,
displayStart: savedPage * savedLength,
search: {search: savedSearch || ""},
ajax: {
url: '{{ route('sfd.hydrant.inspection.index') }}',
data: function (d) {
d.station_id = $('#station_id').val();
d.shift_id = $('#shift_id').val();
d.next_inspection = $('#next_inspection').val();
},
error: function (xhr, error, code) {
console.log("AJAX error response:", xhr, code);
}
},
columns: [
{data: 'id', name: 'id'},
{data: 'hydrantid', name: 'hydrantid'},
{data: 'location_description', name: 'location_description'},
{data: 'station_name', name: 'station_name'},
{data: 'shift_name', name: 'shift_name'},
{
data: 'next_inspection', // Add the next_inspection column here
name: 'next_inspection',
orderable: true, // Allow sorting by this column
searchable: false,
className: 'center-align'
},
{
data: 'action',
name: 'action',
orderable: false,
searchable: false,
className: 'center-align',
render: function (data, type, row) {
return data;
}
}
],
fnDrawCallback: function (oSettings) {
var currentPage = table.page();
setCookie('datatable_page', currentPage, 7);
}
});
$('#dt-basic-example_filter input').on('input', function () {
var searchValue = $(this).val();
setCookie('datatable_search', searchValue, 7);
});
$('#station_id, #shift_id, #next_inspection').on('change', function () {
var station_id = $('#station_id').val();
var shift_id = $('#shift_id').val();
var next_inspection = $('#next_inspection').val();
setCookie('hydrant_station_id', station_id, 30);
setCookie('hydrant_shift_id', shift_id, 30);
setCookie('hydrant_next_inspection', next_inspection, 30);
table.ajax.reload();
});
$('#dt-basic-example').on('length.dt', function (e, settings, len) {
setCookie('datatable_length', len, 7);
});
$('.clear-filters').on('click', function () {
clearCookies();
location.reload();
});
});
</script>
@stop
This is my controller that is supposed to load the data:
public function index(Request $request)
{
// Retrieve filters from the request (passed by DataTables via AJAX)
$station_id = $request->input('station_id');
$shift_id = $request->input('shift_id');
// Fetch divisions, stations, units, and shifts for the filters
$divisions = Division::where('status', 'published')->orderBy('name', 'asc')->get();
$stations = Station::where('status', 'published')->orderBy('name', 'asc')->get();
$units = Unit::where('status', 'published')->orderBy('name', 'asc')->get();
$shifts = Shift::where('status', 'published')->orderBy('name', 'asc')->get();
// If the request is AJAX (for DataTables), apply the filters to the hydrants
if ($request->ajax()) {
$query = Hydrant::with(['shift', 'station', 'inspections'])->select('hydrants.*');
// Apply filters if they are present
if (!is_null($station_id)) {
$query->where('station_id', $station_id);
}
if (!is_null($shift_id)) {
$query->where('shift_id', $shift_id);
}
// Fetch the hydrants data
$hydrants = $query->get();
// Fetch relevant inspections for these hydrants
$hydrantIds = $hydrants->pluck('id')->toArray();
$inspections = HydrantInspection::whereIn('hydrant_id', $hydrantIds)
->where('year', date('Y'))
->get()
->groupBy('hydrant_id');
// Prepare data for DataTables
return DataTables::of($hydrants)
->addColumn('shift_name', function ($hydrant) {
return $hydrant->shift->name ?? '';
})
->addColumn('station_name', function ($hydrant) {
return $hydrant->station->name ?? '';
})
->addColumn('next_inspection', function ($hydrant) {
$hydrantInspections = $hydrant->inspections->keyBy('inspection_number');
// Check if inspections have been completed
$inspection1Completed = isset($hydrantInspections[1]);
$inspection2Completed = isset($hydrantInspections[2]);
$inspection3Completed = isset($hydrantInspections[3]);
// Determine the next inspection
if (!$inspection1Completed) {
return 'Inspection 1';
} elseif ($inspection1Completed && !$inspection2Completed) {
return 'Inspection 2';
} elseif ($inspection2Completed && !$inspection3Completed) {
return 'Inspection 3';
} else {
return 'Done';
}
})
->addColumn('action', function ($hydrant) use ($inspections) {
$hydrantInspections = $inspections->get($hydrant->id, collect());
$inspectionsByNumber = $hydrantInspections->keyBy('inspection_number');
$inspection1Completed = isset($inspectionsByNumber[1]);
$inspection2Completed = isset($inspectionsByNumber[2]);
$inspection3Completed = isset($inspectionsByNumber[3]);
$initialConditionNotDry = $inspection1Completed && $inspectionsByNumber[1]->initial_condition !== 'dry';
$currentDate = now();
$actionHtml = '<div class="text-center">';
$actionHtml .= '<a href="/hydrants/' . $hydrant->id . '" class="btn btn-success btn-sm mb-2">Hydrant Info</a>';
if ($currentDate->month >= 9 && !$inspection1Completed) {
$actionHtml .= '<a href="/sfd/hydrants/' . $hydrant->id . '/inspection1" class="btn btn-primary btn-sm mb-2">Inspection 1</a>';
} elseif ($currentDate->month >= 9 && $inspection1Completed && !$inspection2Completed) {
$actionHtml .= '<a href="/sfd/hydrants/' . $hydrant->id . '/inspection2" class="btn btn-primary btn-sm mb-2">Inspection 2</a>';
} elseif ($currentDate->month >= 10 && $inspection2Completed && $initialConditionNotDry && !$inspection3Completed) {
$actionHtml .= '<a href="/sfd/hydrants/' . $hydrant->id . '/inspection3" class="btn btn-primary btn-sm mb-2">Inspection 3</a>';
}
$actionHtml .= '</div>';
return $actionHtml;
})
->make(true);
}
// Return the view with the necessary data for the form
return view('stamfordct::hydrant_inspections.index', compact('stations', 'units', 'divisions', 'shifts', 'station_id', 'shift_id'));
}
with the following code block commented out, the page loads fine, but without the column I want:
{
data: 'next_inspection', // Add the next_inspection column here
name: 'next_inspection',
orderable: true, // Allow sorting by this column
searchable: false,
className: 'center-align'
},
But with it uncommented, I get circle of death indicating processing, but it never finishes.
In fact, it appears the entire AJAX transaction faills as none of the console debugging code I put in is outputting.
This column is supposed to hold a simple text value in it (such as “Inspection 1") indicating which hydrant inspection is due next.
Is there a limitation as to how many dynamic columns you can have in datatables?