Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/Tutorials/Elements/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,78 @@ $table | Add-PodeWebTableButton -Name 'Excel' -Icon Database -ScriptBlock {

New-PodeWebContainer -Content $table
```

## MultiSelect

You can allow multiple rows to be selected in a table by passing the `-MultiSelect` switch to [`New-PodeWebTable`](../../../Functions/Elements/New-PodeWebTable). This adds a checkbox column to each row.

!!! important
`-MultiSelect` requires `-DataColumn` to be set. The value of that column for each selected row is what gets passed to button scriptblocks as the selection.

```powershell
$table = New-PodeWebTable -Name 'Services' -DataColumn Name -MultiSelect -ScriptBlock {
foreach ($svc in (Get-Service)) {
[ordered]@{
Name = $svc.Name
Status = "$($svc.Status)"
StartType = "$($svc.StartType)"
}
}
}

New-PodeWebContainer -Content $table
```

### Acting on a Selection

When combined with [`Add-PodeWebTableButton`](../../../Functions/Elements/Add-PodeWebTableButton), the `-DataColumn` values of all checked rows are available in the button's scriptblock via `$WebEvent.Data['Selection']` as a comma-separated string.

```powershell
$table = New-PodeWebTable -Name 'Services' -DataColumn Name -MultiSelect -ScriptBlock {
foreach ($svc in (Get-Service)) {
[ordered]@{
Name = $svc.Name
Status = "$($svc.Status)"
StartType = "$($svc.StartType)"
}
}
}

$table | Add-PodeWebTableButton -Name 'StopSelected' -DisplayName 'Stop Selected' -Icon 'Stop-Circle' -WithText -ScriptBlock {
$selected = $WebEvent.Data['Selection'] -split ','
if ($selected.Length -eq 0) {
Show-PodeWebToast -Message 'No services selected' -Title 'StopSelected'
}
else {
foreach ($svc in $selected) {
Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
}
Show-PodeWebToast -Message "Stopped $($selected.Count) service(s)" -Title 'Done'
Sync-PodeWebTable -Name 'Services'
}
}

New-PodeWebContainer -Content $table
```

If you want to confirm the selection before acting on it, you can open a Modal from the button's scriptblock and pass the selected values through using [`Update-PodeWebTextbox`](../../../Functions/Actions/Update-PodeWebTextbox):

```powershell
$table | Add-PodeWebTableButton -Name 'StopSelected' -DisplayName 'Stop Selected' -Icon 'Stop-Circle' -WithText -ScriptBlock {
$selected = $WebEvent.Data['Selection'] -split ','
Show-PodeWebModal -Name 'ConfirmStop' -Actions @(
Update-PodeWebTextbox -Name 'SelectedServices' -Value ($selected -join "`n")
)
}

New-PodeWebModal -Name 'ConfirmStop' -DisplayName 'Stop Selected Services' -AsForm -Content @(
New-PodeWebTextbox -Name 'SelectedServices' -DisplayName 'Selected Services' -Multiline -ReadOnly
) -ScriptBlock {
$names = ($WebEvent.Data['SelectedServices'] -split "`n") | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
foreach ($svc in $names) {
Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
}
Show-PodeWebToast -Message "Stopped $($names.Count) service(s)" -Title 'Done'
Hide-PodeWebModal
}
```
60 changes: 59 additions & 1 deletion examples/tables.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,63 @@ Start-PodeServer -Threads 2 {
)
)

Add-PodeWebPage -Name 'Home' -Path '/' -HomePage -Content $card1, $card2 -Title 'Tables'
$multiTable = New-PodeWebTable `
-Name 'MultiSelect' `
-PageSize 4 `
-Paginate `
-Filter `
-SimpleFilter `
-Compact `
-DataColumn 'ID' `
-MultiSelect `
-ScriptBlock {
$allProcesses = @(Get-Process | ForEach-Object {
[ordered]@{
Name = $_.Name
ID = $_.Id
WorkingSet = $_.WorkingSet
CPU = $_.CPU
}
})

$totalCount = $allProcesses.Count
$pageIndex = [int]$WebEvent.Data.PageIndex
$pageSize = [int]$WebEvent.Data.PageSize
$processes = $allProcesses[(($pageIndex - 1) * $pageSize) .. (($pageIndex * $pageSize) - 1)]

$processes | Update-PodeWebTable -Name $ElementData.Name -PageIndex $pageIndex -TotalItemCount $totalCount
} `
-Columns @(
Initialize-PodeWebTableColumn -Key 'Name'
Initialize-PodeWebTableColumn -Key 'ID'
Initialize-PodeWebTableColumn -Key 'WorkingSet' -Name 'Memory'
Initialize-PodeWebTableColumn -Key 'CPU' -Hide
)

$multiTable | Add-PodeWebTableButton -Name 'StopSelected' -DisplayName 'Stop Selected' -Icon 'delete' -WithText -ScriptBlock {
$selected = $WebEvent.Data['Selection'] -split ','
if ($selected.Length -eq 0) {
Show-PodeWebToast -Message 'No processes selected' -Title 'MultiSelect' -Duration 3000
}
else {
Show-PodeWebModal -Name 'StopSelected' -Actions @(
Update-PodeWebTextbox -Name 'SelectedProcesses' -Value ($selected -join "`n")
)
}
}

