I would like to achieve a similar effect like in WPF application, where you have a XAML hierarchy and disabling one of the ancestors makes all of its children to be disabled as well.
I could not find any similar approach in Blazor components.
I tried to look into Cascading parameters and values but I am not sure if I am on the right track.
Lets say I have a component hierarchy like this below:
<PanelContainer Title="Upload log file">
<ParagraphRow ParagraphType="ParagraphType.Paragraph1" Label="Some test row">
<EditBox Text="Some test text" />
</ParagraphRow>
<ParagraphRow ParagraphType="ParagraphType.Paragraph2" Label="Some fancy test row disabled" IsEnabled="false">
<EditBox Text="Some fancy test text" />
</ParagraphRow>
<ParagraphRow ParagraphType="ParagraphType.Paragraph3" Label="Some other test row">
<EditBox Text="Some disabled test text" IsEnabled="false" />
</ParagraphRow>
</PanelContainer>
All the components here are inherited from a base component class where the IsEnabled Property is declared as a public Property.
Each component should behave different according to their IsEnabled value.
For example:
The EditBox should disable the inner input HTML tag, attach the CSS class to the wrapper tag etc.
If I just disable the EditBox itself it works like a charm, nothing fancy about it (third ParagraphRow).
I would like to get the same result with the second ParagraphRow, where the row itself has been disabled. Here I would like to make some disabling logic to the row component (CSS class for the label, validation logic changes and so on), but also I would like its children (the EditBox in this case) also be "notified" somehow about being disabled, so it can update by itself to the disabled state.
I would prefer a solution where I don't have to throw bindings and cascading value tags all over the place, so it would "just work" out of the box.
Is it even possible in the Blazor architecture?
You are looking for CascadingValues and Parameters:
In action:
Check it out at Blazor REPL.
Simplifiying:
The base component:
#code{
[CascadingParameter]
public bool IsEnabled { get; set; } = true;
public string ImEnabled => IsEnabled?"Enabled":"Disabled";
}
Using components:
<PanelContainer IsEnabled="isEnabled">
<ParagraphRow >
<EditBox />
</ParagraphRow>
</PanelContainer>
<button #onclick="()=>{isEnabled = !isEnabled;}" >Toggle</button>
#code {
protected bool isEnabled = true;
}
PanelContainer
<h1>PanelContainer</h1>
<div style="padding-left:10px;">
<CascadingValue Value="IsEnabled">
#ChildContent
</CascadingValue>
</div>
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool IsEnabled {get; set; }
}
ParagraphRow
#inherits IsEnabledComp
Rapragraph: #ImEnabled
<div style="padding-left:10px;">
<CascadingValue Value="IsEnabled">
#ChildContent
</CascadingValue>
</div>
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
EditBox
#inherits IsEnabledComp
EditBox: #ImEnabled
Be free to change what you need to match to your own requirements.
Related
To add some context, I'm trying to create a Dropdown select Blazor component. I've managed to create a concept of this entirely with CSS, #onclick, and #onfocusout.
I'm trying to pass a reference of the DropDown component to its children, DropDownItem. The only way I know how to achieve this, is by using the #ref and passing it as a parameter to the DropDownItem component.
<DropDown #ref="DropDownReference">
<DropDownItem ParentDropDown=#DropDownReference>Hello</DropDownItem>
<DropDownItem ParentDropDown=#DropDownReference>World</DropDownItem>
</DropDown>
There has to be a cleaner approach here that does not require manually passing the reference down to each child instance. I suppose I could use CascadingValue but that will still require me to store the DropDown reference.
I'm trying to notify DropDown parent when a click event occurs in DropDownItem. This will signal the parent to changes it selected value - as it would traditionally work in a select.
Here is an example of how you could do it using CascadingValue. The DropDownItem component will accept a [CascadingParameter] of type DropDown. There is nothing wrong in doing that, this is how it's done in most (if not all) component libraries.
DropDown.razor
<CascadingValue Value="this" IsFixed="true">
#* Dropdown code *#
<div class="dropdown">
#ChildContent
</div>
</CascadingValue>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
private string selectedItem;
public void SelectItem(string item)
{
selectedItem = item;
StateHasChanged();
}
}
DropDownItem.razor
#* Dropdown item code *#
<div class="dropdown-item" #onclick="OnItemClick">...</div>
#code {
[CascadingParameter] public DropDown ParentDropDown { get; set; }
[Parameter] public string Name { get; set; }
private void OnItemClick()
{
ParentDropDown.SelectItem(Name);
}
}
Usage:
<DropDown>
<DropDownItem Name="Hello">Hello</DropDownItem>
<DropDownItem Name="World">World</DropDownItem>
</DropDown>
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.
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!
My EditForm with a DataAnnotationsValidator contains a custom component that can modify its own state. The trivial code below provides a repro.
When the button in MyComponent is clicked, validation fires on the form. This occurs even when none of the form fields have been modified, which is unexpected and undesirable.
Is this expected behaviour? If so, how can I avoid the validation being triggered by a change to the child component?
This is occurring in a Blazor Server project running .NET 6.0.3.
Index.razor
#page "/"
#using System.ComponentModel.DataAnnotations
<EditForm Model="model">
<DataAnnotationsValidator />
<ValidationSummary />
<label>
Name:
<InputText #bind-Value="model.Name" />
</label>
<MyComponent />
</EditForm>
#code {
MyModel model = new();
class MyModel
{
[Required]
public string? Name { get; set; }
}
}
MyComponent.razor
<p>
MyComponent: #(Switch ? "ON" : "OFF")
<button #onclick="() => Switch = !Switch">Toggle</button>
</p>
#code {
bool Switch = false;
}
In your MyComponent, set the type to button:
<button type="button" #onclick="() => Switch = !Switch">Toggle</button>
For most (you can interpret this as meaning pretty much "all") browsers the default type of button is submit. In Blazor, validation is triggered when a form is submitted, in other words when a button in the EditForm with a type of submit is clicked. This is why you need to set the type to button.
I recommend reading the following article about the type attribute on buttons for some more background.
I work on a project that uses Blazer for the front-end and Cosmos DB in the back-end. I am looping through a list of items and have a button/ anchor tag that OnClick receives a parameter. Currently, I am using the query string parameters to redirect the user from one page to another page. However, I do not want to use query string. I want to make the component non-routable and load it on a button click. Is there a way to pass the parameter on button click and load a non-routable component?
Thank you very much for your explanation, I am using .net 5. Here I am going to write a sample code, so you could help.
My project uses Azure Cosmos DB and Blazor Server.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
///////////////////////////
MoviesListComponent.razor
////////////////////////
#page "/movies"
foreach(movie in ListOfMovies){
<div> movie.ReleaseeDate</div>
<div> movie.Country</div>
<div> movie.Title</div>
<button type="button" #onClick="()=>LoadAnotherComponent(movie.title)"> on button click redirect to(or load) a non-routable compnent and send movie.title as parameter for example MovieTitleAnalysis component</button>
}
#code{
public List<Movie> ListOfMovies {get;set;}
}
on button click inside MoviesListComponent load
MovieTitleAnalysis.razor and pass the parameter. Dispose MovieListComponent
///////////////////////
MovieTitleAnalysis.razor is a non routable component
//////////////////
<div>welcome to movie Title analysis</div>
#code{
[paramter]
public string movieTitle {get;set;}
void PreformSomeOpration(){}
}
As this is conceptual - you have provided no code context - this should provide a good starting point and demo the basic concept. Comment on how well/poorly it fits!
I've pointed to the "routed" components but it will work with any component.
DynamicPage.razor
#if (ComponentType != null)
{
<DynamicComponent Parameters=this.Parameters Type=this.ComponentType />
}
else
{
#this.ChildContent
}
#code {
[Parameter] public Type? ComponentType { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public IDictionary<string, object>? Parameters { get; set; }
}
And Index.razor
#page "/"
#*Example Menu*#
<div class="p-2 m-2">
<button class="btn btn-primary" #onclick="() => this.ChangePage(null)">Change to Index</button>
<button class="btn btn-secondary" #onclick="() => this.ChangePage(typeof(Counter))">Change to Counter</button>
<button class="btn btn-info" #onclick="() => this.ChangePage(typeof(FetchData))">Change to FetchData</button>
</div>
<DynamicPage ComponentType=this.page>
<PageTitle>Index</PageTitle>
<div>Hello World from Blazor</div>
</DynamicPage>
#code {
private Type? page = null;
private void ChangePage(Type? newPage)
=> this.page = newPage;
}
I'm building my page and I was wondering if I can make my life easier and put some simple custom Input boxes inside method and them pass reference to my values to them
<div class="col-12 row">
<label class="col-2">#Caption</label>
<InputNumber class="form-control col-3" #bind-Value="#Value"/>
</div>
<br />
#code {
[Parameter]
public string Caption { get; set; }
[Parameter]
public int Value { get; set; }
}
And then use it like
<CustomInputNumber Caption="Price" Value="#Product.Price" />
It is possible to set value like that? Or pass object as reference? Thanks for help!
The way I would go about this is inheriting from inputbase and basically building your own input. Chrissainty has an excellent blog post about, which I think is much clearer then me citing half of what he already explains in that post.
https://chrissainty.com/building-custom-input-components-for-blazor-using-inputbase/
If however you really want to wrap the already existing inputcomponent, you could do it like this:
The component
<div class="col-12 row">
<label class="col-2">#Caption</label>
<InputNumber class="form-control col-3" Value="Value" TValue="int" ValueChanged="HandleValueChanged" ValueExpression="(()=>Value)" />
</div>
<br />
#code{
[Parameter]
public string Caption { get; set; }
[Parameter]
public int Value { get; set; }
[Parameter]
public EventCallback<int> ValueChanged { get; set; }
public void HandleValueChanged(int newValue)
{
ValueChanged.InvokeAsync(newValue);
}
}
Usage like:
<ExampleComponent #bind-Value="ExampleValue"/>
Here you basically override the existing events that exist on a default inputfield. When the default component notices a change, you call your own valuechanged event to pass the event to it's parent.
Though again, I think the first solution is much cleaner.