bauer-martin.com

bauer-martin.com

Privater Blog über Programmierung, Cloud, Technik und was sonst noch so anfällt

26 Jun 2020

Blazor: Autoresize Textarea

Für ein Projekt benötige ich eine Textarea welche sich in der Größe (Höhe) automatisch an der Länge des Inhaltes anpasst. Leider gibt es hier in Blazor noch nichts fertiges und die üblichen JavaScript-Aufrufe funktionieren leider in meinem Fall auch nicht so wie gewünscht. Daher musste ich mir ein eigenes Input erstellen.

Als Basis dient hierzu die mitgeliefert InputTextArea-Komponenten, dessen Quellcode man bei GitHub hier anschauen kann.

Ergänzt wird hier das Property _rows welches die Anzahl der Zeilen für das TextArea beinhaltet und in der BuildRenderTree-Methode wird der Wert als Attribut mit hinzugefügt

builder.AddAttribute(5, "rows", _rows);

Danach überschreiben wir die OnParametersSetAsync-Methode. Dies wird benötigt um nach einer Änderung des Inhaltes die Größe neu zu berechnen. Diese Methode wird auch bei der Initialisierung der Kombonente aufgerufen, so dass nach dem Laden der Inhalts über das Binding sofort die Größe angepasst wird.

protected override Task OnParametersSetAsync()
{
    CalculateSize(CurrentValue);
    return base.OnParametersSetAsync();
}

Die eigentliche Methode zum berechnen der Größe ist recht einfach gehalten. Sie zählt einfach die Zeilen des Inhalte und berechnet daraus die Anzahl an Zeilen. Dazu stellt sie sicher, dass immer mind. 2 Zeilen angezeigt werden. Man kann hier auch noch eine maximale Anzahl an Zeilen mit einrechnen lassen, so dass das Feld nicht unendlich hoch wird.

protected void CalculateSize(string? value)
{
    if (string.IsNullOrWhiteSpace(value))
    {
        _rows = 2;
        return;
    }

    var rows = Math.Max(value.Split('\n').Length, value.Split('\r').Length);
    rows = Math.Max(rows, 2);
    _rows = Math.Min(rows, MaxRows);
}

Und das war es auch schon. Ganz perfekt ist die Lösung leider nicht. Die größe wird erst geändert sobald der Wert im Hintergrund aktualisiert wird. Das passiert erst beim verlassen des Textfeldes. Leider kann man auch nicht mit bind-Value:event="oninput" arbeiten, da dies nicht vom InputBase<T> unterstützt wird.

Der gesamte Quellcode schaut nun folgendermaßen aus:

public class ResizeableInputTextArea : InputBase<string?>
{
    /// <summary>
    /// Number of rows
    /// </summary>
    private int _rows;

    /// <summary>
    /// Max number of rows
    /// </summary>
    public int MaxRows { set; get; } = 50;

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "textarea");
        builder.AddMultipleAttributes(1, AdditionalAttributes);
        builder.AddAttribute(2, "class", CssClass);
        builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue));
        builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value =>
        {
            CurrentValueAsString = __value;
            //CalculateSize(CurrentValue);
        }, CurrentValueAsString));
        builder.AddAttribute(5, "rows", _rows);
        builder.CloseElement();
    }

    protected override bool TryParseValueFromString(string? value, out string? result, [NotNullWhen(false)] out string? validationErrorMessage)
    {
        result = value;
        validationErrorMessage = null;
        return true;
    }

    protected override Task OnParametersSetAsync()
    {
        CalculateSize(CurrentValue);
        return base.OnParametersSetAsync();
    }

    protected void CalculateSize(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            _rows = 2;
            return;
        }

        var rows = Math.Max(value.Split('\n').Length, value.Split('\r').Length);
        rows = Math.Max(rows, 2);
        _rows = Math.Min(rows, MaxRows);
    }
}