$stopModal = New-PodeWebModal -Name 'StopSelected' -DisplayName 'Stop Selected Processes' -Size 'Medium' -AsForm -Content @(
New-PodeWebTextbox -Name 'SelectedProcesses' -DisplayName 'Selected Process IDs' -Multiline -ReadOnly
) -ScriptBlock {
$ids = ($WebEvent.Data['SelectedProcesses'] -split "`n") | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
foreach ($id in $ids) {
Stop-Process -Id ([int]$id) -Force -ErrorAction SilentlyContinue -WhatIf
}
Show-PodeWebToast -Message "Stopped $($ids.Count) process(es)" -Title 'Done' -Duration 3000
Hide-PodeWebModal
}

$card3 = New-PodeWebCard -Name 'MultiSelect Processes' -Content $multiTable

Add-PodeWebPage -Name 'Home' -Path '/' -HomePage -Content $card1, $card2, $card3, $stopModal -Title 'Tables'
}
4 changes: 4 additions & 0 deletions src/Public/Elements.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,9 @@ function New-PodeWebTable {
[switch]
$AutoRefresh,

[switch]
$MultiSelect,

[switch]
$AsCard
)
Expand Down Expand Up @@ -2968,6 +2971,7 @@ function New-PodeWebTable {
RefreshInterval = ($RefreshInterval * 1000)
NoRefresh = $NoRefresh.IsPresent
NoAuthentication = $NoAuthentication.IsPresent
MultiSelect = $MultiSelect.IsPresent
Paging = @{
Enabled = $Paginate.IsPresent
Size = $PageSize
Expand Down
72 changes: 69 additions & 3 deletions src/Templates/Public/scripts/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -2881,6 +2881,7 @@ class PodeTable extends PodeRefreshableElement {
this.dataColumn = data.DataColumn;
this.clickableRows = data.Click ?? false;
this.clickIsDynamic = data.ClickIsDynamic ?? false;
this.multiSelect = data.MultiSelect ?? false;

this.exportable = {
enabled: data.Export ?? false
Expand Down Expand Up @@ -2943,7 +2944,8 @@ class PodeTable extends PodeRefreshableElement {
<table
class='table table-striped table-hover ${data.Compact ? 'table-sm' : ''} ${data.Click ? 'pode-table-click' : ''}'
pode-dynamic='${this.dynamic}'
pode-sort='${this.sort.enabled}'>
pode-sort='${this.sort.enabled}'
pode-multiselect='${this.multiSelect}'>
<thead></thead>
<tbody></tbody>
</table>
Expand Down Expand Up @@ -3326,8 +3328,59 @@ class PodeTable extends PodeRefreshableElement {
this.listen(this.element.find('.pode-table-button'), 'click', function(e, target) {
obj.tooltip(false, target);
var url = `${obj.url}/button/${target.attr('name')}`;
sendAjaxReq(url, obj.export(), obj, true, null, null, { contentType: 'text/csv' }, $(e.currentTarget));
var reqData, reqOpts;
if (obj.multiSelect) {
var sel = obj.getSelection().join(',');
reqData = `Selection=${encodeURIComponent(sel)}`;
reqOpts = {};
}
else {
reqData = obj.export();
reqOpts = { contentType: 'text/csv' };
}
sendAjaxReq(url, reqData, obj, true, null, null, reqOpts, $(e.currentTarget));
});

// multiselect
if (this.multiSelect) {
// select-all header checkbox
this.listen(this.element.find('table thead th.pode-table-select-col input'), 'click', function(e, target) {
e.stopPropagation();
obj.element.find('table tbody td.pode-table-select-col input').prop('checked', target.prop('checked'));
}, true);

// row checkbox
this.listen(this.element.find('table tbody td.pode-table-select-col input'), 'click', function(e, target) {
e.stopPropagation();
obj.updateSelectAllState();
}, true);

// clicking the select cell (outside the checkbox input) toggles the checkbox
this.listen(this.element.find('table tbody td.pode-table-select-col'), 'click', function(e, target) {
if ($(e.target).is('input')) {
return;
}
var input = target.find('input.pode-row-select');
input.prop('checked', !input.prop('checked'));
obj.updateSelectAllState();
});
}
}

getSelection() {
var selected = [];
this.element.find('table tbody td.pode-table-select-col input:checked').each(function() {
selected.push($(this).val());
});
return selected;
}

updateSelectAllState() {
var total = this.element.find('table tbody td.pode-table-select-col input').length;
var checked = this.element.find('table tbody td.pode-table-select-col input:checked').length;
var allCheck = this.element.find('table thead th.pode-table-select-col input');
allCheck.prop('checked', total > 0 && checked === total);
allCheck.prop('indeterminate', checked > 0 && checked < total);
}

export() {
Expand All @@ -3337,9 +3390,10 @@ class PodeTable extends PodeRefreshableElement {
}

var csv = [];
var obj = this;
rows.each((i, row) => {
var data = [];
var cols = $(row).find('td, th');
var cols = $(row).find('td:not(.pode-table-select-col), th:not(.pode-table-select-col)');

cols.each((i, col) => {
data.push(col.innerText);
Expand Down Expand Up @@ -3459,6 +3513,9 @@ class PodeTable extends PodeRefreshableElement {

if (head.find('th').length == 0 && columnKeys.length > 0) {
value = '<tr>';
if (this.multiSelect) {
value += `<th scope='col' class='pode-table-select-col' style='width:2rem;'><input type='checkbox' class='form-check-input pode-table-select-all-check' title='Select All'></th>`;
}

columnKeys.forEach((key) => {
value += buildTableHeader(columns[key], direction);
Expand All @@ -3484,6 +3541,10 @@ class PodeTable extends PodeRefreshableElement {

// table headers
value = '<tr>';
if (this.multiSelect) {
value += `<th scope='col' class='pode-table-select-col' style='width:2rem;'><input type='checkbox' class='form-check-input pode-table-select-all-check' title='Select All'></th>`;
}

var oldHeader = null;
var header = null;

Expand Down Expand Up @@ -3517,6 +3578,11 @@ class PodeTable extends PodeRefreshableElement {
value = `<tr ${item[this.dataColumn] != null ? `pode-data-value="${item[this.dataColumn]}"` : ''}>`;
elements = [];

if (this.multiSelect) {
var rowVal = item[this.dataColumn] != null ? item[this.dataColumn] : index;
value += `<td class='pode-table-select-col' style='width:2rem;'><input type='checkbox' class='form-check-input pode-row-select' value='${rowVal}'></td>`;
}

keys.forEach((key) => {
header = head.find(`th[name='${key}']`);
if (header.length > 0) {
Expand Down