0

I have found a repetitive pattern in Blazor components when needing to disable an HTML element.

Blazor provides functionality with the HTML disabled attribute, to which you can pass some Razor.

With the example of a single button, we can do:

<button type="button" @onclick="DoSomething" disabled="@buttonIsDisabled">Click me</button>

Blazor will render the button including a disabled attribute when buttonIsDisabled is true. This helps when using a CSS class to alter the styling of the button when disabled as well.

However, this is not very safe since a user can edit the HTML document quickly to remove the disabled attribute from the HTML element. The on-click event would remain and allow the DoSomething method to execute anyways.

To avoid things being bypassed, I use a manual approach instead:

if (buttonIsDisabled)
{
    <button disabled type="button" />
}
else
{
    <button @onclick="DoSomething" type="button" />
}

There is an enabled version of the button with an onclick handler, and another without it for safety (disabled).

The problem is that in more complex scenarios, is not a simple button which has to be rendered, but multiple elements with multiple handlers as well. This ends up in a pattern of repetitive/duplicated HTML code.

Is there a better approach or another way to disable elements so that Blazor does not register the event handlers such the onclick event?

For example if Blazor knew that an element should render the disabled attribute should not trigger any Blazor event.

Edit: This question is not intended to find a solution for the case of a disabling/enabling a simple button and controlling it's event handler, but to the pattern I explain above. Imagine having not only disabled buttons, but also inputs, checkboxes, toggles, etc; they could all be also linked to multiple event handlers, each.

A not-supported idea: As Blazor computes the disabled attribute to render it or not, it could also determine to not include the event handlers for the element when it should be disabled in the next rendering.

6
  • w3schools.com is not authoritative, btw.
    – Dai
    Commented Apr 12, 2022 at 0:35
  • "However, this is not very safe since a user can edit the HTML document quickly to remove the disabled attribute from the HTML element." - this is a non-issue (and you should never trust software running on a remote client, even if you wrote it). You should always validate all user-input, including Blazor events: just ignore events if your server-side code says that input/control should be disabled.
    – Dai
    Commented Apr 12, 2022 at 0:36
  • 1
    Just add a check for the bool value in your event handler, and skip your handling logic if the component is supposed to be disabled. Commented Apr 12, 2022 at 0:58
  • Yes, it's easy to control the event handlers and add validations, but events are being fired anyways, could that be avoided so no events are linked to the element? Since elements can be linked to multiple events, you would have to control each of those. The idea would be to save unneeded resources and simplify things, server side it could be SignalR messages for example. Commented Apr 12, 2022 at 1:31
  • 2
    You're worried about a case in which someone will hack the page to enable a control, thereby triggering a Blazor event, and that the 1 line it takes to check that the event SHOULD be triggered would introduce an inefficiency? If you've spent more than 10 seconds thinking about this, then you've lost far more than the total CPU time that this "inefficiency" will cost if you run your site under high load for the next 100 years. You need to think about more important issues-- like a clean UI and well-organized data structures. Commented Apr 12, 2022 at 1:48

1 Answer 1

1

Applying one of the most basic good coding practices - DRY - Don't Repeat Yourself, here's a simple "button" component:

@if (Show)
{
    @if (this.Disabled)
    {
        <button type="button" disabled @attributes=this.UserAttributes>@ChildContent</button>
    }
    else
    {
        <button type="button" @onclick="this.OnClick" @attributes=this.UserAttributes>@ChildContent</button>
    }
}

@code {
    [Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();
    [Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
    [Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; }
    [Parameter] public bool Disabled { get; set; }
    [Parameter] public bool Show { get; set; } = true;
}

Which you can then use like this:

@page "/"
<h3>Test</h3>

<MyButton class="btn btn-dark" Disabled=this.disabled Show=this.show OnClick=this.OnClick>Click me</MyButton>
<MyButton class="btn btn-primary" OnClick=this.DisableMe>Disable me</MyButton>
<MyButton class="btn btn-outline-info" OnClick=this.ShowMe>Show me</MyButton>

@code {
    private bool disabled;
    private bool show = true;

    private void DisableMe(MouseEventArgs e)
        => disabled = !disabled;

    private void ShowMe(MouseEventArgs e)
        => show = !show;

    private void OnClick(MouseEventArgs e)
    {
        // Do something
    }
}

You can use this same pattern for your more complex stuff. Personally, I have a base component (directly implemented as a class) that implements the basics and then derived classes for the more complex stuff

[Polite] Please don't take this the wrong way, but I'm amazed how often DRY goes out the window when writing Razor code!

2
  • Thank you for this answer, wrapping elements in a component that handles the logic is a good idea. It achieves a solution for the simple button. My question is not seeking a solution for the button case, I'm questioning the general pattern with any element you want to disable and with possible multiple events per element. I'll add an Edit to clarify. Commented Apr 12, 2022 at 16:16
  • I'm not sure what you're expecting here. There's is no "generic" method, and frankly I don't see a need. I'll sign off and see if anyone else come up with a suitable answer! Commented Apr 12, 2022 at 21:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.