I'm using Radzen data grid on my .NET core 3.1 blazor app. On the grid page I have RadzenDataGridColumn component and within that I have FilterTemplate component which is rendered dynamically. My problem is I want to bind FilterValue to the FilterTemplate component and the changes were made to the FilterValue should pass back to the parent level.
<RadzenDataGridColumn Property="#colDef.Property" FilterValue="#colDef.FilterValue" >
<FilterTemplate>
<CascadingValue Value="#colDef.FilterValue">
#colDef.FilterTemplate //this contains a RenderFragment
</CascadingValue>
</FilterTemplate>
</RadzenDataGridColumn>
colDef.FilterTemplate can contain code snippet like bellow.
<RadzenDropDown TValue="string" #bind-Value="FilterValue" Data="#DrpData" TextProperty="#DrpTextProperty" ValueProperty="#DrpValueProperty" ></RadzenDropDown>
#code {
[CascadingParameter]
public string FilterValue { get; set; }
[Parameter]
public string DrpTextProperty { get; set; }
[Parameter]
public string DrpValueProperty { get; set; }
[Parameter]
public IEnumerable<DrpDataType> DrpData { get; set; }
}
Use event callbacks, like in this example. I used the default Blazor wasm template and the existing Counter component to show how it works:
In child component (Counter.razor)
[Parameter]
public EventCallback<string> OnFilterValueChanged { get; set; }
private async Task IncrementCount()
{
currentCount++;
string newFilter = currentCount.ToString();
await OnFilterValueChanged.InvokeAsync(newFilter);
}
The parent (Index.razor):
<Counter OnFilterValueChanged="FilterValueHandler" />
<h2>#FilterValue</h2>
#code {
public string FilterValue { get; set; } = "Initial Value";
private void FilterValueHandler(string filtervalue)
{
FilterValue = filtervalue;
}
}
Now, when you click the button in child component, the variable value in parent component will get updated.
Related
How to make a property created in a generic Component available to the definition of the Render Fragment content
I´m new to blazor and trying to make a generic CRUD component based on the model shown right below. Many of my other classes inherit from this model and don´t add any fields
public class BasicCatalogModel
{
[Key]
public virtual long Id { get; set; }
public virtual String Name { get; set; }
public virtual String Description { get; set; }
public virtual bool Enabled { get; set; }
}
Then I have a generic Data Service that has a signature like so:
public class BasicCatalogService<T>: ICatalogService<T> where T: BasicCatalogModel
This service is injected into a "generic" component that is created in my CustomModelCRUD. What I need to do is add custom Colums and custom Fields for those models that need to add one or two extra fields
<BasicCatalogCRUD TItem="CustomModel" DataService="#CustomService" CrudTitle="#modelLocalizer["Custom.crud.title"]">
<CustomColumns>
<div>HERE are my custom columns and this works since I don´t need to add a reference to the current item</div>
</CustomColumns>
<CustomFields>
I need to add a custom field where I use the currentItem created in BasicCatalogCRUD
I need to make the field visible here to add a custom Field to the form
<input value="currentItem.CustomField">
<CustomFields>
</BasicCatalogCRUD>
This is part of the generic CRUD as it is Right now
#inject IStringLocalizer<ModelResource> modelLocalizer
#inject NotificationService NotificationService
#inject DialogService DialogService
#typeparam TItem
<h1>#CrudTitle</h1>
<Columns>
//Columns Before
#CustomColumns
//Colums After
</Columns>
</div>
#code {
/// <summary>
/// The item I need to access in the RenderFragment CustomFields
/// </summary>
TItem currentItem;
[Parameter] public BasicCatalogoService<TItem> DataService { get; set; }
[Parameter] public String CrudTitle { get; set; }
[Parameter] public RenderFragment CustomColumns { get; set; }
///I also need to pass the CustomFields value to the Dialog
[Parameter] public RenderFragment CustomFields { get; set; }
/// <summary>
/// Here we load the generic Form on to a RadzedDialog
/// </summary>
void LoadDataOnForm(TItem objectInstance)
{
currentItem = objectInstance;
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("CurrentItem", objectInstance);
parameters.Add("FieldTitle", GetActionLabel());
parameters.Add("ItemGrid", itemGrid);
parameters.Add("LabelBuilder", labelBuilder);
parameters.Add("DataService", DataService);
//I add the parameter to the parameters and send it to the dialog.
parameters.Add("CustomFields", CustomFields);
DialogOptions dialogOptions = new DialogOptions() { Width = "700px", Height = "600px" };
DialogService.Open<BasicCatalogoForm<TItem>>(GetActionLabel(), parameters, dialogOptions);
}
}
And finally the code of the form
#inject IStringLocalizer<ModelResource> modelLocalizer
#inject IStringLocalizer<AppResource> appLocalizer
#inject NotificationService NotificationService
#inject DialogService DialogService
#typeparam TItem
#if (CurrentItem != null)
{
<RadzenTemplateForm TItem="TItem" Data="#CurrentItem" Submit=#OnSubmit InvalidSubmit=#OnInvalidSubmit>
<FluentValidationValidator />
<RadzenFieldset>
//Fields Before ...
//The fields should be inserted here
#CustomFields
</RadzenFieldset>
<RadzenButton ButtonType="ButtonType.Submit" Icon="save" Text="#appLocalizer["button.save.label"]" />
</RadzenTemplateForm>
}
#code {
[Parameter] public String FieldTitle { get; set; }
[Parameter] public TItem CurrentItem { get; set; }
[Parameter] public BasicCatalogoService<TItem> DataService { get; set; }
[Parameter] public RenderFragment CustomFields { get; set; }
}
I'm currently researching passing data to the render fragment (This doesn´t seem to solve my problem as far as I can tell) and cascading values (doesn´t seem like the right option either). So, how do I make currentItem available in CustomModelCRUD ?
So, I was actually very wrong and you can accomplish this using RenderFragments.
First, it is necessary to add the render fragment to the generic form. Look for the HERE: comments to see where it was added. By doing it this way, we can pass a value of type TItem to the render fragment
#typeparam TItem
#if (CurrentItem != null)
{
<RadzenTemplateForm TItem="TItem" Data="#CurrentItem" Submit=#OnSubmit InvalidSubmit=#OnInvalidSubmit>
<FluentValidationValidator />
<RadzenFieldset>
//Fields Before ...
//HERE : The fields should be inserted here but we need to check if
//we are receiving the fragment and if the currentItem is set.
//Adding the nullchecks also allows us to make the customFields an
//optional thing
#if (CustomFields != null && CurrentItem != null)
{
#CustomFields(CurrentItem)
}
//... Fields After
</RadzenFieldset>
<RadzenButton ButtonType="ButtonType.Submit" Icon="save" Text="#appLocalizer["button.save.label"]" />
</RadzenTemplateForm>
}
#code {
[Parameter] public String FieldTitle { get; set; }
[Parameter] public TItem CurrentItem { get; set; }
[Parameter] public BasicCatalogoService<TItem> DataService { get; set; }
[Parameter] public RenderFragment<TItem> CustomFields { get; set; } //HERE: This is how I added the render fragment
}
after that we need to add the fragment to the Generic CRUD. Once again look for the HERE: comments.
#inject IStringLocalizer<ModelResource> modelLocalizer
#inject NotificationService NotificationService
#inject DialogService DialogService
#typeparam TItem
<h1>#CrudTitle</h1>
<Columns>
//Columns Before
//HERE: We also need to to a null check for the custom
//columns to make them optional
#if (CustomColumns != null)
{
#CustomColumns
}
//Colums After
</Columns>
</div>
#code {
/// <summary>
/// The item I need to access in the RenderFragment CustomFields
/// </summary>
TItem currentItem;
[Parameter] public BasicCatalogoService<TItem> DataService { get; set; }
[Parameter] public String CrudTitle { get; set; }
//HERE: the custom colums stay as they are.
[Parameter] public RenderFragment CustomColumns { get; set; }
//HERE: We add the RenderFragment. The fragment MUST have the same signature
//as the one in the Generic Form above
[Parameter] public RenderFragment<TItem> CustomFields { get; set; }
/// <summary>
/// Here we load the generic Form on to a RadzedDialog
/// </summary>
void LoadDataOnForm(TItem objectInstance)
{
currentItem = objectInstance;
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("CurrentItem", objectInstance);
parameters.Add("FieldTitle", GetActionLabel());
parameters.Add("ItemGrid", itemGrid);
parameters.Add("LabelBuilder", labelBuilder);
parameters.Add("DataService", DataService);
//HERE: The super important part. it is necessary to do a null check, and pass the CustomFields to the
//generic form. DO NOT do it like this: parameters.Add("CustomFields", CustomFields(CurrentItem));
if (CustomFields != null)
{
parameters.Add("CustomFields", CustomFields);
}
DialogOptions dialogOptions = new DialogOptions() { Width = "700px", Height = "600px" };
DialogService.Open<BasicCatalogoForm<TItem>>(GetActionLabel(), parameters, dialogOptions);
}
}
Finally we need to declare the fragments in the CustomModelCRUD
<BasicCatalogCRUD TItem="CustomModel" DataService="#CustomModelService" CrudTitle="Custom Model CRUD">
#*HERE: this is optional and the models that follow the BasicCatalogModel don´t need to add this.*#
<CustomColumns>
<RadzenGridColumn TItem="CustomModel" Property="CustomProperty" Title="Custom Property" />
</CustomColumns>
#*HERE: this is optional and the models that follow the BasicCatalogModel don´t need to add this.*#
<CustomFields>
#*HERE: we can access the value of the CurrentItem in the BasicCatalogCRUD using the context keyword*#
#*but we must Make a NullCheck because when the BasicCatalogCRUD is initialized there might be no*#
#*current Item set and a nullPointer Exception could be thrown. *#
#if (context != null)
{
<div class="row">
<div class="col-md-4 align-items-center">
<RadzenLabel Text="#modelLocalizer["prioridad.field.CustomProperty.label"]" />
</div>
<div class="col-md-8">
<RadzenNumeric TValue="int" Min="1" Max="100" #bind-Value="context.CustomProperty" />
<span class="field-error-message"><ValidationMessage For="#(() => context.CustomProperty)" /></span>
</div>
</div>
}
</CustomFields>
</BasicCatalogCRUD>
Since the CRUD and DialogForm are generic, we could also create the other CRUDs without the need of the CustomColums and CustomFields tags
<BasicCatalogCRUD TItem="NormalModel" DataService="#NormalModelService" CrudTitle="Custom Model CRUD"></BasicCatalogCRUD>
And with this, I finally got my Generic CRUD and generic Form Dialog that can add optional columns and Fields depending on my needs.
I receive data from a SQL Server and load it into a model:
namespace BlazorServer.Models
{
public class Animal
{
public int height { get; set; }
public string name { get; set; }
public string origin { get; set; }
}
}
In a component I use the model to display data. The user is able to edit the data. Here is a sample:
<input #onchange="args => ValueChanged(args)" value="#animal.name" class="form-control form-control-sm border rounded" />
How do I get which property of the model has changed? I tried the following, but I only get the new value:
private void ValueChanged(ChangeEventArgs args)
{
var newValue = args.Value;
}
I want to update the model in the component (which equals the binding of blazor) AND also send the data to the SQL server right away.
Blazor comes with EditForm which manages an EditContext, and a set of Input controls - InputText in your case - that interface with EditContext. You can access the EditContext, register an event handler on OnFieldChanged and get change events. You get passed a FieldIdentifier that you can use to identify which field has been changed.
See - MS Documentation
Here's a simple razor page that demos using EditContext and OnFieldChanged
#page "/Test"
#implements IDisposable
<EditForm EditContext="this.editContext" class="m-3">
Animal: <InputText #bind-Value="this.model.name"></InputText><br />
Origin: <InputText #bind-Value="this.model.origin"></InputText><br />
</EditForm>
<div class="m-3">FieldChanged:<i>#this.FieldChanged</i> </div>
#code {
public class Animal
{
public int height { get; set; }
public string name { get; set; }
public string origin { get; set; }
}
private Animal model = new Animal() { height = 2, name = "giraffe", origin = "Africa" };
private EditContext editContext;
private string FieldChanged = "none";
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(model);
this.editContext.OnFieldChanged += this.OnFieldChanged;
return base.OnInitializedAsync();
}
private void OnFieldChanged(object sender, FieldChangedEventArgs e)
{
var x = e.FieldIdentifier;
this.FieldChanged = e.FieldIdentifier.FieldName;
}
// Need to Implement IDisosable to unhook event handler
public void Dispose ()
{
this.editContext.OnFieldChanged -= OnFieldChanged;
}
}
i'm trying to create a ChildComponent that send some data to ParentComponent before Parent will render itself.
Firstly i try to send reference of Parent to all ChildComponents and than i try to set easy text in ParentComponent directly from Childs using value of parameters.
Could someone tell me, why code belowe doesn't work?
Thaks to everyone for any help!
Main file:
#page "/test"
<h3>TestTabeli</h3>
<ParentComponent>
<ChildComponent Name="Child nr 1" />
<ChildComponent Name="Child nr 2" />
</ParentComponent>
Parent File:
<h3>ParentComponent</h3>
<CascadingValue Value="Parent" Name="ParentRef">
#body
</CascadingValue>
<br />
<p>My Childrens: #text</p>
#code {
public string text { get; set; } = "blaaaa";
public ParentComponent Parent { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
Parent = this;
}
public RenderFragment body { get; set; }
}
Child Component:
<h3>ChildComponent</h3>
#code {
[Parameter]
public string Name { get; set; } = "Child Name";
[CascadingParameter(Name = "ParentRef")]
public ParentComponent parent { get; set; }
protected override void OnParametersSet()
{
base.OnParametersSet();
parent.text += "<br /> Hi! My name is: " + Name;
}
}
create a ChildComponent that send some data to ParentComponent before Parent
will render itself
That is not possible. Can a woman produce a child before she is born. But you'll see later on that we can deceive the human eyes to believe this is possible. But your question is the reasons why your code is not working, right ?
<CascadingValue Value="Parent" Name="ParentRef">
#body
</CascadingValue>
This is the first reason why your code won't execute. You must use, #ChildContent rather than #body.
You should also have this in code:
[Parameter]
public RenderFragment ChildContent { get; set; }
Note that you must add the [Parameter] attribute.
After making some additional changes to your code, it may work:
ParentComponent.razor
#page "/ParentComponent"
<h3>ParentComponent</h3>
<CascadingValue Value="this" Name="ParentRef">
#ChildContent
</CascadingValue>
<br />
<p>My Childrens: #text</p>
#code {
public string text { get; set; } = "blaaaa";
// public ParentComponent Parent { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
// Parent = this;
}
[Parameter]
public RenderFragment ChildContent { get; set; }
}
ChildComponent.razor
<h3>#Name</h3>
#code {
[Parameter]
public string Name { get; set; } = "Child Name";
[CascadingParameter(Name = "ParentRef")]
public ParentComponent parent { get; set; }
protected override void OnParametersSet()
{
parent.text += "Hi! My name is: " + Name;
}
}
Copy and run that code. Note that the text in the text property won't change. It'll always display the string "blaaaa". The solution to this is to use an EventCallback to pass a new value to the Text property as well as to re-render the parent component.
Generally speaking, the use of the CascadingValue component is not appropriate. And you should not pass a reference to the parent component to the child component. You should usually use component parameters and events (EventCallback 'delegate') to handle the relations between parent and children
You can not render child component inside parentcomponent straightly. Instead you need to use Template parameters, A templated component is defined by specifying one or more component parameters of type RenderFragment or RenderFragment. A render fragment represents a segment of UI to render. RenderFragment takes a type parameter that can be specified when the render fragment is invoked. something like this :
#typeparam TItem
#foreach (var item in Items)
{
#ChildTemplate(item)
}
#code {
[Parameter]
public RenderFragment<TItem> ChildTemplate { get; set; }
[Parameter]
public IReadOnlyList<TItem> Items { get; set; }
}
the template parameters can be specified using child elements that match the names of the parameters.
take a look at Microsoft Document
I am trying to bind a model field to a control, I have it working to the form but I'm trying to refactor the components so that I can reused the control and it's attributes throughout the program.
Here are some snippets to try to portray my situation:
Sample Model
public class MyModel
{
public DateTime DateOpened { get; set; }
}
Used in MyModelFormBase.cs
public class MyModelFormBase : ComponentBase
{
protected MyModal data = new MyModel();
}
Used in MyModelForm.razor
<MyForm Model="#data" AddFunction="#InsertMyModel" DataDismiss="MyModelForm">
<MyDateInput InputId="dateOpened" InputName="Date Opened" #bind-InputValue="#data.DateOpened" />
</MyForm>
MyForm.razor, uses blazorstrap
<BSForm Model="#Model" OnValidSubmit="#AddFunction"
#ChildContent
<BSButton Color="Color.Primary"
ButtonType="ButtonType.Submit"
data-dismiss="#DataDismiss">
Submit</BSButton>
</BSForm>
#code {
[Parameter]
public Object Model { get; set;}
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback AddFunction { get; set; }
[Parameter]
public string DataDismiss { get; set; }
}
MyDateInput.razor, uses blazorstrap
<BSFormGroup>
<BSLabel For="#InputId">#InputName</BSLabel>
<BSInput InputType="InputType.Date" Id="#InputId"
#bind-Value="#InputValue"
ValidateOnChange="true"
#onchange="UpdateData"
/>
<BSFormFeedback For="#(() => InputValue)"/>
</BSFormGroup>
#code {
[Parameter]
public DateTime InputValue { get; set; }
[Parameter]
public EventCallback<DateTime> InputValueChanged { get; set; }
[Parameter]
public string InputId { get; set; }
[Parameter]
public string InputName { get; set; }
private async Task UpdateData()
{
await InputValueChanged.InvokeAsync(InputValue);
}
}
The default data provided by my service is correctly displayed in the control, so it's properly bound downwards, but doesn't propagate any changes back up to the model. :(
The goal is to be able to keep a MyDateInput Component that is repeated throughout the application that is bound 2 ways with the model regardless of layers of components it's passed through.
Any ideas?
It's really quite daunting to answer your question with so much code missing or unknown. However, I'll try to provide some assistance with the hope it can help you solve your ailments.
This code:
await InputValueChanged.InvokeAsync(InputValue);
I guess is a callback to update the value of the bound property MyModel.DateOpened , right ? If not it should be doing that. In any case, this property should be annotated with the PropertyAttribute attribute...
This may be the reason why two-way data binding is not working.
Note that your MyDateInput componet may also need to receive a ValueExpression value. I can't say that for sure, because not all your code is available, so take as a word of warning:
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
I'll put at the end of my answer a small sample to make this clear.
Are you sure this is working? The compiler should be barking:
#bind-Value="#InputValue" ValidateOnChange="true" #onchange="UpdateData"
telling you that you are using two onchange events. This should be, ordinarily
value="#InputValue" ValidateOnChange="true" #onchange="UpdateData", but I cannot be certain about it as I don't see how BSInput is implemented...
Note: BSForm is missing a closing tag before #ChildContent
Sample code:
RazorInputTextTest.razo
-----------------------
#page "/RazorInputTextTest"
<span>Name of the category: #category.Name</span>
<EditForm Model="#category">
<RazorInputText Value="#category.Name" ValueChanged="#OnValueChanged" ValueExpression="#(() => category.Name)" />
</EditForm>
#code{
private Category category { get; set; } = new Category { ID = "1", Name = "Beverages" };
private void OnValueChanged(string name)
{
category.Name = name;
}
}
RazorInputText.razor
--------------------
<div class="form-group">
<InputText class="form-control" #bind-Value="#Value"></InputText>
</div>
#code{
private string _value { get; set; }
[Parameter]
public string Value
{
get { return _value; }
set
{
if (_value != value) {
_value = value;
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(value);
}
}
}
}
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
}
Hope this helps...
I have child component
<input type="text" #bind="#Item" />
<button class="btn btn-primary" onclick="#OnClick">#ButtonText</button>
#code {
[Parameter]
public string ButtonText { get; set; } = "Submit";
[Parameter]
public string Item { get; set; }
[Parameter]
public EventCallback<UIMouseEventArgs> OnClick { get; set; }
}
With its parent component
#using System;
<HeadingComponent HeadingText="#HeadingText" />
<ListItemsComponent Items="#ListItems" />
<SubmitButtonComponent Item="#Item" ButtonText="add item" OnClick="#AddItem" />
#code {
[Parameter]
public string HeadingText { get; set; } = "MyList";
[Parameter]
public List<string> ListItems { get; set; } = new List<string> { "One", "Two" };
public string Item { get; set; }
private void AddItem(UIMouseEventArgs e)
{
ListItems.Add(Item);
}
}
The submit button does work but its not reading the Item value sent from the child component its just empty. Is it possible to build item to the value coming from the child?
Solution1: based on the assumption that the requirement is to pass input values entered into a text box residing in a child component to a parent component:
SubmitButtonComponent.razor
When you click on the submit button in the child component, the method on the parent component is triggered. If you want to pass the item from the child component to the parent component you should do this:
<button class="btn btn-primary" #onclick="#(() => OnClick?.InvokeAsync(Item))">#ButtonText</button>
#code {
[Parameter]
public string ButtonText { get; set; } = "Submit";
public string Item { get; set; }
[Parameter]
public EventCallback<strig> OnClick { get; set; }
}
Note: onclick="#OnClick" should be #onclick="#OnClick": #onclick is an attribute directive telling the compiler what to do. It is not an Html attribute as many tend to believe.
Note: I've removed the Parameter attribute from the Item property.
Note: Instead of using a lambda expression you can define a local method from which you can invoke the OnClick 'delegate'; that is the parent's AddItem method like this:
<button class="btn btn-primary" #onclick="#OnClickInternal">#ButtonText</button>
And:
async Task OnClickInternal()
{
// verify that the delegate associated with this event dispatcher is non-null
if (OnClick.HasDelegate)
{
await OnClick.InvokeAsync(Item);
}
}
Parent Component
#using System;
<HeadingComponent HeadingText="#HeadingText" />
<ListItemsComponent Items="#ListItems" />
<SubmitButtonComponent ButtonText="add item" OnClick="#AddItem" />
#code {
[Parameter]
public string HeadingText { get; set; } = "MyList";
[Parameter]
public List<string> ListItems { get; set; } = new List<string> { "One", "Two" };
// Note: this property has been removed
// public string Item { get; set; }
private void AddItem(string item)
{
ListItems.Add(item);
}
}
Hope this helps...