Search code examples
blazorblazor-webassemblyblazor-component

Allow user to define settings in HTML for custom Razor component


Lets say I have a custom razor component, called MyComponent. This component inherits from ComponentBase, lets assume its a version of data grid control. The code examples I found on internet, use this code as example to configure some behavior:

// index.razor
<MyComponent EditSettings=@_editSettings SelectionSettings=@_selectionSettings>
  ...

</MyComponent>

@code {
  EditSettings _editSettings;
  SelectionSettings _selectionSettings;

  // somewhere in some method
  _editSettings = new EditSettings() { ... };
  _selectionSettings = new SelectionSettings() { ... };
}

Technically speaking, EditSettings and SelectionSettings exist to define behavior of the component. I would like to add support for developer to configure behavior for this component inside HTML, and not in c# code behind. Take a look at this hypothetical example that I am interested in:

// index.razor
<MyComponent>
  <EditSettings AllowEdit="..." EditMode="..." OnEditCompleted="..." />
  <SelectionSettings SelectionMode="..." OnSelectionChange="..."  />
  ...

</MyComponent>

As far as I understand, in order to show them inside razor's HTML code, they need to inherit from ComponentBase? Is this correct?

How would I catch inside MyComponent code that user defined them? Their existence is optional, user can choose not to define them and use default behavior.


Solution

  • You can achieve this by cascading the Settings objects to the child components by using the CascadingValue component. Also add an event to the Settings classes so that when the child components change the settings the parent component gets notified. Example:

    SettingsA.cs:

    public class SettingsA
    {
        private bool? _allowThis;
        private bool? _allowThat;
    
        public bool AllowThis
        {
            get => _allowThis ?? false;
            set
            {
                _allowThis = value;
                NotifyStateChanged();
            }
        }
    
        public bool AllowThat
        {
            get => _allowThat ?? false;
            set
            {
                _allowThat = value;
                NotifyStateChanged();
            }
        }
    
        public event Action? OnChange;
    
        private void NotifyStateChanged() => OnChange?.Invoke();
    }
    

    MyComponentSettingsA.razor:

    @code {
        [CascadingParameter]
        private SettingsA? SettingsA { get; set; }
    
        [Parameter]
        public bool AllowThis { get; set; }
    
        [Parameter]
        public bool AllowThat { get; set; }
    
        protected override void OnInitialized()
        {
            if (SettingsA != null)
            {
                SettingsA.AllowThis = AllowThis;
                SettingsA.AllowThat = AllowThat;
            }
        }
    }
    

    MyComponent.razor:

    @implements IDisposable
    
    <CascadingValue Value="_settingsA">
        @if (ChildContent != null)
        {
            @ChildContent
        }
    </CascadingValue>
    
    @if (_settingsA.AllowThis)
    {
        <p>This is allowed</p>
    }
    
    @if (_settingsA.AllowThat)
    {
        <p>That is allowed</p>
    }
    
    @code {
        private SettingsA _settingsA = new SettingsA();
    
        [Parameter]
        public RenderFragment? ChildContent { get; set; }
    
        protected override void OnInitialized()
        {
            _settingsA.OnChange += StateHasChanged;
        }
    
        public void Dispose()
        {
            _settingsA.OnChange -= StateHasChanged;
        }
    }
    

    Usage:

    <MyComponent>
        <MyComponentSettingsA AllowThis="true" AllowThat="true" />
    </MyComponent>
    

    BlazorFiddle