NonInventPurchasingSystem/CPRNIMS.WebApps/wwwroot/JsFunctions/Utilities/SearchEngine.js
2026-01-20 07:44:30 +08:00

291 lines
12 KiB
JavaScript

// ===========================================
// REUSABLE DATATABLE COLUMN SEARCH UTILITY
// ===========================================
/**
* Initialize column-specific search functionality for DataTable
* @param {Object} config - Configuration object
* @param {string} config.tableId - DataTable selector (e.g., '#PRItemTable')
* @param {Object} config.dataTable - DataTable instance
* @param {Array} config.searchableColumns - Array of column configurations
* @param {string} config.containerId - Container ID for search inputs (optional)
* @param {string} config.searchInputClass - CSS class for search inputs (optional)
*/
function initializeColumnSearch(config) {
const {
tableId,
dataTable,
searchableColumns,
containerId = null,
searchInputClass = 'column-search-input'
} = config;
// Create unique class name based on table ID to avoid conflicts
const uniqueSearchClass = searchInputClass + '-' + tableId.replace('#', '');
// Create search container if not provided
let searchContainer;
if (containerId) {
searchContainer = $('#' + containerId);
} else {
// Create default search container above the table with unique ID
const containerClass = 'column-search-container-' + tableId.replace('#', '');
// Remove existing search container for this table if it exists
$('.' + containerClass).remove();
searchContainer = $('<div class="column-search-container ' + containerClass + ' mb-3"></div>');
$(tableId).parent().prepend(searchContainer);
}
// Clear existing search inputs
searchContainer.empty();
// Unbind any existing events for this specific table's search inputs
$('.' + uniqueSearchClass).off('keyup change');
$('.clear-column-search-' + tableId.replace('#', '')).off('click');
// Create search inputs for each specified column
searchableColumns.forEach(function (columnConfig) {
const columnIndex = columnConfig.columnIndex;
const columnName = columnConfig.columnName;
const placeholder = columnConfig.placeholder;
const searchType = columnConfig.searchType || 'text';
const searchMode = columnConfig.searchMode || 'contains';
const selectOptions = columnConfig.selectOptions || null;
const width = columnConfig.width || 'auto';
// Create input wrapper
const inputWrapper = $('<div class="d-inline-block me-2 mb-2" style="width: ' + width + ';"><label class="form-label small">' + columnName + ':</label></div>');
let searchInput;
// Create different input types based on searchType
switch (searchType) {
case 'select':
searchInput = $('<select class="' + uniqueSearchClass + ' form-select form-select-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" data-table-id="' + tableId + '"><option value="">All ' + columnName + '</option></select>');
// Add options if provided
if (selectOptions && Array.isArray(selectOptions)) {
selectOptions.forEach(function (option) {
searchInput.append('<option value="' + option.value + '">' + option.text + '</option>');
});
}
break;
case 'date':
searchInput = $('<input type="date" class="' + uniqueSearchClass + ' form-control form-control-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" data-table-id="' + tableId + '" placeholder="' + (placeholder || 'Search ' + columnName + '...') + '">');
break;
case 'number':
searchInput = $('<input type="number" class="' + uniqueSearchClass + ' form-control form-control-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" data-table-id="' + tableId + '" placeholder="' + (placeholder || 'Search ' + columnName + '...') + '">');
break;
default: // text
searchInput = $('<input type="text" class="' + uniqueSearchClass + ' form-control form-control-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" data-table-id="' + tableId + '" placeholder="' + (placeholder || 'Search ' + columnName + '...') + '">');
}
inputWrapper.append(searchInput);
searchContainer.append(inputWrapper);
});
// Add clear all button with unique class
const clearButtonClass = 'clear-column-search-' + tableId.replace('#', '');
const clearButton = $('<div class="d-inline-block me-2 mb-2"><label class="form-label small">&nbsp;</label><button type="button" class="btn btn-outline-secondary btn-sm d-block ' + clearButtonClass + '">Clear All</button></div>');
searchContainer.append(clearButton);
// Bind search events with unique class
bindColumnSearchEvents(dataTable, uniqueSearchClass, tableId);
}
/**
* Bind search events to column search inputs
* @param {Object} dataTable - DataTable instance
* @param {string} uniqueSearchClass - Unique CSS class for search inputs
* @param {string} tableId - Table ID for unique identification
*/
function bindColumnSearchEvents(dataTable, uniqueSearchClass, tableId) {
// Individual column search
$('.' + uniqueSearchClass).off('keyup change').on('keyup change', function () {
const columnIndex = $(this).data('column');
const searchValue = $(this).val();
const searchMode = $(this).data('search-mode') || 'contains';
const inputTableId = $(this).data('table-id');
// Only process if this event is for the correct table
if (inputTableId !== tableId) {
return;
}
// Apply column-specific search with regex based on search mode
let searchRegex = '';
if (searchValue) {
switch (searchMode) {
case 'exact':
// Exact match - search for the exact value
searchRegex = '^' + escapeRegex(searchValue) + '$';
break;
case 'starts':
// Starts with - search for values that start with the input
searchRegex = '^' + escapeRegex(searchValue);
break;
case 'ends':
// Ends with - search for values that end with the input
searchRegex = escapeRegex(searchValue) + '$';
break;
default: // 'contains'
// Contains - default DataTable behavior (no regex needed)
searchRegex = searchValue;
}
}
// Apply search with regex if needed
if (searchMode !== 'contains' && searchValue) {
dataTable.column(columnIndex).search(searchRegex, true, false).draw();
} else {
dataTable.column(columnIndex).search(searchValue).draw();
}
// Update URL parameters (optional)
updateSearchParams($(this).data('column-name'), searchValue, tableId);
});
// Clear all searches with unique class
const clearButtonClass = 'clear-column-search-' + tableId.replace('#', '');
$('.' + clearButtonClass).off('click').on('click', function () {
$('.' + uniqueSearchClass).val('');
// Clear all column searches
dataTable.columns().search('').draw();
// Clear URL parameters (optional)
clearAllSearchParams(tableId);
});
}
/**
* Escape special regex characters
* @param {string} string - String to escape
* @returns {string} Escaped string
*/
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Populate select options dynamically from table data
* @param {Object} dataTable - DataTable instance
* @param {number} columnIndex - Column index to extract unique values
* @param {string} selectSelector - jQuery selector for the select element
*/
function populateSelectOptions(dataTable, columnIndex, selectSelector) {
// Get unique values from the column
const uniqueValues = dataTable.column(columnIndex).data().unique().sort();
const selectElement = $(selectSelector);
// Clear existing options (except the first "All" option)
selectElement.find('option:not(:first)').remove();
// Add unique values as options
uniqueValues.each(function (value) {
if (value && value.toString().trim() !== '') {
selectElement.append('<option value="' + value + '">' + value + '</option>');
}
});
}
/**
* Update URL search parameters (optional feature)
* @param {string} paramName - Parameter name
* @param {string} paramValue - Parameter value
* @param {string} tableId - Table ID for unique parameter names
*/
function updateSearchParams(paramName, paramValue, tableId) {
if (typeof URLSearchParams === 'undefined') return;
try {
const url = new URL(window.location);
const paramKey = 'search_' + tableId.replace('#', '') + '_' + paramName;
if (paramValue && paramValue.trim() !== '') {
url.searchParams.set(paramKey, paramValue);
} else {
url.searchParams.delete(paramKey);
}
// Wrap in try-catch to handle potential extension interference
window.history.replaceState({}, '', url);
} catch (error) {
// Silently ignore extension-related errors
if (!error.message.includes('listener indicated an asynchronous response')) {
console.warn('Error updating URL parameters:', error);
}
}
}
/**
* Clear all search parameters from URL for a specific table
* @param {string} tableId - Table ID to clear parameters for
*/
function clearAllSearchParams(tableId) {
if (typeof URLSearchParams === 'undefined') return;
const url = new URL(window.location);
const keysToDelete = [];
const tablePrefix = 'search_' + tableId.replace('#', '') + '_';
for (const key of url.searchParams.keys()) {
if (key.indexOf(tablePrefix) === 0) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(function (key) {
url.searchParams.delete(key);
});
window.history.replaceState({}, '', url);
}
/**
* Restore search values from URL parameters for a specific table
* @param {string} uniqueSearchClass - Unique CSS class for search inputs
* @param {string} tableId - Table ID to restore parameters for
*/
function restoreSearchFromURL(uniqueSearchClass, tableId) {
if (typeof URLSearchParams === 'undefined') return;
const url = new URL(window.location);
const tablePrefix = 'search_' + tableId.replace('#', '') + '_';
$('.' + uniqueSearchClass).each(function () {
const columnName = $(this).data('column-name');
const inputTableId = $(this).data('table-id');
// Only restore for the correct table
if (inputTableId !== tableId) {
return;
}
const searchValue = url.searchParams.get(tablePrefix + columnName);
if (searchValue) {
$(this).val(searchValue).trigger('change');
}
});
}
/**
* Clean up search functionality for a specific table (call this when destroying/reinitializing tables)
* @param {string} tableId - Table ID to clean up
*/
function cleanupColumnSearch(tableId) {
const uniqueSearchClass = 'column-search-input-' + tableId.replace('#', '');
const clearButtonClass = 'clear-column-search-' + tableId.replace('#', '');
const containerClass = 'column-search-container-' + tableId.replace('#', '');
// Unbind events
$('.' + uniqueSearchClass).off('keyup change');
$('.' + clearButtonClass).off('click');
// Remove search container
$('.' + containerClass).remove();
}