Blazor set value inside other component - c#

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.

Related

Passing reference of component to its ChildContent components

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>

How to load a non-routable component in Blazer

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;
}

How to render a component passed as an attribute

Assume Row is a component like so:
<div class="row">
<div class="col-6">
#Column1
</div>
<div class="col-6">
#Column2
</div>
</div>
#code
{
[Parameter]
public ComponentBase Column1 { get; set; }
[Parameter]
public ComponentBase Column2 { get; set; }
}
Then assume that we use this like so:
<Row Column1="foo" Column2="foo"></Row>
#code {
private readonly SomeComponent foo = new();
private readonly SomeComponent bar = new();
}
I'd expect a row containing two columns containing the rendered components for foo and bar, but instead I get two columns containing "Example.Shared.Components.SomeComponent"
How then do you render sub-components passed into other components? Is there a better way to do this (i.e. something like ng-content in Angular?
As a side note, I'm looking for solutions that allow this programatically (as above), and using markup (as below):
<Row>
<Column>
<Foo></Foo>
</Column>
<Column>
<Bar></Bar>
</Column>
</Row>
How about:
<div class="row">
<div class="col-6">
#Column1
</div>
<div class="col-6">
#Column2
</div>
</div>
#code
{
[Parameter]
public RenderFragment Column1 { get; set; }
[Parameter]
public RenderFragment Column2 { get; set; }
}
And then:
<Row>
<Column1>
<SomeComponent value="#somevalue"/>
</Column1>
<Column2>
<SomeOtherComponent/>
</Column2>
</Row>
As already stated, the Renderer instantiates all components and attaches them to the RenderTree.
Also note that all components don't necessarily inherit from ComponentBase, but they do all implement IComponent.
Blazor is mostly declarative. So you should:
<Row>
<Column1>
<Foo></Foo>
</Column1>
<Column2>
<Bar></Bar>
</Column2>
</Row>
You don't need to do new Foo() / new Bar()
Of course, you can take the very circuitous route of programmatically populating the RenderFragment. But it is generally not worth it. Instead, use Generic components and Polymorphism.
I'd recommend not thinking of filling objects with data and passing the objects around. I'd think of Components as the expression of existing data into the html space. Note how for each layer of data, I have nested Razor components. So you pass DATA into the Table component, not RenderFragments or any other kind of object. The following is simplified for clarity.
class TableData {
public List<RowData> Rows;
}
class RowData {
public List<ColumnData> Columns;
}
class ColumnData {
public string DisplayString;
}
TableComponent.razor
#foreach (var row in TableData.Rows){
<DataRowComponent Data=row />
}
#code {
[Parameter]
public TableData TableData{get; set;}
}
DataRowComponent.razor
#foreach (var column in Data.Columns ){
<DataColumnComponent Data=column />
}
#code {
[Parameter]
public RowData Data {get; set;}
}
DataColumnComponent.razor
<div>
#Data.DisplayString
</div>
#code {
[Parameter]
public ColumnData Data {get; set;}
}
What you want is templated components
You still can't use SomeComponent foo = new();.
Instantiation is left to the renderer. But consider that a good thing, it lets you slip in an (item) context.
You'll also want to look at Cascading values, to expose the Row to the Columns.
And finally, there are plenty of commercial and Open versions of a Blazor DataGrid out there. Don't reinvent the whole wheel.

Add a placeholder to InputNumber in blazor

I have a simple question, I would like to add a placeholder to InputNumber component. I tried this code but It didn't work.
//Code behind
public int? Hour { get; set; }
//razor page
<EditForm Model="FilteredEmployees">
<InputNumber #bind-Value="Hour" min="0" class="form-control" max="10" placeholder="Hour"/>
</EditForm>
Thanks for help.
Could be several reasons depending on how you have your code setup. Please try to add more code for a repeatable example, or show the specific error you are getting.
If this input box is displaying 0 instead of "Hour", it's most likely because you are using and int backing field instead of an int? backing field. I just double checked it and having a backing property of
public int? Hour { get; set; }
shows the placeholder text correctly when the textbox content is null.
If you are getting errors (eg null reference errors), it's most likely you are forgetting the EditForm. Please see https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-5.0
As a minimum example:
This works as expected for me, with or without the validation elements
#page "/"
<EditForm Model="#this">
#*<DataAnnotationsValidator />
<ValidationSummary />*#
<InputNumber #bind-Value="Hour" min="0" class="form-control" max="10" placeholder="Hour" />
</EditForm>
#code {
public int? Hour { get; set; }
}

How to disable the component hierarchy in Blazor?

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.

Categories

Resources