From 687338fc1093cbcf59d0e97e88df4b20a73f726e Mon Sep 17 00:00:00 2001 From: Przemyslaw Klys Date: Tue, 3 Jun 2025 20:10:41 +0200 Subject: [PATCH 1/4] Add DataTables Column Highlighter example and documentation - Introduced a new JavaScript library for highlighting specific columns in DataTables responsive child rows based on configurable conditions. - Added example usage and configuration options in README-ColumnHighlighter.md. - Created Example-SearchBuilder.ps1 to demonstrate integration with PowerShell and HTML generation. - Implemented core functionality in datatables-column-highlighter.js for initializing and applying highlighting rules. --- .../Example-SearchBuilder.ps1 | 39 ++++ .../README-ColumnHighlighter.md | 206 ++++++++++++++++++ .../datatables-column-highlighter.js | 189 ++++++++++++++++ 3 files changed, 434 insertions(+) create mode 100644 Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 create mode 100644 Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md create mode 100644 Examples/Example-TableColumnHighlight/datatables-column-highlighter.js diff --git a/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 b/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 new file mode 100644 index 00000000..0b7d1782 --- /dev/null +++ b/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 @@ -0,0 +1,39 @@ +Import-Module .\PSWriteHTML.psd1 -Force + +$ProcessesAll = Get-Process | Select-Object -First 1 #-Property Name, Id, StartTime + +New-HTML -TitleText 'Title' -Online -FilePath $PSScriptRoot\Example-SearchBuilder.html -ShowHTML { + #New-HTMLTableStyle -BackgroundColor Blue -Type RowSelected + #New-HTMLTableStyle -BackgroundColor Yellow -Type RowHover + #New-HTMLTableStyle -BackgroundColor Yellow -Type RowHoverSelected + + New-HTMLSection -HeaderText 'Search Builder 1' { + New-HTMLTable -DataTable $ProcessesAll -SearchBuilder -Buttons excelHtml5, copyHtml5, csvHtml5 { + #New-HTMLTableContent -ColumnName 'PriorityClass' -BackgroundColor Salmon + #New-HTMLTableContent -ColumnName 'Company' -BackgroundColor Salmon + New-HTMLTableCondition -ColumnName 'Name' -BackgroundColor Salmon -Value '1Password' + } + } + # New-HTMLSection -HeaderText 'Search Builder as button' { + # New-HTMLTable -DataTable $ProcessesAll + # } + # # This won't really work - button + searchBuilder + # New-HTMLSection -HeaderText 'Search Builder + button' { + # # Search Builder will be disabled, button will work + # New-HTMLTable -DataTable $ProcessesAll -SearchBuilder + # } +} + +# New-HTML -TitleText 'Title' -FilePath $PSScriptRoot\Example-SearchBuilder2.html { +# New-HTMLSection -HeaderText 'Search Builder 1' { +# New-HTMLTable -DataTable $ProcessesAll -Filtering -ScrollX -SearchBuilder -Buttons excelHtml5, copyHtml5, csvHtml5 +# } +# New-HTMLSection -HeaderText 'Search Builder as button' { +# New-HTMLTable -DataTable $ProcessesAll +# } +# # This won't really work - button + searchBuilder +# New-HTMLSection -HeaderText 'Search Builder + button' { +# # Search Builder will be disabled, button will work +# New-HTMLTable -DataTable $ProcessesAll -SearchBuilder +# } +# } -ShowHTML \ No newline at end of file diff --git a/Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md b/Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md new file mode 100644 index 00000000..0999dc23 --- /dev/null +++ b/Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md @@ -0,0 +1,206 @@ +# DataTables Column Highlighter + +A reusable JavaScript library for highlighting specific columns in DataTables responsive child rows based on configurable conditions. + +## Usage + +### 1. Include the library +```html + +``` + +### 2. Configure highlighting rules + +```javascript +const highlightConfig = [ + { + condition: { anyColumn: true, value: '1Password' }, + targets: [ + { + column: 'Company', + backgroundColor: '#fa8072', + textColor: '#000000' + }, + { + column: 'Product', + backgroundColor: '#87ceeb', + textColor: '#000000' + } + ] + } +]; +``` + +### 3. Initialize after creating your DataTable + +```javascript +// Create your DataTable +var table = $('#myTable').DataTable({ + // your DataTable configuration +}); + +// Setup column highlighting +setupColumnHighlighting('myTable', highlightConfig, table); +``` + +## Configuration Options + +### Condition Types + +#### Any Column Match +```javascript +condition: { anyColumn: true, value: 'SearchValue' } +``` +Matches if any column in the row contains the specified value. + +#### Specific Column Match +```javascript +condition: { column: 0, value: 'SearchValue' } +``` +Matches if the specified column index contains the value. + +#### No Condition (Always Apply) +```javascript +condition: null +``` +Always applies the styling (useful for global styling). + +### Target Options + +#### Basic Styling +```javascript +{ + column: 'Company', // Column name to target + backgroundColor: '#fa8072', // Background color + textColor: '#000000' // Text color +} +``` + +#### Advanced Styling +```javascript +{ + column: 'Company', + backgroundColor: '#fa8072', + textColor: '#ffffff', + css: { // Custom CSS properties + 'font-weight': 'bold', + 'border': '2px solid #000' + }, + highlightParent: true // Also highlight parent element +} +``` + +## Examples + +### Example 1: Single Row, Multiple Columns +```javascript +const config = [ + { + condition: { anyColumn: true, value: '1Password' }, + targets: [ + { column: 'Company', backgroundColor: '#fa8072' }, + { column: 'Product', backgroundColor: '#87ceeb' }, + { column: 'Description', backgroundColor: '#98fb98' } + ] + } +]; +``` + +### Example 2: Multiple Conditions +```javascript +const config = [ + { + condition: { anyColumn: true, value: '1Password' }, + targets: [ + { column: 'Company', backgroundColor: '#fa8072' } + ] + }, + { + condition: { anyColumn: true, value: 'Chrome' }, + targets: [ + { column: 'Company', backgroundColor: '#4285f4', textColor: '#ffffff' } + ] + } +]; +``` + +### Example 3: Global Highlighting +```javascript +const config = [ + { + condition: null, // Always apply + targets: [ + { column: 'CPU', backgroundColor: '#fffacd' }, + { column: 'Memory', backgroundColor: '#f0f8ff' } + ] + } +]; +``` + +### Example 4: Column Index Based +```javascript +const config = [ + { + condition: { column: 0, value: 'SpecificProcess' }, // First column + targets: [ + { column: 'Status', backgroundColor: '#90ee90' } + ] + } +]; +``` + +## Advanced Usage + +### Custom CSS +```javascript +{ + column: 'Priority', + css: { + 'background': 'linear-gradient(45deg, #ff6b6b, #ee5a52)', + 'color': 'white', + 'font-weight': 'bold', + 'border-radius': '4px', + 'padding': '4px 8px' + } +} +``` + +### Conditional Styling Based on Values +You can create multiple configurations for different scenarios: + +```javascript +const config = [ + { + condition: { column: 2, value: 'High' }, // Priority column + targets: [ + { column: 'Status', backgroundColor: '#ff4444', textColor: '#ffffff' } + ] + }, + { + condition: { column: 2, value: 'Medium' }, + targets: [ + { column: 'Status', backgroundColor: '#ffaa00', textColor: '#000000' } + ] + }, + { + condition: { column: 2, value: 'Low' }, + targets: [ + { column: 'Status', backgroundColor: '#44ff44', textColor: '#000000' } + ] + } +]; +``` + +## Minimal Setup Example + +For a quick setup with minimal code in your table: + +```javascript +// After your DataTable is created +setupColumnHighlighting('myTableId', [ + { + condition: { anyColumn: true, value: 'TargetValue' }, + targets: { column: 'ColumnName', backgroundColor: '#color' } + } +], table); +``` \ No newline at end of file diff --git a/Examples/Example-TableColumnHighlight/datatables-column-highlighter.js b/Examples/Example-TableColumnHighlight/datatables-column-highlighter.js new file mode 100644 index 00000000..8e0fc7f1 --- /dev/null +++ b/Examples/Example-TableColumnHighlight/datatables-column-highlighter.js @@ -0,0 +1,189 @@ +/** + * DataTables Column Highlighter for Responsive Child Rows + * Allows highlighting specific columns in expanded child rows based on configuration + */ + +// Main highlighting system +window.DataTablesColumnHighlighter = { + // Storage for table configurations + configurations: {}, + + /** + * Initialize highlighting for a table + * @param {string} tableId - The ID of the DataTable + * @param {Array} config - Array of column highlighting configurations + * @param {Object} table - The DataTable instance + */ + init: function(tableId, config, table) { + console.log('Initializing DataTables Column Highlighter for:', tableId); + + // Store configuration + this.configurations[tableId] = { + config: config, + table: table + }; + + // Set up event handlers + this.setupEventHandlers(tableId, table); + }, + + /** + * Set up event handlers for the table + */ + setupEventHandlers: function(tableId, table) { + const self = this; + + // Method 1: DataTables API event handler + table.on('click', 'td.dtr-control', function (e) { + console.log('Expand button clicked via DataTables API'); + let tr = e.target.closest('tr'); + let row = table.row(tr); + + setTimeout(function() { + if (row.child.isShown()) { + console.log('Row expanded, applying highlighting...'); + self.applyHighlighting(tableId, $(tr), row.data()); + } + }, 100); + }); + + // Method 2: jQuery fallback event handler + $(`#${tableId} tbody`).on('click', 'td.dtr-control, td.dt-control, .dtr-control, .dt-control', function () { + console.log('Expand button clicked via jQuery fallback'); + let $tr = $(this).closest('tr'); + + setTimeout(function() { + let childRow = $tr.next('tr.child'); + if (childRow.length > 0) { + console.log('Child row found, applying highlighting...'); + // Get row data from the DOM since we don't have the DataTables row object + let rowData = []; + $tr.find('td').each(function(index) { + if (!$(this).hasClass('dtr-control') && !$(this).hasClass('dt-control')) { + rowData.push($(this).text().trim()); + } + }); + self.applyHighlighting(tableId, $tr, rowData); + } + }, 100); + }); + }, + + /** + * Apply highlighting based on configuration + */ + applyHighlighting: function(tableId, $parentRow, rowData) { + const config = this.configurations[tableId].config; + const childRow = $parentRow.next('tr.child'); + + if (childRow.length === 0) { + console.log('No child row found'); + return; + } + + console.log('Applying highlighting with config:', config); + console.log('Row data:', rowData); + + // Process each configuration rule + config.forEach(rule => { + this.processRule(rule, childRow, rowData); + }); + }, + + /** + * Process a single highlighting rule + */ + processRule: function(rule, childRow, rowData) { + console.log('Processing rule:', rule); + + // Check if the condition matches + if (this.matchesCondition(rule.condition, rowData)) { + console.log('Condition matched, applying styling...'); + + // Apply styling to target columns + rule.targets.forEach(target => { + this.applyColumnStyling(childRow, target); + }); + } + }, + + /** + * Check if a condition matches the row data + */ + matchesCondition: function(condition, rowData) { + if (!condition) return true; // No condition means always apply + + // Support different condition types + if (condition.column !== undefined && condition.value !== undefined) { + // Column index-based condition + if (typeof condition.column === 'number') { + return rowData[condition.column] === condition.value; + } + // Column name-based condition would require header mapping + } + + if (condition.anyColumn && condition.value !== undefined) { + // Check if any column contains the value + return rowData.some(cellValue => cellValue === condition.value); + } + + return false; + }, + + /** + * Apply styling to a specific column in the child row + */ + applyColumnStyling: function(childRow, target) { + console.log('Applying styling to column:', target.column); + + // Find the target column in the child row + childRow.find('*').each(function() { + const $elem = $(this); + const text = $elem.text().trim(); + + if (text === target.column) { + console.log('Found target column element:', target.column); + + // Apply styling to the data element + const $dataElem = $elem.siblings('.dtr-data').length > 0 ? + $elem.siblings('.dtr-data') : + $elem.next(); + + if (target.backgroundColor) { + $dataElem.css('background-color', target.backgroundColor); + } + + if (target.textColor) { + $dataElem.css('color', target.textColor); + } + + if (target.css) { + $dataElem.css(target.css); + } + + // Also try parent element in case structure is different + if (target.highlightParent) { + $elem.parent().css('background-color', target.backgroundColor); + if (target.textColor) { + $elem.parent().css('color', target.textColor); + } + } + } + }); + }, + + /** + * Helper method to create simple configuration + */ + createConfig: function(conditions, targets) { + return [{ + condition: conditions, + targets: Array.isArray(targets) ? targets : [targets] + }]; + } +}; + +// Helper function for easy setup +window.setupColumnHighlighting = function(tableId, config, table) { + DataTablesColumnHighlighter.init(tableId, config, table); +}; \ No newline at end of file From 764e14e9769bf8d2f8e39e2d4ff7f3d2523a5d77 Mon Sep 17 00:00:00 2001 From: Przemyslaw Klys Date: Thu, 4 Sep 2025 16:09:48 +0200 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20=F0=9F=94=A7=20Improve=20redraw?= =?UTF-8?q?=20logic=20for=20tab=20visibility=20and=20table=20resizing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhanced the `callback` function in `tabbisAdditional.js` to ensure `findObjectsToRedraw` is called after the tab pane becomes visible. * Updated `resizeTable` function in `redrawObjects.js` to include checks for hidden tables and added logging for skipped tables. * Streamlined the `show` and `hide` functions in `HideSection.js` to manage class additions/removals based on flex direction. --- Resources/JS/HideSection.js | 20 +---- Resources/JS/redrawObjects.js | 142 ++++++++++++++++++++++++------- Resources/JS/tabbisAdditional.js | 23 +++-- 3 files changed, 131 insertions(+), 54 deletions(-) diff --git a/Resources/JS/HideSection.js b/Resources/JS/HideSection.js index 9422adee..a0180802 100644 --- a/Resources/JS/HideSection.js +++ b/Resources/JS/HideSection.js @@ -7,25 +7,13 @@ function show(obj) { var flexDirection = window.getComputedStyle(topSectionDiv).getPropertyValue("flex-direction"); if (flexDirection == 'column') { //console.log('flexDirection 1' + flexDirection) - } else{ + } else { document.getElementById(obj).parentNode.classList.add('sectionShow'); document.getElementById(obj).parentNode.classList.remove('sectionHide'); //console.log('flexDirection 2' + flexDirection) } - // resize tables within section - try { - var table = document.getElementById(obj).querySelectorAll('table'); - table.forEach(resizeTable) - } catch (e) { - console.log('No datatables available.'); - } - // redraw calendars within section - try { - var calendar = document.getElementById(obj).querySelectorAll('div[id^="Calendar-"]'); - calendar.forEach(redrawCalendar) - } catch (e) { - console.log('No calendars available.'); - } + // reflow/redraw objects within this section (tables, charts, carousels, calendars, diagrams) + try { findObjectsToRedraw(obj); } catch (e) { } } function hide(obj) { @@ -38,7 +26,7 @@ function hide(obj) { var flexDirection = window.getComputedStyle(topSectionDiv).getPropertyValue("flex-direction"); if (flexDirection == 'column') { //console.log('flexDirection 1' + flexDirection) - } else{ + } else { document.getElementById(obj).parentNode.classList.remove('sectionShow'); document.getElementById(obj).parentNode.classList.add('sectionHide'); //console.log('flexDirection 2' + flexDirection) diff --git a/Resources/JS/redrawObjects.js b/Resources/JS/redrawObjects.js index cd16bbad..dd2bdd2d 100644 --- a/Resources/JS/redrawObjects.js +++ b/Resources/JS/redrawObjects.js @@ -4,21 +4,37 @@ console.log('Fitting calendar with id ' + calendar.id); } function resizeTable(table) { - if (table.id) { - if ($.fn.DataTable.isDataTable("#" + table.id)) { - try { - $("#" + table.id).DataTable().columns.adjust().responsive.recalc(); - console.log('Fitting table with id ' + table.id); - } catch (e) { - try { - $("#" + table.id).DataTable().columns.adjust(); - } catch (e) { - console.log('Failed to fit table with id ' + table.id); + if (!table || !table.id) return; + // Skip hidden tables + if (table.offsetParent === null) return; + + if ($.fn.DataTable.isDataTable("#" + table.id)) { + try { + var dt = $("#" + table.id).DataTable(); + var opts = (window.pswhRedrawOptions || {}); + var recordsTotal = 0; + try { recordsTotal = dt.page().info().recordsTotal || 0; } catch (e) { } + var largeThreshold = typeof opts.dataTablesLargeThreshold === 'number' ? opts.dataTablesLargeThreshold : 20000; + var skipResponsive = opts.skipDataTablesResponsive === true || recordsTotal >= largeThreshold; + var skipAll = opts.skipDataTables === true; + + if (!skipAll) { + if (!skipResponsive) { + dt.columns.adjust().responsive.recalc(); + } else { + dt.columns.adjust(); } + console.log('Fitting table with id ' + table.id + (skipResponsive ? ' (light)' : '')); + } + } catch (e) { + try { + $("#" + table.id).DataTable().columns.adjust(); + } catch (e2) { + console.log('Failed to fit table with id ' + table.id); } - } else { - console.log('Skipping fitting table id ' + table.id); } + } else { + console.log('Skipping fitting table id ' + table.id); } } function redrawDiagram(diagram) { @@ -39,32 +55,96 @@ function redrawFixedHeaderFooter() { } } } -function findObjectsToRedraw(id) { - // redrawTables +function redrawApexCharts(container) { try { - var table = document.getElementById(id).querySelectorAll('table.dataTables'); //.querySelectorAll('table[id^="DT-"]'); - table.forEach(resizeTable); + // Reflow visible ApexCharts within the container + var charts = container.querySelectorAll('.apexcharts-canvas'); + charts.forEach(function (el) { + var chart = el.__apexcharts__; + if (chart && el.offsetParent !== null) { + try { + if (!(window.pswhRedrawOptions && window.pswhRedrawOptions.skipApexCharts === true)) { + chart.windowResizeHandler(); + } + console.log('Resized ApexChart for element ' + (el.id || '[no-id]')); + } catch (e) { + console.log('ApexChart resize failed for element ' + (el.id || '[no-id]')); + } + } + }); } catch (e) { - console.log('No datatables available.'); + console.log('No ApexCharts available.'); } - // redrawCalendar +} +function refreshKinetoCarousels(container) { try { - var calendar = document.getElementById(id).querySelectorAll('div.calendarFullCalendar'); - calendar.forEach(redrawCalendar); + if (window.Kineto) { + // Refresh each Kineto carousel within the container + var carousels = container.querySelectorAll('div.carousel'); + carousels.forEach(function (c) { + try { + // Kineto exposes a static refresh API that accepts a container element + if (!(window.pswhRedrawOptions && window.pswhRedrawOptions.skipCarousels === true)) { + window.Kineto.refresh(c); + } + console.log('Refreshed Kineto carousel for element ' + (c.id || '[no-id]')); + } catch (e) { + console.log('Kineto refresh failed for element ' + (c.id || '[no-id]')); + } + }); + } } catch (e) { - console.log('No calendars available.'); + console.log('No Kineto carousels available.'); } - // redrawDiagram +} +function findObjectsToRedraw(id) { try { - var diagram = document.getElementById(id).querySelectorAll('div.diagramObject'); - diagram.forEach(redrawDiagram); + var root = document.getElementById(id) || document; + + // redrawTables + try { + var table = root.querySelectorAll('table.dataTables'); //.querySelectorAll('table[id^="DT-"]'); + table.forEach(resizeTable); + } catch (e) { + console.log('No datatables available.'); + } + + // redrawCalendar + try { + var calendar = root.querySelectorAll('div.calendarFullCalendar'); + calendar.forEach(redrawCalendar); + } catch (e) { + console.log('No calendars available.'); + } + + // redrawDiagram + try { + var diagram = root.querySelectorAll('div.diagramObject'); + diagram.forEach(redrawDiagram); + } catch (e) { + console.log('No diagrams available.'); + } + + // Resize ApexCharts in the visible container + redrawApexCharts(root); + + // Refresh Kineto carousels in the visible container + refreshKinetoCarousels(root); + + // finds all tables with fixed headers and footers and forces them to check if they are visible or not and hide or show accordingly + try { + redrawFixedHeaderFooter(); + } catch (e) { + console.log('No datatables fixed header/footer available.'); + } } catch (e) { - console.log('No diagrams available.'); + console.log('Redraw failed for id ' + id); } - // finds all tables with fixed headers and footers and forces them to check if they are visible or not and hide or show accordingly + + // As a safety net, dispatch a window resize to notify components try { - redrawFixedHeaderFooter(); - } catch (e) { - console.log('No datatables fixed header/footer available.'); - } -} \ No newline at end of file + if (!(window.pswhRedrawOptions && window.pswhRedrawOptions.skipWindowResizeEvent === true)) { + window.dispatchEvent(new Event('resize')); + } + } catch (e) { } +} diff --git a/Resources/JS/tabbisAdditional.js b/Resources/JS/tabbisAdditional.js index 14a24379..81a6a769 100644 --- a/Resources/JS/tabbisAdditional.js +++ b/Resources/JS/tabbisAdditional.js @@ -4,13 +4,22 @@ var tabs = tabbis.init({ tabActive: "active", paneActive: "active", callback: function (tab, pane) { - // console.log("TAB id:" + tab.id + pane.id + tableid); - // this makes sure to refresh tables on given tab change to make sure they have buttons and everything - // which tab has which table - findObjectsToRedraw(tab.id + "-Content"); + // Ensure redraw runs after pane becomes visible + try { + var id = (tab && tab.id ? (tab.id + "-Content") : (pane && pane.id ? pane.id : null)); + if (id) { + setTimeout(function () { + try { findObjectsToRedraw(id); } catch (e) {} + try { document.dispatchEvent(new CustomEvent('pswh:tab:shown', { detail: { id: id } })); } catch (e) {} + }, 80); + } + } catch (e) {} } }); -// in theory should take care of removing local storage for tabbis -// some errors occurs if the local storage is not cleaned after a while -window.addEventListener("unload", tabbis.remove, false); +// Safely clear tab memory on unload (if supported) +try { + if (tabbis && typeof tabbis.reset === 'function') { + window.addEventListener("unload", tabbis.reset, false); + } +} catch (e) {} From 0eadf6f40a28de678fa85a4131b23638867ac05d Mon Sep 17 00:00:00 2001 From: Przemyslaw Klys Date: Thu, 4 Sep 2025 16:15:32 +0200 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20example=20for=20c?= =?UTF-8?q?arousels=20with=20charts=20and=20tables=20in=20tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced a new demo script `Example-Carousel03.ps1`. - Demonstrates the use of carousels, charts, and tables within HTML tabs. - Ensures charts/tables render correctly in both active and non-active tabs. --- .../Example-Carousel/Example-Carousel03.ps1 | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Examples/Example-Carousel/Example-Carousel03.ps1 diff --git a/Examples/Example-Carousel/Example-Carousel03.ps1 b/Examples/Example-Carousel/Example-Carousel03.ps1 new file mode 100644 index 00000000..4081d85f --- /dev/null +++ b/Examples/Example-Carousel/Example-Carousel03.ps1 @@ -0,0 +1,68 @@ +Import-Module .\PSWriteHTML.psd1 -Force + +# Minimal demo showing carousels + charts + tables inside tabs +# Charts/tables in the non-active tab should render correctly on first show + +$Data = Get-Process | + Select-Object -First 12 Name, Id, CPU + +New-HTML -Name 'Tabs + Carousel' -FilePath "$PSScriptRoot\Example-Carousel03.html" -Format -Show { + New-HTMLTab -Name 'Tab 1' -Heading 'First' { + New-HTMLSection -HeaderText 'Visible Tab' -Invisible { + New-HTMLCarousel { + New-CarouselSlide { + New-HTMLChart { + New-ChartPie -Name 'A' -Value 10 + New-ChartPie -Name 'B' -Value 20 + New-ChartPie -Name 'C' -Value 30 + } -Title 'Pie - Tab 1' + } + New-CarouselSlide { + New-HTMLText -Text 'Second slide (Tab 1)' + } + } + } + New-HTMLSection -HeaderText 'Table' -Invisible { + New-HTMLTable -DataTable $Data -HideFooter + } + } + + New-HTMLTab -Name 'Tab 2' -Heading 'Second' { + # This tab is initially hidden; content should reflow on tab activation + New-HTMLSection -HeaderText 'Hidden Tab (initial)' -Invisible { + New-HTMLCarousel { + New-CarouselSlide { + New-HTMLChart { + New-ChartPie -Name 'X' -Value 30 + New-ChartPie -Name 'Y' -Value 40 + New-ChartPie -Name 'Z' -Value 50 + } -Title 'Pie - Tab 2' + } + New-CarouselSlide { + New-HTMLText -Text 'Second slide (Tab 2)' + } + } + } + New-HTMLSection -HeaderText 'Table in hidden Tab' -Invisible { + New-HTMLTable -DataTable $Data -HideFooter + } + + # Collapsible section to verify reflow on show/hide + New-HTMLSection -HeaderText 'Collapsible Carousel' -CanCollapse -Collapsed { + New-HTMLCarousel { + New-CarouselSlide { + New-HTMLChart { + New-ChartLegend -LegendPosition bottom -HorizontalAlign right + New-ChartBarOptions -Distributed + New-ChartBar -Name 'One' -Value 10 + New-ChartBar -Name 'Two' -Value 20 + New-ChartBar -Name 'Three' -Value 15 + } -Title 'Bar - Collapsible' + } + New-CarouselSlide { + New-HTMLText -Text 'Collapsed initially; open to reflow.' + } + } + } + } +} From a6fcb34d2c04d68f803cf561241ca475bd843c73 Mon Sep 17 00:00:00 2001 From: Przemyslaw Klys Date: Thu, 4 Sep 2025 16:38:31 +0200 Subject: [PATCH 4/4] Update DataTables conditional formatting and introduce column highlighter - Updated the configuration URL to version 0.0.31. - Added new function `Convert-TableConditionsToHighlighterRules` to convert conditional formatting rules for use with the new column highlighter. - Introduced `New-TablePluginCondition` to map PSWriteHTML conditions to the plugin condition schema. - Enhanced `New-HTMLTable` to support plugin-based conditional formatting, integrating column highlighter rules. - Modified `New-TableCondition` and `New-TableConditionGroup` to include a new parameter for child row fill options. - Added new JavaScript file `dataTables.columnHighlighter.js` for standalone column highlighting functionality. - Removed deprecated `dataTables.conditions.js` file as its functionality is now integrated into the new column highlighter. --- .../Example-Carousel/Example-Carousel03.ps1 | 2 +- .../Example-SearchBuilder.ps1 | 15 +- .../README-ColumnHighlighter.md | 206 --------------- .../datatables-column-highlighter.js | 189 -------------- Private/Parameters.Configuration.ps1 | 17 +- ...vert-TableConditionsToHighlighterRules.ps1 | 163 ++++++++++++ Private/Tables/New-TablePluginCondition.ps1 | 52 ++++ Public/New-HTMLTable.ps1 | 23 +- Public/New-TableCondition.ps1 | 7 +- Public/New-TableConditionGroup.ps1 | 5 +- Resources/JS/dataTables.columnHighlighter.js | 234 +++++++++++++++++ Resources/JS/dataTables.conditions.js | 238 ------------------ 12 files changed, 500 insertions(+), 651 deletions(-) delete mode 100644 Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md delete mode 100644 Examples/Example-TableColumnHighlight/datatables-column-highlighter.js create mode 100644 Private/Tables/Convert-TableConditionsToHighlighterRules.ps1 create mode 100644 Private/Tables/New-TablePluginCondition.ps1 create mode 100644 Resources/JS/dataTables.columnHighlighter.js delete mode 100644 Resources/JS/dataTables.conditions.js diff --git a/Examples/Example-Carousel/Example-Carousel03.ps1 b/Examples/Example-Carousel/Example-Carousel03.ps1 index 4081d85f..ba70e07a 100644 --- a/Examples/Example-Carousel/Example-Carousel03.ps1 +++ b/Examples/Example-Carousel/Example-Carousel03.ps1 @@ -6,7 +6,7 @@ $Data = Get-Process | Select-Object -First 12 Name, Id, CPU -New-HTML -Name 'Tabs + Carousel' -FilePath "$PSScriptRoot\Example-Carousel03.html" -Format -Show { +New-HTML -Name 'Tabs + Carousel' -FilePath "$PSScriptRoot\Example-Carousel03.html" -Online -Show { New-HTMLTab -Name 'Tab 1' -Heading 'First' { New-HTMLSection -HeaderText 'Visible Tab' -Invisible { New-HTMLCarousel { diff --git a/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 b/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 index 0b7d1782..557676b8 100644 --- a/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 +++ b/Examples/Example-TableColumnHighlight/Example-SearchBuilder.ps1 @@ -1,6 +1,6 @@ Import-Module .\PSWriteHTML.psd1 -Force -$ProcessesAll = Get-Process | Select-Object -First 1 #-Property Name, Id, StartTime +$ProcessesAll = Get-Process | Select-Object -First 3 #-Property Name, Id, StartTime New-HTML -TitleText 'Title' -Online -FilePath $PSScriptRoot\Example-SearchBuilder.html -ShowHTML { #New-HTMLTableStyle -BackgroundColor Blue -Type RowSelected @@ -10,9 +10,18 @@ New-HTML -TitleText 'Title' -Online -FilePath $PSScriptRoot\Example-SearchBuilde New-HTMLSection -HeaderText 'Search Builder 1' { New-HTMLTable -DataTable $ProcessesAll -SearchBuilder -Buttons excelHtml5, copyHtml5, csvHtml5 { #New-HTMLTableContent -ColumnName 'PriorityClass' -BackgroundColor Salmon - #New-HTMLTableContent -ColumnName 'Company' -BackgroundColor Salmon - New-HTMLTableCondition -ColumnName 'Name' -BackgroundColor Salmon -Value '1Password' + New-HTMLTableContent -ColumnName 'HandleCount' -BackGroundColor Salmon + New-HTMLTableCondition -ColumnName 'Product' -BackgroundColor Salmon -Value '1Password' -ChildRowFill Both + New-HTMLTableCondition -ColumnName 'Name' -BackgroundColor AirForceBlue -Value '1password' } + New-HTMLTable -DataTable $ProcessesAll -SearchBuilder -Buttons excelHtml5, copyHtml5, csvHtml5 { + #New-HTMLTableContent -ColumnName 'PriorityClass' -BackgroundColor Salmon + New-HTMLTableContent -ColumnName 'HandleCount' -BackGroundColor Salmon + New-HTMLTableCondition -ColumnName 'Product' -BackgroundColor Salmon -Value '1Password' + New-HTMLTableCondition -ColumnName 'Name' -BackgroundColor AirForceBlue -Value '1password' + + New-HTMLTableCondition -ColumnName 'VM' -BackgroundColor Alizarin + } -DataStore JavaScript } # New-HTMLSection -HeaderText 'Search Builder as button' { # New-HTMLTable -DataTable $ProcessesAll diff --git a/Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md b/Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md deleted file mode 100644 index 0999dc23..00000000 --- a/Examples/Example-TableColumnHighlight/README-ColumnHighlighter.md +++ /dev/null @@ -1,206 +0,0 @@ -# DataTables Column Highlighter - -A reusable JavaScript library for highlighting specific columns in DataTables responsive child rows based on configurable conditions. - -## Usage - -### 1. Include the library -```html - -``` - -### 2. Configure highlighting rules - -```javascript -const highlightConfig = [ - { - condition: { anyColumn: true, value: '1Password' }, - targets: [ - { - column: 'Company', - backgroundColor: '#fa8072', - textColor: '#000000' - }, - { - column: 'Product', - backgroundColor: '#87ceeb', - textColor: '#000000' - } - ] - } -]; -``` - -### 3. Initialize after creating your DataTable - -```javascript -// Create your DataTable -var table = $('#myTable').DataTable({ - // your DataTable configuration -}); - -// Setup column highlighting -setupColumnHighlighting('myTable', highlightConfig, table); -``` - -## Configuration Options - -### Condition Types - -#### Any Column Match -```javascript -condition: { anyColumn: true, value: 'SearchValue' } -``` -Matches if any column in the row contains the specified value. - -#### Specific Column Match -```javascript -condition: { column: 0, value: 'SearchValue' } -``` -Matches if the specified column index contains the value. - -#### No Condition (Always Apply) -```javascript -condition: null -``` -Always applies the styling (useful for global styling). - -### Target Options - -#### Basic Styling -```javascript -{ - column: 'Company', // Column name to target - backgroundColor: '#fa8072', // Background color - textColor: '#000000' // Text color -} -``` - -#### Advanced Styling -```javascript -{ - column: 'Company', - backgroundColor: '#fa8072', - textColor: '#ffffff', - css: { // Custom CSS properties - 'font-weight': 'bold', - 'border': '2px solid #000' - }, - highlightParent: true // Also highlight parent element -} -``` - -## Examples - -### Example 1: Single Row, Multiple Columns -```javascript -const config = [ - { - condition: { anyColumn: true, value: '1Password' }, - targets: [ - { column: 'Company', backgroundColor: '#fa8072' }, - { column: 'Product', backgroundColor: '#87ceeb' }, - { column: 'Description', backgroundColor: '#98fb98' } - ] - } -]; -``` - -### Example 2: Multiple Conditions -```javascript -const config = [ - { - condition: { anyColumn: true, value: '1Password' }, - targets: [ - { column: 'Company', backgroundColor: '#fa8072' } - ] - }, - { - condition: { anyColumn: true, value: 'Chrome' }, - targets: [ - { column: 'Company', backgroundColor: '#4285f4', textColor: '#ffffff' } - ] - } -]; -``` - -### Example 3: Global Highlighting -```javascript -const config = [ - { - condition: null, // Always apply - targets: [ - { column: 'CPU', backgroundColor: '#fffacd' }, - { column: 'Memory', backgroundColor: '#f0f8ff' } - ] - } -]; -``` - -### Example 4: Column Index Based -```javascript -const config = [ - { - condition: { column: 0, value: 'SpecificProcess' }, // First column - targets: [ - { column: 'Status', backgroundColor: '#90ee90' } - ] - } -]; -``` - -## Advanced Usage - -### Custom CSS -```javascript -{ - column: 'Priority', - css: { - 'background': 'linear-gradient(45deg, #ff6b6b, #ee5a52)', - 'color': 'white', - 'font-weight': 'bold', - 'border-radius': '4px', - 'padding': '4px 8px' - } -} -``` - -### Conditional Styling Based on Values -You can create multiple configurations for different scenarios: - -```javascript -const config = [ - { - condition: { column: 2, value: 'High' }, // Priority column - targets: [ - { column: 'Status', backgroundColor: '#ff4444', textColor: '#ffffff' } - ] - }, - { - condition: { column: 2, value: 'Medium' }, - targets: [ - { column: 'Status', backgroundColor: '#ffaa00', textColor: '#000000' } - ] - }, - { - condition: { column: 2, value: 'Low' }, - targets: [ - { column: 'Status', backgroundColor: '#44ff44', textColor: '#000000' } - ] - } -]; -``` - -## Minimal Setup Example - -For a quick setup with minimal code in your table: - -```javascript -// After your DataTable is created -setupColumnHighlighting('myTableId', [ - { - condition: { anyColumn: true, value: 'TargetValue' }, - targets: { column: 'ColumnName', backgroundColor: '#color' } - } -], table); -``` \ No newline at end of file diff --git a/Examples/Example-TableColumnHighlight/datatables-column-highlighter.js b/Examples/Example-TableColumnHighlight/datatables-column-highlighter.js deleted file mode 100644 index 8e0fc7f1..00000000 --- a/Examples/Example-TableColumnHighlight/datatables-column-highlighter.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * DataTables Column Highlighter for Responsive Child Rows - * Allows highlighting specific columns in expanded child rows based on configuration - */ - -// Main highlighting system -window.DataTablesColumnHighlighter = { - // Storage for table configurations - configurations: {}, - - /** - * Initialize highlighting for a table - * @param {string} tableId - The ID of the DataTable - * @param {Array} config - Array of column highlighting configurations - * @param {Object} table - The DataTable instance - */ - init: function(tableId, config, table) { - console.log('Initializing DataTables Column Highlighter for:', tableId); - - // Store configuration - this.configurations[tableId] = { - config: config, - table: table - }; - - // Set up event handlers - this.setupEventHandlers(tableId, table); - }, - - /** - * Set up event handlers for the table - */ - setupEventHandlers: function(tableId, table) { - const self = this; - - // Method 1: DataTables API event handler - table.on('click', 'td.dtr-control', function (e) { - console.log('Expand button clicked via DataTables API'); - let tr = e.target.closest('tr'); - let row = table.row(tr); - - setTimeout(function() { - if (row.child.isShown()) { - console.log('Row expanded, applying highlighting...'); - self.applyHighlighting(tableId, $(tr), row.data()); - } - }, 100); - }); - - // Method 2: jQuery fallback event handler - $(`#${tableId} tbody`).on('click', 'td.dtr-control, td.dt-control, .dtr-control, .dt-control', function () { - console.log('Expand button clicked via jQuery fallback'); - let $tr = $(this).closest('tr'); - - setTimeout(function() { - let childRow = $tr.next('tr.child'); - if (childRow.length > 0) { - console.log('Child row found, applying highlighting...'); - // Get row data from the DOM since we don't have the DataTables row object - let rowData = []; - $tr.find('td').each(function(index) { - if (!$(this).hasClass('dtr-control') && !$(this).hasClass('dt-control')) { - rowData.push($(this).text().trim()); - } - }); - self.applyHighlighting(tableId, $tr, rowData); - } - }, 100); - }); - }, - - /** - * Apply highlighting based on configuration - */ - applyHighlighting: function(tableId, $parentRow, rowData) { - const config = this.configurations[tableId].config; - const childRow = $parentRow.next('tr.child'); - - if (childRow.length === 0) { - console.log('No child row found'); - return; - } - - console.log('Applying highlighting with config:', config); - console.log('Row data:', rowData); - - // Process each configuration rule - config.forEach(rule => { - this.processRule(rule, childRow, rowData); - }); - }, - - /** - * Process a single highlighting rule - */ - processRule: function(rule, childRow, rowData) { - console.log('Processing rule:', rule); - - // Check if the condition matches - if (this.matchesCondition(rule.condition, rowData)) { - console.log('Condition matched, applying styling...'); - - // Apply styling to target columns - rule.targets.forEach(target => { - this.applyColumnStyling(childRow, target); - }); - } - }, - - /** - * Check if a condition matches the row data - */ - matchesCondition: function(condition, rowData) { - if (!condition) return true; // No condition means always apply - - // Support different condition types - if (condition.column !== undefined && condition.value !== undefined) { - // Column index-based condition - if (typeof condition.column === 'number') { - return rowData[condition.column] === condition.value; - } - // Column name-based condition would require header mapping - } - - if (condition.anyColumn && condition.value !== undefined) { - // Check if any column contains the value - return rowData.some(cellValue => cellValue === condition.value); - } - - return false; - }, - - /** - * Apply styling to a specific column in the child row - */ - applyColumnStyling: function(childRow, target) { - console.log('Applying styling to column:', target.column); - - // Find the target column in the child row - childRow.find('*').each(function() { - const $elem = $(this); - const text = $elem.text().trim(); - - if (text === target.column) { - console.log('Found target column element:', target.column); - - // Apply styling to the data element - const $dataElem = $elem.siblings('.dtr-data').length > 0 ? - $elem.siblings('.dtr-data') : - $elem.next(); - - if (target.backgroundColor) { - $dataElem.css('background-color', target.backgroundColor); - } - - if (target.textColor) { - $dataElem.css('color', target.textColor); - } - - if (target.css) { - $dataElem.css(target.css); - } - - // Also try parent element in case structure is different - if (target.highlightParent) { - $elem.parent().css('background-color', target.backgroundColor); - if (target.textColor) { - $elem.parent().css('color', target.textColor); - } - } - } - }); - }, - - /** - * Helper method to create simple configuration - */ - createConfig: function(conditions, targets) { - return [{ - condition: conditions, - targets: Array.isArray(targets) ? targets : [targets] - }]; - } -}; - -// Helper function for easy setup -window.setupColumnHighlighting = function(tableId, config, table) { - DataTablesColumnHighlighter.init(tableId, config, table); -}; \ No newline at end of file diff --git a/Private/Parameters.Configuration.ps1 b/Private/Parameters.Configuration.ps1 index e173b192..366004e8 100644 --- a/Private/Parameters.Configuration.ps1 +++ b/Private/Parameters.Configuration.ps1 @@ -15,7 +15,7 @@ param( ) - $ConfigurationURL = 'https://cdn.jsdelivr.net/gh/evotecit/cdn@0.0.30' + $ConfigurationURL = 'https://cdn.jsdelivr.net/gh/evotecit/cdn@0.0.31' $Configuration = [ordered] @{ Features = [ordered] @{ Inject = @{ @@ -959,14 +959,17 @@ Email = $false } DataTablesConditions = @{ - Comment = 'DataTables Conditions' - FooterAlways = @{ - JS = @( - "$PSScriptRoot\..\Resources\JS\dataTables.conditions.js" + Comment = 'DataTables Conditions' + Header = @{ + JsLink = @( + "https://cdn.jsdelivr.net/npm/@evotecit/htmlextensions@0.1.2/dist/datatables.columnHighlighter.js" + ) + JS = @( + "$PSScriptRoot\..\Resources\JS\dataTables.columnHighlighter.js" ) } - Default = $true - Email = $false + Default = $true + Email = $false } DataTablesColReorder = @{ Comment = 'DataTables ColReorder Features' diff --git a/Private/Tables/Convert-TableConditionsToHighlighterRules.ps1 b/Private/Tables/Convert-TableConditionsToHighlighterRules.ps1 new file mode 100644 index 00000000..0dd582eb --- /dev/null +++ b/Private/Tables/Convert-TableConditionsToHighlighterRules.ps1 @@ -0,0 +1,163 @@ +function Convert-TableConditionsToHighlighterRules { + [CmdletBinding()] + param( + [Parameter(Mandatory)][System.Collections.IEnumerable] $ConditionalFormatting, + [Parameter(Mandatory)][string[]] $Header + ) + + # Build rules with idiomatic array comprehensions for clarity and simplicity + [array] $rules = @( + foreach ($Entry in $ConditionalFormatting) { + if (-not $Entry) { continue } + + $ct = $Entry.ConditionType + + if ($ct -eq 'Condition') { + # Single condition or unconditional highlight + $isUnconditional = ($Entry.PSObject.Properties.Name -notcontains 'Value') -or ($null -eq $Entry.Value) + if (-not $isUnconditional) { + $cond = New-TablePluginCondition -Condition $Entry + } + + # Determine target names + $targetNames = if ($Entry.Row) { + $Header + } elseif ($Entry.HighlightHeaders) { + $Entry.HighlightHeaders + } else { + @($Entry.Name) + } + + # Build targets + $fill = $null + if ($Entry.ChildRowFill) { + if ($Entry.ChildRowFill -eq 'Parent') { $fill = 'parent' } + elseif ($Entry.ChildRowFill -eq 'Both') { $fill = 'both' } + } + [array] $targets = @( + foreach ($n in $targetNames) { + $t = [ordered]@{ + column = $n + css = $Entry.Style + } + if ($fill) { $t['fill'] = $fill } + $t + } + ) + + # Build failTargets, if any + $failTargets = $null + if ($Entry.FailStyle.Keys.Count -gt 0) { + $failTargets = @( + foreach ($n in $targetNames) { + $ft = [ordered]@{ + column = $n + css = $Entry.FailStyle + } + # Do not propagate fill to fail targets by default + $ft + } + ) + } + + if ($isUnconditional) { + $rule = [ordered]@{ + conditionsContainer = @() # unconditional + targets = $targets + } + } else { + $rule = [ordered]@{ + conditionsContainer = @( + [ordered]@{ + logic = 'AND' + conditions = @($cond) + } + ) + targets = $targets + } + } + if ($failTargets) { + $rule['failTargets'] = $failTargets + } + + [pscustomobject]$rule + + } elseif ($ct -eq 'ConditionGroup') { + # Grouped conditions + [array] $conditions = @( + foreach ($Nested in $Entry.Conditions) { + if ($Nested.Type -eq 'TableCondition') { + New-TablePluginCondition -Condition $Nested.Output + } + } + ) + $groupUnconditional = ($conditions.Count -eq 0) + + # Determine targets + $targetNames = if ($Entry.Row) { + $Header + } elseif ($Entry.HighlightHeaders) { + $Entry.HighlightHeaders + } else { + @( + foreach ($Nested in $Entry.Conditions) { + if ($Nested.Type -eq 'TableCondition') { $Nested.Output.Name } + } + ) + } + + $fill = $null + if ($Entry.ChildRowFill) { + if ($Entry.ChildRowFill -eq 'Parent') { $fill = 'parent' } + elseif ($Entry.ChildRowFill -eq 'Both') { $fill = 'both' } + } + [array] $targets = @( + foreach ($n in $targetNames) { + $t = [ordered]@{ + column = $n + css = $Entry.Style + } + if ($fill) { $t['fill'] = $fill } + $t + } + ) + + $failTargets = $null + if ($Entry.FailStyle.Keys.Count -gt 0) { + $failTargets = @( + foreach ($n in $targetNames) { + [ordered]@{ + column = $n + css = $Entry.FailStyle + } + } + ) + } + + if ($groupUnconditional) { + $rule = [ordered]@{ + conditionsContainer = @() # unconditional + targets = $targets + } + } else { + $rule = [ordered]@{ + conditionsContainer = @( + [ordered]@{ + logic = $Entry.Logic + conditions = $conditions + } + ) + targets = $targets + } + } + if ($failTargets) { + $rule['failTargets'] = $failTargets + } + + [pscustomobject]$rule + } + } + ) + + return ,$rules +} diff --git a/Private/Tables/New-TablePluginCondition.ps1 b/Private/Tables/New-TablePluginCondition.ps1 new file mode 100644 index 00000000..2298867e --- /dev/null +++ b/Private/Tables/New-TablePluginCondition.ps1 @@ -0,0 +1,52 @@ +function New-TablePluginCondition { + [CmdletBinding()] + param( + [Parameter(Mandatory)][pscustomobject] $Condition + ) + + # Map PSWriteHTML condition to plugin condition schema + $plugin = [ordered]@{ + columnName = $Condition.Name + operator = ($Condition.Operator | ForEach-Object { $_.ToLower() }) + type = ($Condition.Type | ForEach-Object { $_.ToLower() }) + value = $Condition.Value + caseSensitive = $Condition.CaseSensitive + dateTimeFormat = $Condition.DateTimeFormat + reverseCondition = $Condition.ReverseCondition + } + + if ($plugin.type -eq 'date' -and $null -ne $Condition.Value) { + if ($Condition.Value -is [datetime]) { + [void]$plugin.Remove('value') + $plugin['valueDate'] = [ordered]@{ + year = $Condition.Value.Year + month = $Condition.Value.Month + day = $Condition.Value.Day + hours = $Condition.Value.Hour + minutes = $Condition.Value.Minute + seconds = $Condition.Value.Second + } + } elseif ($Condition.Value -is [System.Collections.IEnumerable]) { + [array] $dates = @( + foreach ($dv in $Condition.Value) { + if ($dv -is [datetime]) { + [ordered]@{ + year = $dv.Year + month = $dv.Month + day = $dv.Day + hours = $dv.Hour + minutes = $dv.Minute + seconds = $dv.Second + } + } + } + ) + if ($dates.Count -gt 0) { + [void]$plugin.Remove('value') + $plugin['valueDate'] = $dates + } + } + } + + [pscustomobject]$plugin +} diff --git a/Public/New-HTMLTable.ps1 b/Public/New-HTMLTable.ps1 index ec64681f..505c0e58 100644 --- a/Public/New-HTMLTable.ps1 +++ b/Public/New-HTMLTable.ps1 @@ -1265,7 +1265,20 @@ $Options.autoWidth = $false } - $Options = $Options | ConvertTo-Json -Depth 6 #ConvertTo-JsonLiteral -Depth 6 -AdvancedReplace @{ '.' = '\.'; '$' = '\$' } + # Prefer plugin-based conditional formatting: serialize rules into columnHighlighter + + if ($ConditionalFormatting -and $ConditionalFormatting.Count -gt 0) { + $rules = Convert-TableConditionsToHighlighterRules -ConditionalFormatting $ConditionalFormatting -Header $HeaderNames + if ($rules -and $rules.Count -gt 0) { + if ($Options.Contains('createdRow')) { + $Options.Remove('createdRow') + } + $Options['columnHighlighter'] = [ordered]@{ rules = $rules } + } + } + + + $Options = $Options | ConvertTo-Json -Depth 8 #ConvertTo-JsonLiteral -Depth 6 -AdvancedReplace @{ '.' = '\.'; '$' = '\$' } # cleans up $Options for ImmediatelyShowHiddenDetails # Since it's JavaScript inside we're basically removing double quotes from JSON in favor of no quotes at all @@ -1339,8 +1352,10 @@ $Table = $null } - # Process Conditional Formatting. Ugly JS building - $Options = New-TableConditionalFormatting -Options $Options -ConditionalFormatting $ConditionalFormatting -Header $HeaderNames -DataStore $DataStore + # Process Conditional Formatting using rowCallback only if plugin rules are not present + # if ($Options -notmatch '"columnHighlighter"\s*:\s*\{') { + # $Options = New-TableConditionalFormatting -Options $Options -ConditionalFormatting $ConditionalFormatting -Header $HeaderNames -DataStore $DataStore + # } # Process Row Grouping. Ugly JS building if ($RowGroupingColumnID -ne -1) { $Options = Convert-TableRowGrouping -Options $Options -RowGroupingColumnID $RowGroupingColumnID @@ -1501,4 +1516,4 @@ } $AfterTable } -} \ No newline at end of file +} diff --git a/Public/New-TableCondition.ps1 b/Public/New-TableCondition.ps1 index 89cc5b6f..87e02c4e 100644 --- a/Public/New-TableCondition.ps1 +++ b/Public/New-TableCondition.ps1 @@ -115,12 +115,14 @@ function New-TableCondition { [string[]] $HighlightHeaders, [alias('Type')][ValidateSet('number', 'string', 'bool', 'date')][string] $ComparisonType = 'string', [ValidateSet('lt', 'le', 'eq', 'ge', 'gt', 'ne', 'contains', 'like', 'notlike', 'notcontains', 'between', 'betweenInclusive', 'in', 'notin')][string] $Operator = 'eq', - [parameter(Mandatory)][Object] $Value, + [Object] $Value, [switch] $Row, [switch] $Inline, [switch] $CaseSensitive, [string] $DateTimeFormat, [switch] $ReverseCondition, + # Child row fill for responsive lists + [ValidateSet('Parent','Both')][string] $ChildRowFill, # Style for PASS [string]$Color, [string]$BackgroundColor, @@ -192,6 +194,7 @@ function New-TableCondition { CaseSensitive = $CaseSensitive.IsPresent DateTimeFormat = $DateTimeFormat ReverseCondition = $ReverseCondition.IsPresent + ChildRowFill = $ChildRowFill } [PSCustomObject] @{ Type = if ($Inline) { 'TableConditionInline' } else { 'TableCondition' } @@ -202,4 +205,4 @@ function New-TableCondition { Register-ArgumentCompleter -CommandName New-TableCondition -ParameterName Color -ScriptBlock $Script:ScriptBlockColors Register-ArgumentCompleter -CommandName New-TableCondition -ParameterName BackgroundColor -ScriptBlock $Script:ScriptBlockColors Register-ArgumentCompleter -CommandName New-TableCondition -ParameterName FailColor -ScriptBlock $Script:ScriptBlockColors -Register-ArgumentCompleter -CommandName New-TableCondition -ParameterName FailBackgroundColor -ScriptBlock $Script:ScriptBlockColors \ No newline at end of file +Register-ArgumentCompleter -CommandName New-TableCondition -ParameterName FailBackgroundColor -ScriptBlock $Script:ScriptBlockColors diff --git a/Public/New-TableConditionGroup.ps1 b/Public/New-TableConditionGroup.ps1 index 9985f4d6..f2645d12 100644 --- a/Public/New-TableConditionGroup.ps1 +++ b/Public/New-TableConditionGroup.ps1 @@ -107,6 +107,8 @@ [string[]] $HighlightHeaders, [switch] $Row, [switch] $Inline, + # Child row fill for responsive lists + [ValidateSet('Parent','Both')][string] $ChildRowFill, # Style for PASS [string]$Color, [string]$BackgroundColor, @@ -174,6 +176,7 @@ Logic = $Logic HighlightHeaders = $HighlightHeaders DateTimeFormat = $DateTimeFormat + ChildRowFill = $ChildRowFill } [PSCustomObject] @{ Type = if ($Inline) { 'TableConditionGroupInline' } else { 'TableConditionGroup' } @@ -185,4 +188,4 @@ Register-ArgumentCompleter -CommandName New-TableConditionGroup -ParameterName Color -ScriptBlock $Script:ScriptBlockColors Register-ArgumentCompleter -CommandName New-TableConditionGroup -ParameterName BackgroundColor -ScriptBlock $Script:ScriptBlockColors Register-ArgumentCompleter -CommandName New-TableConditionGroup -ParameterName FailColor -ScriptBlock $Script:ScriptBlockColors -Register-ArgumentCompleter -CommandName New-TableConditionGroup -ParameterName FailBackgroundColor -ScriptBlock $Script:ScriptBlockColors \ No newline at end of file +Register-ArgumentCompleter -CommandName New-TableConditionGroup -ParameterName FailBackgroundColor -ScriptBlock $Script:ScriptBlockColors diff --git a/Resources/JS/dataTables.columnHighlighter.js b/Resources/JS/dataTables.columnHighlighter.js new file mode 100644 index 00000000..4e5d6679 --- /dev/null +++ b/Resources/JS/dataTables.columnHighlighter.js @@ -0,0 +1,234 @@ +// DataTables Column Highlighter (standalone) +// Combines conditional engine and responsive child-row + visible cell styling +// Usage: +// $('#table').DataTable({ +// columnHighlighter: { rules: [ /* see README.md */ ] } +// }); +/* + DataTables Column Highlighter v1.0.0 + (c) 2025 EvotecIT | MIT + https://github.com/EvotecIT/HTMLExtensions +*/ +(function(){ + if (typeof window === 'undefined') { return; } + + function isEmptyOrSpaces(str) { return !str || str.trim() === ''; } + + function dataTablesCheckCondition(condition, data) { + var columnName = condition['columnName']; + var reverseCondition = condition['reverseCondition']; + var columnId = condition['columnId']; + var operator = (condition['operator'] || '').toLowerCase(); + var columnValue; + if ((condition['dataStore'] || '').toLowerCase() != 'html') { + var columnExists = false; + Object.getOwnPropertyNames(data).forEach(function (val) { + if (val.toLowerCase() == columnName.toLowerCase()) { + columnName = val; columnExists = true; return; + } + }); + if (!columnExists) { return false; } + columnValue = data[columnName]; + } else { + if (columnId == -1) { return false; } + columnValue = data[columnId]; + } + var conditionValue = condition['value']; + if (condition['type'] == 'bool') { + columnValue = String(columnValue).toLowerCase(); + conditionValue = String(conditionValue).toLowerCase(); + } else if (condition['type'] == 'string') { + if (!condition['caseSensitive']) { + columnValue = String(columnValue).toLowerCase(); + conditionValue = String(conditionValue).toLowerCase(); + } + } else if (condition['type'] == 'number') { + if (Array.isArray(conditionValue)) { + var tmp = []; + for (var i = 0; i < conditionValue.length; i++) { + if (!isEmptyOrSpaces(String(conditionValue[i]))) { tmp.push(Number(conditionValue[i])); } + else { tmp.push(undefined); } + } + conditionValue = tmp; + if (!isEmptyOrSpaces(String(columnValue))) { columnValue = Number(columnValue); } + else { columnValue = undefined; } + } else { + if (!isEmptyOrSpaces(String(conditionValue))) { conditionValue = Number(conditionValue); } + else { conditionValue = undefined; } + if (!isEmptyOrSpaces(String(columnValue))) { columnValue = Number(columnValue); } + else { columnValue = undefined; } + } + } else if (condition['type'] == 'date') { + if (Array.isArray(condition['valueDate'])) { + var tmp2 = []; + for (var j = 0; j < condition['valueDate'].length; j++) { + var valueDate = condition['valueDate'][j]; + tmp2.push(new Date(valueDate.year, valueDate.month - 1, valueDate.day, valueDate.hours, valueDate.minutes, valueDate.seconds)); + } + conditionValue = tmp2; + } else { + var vd = condition['valueDate']; + conditionValue = new Date(vd.year, vd.month - 1, vd.day, vd.hours, vd.minutes, vd.seconds); + } + var momentConversion = (typeof moment !== 'undefined') ? moment(columnValue, condition['dateTimeFormat']) : columnValue; + columnValue = new Date(momentConversion); + } + var left, right; if (reverseCondition) { left = conditionValue; right = columnValue; } else { left = columnValue; right = conditionValue; } + if (operator == 'eq') { return left == right; } + else if (operator == 'ne') { return left != right; } + else if (operator == 'gt') { return left > right; } + else if (operator == 'lt') { return left < right; } + else if (operator == 'le') { return left <= right; } + else if (operator == 'ge') { return left >= right; } + else if (operator == 'in') { return Array.isArray(right) && right.indexOf(left) != -1; } + else if (operator == 'notin') { return Array.isArray(right) && right.indexOf(left) == -1; } + else if (operator == 'contains' || operator == 'like') { var rx = new RegExp(right); return rx.test(left); } + else if (operator == 'notcontains' || operator == 'notlike') { var rx2 = new RegExp(right); return !rx2.test(left); } + else if (operator == 'betweeninclusive') { return Array.isArray(right) && left >= right[0] && left <= right[1]; } + else if (operator == 'between') { return Array.isArray(right) && left > right[0] && left < right[1]; } + return false; + } + + function getHeaderNames(tableId) { + try { + var names = []; var $ths = jQuery('#' + tableId + ' thead th'); + $ths.each(function(){ names.push(jQuery(this).text().trim()); }); return names; + } catch (e) { return []; } + } + function indexToHeaderName(tableId, index) { + var headers = getHeaderNames(tableId); if (index >= 0 && index < headers.length) { return headers[index]; } return null; + } + function headerNameToIndex(tableId, name) { + var headers = getHeaderNames(tableId); if (!name) return -1; var lname = ('' + name).toLowerCase(); + for (var i = 0; i < headers.length; i++) { if (('' + headers[i]).toLowerCase() === lname) return i; } return -1; + } + + window.DataTablesColumnHighlighter = { + configurations: {}, + detectStore: function(table){ + try { if (table && table.rows) { var d = table.rows({ page: 'current' }).data(); var f = d && d.length ? d[0] : null; if (Array.isArray(f)) return 'HTML'; if (f && typeof f === 'object') return 'JavaScript'; } } catch(e) {} + return 'HTML'; + }, + normalizeCondition: function(c, headers, store){ + if (!c) return c; try { + if (!c.dataStore) c.dataStore = store; + if ((c.columnId === undefined || c.columnId === -1) && c.columnName) { + var lname = ('' + c.columnName).toLowerCase(); for (var h = 0; h < headers.length; h++) { if ((headers[h] + '').toLowerCase() === lname) { c.columnId = h; break; } } + } + if ((!c.columnName || c.columnName === '') && (typeof c.columnId === 'number')) { if (c.columnId >= 0 && c.columnId < headers.length) { c.columnName = headers[c.columnId]; } } + } catch(e) {} + return c; + }, + normalizeRules: function(tableId, config, table){ + try { + var headers = getHeaderNames(tableId); var store = this.detectStore(table); + for (var i = 0; i < config.length; i++) { + var rule = config[i]; if (rule && rule.conditionsContainer) { + for (var r = 0; r < rule.conditionsContainer.length; r++) { + var container = rule.conditionsContainer[r]; if (container && container.conditions) { + for (var k = 0; k < container.conditions.length; k++) { container.conditions[k] = this.normalizeCondition(container.conditions[k], headers, store); } + } + } + } + } + } catch(e) {} + return config; + }, + normalizeRowData: function(rowData) { + try { if (Array.isArray(rowData)) { return rowData.map(function(v){ try { return jQuery('

