-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Blazor InputLargeTextArea
#34856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blazor InputLargeTextArea
#34856
Changes from all commits
48649a9
77ae1dc
a18f82b
cb9c856
29aaa05
fe9d5ef
af4f4b7
9eef2b0
75c4071
fdfffda
4893067
44f71a5
87903e5
c2af324
fff1afe
3d99404
2503d28
a3b90f1
6c690cd
588590d
1a8bc1d
cb7b3da
3756c06
9654117
39eba0e
b2f36fd
b380039
0361961
80d884e
e3f8efe
03f071b
dc519e9
ff7d5d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { DotNet } from '@microsoft/dotnet-js-interop'; | ||
|
|
||
| export const InputLargeTextArea = { | ||
| init, | ||
| getText, | ||
| setText, | ||
| enableTextArea, | ||
| }; | ||
|
|
||
| function init(callbackWrapper: any, elem: HTMLTextAreaElement): void { | ||
| elem.addEventListener('change', function(): void { | ||
| callbackWrapper.invokeMethodAsync('NotifyChange', elem.value.length); | ||
| }); | ||
| } | ||
|
|
||
| function getText(elem: HTMLTextAreaElement): Uint8Array { | ||
| const textValue = elem.value; | ||
| const utf8Encoder = new TextEncoder(); | ||
| const encodedTextValue = utf8Encoder.encode(textValue); | ||
| return encodedTextValue; | ||
| } | ||
|
|
||
| async function setText(elem: HTMLTextAreaElement, streamRef: DotNet.IDotNetStreamReference): Promise<void> { | ||
| const bytes = await streamRef.arrayBuffer(); | ||
| const utf8Decoder = new TextDecoder(); | ||
| const newTextValue = utf8Decoder.decode(bytes); | ||
| elem.value = newTextValue; | ||
| } | ||
|
|
||
| function enableTextArea(elem: HTMLTextAreaElement, disabled: boolean): void { | ||
| elem.disabled = disabled; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.IO; | ||
| using System.Text; | ||
| using Microsoft.AspNetCore.Components.Rendering; | ||
| using Microsoft.JSInterop; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Forms | ||
| { | ||
| /// <summary> | ||
| /// A multiline input component for editing large <see cref="string"/> values, supports async | ||
| /// content access without binding and without validations. | ||
| /// </summary> | ||
| public class InputLargeTextArea : ComponentBase, IDisposable | ||
| { | ||
| private const string JsFunctionsPrefix = "Blazor._internal.InputLargeTextArea."; | ||
| private ElementReference _inputLargeTextAreaElement; | ||
| private IDisposable? _dotNetReference; | ||
|
|
||
| [Inject] | ||
| private IJSRuntime JSRuntime { get; set; } = default!; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the event callback that will be invoked when the textarea content changes. | ||
| /// </summary> | ||
| [Parameter] | ||
| public EventCallback<InputLargeTextAreaChangeEventArgs> OnChange { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a collection of additional attributes that will be applied to the input element. | ||
| /// </summary> | ||
| [Parameter(CaptureUnmatchedValues = true)] | ||
| public IDictionary<string, object>? AdditionalAttributes { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the associated <see cref="ElementReference"/>. | ||
| /// <para> | ||
| /// May be <see langword="null"/> if accessed before the component is rendered. | ||
| /// </para> | ||
| /// </summary> | ||
| [DisallowNull] | ||
| public ElementReference? Element | ||
| { | ||
| get => _inputLargeTextAreaElement; | ||
| protected set => _inputLargeTextAreaElement = value!.Value; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override async Task OnAfterRenderAsync(bool firstRender) | ||
| { | ||
| if (firstRender) | ||
| { | ||
| _dotNetReference = DotNetObjectReference.Create(this); | ||
| await JSRuntime.InvokeVoidAsync(JsFunctionsPrefix + "init", _dotNetReference, _inputLargeTextAreaElement); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void BuildRenderTree(RenderTreeBuilder builder) | ||
| { | ||
| builder.OpenElement(0, "textarea"); | ||
| builder.AddMultipleAttributes(1, AdditionalAttributes); | ||
| builder.AddElementReferenceCapture(2, elementReference => _inputLargeTextAreaElement = elementReference); | ||
| builder.CloseElement(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Invoked from the client when the textarea's onchange event occurs. | ||
| /// </summary> | ||
| /// <param name="length">The updated length of the textarea.</param> | ||
| [JSInvokable] | ||
| public Task NotifyChange(int length) | ||
| => OnChange.InvokeAsync(new InputLargeTextAreaChangeEventArgs(this, length)); | ||
|
|
||
| /// <summary> | ||
| /// Retrieves the textarea value asyncronously. | ||
| /// </summary> | ||
| /// <param name="maxLength">The maximum length of content to fetch from the textarea.</param> | ||
| /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> used to relay cancellation of the request.</param> | ||
| /// <returns>A <see cref="System.IO.TextReader"/> which facilitates reading of the textarea value.</returns> | ||
| public virtual async ValueTask<StreamReader> GetTextAsync(int maxLength = 32_000, CancellationToken cancellationToken = default) | ||
| { | ||
| try | ||
| { | ||
| var streamRef = await JSRuntime.InvokeAsync<IJSStreamReference>(JsFunctionsPrefix + "getText", cancellationToken, _inputLargeTextAreaElement); | ||
| var stream = await streamRef.OpenReadStreamAsync(maxLength, cancellationToken); | ||
| var streamReader = new StreamReader(stream); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine we wouldn't want to expose the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's necessary, but I don't feel strongly, so happy to add it in if you'd prefer. |
||
| return streamReader; | ||
| } | ||
| catch (JSException jsException) | ||
| { | ||
| // Special casing support for empty textareas. Due to security considerations | ||
| // 0 length streams/textareas aren't permitted from JS->.NET Streaming Interop. | ||
| if (jsException.InnerException is ArgumentOutOfRangeException outOfRangeException && | ||
| outOfRangeException.ActualValue is not null && | ||
| outOfRangeException.ActualValue is int actualLength && | ||
| actualLength == 0) | ||
| { | ||
| return StreamReader.Null; | ||
| } | ||
|
|
||
| throw; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sets the textarea value asyncronously. | ||
| /// </summary> | ||
| /// <param name="streamWriter">A <see cref="System.IO.StreamWriter"/> used to set the value of the textarea.</param> | ||
| /// <param name="leaveTextAreaEnabled"><see langword="false" /> to disable the textarea while setting new content from the stream, otherwise <see langword="true" /> to allow it to be editable. Defaults to <see langword="false" />.</param> | ||
| /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> used to relay cancellation of the request.</param> | ||
| public virtual async ValueTask SetTextAsync(StreamWriter streamWriter, bool leaveTextAreaEnabled = false, CancellationToken cancellationToken = default) | ||
| { | ||
| if (streamWriter.Encoding.CodePage != Encoding.UTF8.CodePage) | ||
| { | ||
| throw new FormatException($"The encoding '{streamWriter.Encoding}' is not supported. SetTextAsync only allows UTF-8 encoded data."); | ||
| } | ||
|
|
||
| // Ensure we're reading from the beginning of the stream, | ||
| // the StreamWriter.BaseStream.Position will be at the end by default | ||
| var stream = streamWriter.BaseStream; | ||
| if (stream.Position != 0) | ||
| { | ||
| stream.Position = 0; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| if (!leaveTextAreaEnabled) | ||
| { | ||
| await JSRuntime.InvokeVoidAsync(JsFunctionsPrefix + "enableTextArea", cancellationToken, _inputLargeTextAreaElement, /* disabled: */ true); | ||
| } | ||
|
|
||
| using var streamRef = new DotNetStreamReference(stream); | ||
| await JSRuntime.InvokeVoidAsync(JsFunctionsPrefix + "setText", cancellationToken, _inputLargeTextAreaElement, streamRef); | ||
| } | ||
| finally | ||
| { | ||
| if (!leaveTextAreaEnabled) | ||
| { | ||
| await JSRuntime.InvokeVoidAsync(JsFunctionsPrefix + "enableTextArea", cancellationToken, _inputLargeTextAreaElement, /* disabled: */ false); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void IDisposable.Dispose() | ||
| { | ||
| _dotNetReference?.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,35 @@ | ||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||
|
|
||||||
| using System; | ||||||
| using System.Collections.Generic; | ||||||
|
|
||||||
| namespace Microsoft.AspNetCore.Components.Forms | ||||||
| { | ||||||
| /// <summary> | ||||||
| /// Supplies information about an <see cref="Microsoft.AspNetCore.Components.Forms.InputLargeTextArea.OnChange"/> event being raised. | ||||||
| /// </summary> | ||||||
| public sealed class InputLargeTextAreaChangeEventArgs : EventArgs | ||||||
| { | ||||||
| /// <summary> | ||||||
| /// Constructs a new <see cref="InputLargeTextAreaChangeEventArgs"/> instance. | ||||||
| /// </summary> | ||||||
| /// <param name="sender">The textarea element for which the event was raised.</param> | ||||||
| /// <param name="length">The length of the textarea value.</param> | ||||||
| public InputLargeTextAreaChangeEventArgs(InputLargeTextArea sender, int length) | ||||||
| { | ||||||
| Sender = sender; | ||||||
| Length = length; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// The textarea element for which the event was raised. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| /// </summary> | ||||||
| public InputLargeTextArea Sender { get; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets the length of the textarea value. | ||||||
| /// </summary> | ||||||
| public int Length { get; } | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the type be |
||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.