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...
Related
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.
I have split big component into two, where currently one is child and another is parent. However currently after clicking Submit button on parent component values are somehow getting null and no data is passed into database. Why so and how to fix that?
In Record.razor.cs
[Parameter]
public string Title { get; set; }
[Parameter]
public string Description { get; set; }
[Parameter]
public DateTime SelectedDate { get; set; }
Record.razor:
<Form Model="this" LabelColSpan="8" WrapperColSpan="16">
<FormItem Label="Record title" NoStyle>
<Input #bind-Value="#this.Title" />
</FormItem>
<FormItem Label="Description" NoStyle>
<Input #bind-Value="#this.Description" />
</FormItem>
<FormItem Label="Date" NoStyle>
<DatePicker #bind-Value="#this.SelectedDate" ShowTime="#true" OnChange="this.OnDateSelected" />
</FormItem>
</Form>
Index.razor.cs:
private DiaryRecord DiaryRecordModel { get; set; }
private bool isDialogVisible;
private DateTime SelectedDate { get; set; }
public Index()
{
this.DiaryRecordModel = new DiaryRecord();
this.SelectedDate = DateTime.Now;
}
Index.razor:
<Form Model="this.DiaryRecordModel"
OnFinish="(e) => this.OnCreateNewDiaryRecord()">
<FormItem>
<Record Description="#context.Description"
SelectedDate="this.SelectedDate"
Title="#context.Title" />
</FormItem>
<FormItem WrapperColOffset="8" WrapperColSpan="16">
<Button Type="#ButtonType.Primary" HtmlType="submit">
Submit
</Button>
<Button OnClick="(e)=>{this.isDialogVisible = false;}">Cancel</Button>
</FormItem>
</Form>
DiaryRecord.cs
public class DiaryRecord
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
For example todays date is always passed into model, instead of actually selected date (other values are not taken into model):
First of all - don't use form inside a form. Use just the one on Index.razor.
Second:
Once you do this: Description="#context.Description" your context looses the connection to whatever is happening with the string.
You have two options here.
1] Use two-way binding, which will result in #bind-Description="#context.Description", you also need to create EventCaller for every property inside Record component.
2] (better one) Send your whole model to Record component:
[Parameter] DiaryRecord DiaryRecord {get;set;}
<Input #bind-Value="#DiaryRecord.Title" />
<Record DiaryRecord=this.DiaryRecordModel/>
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 using Blazor to create a dynamic form from the properties of my model.
I am using a for each loop to loop through the properties of the model.
public class SensorType
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
[Display(Name = "Sensor Type Name")]
public string Name { get; set; }
[Required]
[MaxLength(500)]
public string Description { get; set; }
[Required]
[MaxLength(2048)]
[Display(Name = "Datasheet URL")]
public string DatasheetUrl { get; set; }
}
I implemented this razor view, where I try to bind to public SensorType sensortype { get; set; }. But I need to bind to sensortype.property where property is whatever property the model has that is in the for each loop. But I cannot simply just call say #bind-Value="sensortype.property". Any ideas on how to do this? I don't want to have to manually type every field. Thanks!
<EditForm Model="#sensortype" OnValidSubmit="#SaveSensorType">
#foreach(var property in typeof(SensorType).GetProperties())
{
if(property.Name == "Id")
{
continue;
}
<div class="form-group row">
<label class="col-sm-2 col-form-label">#(GetAttribute(property, false)) </label> //This function get the name of the property in a human-readable way.
<div class="col-sm-10">//I would like to bind here to sensortype's property in the for each loop but sensortype.property gives me an error.
<InputTextArea class="form-control" #bind-Value="sensortype.property" value="sensortype.property.Name" placeholder="Description of the Type of Sensor" />
</div>
</div>
}
I am playing this days with blazor and I made a kind of DynamicForm, is not perfect but is working. I just want to show you my dynamic form as proof of concept, not at something which I wold use in production.
Basicly we want to write something like this:
<DynamicForm #bind-Model="MySensorType" />
//this will generate a form with fields for all properties of the model
So, on Index view let's create a property for MySensorType and some markup to see if the model is changing when the form fields are edited.
#page "/"
<div style="display:flex">
<div>
<DynamicForm #bind-Model="MySensorType" />
</div>
<div style="background:yellow;flex:1;margin:20px;">
<p>Id: #MySensorType.Id</p>
<p>Name: #MySensorType.Name</p>
<p>Description: #MySensorType.Description</p>
<p>Url: #MySensorType.DatasheetUrl</p>
</div>
</div>
#code {
public SensorType MySensorType { get; set; } = new SensorType();
public class SensorType
{
public int Id { get; set; } = 1;
public string Name { get; set; } = "Some Name";
public string Description { get; set; } = "Some Description";
public string DatasheetUrl { get; set; } = "This is a URL";
}
}
In order to generate automatically the fields, we need some kind of DynamicField.
Below component is for named "DynamicField"
<div>
<label>#Caption</label>
#if (Value is String sValue)
{
<input type="text" value="#sValue" #onchange="OnChange"/>
}
#if (Value is int iValue)
{
<input type="number" value="#iValue" #onchange="OnChange" />
}
</div>
#code {
[Parameter] public string Caption { get; set; }
[Parameter] public object Value { get; set; }
[Parameter] public EventCallback<object> ValueChanged { get; set; }
async void OnChange(ChangeEventArgs e)
{
await ValueChanged.InvokeAsync(e.Value);
}
}
Now, we can create the wrapper so called DynamicForm:
#typeparam T
#foreach (var p in Properties)
{
<DynamicField Value="#p.Value" Caption="#p.Key" ValueChanged="#((e)=>OnValueChanged(e,p.Key))"/>
}
#code{
[Parameter] public T Model { get; set; }
[Parameter] public EventCallback<T> ModelChanged { get; set; }
public Dictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
protected override void OnInitialized()
{
var props = Model.GetType().GetProperties();
foreach (var p in props)
{
Properties.Add(p.Name, p.GetValue(Model));
}
}
void OnValueChanged(object e, string prop)
{
var p = Model.GetType().GetProperty(prop);
if (p.PropertyType == typeof(int))
{
var intValue = Convert.ToInt32(e);
p.SetValue(Model, intValue);
}
if (p.PropertyType == typeof(string))
{
p.SetValue(Model, e.ToString());
}
ModelChanged.InvokeAsync(Model);
}
}
What actually is happening here, we are using Reflection to get all properties of the model, send them to DynamicFields, and when those values are changed we set the new value to the model and call ModelChanged to send new values.
On my computer this works, and every time when I change a value, MySensorType is showing the new value on Index component.
You can see that I created dynamic fields only for Number and String, if you have DateTime or Select, you need to extend this DynamicField, for select will be more difficult.
By the way, on Index view you can put a button and call SaveChanges with your logic and use MySensorType.
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...