I am trying to freeze the first four columns of this datatable when a user is scrolling it. But the style “dtfc-has-left” style=”position: relative;”” is not applied when I inspect the datatable in the browser, and the feature is not working. In case I miss something, I am adding almost all the code here. Please let me know if anyone can see the reason why the frozen columns is not applied.
<head>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/fixedcolumns/4.2.2/css/fixedColumns.dataTables.min.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.1/css/buttons.dataTables.min.css" />
</head>
<!-- Tender Selection Dropdown -->
<div class="mb-3">
<p class="important-note">
<i class="fas fa-file-contract me-2"></i>Select a tender to add a new estimation. Only eligible tenders are shown in the dropdown below.
</p>
<select id="tenderSelect" name="TenderID" class="form-select" size="6">
<option value="0" selected="@(ViewBag.SelectedTenderID == 0 ? "selected" : null)">
All Tenders' Estimations
</option>
@foreach (var item in ViewBag.Tenders as List<SelectListItem>)
{
<option value="@item.Value" selected="@(ViewBag.SelectedTenderID?.ToString() == item.Value ? "selected" : null)">
@item.Text
</option>
}
</select>
</div>
@if (Model == null || !Model.Any())
{
<p>No Estimations Available At this Time.</p>
}
else
{
<div class="dropdown mb-3">
<button class="btn btn-secondary dropdown-toggle" type="button" id="columnToggleDropdown" data-bs-toggle="dropdown" aria-expanded="false" title="Add/ Remove columns from the view and exports.">
<i class="fas fa-table-columns me-1"></i> Toggle Columns
</button>
@* <button class="btn btn-secondary dropdown-toggle" type="button" id="columnToggleDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Toggle Columns
</button> *@
<ul class="dropdown-menu" aria-labelledby="columnToggleDropdown" id="columnVisibilityOptions">
<li>
<label class="dropdown-item">
<input type="checkbox" id="selectAllColumns" checked> Select All
</label>
</li>
<li><hr class="dropdown-divider" /></li>
<!-- Column checkboxes will be added dynamically -->
</ul>
</div>
@* class="table-responsive mx-auto" style="overflow-x: auto; width: 100%;" *@
<div class="div1">
<table class="table table-bordered display nowrap" style="width:100%" id="estimationTable">
<thead>
<tr>
</tr>
</thead>
<tbody>
@foreach (var estimation in Model)
{
<tr>
<td>
@{
var status1 = (EstimationStatusEnum)estimation.EstimationStepStatus;
string actionName = status1 switch
{
EstimationStatusEnum.PileSetupPending => "EstimationPileWizard",
EstimationStatusEnum.ValuationPending => "EstimationValWizard",
EstimationStatusEnum.SubmissionPending => "EstimationSubmissionWizard",
EstimationStatusEnum.ResultPending => "EstimationResultWizard",
EstimationStatusEnum.ResultSubmitted => "EstimationResultWizard",
_ => null
};
bool isReview = status1 == EstimationStatusEnum.ResultSubmitted;
if (!string.IsNullOrEmpty(actionName))
{
if (isReview || estimation.EstimationStatus == false)
{
<a href="@Url.Action(actionName, "Estimation", new { estID = estimation.EstID, tenderID = ViewBag.SelectedTenderID })"
class="btn btn-sm"
title="Review or update completed estimation"
style="background-color: yellow; color: black; border: 1px solid #ccc;">
<i class="fas fa-eye me-1"></i> Review
</a>
}
else
{
<a href="@Url.Action(actionName, "Estimation", new { estID = estimation.EstID, tenderID = ViewBag.SelectedTenderID })"
class="btn btn-primary btn-sm"
title="Proceed or update incomplete estimation record">
<i class="fas fa-step-forward me-1"></i> Proceed
</a>
}
}
}
</td>
<td>@estimation.EstID</td>
<td>@estimation.EstRefID</td>
<td>@estimation.TenderProjName</td>
<td>
@{
var status = (EstimationStatusEnum)estimation.EstimationStepStatus;
var stepLabels = new Dictionary<EstimationStatusEnum, (string Text, string Badge)>
{
{ EstimationStatusEnum.PileSetupPending, ("Pile Setup Pending", "info") },
{ EstimationStatusEnum.ValuationPending, ("Valuation Pending", "primary") },
{ EstimationStatusEnum.SubmissionPending, ("Submission Pending", "success") },
{ EstimationStatusEnum.ResultPending, ("Result Pending", "secondary") },
{ EstimationStatusEnum.ResultSubmitted, ("Completed", "dark") }
};
var (label, badgeClass) = stepLabels[status];
}
<span class="badge bg-@badgeClass">@label</span>
</td>
<td>
@{
string result = estimation.Result?.ToString() ?? "";
string displayText = result;
string resultBadgeClass = result switch
{
"Won" => "success",
"Lost" => "danger",
"In Abeyance" => "warning",
"Cancelled" => "secondary",
_ => "info"
};
if (string.IsNullOrWhiteSpace(result) || !new[] { "Won", "Lost", "In Abeyance", "Cancelled" }.Contains(result))
{
displayText = "NAY";
}
}
<span class="badge bg-@resultBadgeClass">@displayText</span>
</td>
<td>
@if (estimation.EstimationStatus == true)
{
<a asp-controller="ChangeRequest" asp-action="ChangeRequest" asp-route-estID="@estimation.EstID" asp-route-tenderID="@estimation.TenderID"
class="btn btn-sm btn-outline-secondary change-request-link"
title="Access Change Requests for this Estimation">
<i class="fas fa-exchange-alt me-1"></i> @estimation.ChangeRequestCount
</a>
}
else
{
@estimation.ChangeRequestCount
}
</td>
<td>@estimation.CompanyName</td>
<td>@($"{estimation.CompPhoneCode} {estimation.CompPhone}")</td>
<td>@estimation.CompEmail</td>
<td>@estimation.RepFirstName @estimation.RepLastName</td>
<td>@($"{estimation.RepPhoneCode} {estimation.RepPhone}")</td>
<td>@estimation.RepEmail</td>
<td style="text-align: right; color:@(estimation.ClientBudget == 0.00M ? "red" : "black")">
@(estimation.ClientBudget == 0.00M ? "Not Provided" : estimation.ClientBudget.ToString("#,0.00"))
</td>
<td style="text-align: right; color:@((estimation.MaterialValue == null || estimation.MaterialValue == 0.00M) ? "red" : "black")">
@((estimation.MaterialValue == null || estimation.MaterialValue == 0.00M)
? "NAY"
: estimation.MaterialValue?.ToString("#,0.00"))
</td>
<td style="text-align: right; color:@((estimation.TenderValue == null || estimation.TenderValue == 0.00M) ? "red" : "black")">
@((estimation.TenderValue == null || estimation.TenderValue == 0.00M)
? "NAY"
: estimation.TenderValue?.ToString("#,0.00"))
</td>
<td style="text-align: right; color:@((estimation.MarkUpPercentage == null || estimation.MarkUpPercentage == 0.00M) ? "red" : "black")">
@((estimation.MarkUpPercentage == null || estimation.MarkUpPercentage == 0.00M)
? "NAY"
: estimation.MarkUpPercentage?.ToString("F2"))
</td>
<td style="text-align: right; color:@((estimation.EstimatedDurationDays == null || estimation.EstimatedDurationDays == 0.00M) ? "red" : "black")">
@((estimation.EstimatedDurationDays == null || estimation.EstimatedDurationDays == 0.00M)
? "NAY"
: estimation.EstimatedDurationDays?.ToString("F2"))
</td>
<td>@estimation.TenderReceiptDate.ToShortDateString()</td>
<td>@estimation.TenderDueDate.ToShortDateString()</td>
<td>@(estimation.SubmissionDate?.ToShortDateString() ?? "-")</td>
<td>@estimation.EstFirstName @estimation.EstLastName</td>
<td>@estimation.TLFirstName @estimation.TLLastName</td>
<td>@(estimation.EstResReceivedDate?.ToShortDateString() ?? "-")</td>
<td>@estimation.WonCompName</td>
<td>@estimation.SuccessPrice?.ToString("#,0.00")</td>
<td>
<span title="@estimation.EstFeedback" style="max-width:200px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display: inline-block;">
@estimation.EstFeedback
</span>
</td>
<td>@estimation.EstContractRefID</td>
<td data-status="@(estimation.EstimationStatus ? "Active" : "Inactive")">
@if (estimation.EstimationStatus == true)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</td>
<td>
@if (estimation.HasSoilProfile)
{
<button type="button" title="View soil profile for this estimation based tender"
class="btn btn-info btn-sm view-soil-btn"
style="background-color: darkgrey; color: black; border: 1px solid #ccc;"
data-tender-id="@estimation.TenderID">
<i class="fas fa-seedling me-1"></i> View Soil Profile
</button>
}
</td>
<td>
@if (estimation.HasPileReq)
{
<button type="button" title="View a comparison of client requested and estimation suggested pile"
class="btn btn-info btn-sm view-pile-btn"
style="background-color: darkgrey; color: black; border: 1px solid #ccc;"
data-tender-id="@estimation.EstID">
<i class="fas fa-hammer me-1"></i> View Pile Setup
</button>
}
</td>
</tr>
}
</tbody>
</table>
</div>
@* Modal to view soil profile *@
<div class="modal fade" id="soilProfileModal" tabindex="-1" role="dialog" aria-labelledby="soilProfileModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="soilProfileContent">
<!-- Partial View here -->
</div>
</div>
</div>
@* Modal to view requested pile *@
<div class="modal fade" id="pileReqModal" tabindex="-1" role="dialog" aria-labelledby="pileReqModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="pileReqContent">
<!-- Partial View here -->
</div>
</div>
</div>
}
<style>
.dataTables_wrapper .dataTables_scroll {
overflow: auto;
}
th, td {
white-space: nowrap;
}
.dt-button {
margin-right: 5px;
}
.estimation-link:hover {
background-color: lightgreen !important;
color: black !important;
}
.change-request-link:hover {
background-color: #e91e63 !important;
color: black !important;
}
/* Initially hide optional columns */
th.toggle-optional,
td.toggle-optional {
display: none;
}
/* Add here */
#columnVisibilityOptions {
max-height: 250px;
overflow-y: auto;
overflow-x: hidden;
padding-right: 10px;
}
.div1 th {
background: #198754 !important;
}
</style>
@section Scripts {
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/fixedcolumns/4.2.2/js/fixedColumns.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.1/js/dataTables.buttons.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.html5.min.js"></script>
<script>
$(document).ready(function () {
// Setup filter for Active Inactive Status
let statusFilterValue = '';
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
if (!statusFilterValue) return true;
const row = settings.aoData[dataIndex].nTr;
const status = $('td:eq(28)', row).data('status');
return status === statusFilterValue;
});
// Export file title setup
var now = new Date();
var formattedDateTime = now.toLocaleString('en-GB', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit', second: '2-digit',
hour12: false
}).replace(/[/:]/g, '-').replace(',', '');
var exportTitle = 'Estimation Master List - Exported Date Time: ' + formattedDateTime;
var exportFileName = 'Estimation_Master_Export_ ' + formattedDateTime;
// Tender status dictionary
const tenderStatuses = @Html.Raw(Json.Serialize(ViewBag.TenderStatuses));
function updateAddEstimationButtonVisibility() {
const selectedTenderId = $('#tenderSelect').val();
const addBtn = $('#addEstimationBtn');
// If "All Tenders" (value 0) is selected, hide the button
if (selectedTenderId === "0") {
addBtn.hide();
return;
}
// Convert selectedTenderId to a number for lookup
const tenderIdNum = parseInt(selectedTenderId, 10);
const status = tenderStatuses[tenderIdNum]; // Look up using the numeric ID
if (status === true) {
addBtn.show();
} else {
addBtn.hide();
}
}
// --- Event Handlers ---
// Handle Tender Dropdown Change
$('#tenderSelect').on('change', function () {
const selectedValue = $(this).val();
const url = selectedValue === "0"
? "/Estimation/Index"
: `/Estimation/Index?tenderID=${selectedValue}`;
window.location.href = url;
});
// Handle Add Estimation Button Click
$('#addEstimationBtn').on('click', function () {
const selectedTenderId = $('#tenderSelect').val();
if (!selectedTenderId || selectedTenderId === "0") {
alert("Please select a valid Tender to create an estimation.");
return;
}
window.location.href = `/Estimation/EstimationCreateWizard?tenderID=${selectedTenderId}&estID=0`;
});
// --- Initializations ---
// Initial check on page load to set button visibility
updateAddEstimationButtonVisibility();
// Initialize DataTable if table exists
if ($('#estimationTable').length > 0) {
let $filterRow = $('<tr class="filters"></tr>');
$('#estimationTable thead tr:eq(0) th').each(function () {
$filterRow.append('<th><input type="text" placeholder="Search" style="width: 100%;" /></th>');
});
$('#estimationTable thead').append($filterRow);
var table = $('#estimationTable').DataTable({
dom: 'lfrtipB',
buttons: [
{
extend: 'csvHtml5',
text: '<i class="fas fa-file-csv me-1"></i> Export CSV',
className: 'btn btn-sm btn-secondary export-btn',
title: exportTitle,
filename: exportFileName,
exportOptions: {
columns: function (idx, data, node) {
var columnCount = table.columns().count();
return $(node).is(':visible') && idx !== 0 && idx !== columnCount - 2 && idx !== columnCount - 1;
},
modifier: { search: 'applied', order: 'current' }
}
},
{
extend: 'excelHtml5',
text: '<i class="fas fa-file-excel me-1"></i> Export Excel',
className: 'btn btn-sm btn-secondary export-btn',
title: exportTitle,
filename: exportFileName,
exportOptions: {
columns: function (idx, data, node) {
var columnCount = table.columns().count();
return $(node).is(':visible') && idx !== 0 && idx !== columnCount - 2 && idx !== columnCount - 1;
},
modifier: { search: 'applied', order: 'current' }
}
}
],
scrollX: true,
scrollY: '500px',
fixedColumns: {
leftColumns: 4
},
scrollCollapse: true,
paging: true,
//fixedHeader: true,
orderCellsTop: true,
order: [[1, 'asc']],
columnDefs: [
{
targets: [13, 14, 15, 16, 17, 25], // Client Budget, Material Value, Tender Value, MarkUp %, Estimated Duration (Days), Success Price
render: function (data, type, row) {
if (type === 'filter' || type === 'sort') {
let cleanData = data.toString().replace(/£/g, '').replace(/,/g, '');
if (cleanData === "NAY" || cleanData === "Not Provided" || cleanData.trim() === "" || isNaN(parseFloat(cleanData))) {
return -Infinity;
}
return parseFloat(cleanData);
}
// For display, return the data as is (with commas)
return data;
},
type: 'num-fmt'
// targets: [12,13,14,24],
// createdCell: function (td, cellData) {
// var plainText = cellData ? cellData.toString().trim() : "";
// $(td).html('<span class="d-none">' + plainText + '</span>' + plainText);
// }
},
{ orderable: false, targets: 0 }
],
initComplete: function () {
var api = this.api();
api.columns().eq(0).each(function (colIdx) {
var cell = $('.filters th').eq(colIdx);
var colHeader = $('#estimationTable thead tr:eq(0) th').eq(colIdx).text().trim(); // For debugging
//console.log(`Column Index: ${colIdx}, Header: "${colHeader}"`);
if (colIdx === 4) {
$(cell).html(`
<select class="form-select form-select-sm" style="width: 100%;">
<option value="">All</option>
<option value="Pile Setup Pending">Pile Setup Pending</option>
<option value="Valuation Pending">Valuation Pending</option>
<option value="Submission Pending">Submission Pending</option>
<option value="Result Pending">Result Pending</option>
<option value="Completed">Completed</option>
</select>`);
}
else if (colIdx === 5) { // Column index for "Result"
$(cell).html(`
<select class="form-select form-select-sm" style="width: 100%;">
<option value="">All</option>
<option value="Won">Won</option>
<option value="Lost">Lost</option>
<option value="In Abeyance">In Abeyance</option>
<option value="Cancelled">Cancelled</option>
<option value="NAY">NAY</option>
</select>
`);
}
// Active/ Inactive filter
else if (colIdx === 28) {
$(cell).html(`
<select class="form-select form-select-sm" style="width: 100%;">
<option value="">All</option>
<option>Active</option>
<option>Inactive</option>
</select>`);
$('select', cell).on('change', function () {
statusFilterValue = this.value;
api.draw(); // ⬅ trigger redraw with updated filter
});
}
else if (colIdx !== 0 && colIdx !== 29 && colIdx !== 30 ) { // Not the 'Action' column
$(cell).html('<input type="text" placeholder="Search" style="width: 100%;" />');
}
else { // 'Action' column
$(cell).html('');
}
$('input, select', cell).on('keyup change', function () {
api.column(colIdx).search(this.value).draw();
});
});
}
});
}
});
</script>
}




