Skip to content
Merged
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
58 changes: 49 additions & 9 deletions docs/Tutorials/Elements/Textbox.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
# Textbox

| Support | |
| ------- |-|
| Events | Yes |
| Support | |
| ------- | --- |
| Events | Yes |

A textbox element is a form input element; you can render a textbox, single and multiline, to your page using [`New-PodeWebTextbox`](../../../Functions/Elements/New-PodeWebTextbox).
A Textbox element is a form input element; you can render a Textbox, single and multiline, to your page using [`New-PodeWebTextbox`](../../../Functions/Elements/New-PodeWebTextbox).

A textbox by default is a normal plain single lined textbox, however you can customise its `-Type` to Email/Password/etc. To change the textbox to be a multiline textbox you can supply the `-Multiline` switch.
A Textbox by default is a normal single lined textbox, however you can customise its `-Type` to Email/Password/etc. To change the textbox to be a multiline textbox you can supply the `-Multiline` switch.

Textboxes also allow you to specify `-AutoComplete` values ([see here](#autocomplete)).
Supported types are:
* Text
* Email
* Password
* Number
* Date
* Time
* File
* DateTime

Textboxes also allow you to specify `-AutoComplete` options, as ([described here](#autocomplete)).

## Single

A default textbox is just a simple single lined textbox. You can change the type to Email/Password/etc using the `-Type` parameter:
A default Textbox is just a simple single lined textbox. You can change the type to Email/Password/etc using the `-Type` parameter:

```powershell
New-PodeWebCard -Content @(
Expand All @@ -34,7 +44,7 @@ Which looks like below:

### AutoComplete

For a single textbox, you can supply autocomplete values via a scriptblock passed to `-AutoComplete`. This scriptblock should return an array of strings, and will be called once when the textbox is initially loaded:
For a single Textbox, you can supply autocomplete options via a scriptblock passed to `-AutoComplete`. This scriptblock should return an array of strings, and will be called once when the textbox is initially loaded:

```powershell
New-PodeWebCard -Content @(
Expand All @@ -52,9 +62,39 @@ Which looks like below:

![textbox_auto](../../../images/textbox_auto.png)

#### Delay

You can delay when the autocomplete options are shown by supplying `-AutoCompleteMinLength`. By default this is `1`, and the value refers to the number of characters typed into the Textbox before the option are displayed.

!!! note
The options will still be loaded on initial page load, but will not render until the specified number of characters.

#### Dynamic

To have the autocomplete scriptblock be invoked on every character, instead of once on page load, you supply `-AutoCompleteType Always`. The default is `Once`, and `Always` will invoke the scriptblock every time.

To help with dynamic filtering, the current value entered into the Textbox is supplied as `$WebEvent.Data.Value` - this is only available when using the `Always` autocomplete type, and **not** in the default `Once` type.

```powershell
New-PodeWebCard -Content @(
New-PodeWebForm -Name 'Example' -ScriptBlock {
$svcName = $WebEvent.Data['Service Name']
} -Content @(
New-PodeWebTextbox -Name 'Service Name' -AutoCompleteType Always -AutoComplete {
return Get-Service |
Where-Object { $_.Name -ilike "*$($WebEvent.Data.Value)*" } |
Select-Object -ExpandProperty Name
}
)
)
```

!!! tip
The `-AutoCompleteMinLength` parameter still work here, and the scriptblock will only be invoked after the specified number of characters.

## Multiline

A mutlilined textbox can be displayed by passing `-Multiline`. You cannot change the type of this textbox, it will always allow freestyle text:
A multi-lined Textbox can be displayed by passing `-Multiline`. You cannot change the type of this Textbox as only `Text` types support multi-line:

```powershell
New-PodeWebCard -Content @(
Expand Down
50 changes: 50 additions & 0 deletions examples/autocomplete.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Import-Module Pode -MaximumVersion 2.99.99 -Force
Import-Module ..\src\Pode.Web.psd1 -Force

Start-PodeServer -Threads 2 {
# add a simple endpoint
Add-PodeEndpoint -Address localhost -Port 8091 -Protocol Http
New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging

# set the use of templates, and set a login page
Initialize-PodeWebTemplates -Title 'Autocomplete' -Theme Dark

# form with load once autocomplete
$onceInstant = New-PodeWebForm -Name 'OnceInstant' -ButtonType Submit -AsCard -ScriptBlock {
$WebEvent.Data.OnceInstantOutput | Show-PodeWebToast
} -Content @(
New-PodeWebTextbox -Name 'OnceInstantInput' -AutoComplete {
return @('One', 'Two', 'Three', 'Four', 'Five')
}
)

# form with load every time autocomplete
$alwaysInstant = New-PodeWebForm -Name 'AlwaysInstant' -ButtonType Submit -AsCard -ScriptBlock {
$WebEvent.Data.AlwaysInstantOutput | Show-PodeWebToast
} -Content @(
New-PodeWebTextbox -Name 'AlwaysInstantInput' -AutoCompleteType Always -AutoComplete {
return @('One', 'Two', 'Three', 'Four', 'Five') | Where-Object { $_ -imatch "^$($WebEvent.Data.Value)" }
}
)

# form with load once autocomplete on 3 char delay
$onceDelay = New-PodeWebForm -Name 'OnceDelay' -ButtonType Submit -AsCard -ScriptBlock {
$WebEvent.Data.OnceDelayOutput | Show-PodeWebToast
} -Content @(
New-PodeWebTextbox -Name 'OnceDelayInput' -AutoCompleteMinLength 3 -AutoComplete {
return @('One', 'Two', 'Three', 'Four', 'Five')
}
)

# form with load every time autocomplete on 3 char delay
$alwaysDelay = New-PodeWebForm -Name 'AlwaysDelay' -ButtonType Submit -AsCard -ScriptBlock {
$WebEvent.Data.AlwaysDelayOutput | Show-PodeWebToast
} -Content @(
New-PodeWebTextbox -Name 'AlwaysDelayInput' -AutoCompleteType Always -AutoCompleteMinLength 3 -AutoComplete {
return @('One', 'Two', 'Three', 'Four', 'Five') | Where-Object { $_ -imatch "^$($WebEvent.Data.Value)" }
}
)

# add forms to page
Add-PodeWebPage -Name 'Home' -Path '/' -Content $onceInstant, $alwaysInstant, $onceDelay, $alwaysDelay -Title 'Testing Autocomplete' -HomePage
}
34 changes: 24 additions & 10 deletions src/Public/Actions.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ function Clear-PodeWebTextbox {
function Show-PodeWebToast {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]
$Message,

Expand All @@ -680,17 +680,31 @@ function Show-PodeWebToast {
$Icon = 'information'
)

if ($Duration -le 0) {
$Duration = 3000
begin {
$items = @()
}

Send-PodeWebAction -Value @{
Operation = 'Show'
ObjectType = 'Toast'
Message = [System.Net.WebUtility]::HtmlEncode($Message)
Title = [System.Net.WebUtility]::HtmlEncode($Title)
Duration = $Duration
Icon = (Protect-PodeWebIconType -Icon $Icon -Element 'Toast')
process {
if (![string]::IsNullOrWhiteSpace($Message)) {
$items += $Message
}
}

end {
if ($Duration -le 0) {
$Duration = 3000
}

foreach ($msg in $items) {
Send-PodeWebAction -Value @{
Operation = 'Show'
ObjectType = 'Toast'
Message = [System.Net.WebUtility]::HtmlEncode($msg)
Title = [System.Net.WebUtility]::HtmlEncode($Title)
Duration = $Duration
Icon = (Protect-PodeWebIconType -Icon $Icon -Element 'Toast')
}
}
}
}

Expand Down
16 changes: 15 additions & 1 deletion src/Public/Elements.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ function New-PodeWebTextbox {
[scriptblock]
$AutoComplete,

[Parameter(ParameterSetName = 'Single')]
[ValidateSet('Once', 'Always')]
[string]
$AutoCompleteType = 'Once',

[Parameter(ParameterSetName = 'Single')]
[ValidateRange(1, [int]::MaxValue)]
[int]
$AutoCompleteMinLength = 1,

[Parameter()]
[string[]]
$EndpointName,
Expand Down Expand Up @@ -143,7 +153,11 @@ function New-PodeWebTextbox {
HelpText = [System.Net.WebUtility]::HtmlEncode($HelpText)
ReadOnly = $ReadOnly.IsPresent
Disabled = $Disabled.IsPresent
IsAutoComplete = ($null -ne $AutoComplete)
AutoComplete = @{
Enabled = ($null -ne $AutoComplete)
Type = $AutoCompleteType.ToLowerInvariant()
MinLength = $AutoCompleteMinLength
}
Value = $items
Prepend = @{
Enabled = (![string]::IsNullOrWhiteSpace($PrependText) -or ![string]::IsNullOrWhiteSpace($PrependIcon))
Expand Down
2 changes: 2 additions & 0 deletions src/Public/Pages.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ function Set-PodeWebLoginPage {
GrantType = $grantType
IsSystem = $true
ConnectionType = (Get-PodeWebConnectionType)
Features = (Get-PodeWebState -Name 'features')
}

# set auth system urls
Expand Down Expand Up @@ -427,6 +428,7 @@ function Add-PodeWebPage {
Users = @($AccessUsers)
}
ConnectionType = (Get-PodeWebConnectionType)
Features = (Get-PodeWebState -Name 'features')
}

# does the page need auth?
Expand Down
5 changes: 5 additions & 0 deletions src/Public/Utilities.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ function Initialize-PodeWebTemplates {
Set-PodeWebState -Name 'custom-js' -Value @()
Set-PodeWebState -Name 'conn-type' -Value $ConnectionType.ToLowerInvariant()

# setup default features for frontend parsing
Set-PodeWebState -Name 'features' -Value @{
ParseDateTime = !(Test-PodeIsPSCore)
}

# themes
Set-PodeWebState -Name 'theme' -Value $Theme.ToLowerInvariant()
Set-PodeWebState -Name 'custom-themes' -Value @{
Expand Down
22 changes: 22 additions & 0 deletions src/Templates/Public/scripts/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var tooltips = function() {
};
tooltips();

var FEATURES = {};
var pageLoaded = false;
var contentLoaded = false;

Expand All @@ -26,6 +27,9 @@ $(() => {
}
pageLoaded = true;

// load features from body attributes
loadFeatures();

// check theme
if (checkAutoTheme()) {
return;
Expand All @@ -47,6 +51,12 @@ $(() => {
setupClientConnection();
});

function loadFeatures() {
FEATURES = {
ParseDateTime: ($('body').attr('pode-parse-datetime') === 'True')
};
}

function loadContent() {
if (contentLoaded) {
return;
Expand Down Expand Up @@ -1209,6 +1219,18 @@ function getTimeString() {
return (new Date()).toLocaleTimeString().split(':').slice(0, 2).join(':');
}

function convertDateTimeString(value) {
if (!value || typeof value !== 'string') {
return value;
}

// find references to "/Date(...)/" and convert to datetime object
return value.replace(/\/Date\((\d+)\)\//g, function(match, timestamp) {
// return in YYYY-MM-DDTHH:mm:ss format - same as .NET's default JSON date format
return new Date(parseInt(timestamp)).toISOString().split('.')[0];
});
}

function actionHref(action) {
if (!action) {
return;
Expand Down
56 changes: 46 additions & 10 deletions src/Templates/Public/scripts/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,14 @@ class PodeElement {
element.off(evt);
}

sanitize(value) {
if (FEATURES.ParseDateTime) {
value = convertDateTimeString(value);
}

return value;
}

spinner(show) {
if (!this.hasSpinner || (!show && this.loading)) {
return;
Expand Down Expand Up @@ -3370,6 +3378,7 @@ class PodeTable extends PodeRefreshableElement {
break;

default:
console.log(data);
this.updateTable(data, sender, opts);
break;
}
Expand Down Expand Up @@ -3413,7 +3422,7 @@ class PodeTable extends PodeRefreshableElement {
elements.push(...(renderResult.elements));
}
else {
html = rowData;
html = this.sanitize(rowData);
}

row.find(`td[pode-column="${key}"]`).html(html);
Expand Down Expand Up @@ -3540,10 +3549,10 @@ class PodeTable extends PodeRefreshableElement {
});
}
else if (item[key] != null) {
value += item[key];
value += this.sanitize(item[key]);
}
else if (!item[key] && header.length > 0) {
value += header.attr('default-value');
value += this.sanitize(header.attr('default-value'));
}

value += `</td>`;
Expand Down Expand Up @@ -3794,7 +3803,11 @@ class PodeTextbox extends PodeFormElement {
constructor(data, sender, opts) {
super(data, sender, opts);
this.multiline = data.Multiline ?? false;
this.autoComplete = data.IsAutoComplete ?? false;
this.autoComplete = {
enabled: data.AutoComplete.Enabled ?? false,
type: data.AutoComplete.Type ?? 'once',
minLength: data.AutoComplete.MinLength ?? 1
}
}

new(data, sender, opts) {
Expand Down Expand Up @@ -3859,12 +3872,35 @@ class PodeTextbox extends PodeFormElement {

var obj = this;

if (this.autoComplete) {
sendAjaxReq(`${this.url}/autocomplete`, null, null, false, null, null, {
customActionCallback: (res) => {
obj.element.autocomplete({ source: res.Values });
}
});
// bind autocomplete handlers
if (this.autoComplete.enabled) {
switch (this.autoComplete.type) {
// load autocomplete options once, and cache on the element
case 'once':
sendAjaxReq(`${this.url}/autocomplete`, null, null, false, null, null, {
customActionCallback: (res) => {
obj.element.autocomplete({
source: convertToArray(res.Values),
minLength: obj.autoComplete.minLength
});
}
});
break;

// load autocomplete options on every char press
case 'always':
this.element.autocomplete({
source: function(request, response) {
sendAjaxReq(`${obj.url}/autocomplete`, `Value=${request.term}`, null, false, null, null, {
customActionCallback: (res) => {
response(convertToArray(res.Values));
}
});
},
minLength: obj.autoComplete.minLength
});
break;
}
}
}

Expand Down
Loading