' + v + '

').text().trim(); } catch(e) { return ('' + v).trim(); } }); } } catch(e) {} + return rowData; + }, + init: function(tableId, config, table) { + var normalized = this.normalizeRules(tableId, Array.isArray(config) ? config : [config], table); + this.configurations[tableId] = { config: normalized, table: table }; + this.setupEventHandlers(tableId, table); + }, + setupEventHandlers: function(tableId, table) { + var self = this; + if (table && table.on) { + table.on('responsive-display', function (e, datatable, row, showHide) { + try { if (showHide) { var tr = row.node(); var $tr = jQuery(tr); var data = row.data(); self.applyHighlighting(tableId, $tr, data); } } catch (err) { } + }); + table.on('draw.dt', function(){ + try { table.rows({ page: 'current' }).every(function(){ var tr = this.node(); var $tr = jQuery(tr); var data = this.data(); self.applyHighlighting(tableId, $tr, data); }); } catch (err) { } + }); + setTimeout(function(){ try { table.rows({ page: 'current' }).every(function(){ var tr = this.node(); var $tr = jQuery(tr); var data = this.data(); self.applyHighlighting(tableId, $tr, data); }); } catch (err) { } }, 0); + } + if (!(table && table.on)) { + jQuery('#' + tableId + ' tbody').on('click', 'td.dtr-control, td.dt-control, .dtr-control, .dt-control', function () { + var $tr = jQuery(this).closest('tr'); setTimeout(function() { var childRow = $tr.next('tr.child'); if (childRow.length > 0) { var rowData = []; $tr.find('td').each(function(){ var $td = jQuery(this); if (!$td.hasClass('dtr-control') && !$td.hasClass('dt-control')) { rowData.push($td.text().trim()); } }); self.applyHighlighting(tableId, $tr, rowData); } }, 100); + }); + } + }, + applyHighlighting: function(tableId, $parentRow, rowData) { + var cfg = this.configurations[tableId]; if (!cfg) { return; } + var childRow = $parentRow.next('tr.child'); var norm = this.normalizeRowData(rowData); + var styleMap = {}; + function pushStyle(col, style, isPass) { if (!col) return; if (!styleMap[col]) styleMap[col] = { pass: [], fail: [] }; (isPass ? styleMap[col].pass : styleMap[col].fail).push(style || {}); } + function normalizeTarget(t) { return { column: t.column, backgroundColor: t.backgroundColor, textColor: t.textColor, css: t.css, highlightParent: t.highlightParent }; } + function mergedStyle(entry) { var src = entry.pass.length > 0 ? entry.pass : entry.fail; if (src.length === 0) return null; var out = {}; for (var i=0;i0){ DataTablesColumnHighlighter.applyColumnStyling(childRow, target); } DataTablesColumnHighlighter.applyVisibleCellStyling(tableId, $parentRow, target); } + }, + applyColumnStyling: function(childRow, target) { + childRow.find('.dtr-title').each(function() { var $elem = jQuery(this); var text = $elem.text().trim(); if (text === target.column) { var $dataElem = $elem.siblings('.dtr-data').length > 0 ? $elem.siblings('.dtr-data') : $elem.next(); if (target.backgroundColor) { $dataElem.css('background-color', target.backgroundColor); } if (target.textColor) { $dataElem.css('color', target.textColor); } if (target.css) { $dataElem.css(target.css); } if (target.highlightParent) { $elem.parent().css('background-color', target.backgroundColor); if (target.textColor) { $elem.parent().css('color', target.textColor); } } } }); + }, + applyVisibleCellStyling: function(tableId, $parentRow, target) { + try { var idx = (typeof target.column === 'number') ? target.column : headerNameToIndex(tableId, target.column); if (idx < 0) return; var $cells = $parentRow.find('td'); if ($cells.length === 0) return; var $cell = $cells.eq(idx); if ($cell && $cell.length > 0) { if (target.backgroundColor) { $cell.css('background-color', target.backgroundColor); } if (target.textColor) { $cell.css('color', target.textColor); } if (target.css) { $cell.css(target.css); } } } catch (e) { } + }, + createConfig: function(conditions, targets) { return [{ condition: conditions, targets: Array.isArray(targets) ? targets : [targets] }]; } + }; + + window.setupChildRowConditionalFormatting = function(tableId, conditionsContainer, highlightColumn, css, failCss, table) { + try { + var headers = getHeaderNames(tableId); + var detectStore = function(){ try { if (table && table.rows) { var d = table.rows({ page: 'current' }).data(); var first = d && d.length ? d[0] : null; if (Array.isArray(first)) return 'HTML'; if (first && typeof first === 'object') return 'JavaScript'; } } catch(e) {} return 'HTML'; }; + var dataStoreType = detectStore(); + try { + for (var i = 0; i < conditionsContainer.length; i++) { + var container = conditionsContainer[i]; if (!container || !container.conditions) continue; + for (var k = 0; k < container.conditions.length; k++) { + var c = container.conditions[k] || {}; + if (!c.dataStore) { c.dataStore = dataStoreType; } + if ((c.columnId === undefined || c.columnId === -1) && c.columnName) { var lname = ('' + c.columnName).toLowerCase(); for (var h = 0; h < headers.length; h++) { if ((headers[h] + '').toLowerCase() === lname) { c.columnId = h; break; } } } + if ((!c.columnName || c.columnName === '') && (typeof c.columnId === 'number')) { if (c.columnId >= 0 && c.columnId < headers.length) { c.columnName = headers[c.columnId]; } } + container.conditions[k] = c; + } + } + } catch (e) { } + var targets = []; + if (Array.isArray(highlightColumn)) { + for (var i = 0; i < highlightColumn.length; i++) { + var entry = highlightColumn[i]; var header = null; if (typeof entry === 'number') { header = indexToHeaderName(tableId, entry); } else if (typeof entry === 'string') { header = entry; } else if (entry && typeof entry === 'object' && entry.column) { header = entry.column; } + if (header !== null && header !== undefined) { var t = { column: header }; if (css) { if (css['background-color']) t.backgroundColor = css['background-color']; if (css['color']) t.textColor = css['color']; t.css = css; } targets.push(t); } + } + } + var newRule = { conditionsContainer: conditionsContainer, targets: targets }; + if (window.DataTablesColumnHighlighter && window.DataTablesColumnHighlighter.configurations && window.DataTablesColumnHighlighter.configurations[tableId]) { + try { window.DataTablesColumnHighlighter.configurations[tableId].config.push(newRule); var tbl = table || window.DataTablesColumnHighlighter.configurations[tableId].table; if (tbl && tbl.rows) { tbl.rows({ page: 'current' }).every(function(){ var tr = this.node(); var $tr = jQuery(tr); var data = this.data(); window.DataTablesColumnHighlighter.applyHighlighting(tableId, $tr, data); }); } } catch (e) { window.DataTablesColumnHighlighter.init(tableId, [newRule], table || window.DataTablesColumnHighlighter.configurations[tableId].table); } + } else { window.DataTablesColumnHighlighter.init(tableId, [newRule], table); } + } catch (e) { } + }; + + window.setupColumnHighlighting = function(tableId, config, table) { DataTablesColumnHighlighter.init(tableId, config, table); }; + + function autoInitFromSettings(settings) { + try { + var api = new jQuery.fn.dataTable.Api(settings); + var tableId = settings && settings.nTable ? settings.nTable.getAttribute('id') : null; if (!tableId) return; + if (window.DataTablesColumnHighlighter && window.DataTablesColumnHighlighter.configurations && window.DataTablesColumnHighlighter.configurations[tableId]) { return; } + var oInit = settings.oInit || {}; var ch = oInit.columnHighlighter; if (!ch) return; + var rules = ch.rules || ch.config || []; if (!rules || (Array.isArray(rules) && rules.length === 0)) return; + window.DataTablesColumnHighlighter.init(tableId, rules, api); + try { api.rows({ page: 'current' }).every(function(){ var tr = this.node(); var $tr = jQuery(tr); var data = this.data(); window.DataTablesColumnHighlighter.applyHighlighting(tableId, $tr, data); }); } catch(err) { } + } catch(err) { } + } + try { jQuery(document).on('preInit.dt', function(e, settings){ autoInitFromSettings(settings); }); jQuery(document).on('init.dt', function(e, settings){ autoInitFromSettings(settings); }); } catch(e) { } + try { jQuery(function(){ try { if (!jQuery.fn.dataTable) return; var doScan = function(){ try { var apis = jQuery.fn.dataTable.tables({ api: true }); apis.every(function(){ try { autoInitFromSettings(this.settings()[0]); } catch(err) { } }); return apis && apis.count && apis.count() > 0; } catch(err) { return false; } }; doScan(); var tries = 0; var timer = setInterval(function(){ var found = doScan(); tries++; if (found || tries > 40) { clearInterval(timer); } }, 50); } catch(err) { } }); } catch(e) { } +})(); diff --git a/Resources/JS/dataTables.conditions.js b/Resources/JS/dataTables.conditions.js deleted file mode 100644 index cbde940f..00000000 --- a/Resources/JS/dataTables.conditions.js +++ /dev/null @@ -1,238 +0,0 @@ -function isEmptyOrSpaces(str) { - return !str || str.trim() === ''; -} -function dataTablesCheckCondition(condition, data) { - var columnName = condition['columnName']; - var reverseCondition = condition['reverseCondition']; - var columnId = condition['columnId']; - var operator = condition['operator'].toLowerCase(); - if (condition['dataStore'].toLowerCase() != 'html') { - var columnExists = false; - // we need to find whether the column name exists or not, and to make sure we know the column name exact case (it's case sensitive) just in case user provided it wrong - Object.getOwnPropertyNames(data).forEach( - function (val) { - if (val.toLowerCase() == columnName.toLowerCase()) { - columnName = val; - columnExists = true; - return - } - } - ); - if (!columnExists) { - return false; - } - var columnValue = data[columnName]; - } else { - // check if columnid is set - if it's not set it means the column doesn't exists so we dont' proceed - if (columnId == -1) { - return false; - } - var columnValue = data[columnId]; - } - var conditionValue = condition['value']; - - //console.log('before: ' + conditionValue + ' || ' + columnValue + ' type: ' + condition['type']); - if (condition['type'] == 'bool') { - columnValue = columnValue.toString().toLowerCase(); - conditionValue = conditionValue.toString().toLowerCase(); - } else if (condition['type'] == 'string') { - if (!condition['caseSensitive']) { - columnValue = columnValue.toString().toLowerCase(); - conditionValue = conditionValue.toString().toLowerCase(); - } - } else if (condition['type'] == 'number') { - if (Array.isArray(conditionValue)) { - // this will be used for between, betweenInclusive - // if its an array we need to make sure to convert conditionValue within an array - var conditionValueTemporary = []; - - for (var i = 0; i < conditionValue.length; i++) { - //for (let value of conditionValue) { - if (!isEmptyOrSpaces(conditionValue[i].toString())) { - conditionValueTemporary.push(Number(conditionValue[i])); - } else { - conditionValueTemporary.push(undefined); - } - } - conditionValue = conditionValueTemporary; - if (!isEmptyOrSpaces(columnValue.toString())) { - columnValue = Number(columnValue); - } else { - columnValue = undefined; - } - } else { - // This logic is to get rid of empty string which is normally treated as 0 - if (!isEmptyOrSpaces(conditionValue.toString())) { - conditionValue = Number(conditionValue); - } else { - conditionValue = undefined; - } - if (!isEmptyOrSpaces(columnValue.toString())) { - columnValue = Number(columnValue); - } else { - columnValue = undefined; - } - } - } else if (condition['type'] == 'date') { - if (Array.isArray(condition['valueDate'])) { - var conditionValueTemporary = []; - for (var i = 0; i < condition['valueDate'].length; i++) { - //for (let value of condition['valueDate']) { - var valueDate = condition['valueDate'][i]; - conditionValueTemporary.push(new Date(valueDate.year, valueDate.month - 1, valueDate.day, valueDate.hours, valueDate.minutes, valueDate.seconds)); - } - conditionValue = conditionValueTemporary; - } else { - // bring conditionValue to proper date - var valueDate = condition['valueDate']; - conditionValue = new Date(valueDate.year, valueDate.month - 1, valueDate.day, valueDate.hours, valueDate.minutes, valueDate.seconds); - } - // bring columnValue based on dateTimeFormat provided - var momentConversion = moment(columnValue, condition['dateTimeFormat']); - columnValue = new Date(momentConversion); - } - - if (reverseCondition) { - var sideLeft = conditionValue; - var sideRight = columnValue; - } else { - var sideLeft = columnValue; - var sideRight = conditionValue; - } - //console.log('after: ' + conditionValue + ' || ' + columnValue); - if (operator == 'eq') { - if (sideLeft == sideRight) { - return true; - } - } else if (operator == 'ne') { - if (sideLeft != sideRight) { - return true; - } - } else if (operator == 'gt') { - if (sideLeft > sideRight) { - return true; - } - } else if (operator == 'lt') { - if (sideLeft < sideRight) { - return true; - } - } else if (operator == 'le') { - if (sideLeft <= sideRight) { - return true; - } - } else if (operator == 'ge') { - if (sideLeft >= sideRight) { - return true; - } - } else if (operator == 'in') { - if (sideRight.indexOf(sideLeft) != -1) { - return true; - } - } else if (operator == 'notin') { - if (sideRight.indexOf(sideLeft) == -1) { - return true; - } - } else if (operator == 'contains' || operator == 'like') { - //var compareValue = conditionValue.replace('*', '.*') - var regex = new RegExp(sideRight); - if (regex.test(sideLeft)) { - return true; - } - } else if (operator == 'notcontains' || operator == 'notlike') { - //var compareValue = conditionValue.replace('*', '.*') - var regex = new RegExp(sideRight) - if (!regex.test(sideLeft)) { - return true; - } - } else if (operator == 'betweeninclusive') { - if (Array.isArray(sideRight) && sideLeft >= sideRight[0] && sideLeft <= sideRight[1]) { - return true; - } - } else if (operator == 'between') { - if (Array.isArray(sideRight) && sideLeft > sideRight[0] && sideLeft < sideRight[1]) { - return true; - } - } - return false; -} -function dataTablesConditionalFormatting(row, data, conditionsContainer, highlightColumn, css, failCss) { - var conditionsMatch = []; - var found = false; - for (var i = 0; i < conditionsContainer.length; i++) { - var container = conditionsContainer[i]; - for (var k = 0; k < container['conditions'].length; k++) { - var condition = container['conditions'][k]; - conditionsMatch.push( - dataTablesCheckCondition(condition, data) - ); - } - if (container['logic'] == 'AND') { - // if (conditionsMatch.every(value => value === true)) { - // found = true; - // } - - for (var a = 0; a < conditionsMatch.length; a++) { - if (conditionsMatch[a] !== true) { - found = false; - break; - } else { - found = true; - } - } - - - } else if (container['logic'] == 'OR') { - //if (conditionsMatch.some(value => value === true)) { - // found = true; - //} - - for (var a = 0; a < conditionsMatch.length; a++) { - if (conditionsMatch[a] === true) { - found = true; - break; - } - } - - } else if (container['logic'] == 'NONE') { - // if (conditionsMatch.every(value => value != true)) { - // found = true; - //} - - for (var a = 0; a < conditionsMatch.length; a++) { - if (conditionsMatch[a] !== false) { - found = false; - break; - } else { - found = true; - } - } - } - } - if (found) { - if (highlightColumn == null) { - $('td', row).css(css); - } else { - for (var a = 0; a < highlightColumn.length; a++) { - var column = highlightColumn[a]; - //for (let column of highlightColumn) { - $("td:eq(" + column + ")", row).css(css); - - //if (data.Type == "group") { - // $('td:eq(6)', row).html('A'); - //} - } - } - } else { - if (failCss) { - if (highlightColumn == null) { - $('td', row).css(failCss); - } else { - for (var a = 0; a < highlightColumn.length; a++) { - var column = highlightColumn[a]; - //for (let column of highlightColumn) { - $("td:eq(" + column + ")", row).css(failCss); - } - } - } - } -} \ No newline at end of file