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>
Related
I am writing a blazor app, where the user has to select a group of options in the form of a radio buttons. This selection needs to be done inside a component. Which also meant, I needed to pass data from a radio group in a child component to a parent component.
The Test
Below are code snippets for both child and parent components. The test involves selecting one of the four seasons (spring, summer, autum, and winter) in the child component. When the user selects any of the four seasons, that selection needs to be updated in the parent component as well.
Attempt
First attempt was to use an Editform.
This is the child component. Pages\Child.razor
<h1>Child</h1>
EditForm Model="model">
<InputRadioGroup #bind-Value="SelectedValue">
#foreach (var revOption in (Season[])Enum
.GetValues(typeof(Season)))
{
<InputRadio Value="#revOption" #onchange="#UpdateSelection"/>
<text> </text>
#revOption <text> </text>
}
</InputRadioGroup>
<p>Selected Season inside Child: #SelectedValue</p>
</EditForm>
#code {
[Parameter]
public string SelectedValue { get; set; } = "No seasons selected.";
[Parameter]
public EventCallback<string> TestEvent { get; set; }
Model model = new Model();
class Model
{
public Season Season { get; set; }
}
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
private async Task UpdateSelection()
{
SelectedValue = model.Season.ToString();
await TestEvent.InvokeAsync(SelectedValue);
}
}
and the parent component index.razor
<h1>Parent</h1>
<Child TestEvent=#UpdateChildSeason />
<p>Season data from Child: #ChildSeason</p>
#code
{
string ChildSeason;
void UpdateChildSeason(string value)
{
ChildSeason = value;
}
}
This does not work properly. I not pass the data to the parent
p.s.
At the same time, if I select any of the radio buttons, all of the radio buttons will be cover in green buttons.
I have no idea why that happens.
It's only a Css issue.
If you inspect the element you'll see:
<input class="modified valid" type="radio" name="81920f03ae5d4345bf72369b58ea59e1" value="Autumn" _bl_7ea09091-cc5a-4fb1-977c-dd45c318c204="">
Note the valid which gives a green box around normal input elements when the entered data is valid. It's part of the Css formatting created in InputBase.
The problem is the Css that styles this is (site.css: line 29) :
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
It doesn't exclude Radio.
There are various ways to solve this. This works:
.valid.modified:not([type=checkbox, radio]) {
outline: 1px solid #26b050;
}
So, the solution I have is to not use EditForm. Well, since I am not worried about data validation, I can do away with EditForm for now.
Here is my solution.
This is the child component. Pages\Child.razor
<p>Please select your choice.</p>
#foreach (var seasonOption in (Season[])Enum
.GetValues(typeof(Season)))
{
<label>
<input type="radio"
name="seasonOption"
value="#seasonOption"
onchange="#OnValueChanged" />
#seasonOption.ToString()
</label>
}
<br />
<p>SelectedValue: #SelectedValue</p>
#code {
[Parameter]
public string SelectedValue { get; set; } = "No season selected.";
[Parameter]
public EventCallback<string> SelectedValueChanged { get; set; }
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
private Task OnValueChanged(ChangeEventArgs e)
{
SelectedValue = e.Value.ToString();
return SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
and the parent component index.razor
<Child2 #bind-SelectedValue="childData" />
<p>Data from Child: #childData</p>
#code
{
string childData = "No child data.";
}
Now, the data is finally passed to the parent. This will work for me now. Does anyone have a solution for the EditForm version and how to remove that green boxes from the radio buttons?
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 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.
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.
Just thinking about coming up with a templated blazor component to do a CRUD style Single Page App which can have a specific object passed in so I don't have to write the same boilerplate code over and again.
so for instance as below parts of it can be templated by using RenderFragment objects:
#typeparam TItem
<div>
#if (AddObjectTemplate != null)
{
#AddObjectTemplate
}
else
{
<div style="float:left">
<button class="btn btn-primary" #onclick="AddObject">Add Object</button>
</div>
}
</div>
#code {
[Parameter]
public RenderFragment AddObjectTemplate { get; set; }
[Parameter]
public IList<TItem> Items { get; set; }
}
However further down I might want to have something like this:
<button class="btn btn-default" #onclick="#(() => EditObject(item.Id))">Edit</button>
protected void EditObject(int id)
{
TItem cust = _itemServices.Details(id);
}
The issue is that the above call to EditObject(item.Id) cannot resolve to a specific object at this moment because it does not know what TItem is. Is there a way to use a specific interface in the template component that each object must implement or is there another way of doing this?
The idea would be to have AddObject, EditObject, DeleteObject etc which all basically do the same thing but with different types of object.
Since you have the IList<TItem> as a parameter, the list exists at another level of the component structure outside of this component. Because of this you might be better off using the EventCallBack<T> properties for your Add, Edit, and Delete methods, and having the actual methods set as you wire the component up. This makes your template component a rendering object only, and you keep the real "work" to be done close to the actual list that needs the work done.
When you set up your template component, you might try something like this which I've had good results with.
Templator.razor
#typeparam TItem
<h3>Templator</h3>
#foreach (var item in Items)
{
#ItemTemplate(item)
<button #onclick="#(() => EditItemCallBack.InvokeAsync(item))">Edit Item</button>
}
#code {
[Parameter]
public IList<TItem> Items { get; set; }
[Parameter]
public EventCallback<TItem> EditItemCallBack { get; set; }
[Parameter]
public RenderFragment<TItem> ItemTemplate
}
Container.Razor
<h3>Container</h3>
<Templator TItem="Customer" Items="Customers" EditItemCallBack="#EditCustomer">
<ItemTemplate Context="Cust">
<div>#Cust.Name</div>
</ItemTemplate>
</Templator>
#code {
public List<Customer> Customers { get; set; }
void EditCustomer(Customer customer)
{
var customerId = customer.Id;
//Do something here to update the customer
}
}
Customer.cs
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
The key points here would be as follows:
The actual root list is living outside the templated component, as are the methods to work on that list, so all of the above are at the same level of abstraction and the same chunk of code.
The template component receives a list, a type to specify what type of the list items will be, and a method callback to execute on each item. (or series of methods, you can add the "Add" and "Delete" methods as you see fit using the same approach). It also receives the <ItemTemplate> render fragment that you specify when calling the code in the Container.razor file.
The 'foreach' in the templated item makes sure each TItem gets set up for it's own RenderFragment, set of buttons and callback functions.
Using the EventCallBack<TItem> as a parameter means that you assign a method to it that expects back the whole object of TItem. This is good, as the template now doesn't care what type the TItem is, only that is has the ability to call a method that takes a TItem as an argument! Handling the instance of whatever TItem is is now the responsibility of the calling code, and you don't have to try to constrain the generic type TItem. (Which I haven't had any luck doing in Blazor yet, maybe future releases)
As for how to render whatever TItem you feed into it, that is explained well in the documentation HERE.
Hope this helps!