Unplanned
Last Updated: 05 Apr 2021 07:42 by ADMIN
Graham
Created on: 04 Dec 2020 11:21
Category: UI for Blazor
Type: Feature Request
25
Add a Focus method for all applicable component references

I would like components like the TextBox, NumericTextBox, TextArea, Button to have a method in their reference similar to the FocusAsync() which Microsoft included to the ElementReference in .NET5.

---

ADMIN EDIT

For the time being, you can use JS interop and prepare a suitable selector. Here is a basic example for the button:

@inject IJSRuntime _js

Notes:<br />
- Move this script to a proper place, it is hacked into the component to make this snippet short
- Make sure the ID is unique if you use IDs. THere are other selectors you can use (such as classes, or you can even cascade your selectors to make them more specific)
<br /><br />

<script suppress-error="BL9992">
    function focusElement(selector) {
        var elem = document.querySelector(selector);
        if (elem && elem.focus) {
            setTimeout(function () {
                elem.focus();
            }, 30);
        }
    }
</script>

<TelerikButton OnClick="@FocusBtn">Focus the other button</TelerikButton>

<br /><br />

<TelerikButton Id="@btnId" OnClick="@SpecialBtnAction">I will be focused programmatically</TelerikButton>

@code{
    string btnId = "my-special-btn";

    async Task FocusBtn()
    {
        await _js.InvokeVoidAsync("focusElement", $"#{btnId}");
    }

    async Task SpecialBtnAction()
    {
        Console.WriteLine("special button clicked");
    }
}

---

4 comments
ADMIN
Marin Bratanov
Posted on: 05 Apr 2021 07:42

Hello Chris,

You can see how to capture a variety of events like onfocusin and onclick here: https://docs.telerik.com/blazor-ui/knowledge-base/inputs-handle-keyboard-events

We don't expose all possible events because that would be a massive performance hit. We do components, not plain HTML inputs, and this means that every event we expose needs to be a parameter of type EventCallback, and needs to be hooked to the underlying DOM - even if the parent component does not consume it. Moreover, having many parameters on a component also comes with a performance penalty, even if they are not used. This is a hit for all our users, and needing specific events like that is a rare case that can easily be handled with a wrapping element. This is why we can't expose them all, and we must limit the events we provide to a low number.

Regards,
Marin Bratanov
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

Chris
Posted on: 01 Apr 2021 20:37

If inheriting components is not recommended, then all elements should expose the possible event triggers.

There's no real rationale for why only OnChange, OnBlur, and ValueChanged are exposed for Textbox from the native input element.

There's clear reasons why developers might choose to use onfocus and onclick.

Currently, it's not easy to implement those on the underlying input elements.

My specific case is that I'd like to switch TabStrip Tabs depending on which element is currently focused.

 

ADMIN
Marin Bratanov
Posted on: 25 Jan 2021 10:02

Hi,

I must make two notes about this solution:

  • we do not support inheriting our components
  • overriding the rendering to a manual render tree implementation will remove functionality from the component and can also prevent you from getting future updates, and can even cause other issues

If you need to focus the button right now, I recommend using JS Interop for that. Here's an example:

@inject IJSRuntime _js

Notes:<br />
- Move this script to a proper place, it is hacked into the component to make this snippet short
- Make sure the ID is unique if you use IDs. THere are other selectors you can use (such as classes, or you can even cascade your selectors to make them more specific)
<br /><br />

<script suppress-error="BL9992">
    function focusElement(selector) {
        var elem = document.querySelector(selector);
        if (elem && elem.focus) {
            setTimeout(function () {
                elem.focus();
            }, 30);
        }
    }
</script>

<TelerikButton OnClick="@FocusBtn">Focus the other button</TelerikButton>

<br /><br />

<TelerikButton Id="@btnId" OnClick="@SpecialBtnAction">I will be focused programmatically</TelerikButton>

@code{
    string btnId = "my-special-btn";

    async Task FocusBtn()
    {
        await _js.InvokeVoidAsync("focusElement", $"#{btnId}");
    }

    async Task SpecialBtnAction()
    {
        Console.WriteLine("special button clicked");
    }
}

 

Regards,
Marin Bratanov
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

jura
Posted on: 25 Jan 2021 08:33

For now, here is my take:

 

using HaSaM.Systems;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.CompilerServices;
using Microsoft.AspNetCore.Components.Rendering;
using System;
using System.Reflection;
using System.Threading.Tasks;
using Telerik.Blazor.Components;

namespace HaSaM.Web.Components
{
    public class TelerikButton :
        Telerik.Blazor.Components.TelerikButton,
        IFocusable
    {
        public TelerikButton()
        {
            Type type = typeof(Telerik.Blazor.Components.TelerikButton);
            classToRender = type.CreatePropertyGetMethodDelegate<string>(nameof(ClassToRender), BindingFlags.NonPublic, this);
            currentTabIndex = type.CreatePropertyGetMethodDelegate<int>(nameof(CurrentTabIndex), BindingFlags.NonPublic, this);
            typeToRender = type.CreatePropertyGetMethodDelegate<string>(nameof(TypeToRender), BindingFlags.NonPublic, this);
        }

        public async ValueTask FocusAsync() => await element.FocusAsync();

        protected override void BuildRenderTree(RenderTreeBuilder __builder)
        {
            __builder.OpenElement(0, "button");
            __builder.AddAttribute(1, "onclick", EventCallback.Factory.Create(this, OnClick));
            __builder.AddAttribute(2, "class", ClassToRender);
            __builder.AddAttribute(3, "id", base.Id);
            __builder.AddAttribute(4, "tabindex", CurrentTabIndex);
            __builder.AddAttribute(5, "title", base.Title);
            __builder.AddAttribute(6, "aria-disabled", ToAttributeValue(!base.Enabled));
            __builder.AddAttribute(7, "disabled", !base.Enabled);
            __builder.AddAttribute(8, "type", TypeToRender);
            __builder.OpenComponent<TelerikIcon>(9);
            __builder.AddAttribute(10, "Icon", RuntimeHelpers.TypeCheck(base.Icon));
            __builder.AddAttribute(11, "ImageUrl", RuntimeHelpers.TypeCheck(base.ImageUrl));
            __builder.AddAttribute(12, "IconClass", RuntimeHelpers.TypeCheck(base.IconClass));
            __builder.AddAttribute(13, "SpriteClass", RuntimeHelpers.TypeCheck(base.SpriteClass));
            __builder.CloseComponent();
            __builder.AddMarkupContent(14, "\r\n\r\n    ");
            __builder.AddContent(15, base.ChildContent);
            __builder.AddElementReferenceCapture(16, element => this.element = element);
            __builder.CloseElement();
        }

        private string ClassToRender => classToRender();
        private int CurrentTabIndex => currentTabIndex();
        private string TypeToRender => typeToRender();

        private static string ToAttributeValue(bool value) => value.ToString().ToLower();

        private ElementReference element;
        private readonly Func<string> classToRender;
        private readonly Func<int> currentTabIndex;
        private readonly Func<string> typeToRender;
    }
}

 

public interface IFocusable
    {
        public ValueTask FocusAsync();
    }

 

 public static Func<T> CreatePropertyGetMethodDelegate<T>(this Type type, string name, BindingFlags bindingAttr = BindingFlags.Public, object instance = null)
        {
            Validate(type);
            ValidateName(name);
            bindingAttr = AddStaticOrInstanceFlag(bindingAttr, instance);
            return (Func<T>)type.
                GetProperty(name, bindingAttr)?.
                GetGetMethod(nonPublic: bindingAttr.HasFlag(BindingFlags.NonPublic))?.
                CreateDelegate(typeof(Func<T>), instance);
        }

 

Marek Ištvánek