I'm playing around with the custom template in Blazor and I'm trying to find to a way to two-way bind a CascadingValue or achieve something similar. Right now I have the following template.
#if (PopupVisible)
{
<DxPopup>
<HeaderTemplate>
<h4 class="modal-title">#HeaderText</h4>
<button type="button" class="close" #onclick="#UpdatePopupVisible">×</button>
</HeaderTemplate>
<ChildContent>
<div class="modal-body">
<div class="container-fluid">
#bodyContent
</div>
</div>
<div class="modal-footer">
#footerContent
<button class="btn btn-secondary" #onclick="UpdatePopupVisible">Cancel</button>
</div>
</ChildContent>
</DxPopup>
}
#code {
[CascadingParameter] public bool PopupVisible { get; set; }
[CascadingParameter] public EventCallback<bool> PopupVisibleChanged { get; set; }
[Parameter] public RenderFragment HeaderText { get; set; }
[Parameter] public RenderFragment footerContent { get; set; }
[Parameter] public RenderFragment bodyContent { get; set; }
private async Task UpdatePopupVisible()
{
PopupVisible = false;
await PopupVisibleChanged.InvokeAsync(PopupVisible);
}
}
Then I have a component that implements this template(child), and then I have that component called with a button press(parent). What I want to know is if there is a way to bind the PopupVisible parameter from the parent without having to bind it the child and having the child pass it to the template. I haven't found a way to two-way bind a cascading parameter but if possible I think that would be the best way to do so. Outside of that, I'm not sure if there is another way or I'm going to have to go with my current idea of passing the value.
You can't do two-way binding with cascading parameters. Cascading means flowing downstream, from parent to child, and not the other way around.
I'm not sure I understand your question...however, if you wish to pass a value from a parent component and back; you can do the following:
Note: This is a two-way Component data binding
Child Component
#code
{
private bool visible;
[Parameter]
public bool PopupVisible
{
get { return visible }
set
{
if (visible != value)
{
visible = value;
}
}
}
[Parameter] public EventCallback<bool> PopupVisibleChanged { get; set; }
// Invoke the EventCallback to update the parent component' private field visible with the new value.
private Task UpdatePopupVisible()
{
PopupVisible = false;
return PopupVisibleChanged.InvokeAsync(PopupVisible);
}
}
Usage
#page "/"
<DxPopup #bind-PopupVisible="visible" />
#code {
private bool visible;
}
Note: If you need some explanation, and believe that I did not answer your question, don't hesitate to tell me, but please take the time to phrase your questions... I could not completely understand questions.
what you can do is, Cascade the parent component and in the child component, access the parent Property you want to change like this:
Parent:
<CascadingValue Value="this">
<Child />
</CascadingValue>
Child:
[CascadingParameter]
public Parent Parent { get; set; }
.....
private void ChangeParentProperty()
{
Parent.Property = ....;
}
Any doubt feel free to ask.
Related
I'm developing a UI library on top of Blazor and I like to enable the bind* syntax for my components so consumers could use it too.
Context
I've read and seen plenty of examples so on the most basic level let's say we have the following custom component named RawCustomInput.razor:
<input type="text" value="#Value" #onchange="OnValueChanged" style="width: 5rem" />
#code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
And another component that consumes it named InputPage.razor like this:
#page "/input"
<PageTitle>InputPage</PageTitle>
<RawCustomInput
#bind-Value="#_name" />
#if (!string.IsNullOrEmpty(_name))
{
<p>#_name</p>
}
#code {
private string? _name = null;
}
Problem
Now, the above works but say I want to change the event from onchange to oninput on the consumer side? so I tried to do something like this #bind-Value:event="oninput" but then I get the following error:
does not have a property matching the name 'oninput'
So I thought I could solve this by introducing #oninput="OnValueChanged" to RawCustomInput.razor which looks like this:
<input type="text" value="#Value" #onchange="OnValueChanged" #oninput="OnValueChanged" style="width: 5rem" />
#code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
But this didn't work so I read the error again and realized that something like #bind-Value:event="ValueChanged" might work and it worked! so what I did is add another property that looks like this:
[Parameter]
public EventCallback<string> ValueChanging { get; set; }
So now my RawCustomInput.razor looks like this:
<input type="text" value="#Value" #onchange="OnChange" #oninput="OnInput" style="width: 5rem" />
#code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public EventCallback<string> ValueChanging { get; set; }
private async Task OnChange(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
private async Task OnInput(ChangeEventArgs args)
=> await ValueChanging.InvokeAsync(args.Value as string);
}
And finally I could use the syntax below that worked too which is great but raises few questions.
<RawCustomInput
#bind-Value="#_name"
#bind-Value:event="ValueChanging" />
Questions
It's possible to use the syntax bind-{Property}="{Expression}" for binding but is it possible to enable this syntax bind="{Expression}" on custom components? because when I'm trying to do something like this <RawCustomInput #bind="#_name" /> it throws with the following error does not have a property matching the name '#bind' but this <input type="text" #bind="#_name" /> works, why?
It's possible to do <input type="text" #bind="#_name" #bind:event="oninput" /> but doing <RawCustomInput #bind-Value="#_name" #bind-Value:event="oninput" /> throws with the following error does not have a property matching the name 'oninput', why? to achieve a similar thing I have to introduce additional property as I pointed above but then it's seems a bit odd that for bind:event I need to provide the name of the event whereas for bind-{Property}:event I need to pass a property which might makes sense but I'm not sure I understand the reason.
First you need to understand the difference between components and elements.
<input type="text" value="#Value" .. is an element declaration. It's html code.
<RawCustomInput #bind-Value="#_name" /> is a component declaration.
Second, you are defining stuff in the Razor language, not true C#. #bind and #bind-value are directives to the Razor compiler, and are handled differently.
The #bind is used to bind elements. It gets compiled by the Razor compiler into C# code like this:
__builder.AddAttribute(19, "value", BindConverter.FormatValue(this.surName));
__builder.AddAttribute(20, "onchange", EventCallback.Factory.CreateBinder(this, __value => this.surName = __value, this.surName));
Note that it uses various factory classes to build the "getter" to provide a correctly formatted string to the input value and a "setter" to handle the generated onchange event from the input to update the variable.
In comparison, this is the code for the component #bind-value on a component
__builder.AddAttribute(11, "Value", RuntimeHelpers.TypeCheck<String>(this.firstName));
__builder.AddAttribute(12, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<String>>(EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => this.firstName = __value, this.firstName))));
It's setting the value of the Parameter Value and creating an anonymous function to set the value from the eventcallback ValueChanged. There is no direct wiring into the underlying input element declared within the component. It's setting the component parameter and sinking the component event. How they are wired up is up to you the designer.
To demonstrate, here's a very different component wiring for a "checkbox":
<button class="#this.ButtonCss" #onclick=this.OnValueChanged>#this.ButtonText</button>
#code {
[Parameter] public bool Value { get; set; }
[Parameter] public EventCallback<bool> ValueChanged { get; set; }
private string ButtonCss => Value ? "btn btn-success": "btn btn-danger";
private string ButtonText => Value ? "Enabled" : "Disabled";
private async Task OnValueChanged()
=> await ValueChanged.InvokeAsync(!this.Value);
}
When you declare your components as Razor Components you are restricted by the Razor language. For 99% of components this is Ok. But, if you want to do more exotic stuff, you need to drop back to writing your component directly as a C# class.
This shows one way of handling onchange/oninput.
#if(this.ChangeOnInput)
{
<input type="text" value="#Value" #oninput="OnValueChanged" class="form-control" />
}
else
{
<input type="text" value="#Value" #onchange="OnValueChanged" class="form-control" />
}
#code {
[Parameter] public string? Value { get; set; }
[Parameter] public bool ChangeOnInput { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
I have a modal component in Blazor that I am attempting to make more reusable. The modal has a base class which implements methods for Showing/Hiding the modal, and callbacks for Show and Hide.
public class Modalbase : ComponentBase
{
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public Action? Closed { get; set; }
[Parameter] public Action? Opened { get; set; }
protected bool show;
// ANCHOR - public methods
public void Hide()
{
show = false;
Closed?.Invoke();
this.StateHasChanged();
}
public void Show()
{
show = true;
Opened?.Invoke();
this.StateHasChanged();
}
}
For the razor implementation, I have a very simple component which provides the markup and renders content provided. This is in Modal.razor:
#inherits Modalbase
#if (this.show)
{
<div class='modal-container #(this.show ? "show" : "hidden")'>
<div class="overlay" #onclick="Hide" style="cursor:pointer;"></div>
<div class='modal'>
<div id="close-container" #onclick="Hide">
<div class="close"><i class="fa-solid fa-x fa-md"></i></div>
</div>
#this.ChildContent
</div>
</div>
}
which is then used like this:
<Modal #ref="modalRef">
<div>childcontent here</div>
</Modal>
#code {
Modal modalRef;
public void Show()
{
modalRef.Show();
}
}
The problem with this is that across many implementations of Modals, I have to keep implementing the Show and Hide methods. Those methods are on the Modal component reference, and not in the reference to the implementation of Modal.
Is there a way with OOP to have these methods available directly on the implementation of the Modal component, but also allow them to be overriden if something additional needs to be done before opening/closing the modal?
A workaround is just making modalRef public in implementations, and calling the methods directly from there. Like: childModal.modalRef.Show() But that feels like it could cause issues in cases where maybe I want to check if the user is logged in before showing the modal.
I am trying to two-way bind a text area inside a child component in Blazor and I just can't figure it out.
Parent
#page "/test"
<h3>Parent Component</h3>
<input type="text" #bind="mydata" />
<TWBTextArea #bind-ChildData=#mydata></TWBTextArea>
#code {
public string mydata = "test";
}
Child
<h4>Child Component</h4>
<textarea #bind=#ChildData></textarea>
#code {
[Parameter] public string ChildData { get; set; }
[Parameter]
public EventCallback<string> ChildDataChanged { get; set; }
}
When I update from the parent component, the child textarea updates, but when I update the child text area, the parent is not updated.
Additional note : If I change the value being passed from a string to an object with a string property and I pass that object to the Child Component, two way binding DOES work but only after an update to the parent component.
Thanks in advance for any help!
Important: You should not bind to components' parameters as it may have side-effects on your app. Read this post by Steve Sanderson
Note that I define a local variable, named data, into which I assign the ChildData parameter property's value from the OnParametersSet method. That is done, as I've said before, in order to refrain from binding to a component's parameter.
Since we are creating a two-way data-binding, the value attribute of the textarea element is bound to the variable data. The flow of data is from the variable to the element. We also need to create an event handler, named here HandleOnChange, whose role is to update the local variable data, as well as to invoke the EventCallback 'delegate', passing the new value stored in the data variable. This value is gladly received in the parent component's mydata field, after which, a re-rendering occurs to reflect the new changes.
Note that I'm using the input event, instead of the change event, to make life easier and more interesting.
Child component
<h4>Child Component</h4>
<textarea #oninput="HandleOnChange">#data</textarea>
#code {
private string data;
[Parameter] public string ChildData { get; set; }
[Parameter]
public EventCallback<string> ChildDataChanged { get; set; }
private async Task HandleOnChange(ChangeEventArgs args)
{
data = args.Value.ToString();
await ChildDataChanged.InvokeAsync(data);
}
protected override void OnParametersSet()
{
data = ChildData;
base.OnParametersSet();
}
}
Usage
#page "/test"
<h3>Parent Component</h3>
<input type="text" #bind="mydata" #bind:event="oninput" />
<ChildComponent #bind-ChildData="mydata" />
#code {
private string mydata = "test";
}
I seem to have it working now by inheriting InputBase and binding to the CurrentValue property, but it was definitely something of an uphill battle.
#inherits InputBase<string>
<textarea class="#Class" #bind="CurrentValue"
#attributes="AdditionalAttributes" />
#code
{
[Parameter]
public string Id { get; set; }
[Parameter]
public string Class { get; set; }
protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)
{
result = value;
validationErrorMessage = null;
return true;
}
}
Let's say I have a collection of data in my page loaded via OnInitializedAsync(). The data is shown graphically in a table but later on also in more detail in another table further down on the page.
Since the rows in the detailed table has a lot of controls and logic I decided to make a component for the row e.g. <RowData Data="#rowdata" /> and bound each row data.
The problem is that if the data gets changed in my child controller (RowData) it won't reflect in my first table in the "parent" component where the same data is also listed.
Is there an easy way to signal change or should I avoid making child components?
I have sovled it by making an EventCallback in my child component and updating via callback in my parent component. But I have the feeling I'm missing something.
The following sample shows how to perform two-way data-binding between a parent
component and its child component. In each of these two component is a text box controls. When you type text in the parent component's text box, the text in the child component's text box changes to reflect the changes made in the parent,
and vice versa...
ChildComponent.razor
<div style="border:solid 1px red">
<h2>Child Component</h2>
<input type="text" #bind="Text" #bind:event="oninput" />
</div>
#code {
private string text { get; set; }
[Parameter]
public string Text
{
get { return text; }
set
{
if (text != value) {
text = value;
if (TextChanged.HasDelegate)
{
TextChanged.InvokeAsync(value);
}
}
}
}
[Parameter]
public EventCallback<string> TextChanged { get; set; }
}
ParentComponent.razor
#page "/ParentComponent"
<h1>Parent Component</h1>
<input type="text" #bind="Text" #bind:event="oninput" />
<p></p>
<ChildComponent #bind-Text="Text" />
#code {
[Parameter]
public string Text { get; set; } = "Hello Blazor";
}
I have sovled it by making an EventHandler in my child component and updating via callback. But I have the feeling I'm missing something
What you've been missing is the existence of the EventCallback 'delegate' used in this sample to call the parent component and pass it the value entered in the child component. This is how we define the 'delegate'
[Parameter]
public EventCallback<string> TextChanged { get; set; }
And this is how we invoke it, when the value of the Text property changes:
TextChanged.InvokeAsync(value);
What delegate did you use ? Note that the EventCallback's target is not the child component, but the parent component...
Good luck... If something is not clear, don't hesitate to ask...
If you have a root component with N levels of nested components (children within children ad-nauseam) then you can use a cascading value. Try something like this
public class MyState
{
public List<MyObject> Objects { get; set; }
public Action OnModified { get; }
public MyState(List<MyObject> objects, Action onModified)
{
Objects = objects;
OnModified = onModified;
}
}
In your parent component
MyState State;
protected override OnInitialized()
{
State = new MyState(your objects, () => InvokeAsync(StateHasChanged));
}
In your parent markup
<CascadingValue Value=State>
All your child content here
</CascadingValue>
In your various child components that need access
[CascadingParameter]
public MyState State { get; set; }
protected void SomeEditWasMade()
{
State.Objects[23].Name = "Bob";
State.OnModified();
}
That should call the () => InvokeAsync(StateHasChanged) in the parent, and then that component and every component that consumes the MyState cascading value will get rerendered.
Or you could use something like Fluxor :)
I was watching a tutorial in Blazor. Then I came across this code and I can't seem to find it in the Internet or I think I'm not using the right terms for searching atleast.
#code{
[Parameter]
public IList<Todo> Todo {get; set;}
}
Is it only exclusive in blazor or it is available in c#.
Kindly give some references. Thanks in advance.
This is explained in Create and use ASP.NET Core Razor components, specifically in the Component Parameters section.
[Parameter] is used to mark the component parameters that can be set when the component is used in another page. Borrowing from the doc example this component doesn't have any parameters :
<div class="panel panel-default">
<div class="panel-heading">#Title</div>
<div class="panel-body">#ChildContent</div>
<button class="btn btn-primary" #onclick="OnClick">
Trigger a Parent component method
</button>
</div>
#code {
public string Title { get; set; }
public RenderFragment ChildContent { get; set; }
public EventCallback<MouseEventArgs> OnClick { get; set; }
}
Without the [Parameter] attribute, those are just public properties that can't be set from other pages. The following line would be invalid :
<ChildComponent Title="Panel Title from Parent" />
While this :
<div class="panel panel-default">
<div class="panel-heading">#Title</div>
<div class="panel-body">#ChildContent</div>
<button class="btn btn-primary" #onclick="OnClick">
Trigger a Parent component method
</button>
</div>
#code {
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }
}
Allows us to set the parameters whenever we use that component :
<ChildComponent Title="Panel Title from Parent"
OnClick="#ShowMessage">
Content of the child component is supplied
by the parent component.
</ChildComponent>
All attributes in C# have to reference a type defining that attribute somewhere. That Blazor code is still C#.
In this case, I believe it it refers to Microsoft.AspNetCore.Components.ParameterAttribute - the documentation is currently MIA, but that may well improve over time. There's more detail in the Blazor documentation.
In general, if you have the code in front of you (generally a good idea when watching a tutorial, if at all possible) you can hover over the attribute in Visual Studio to see its fully qualified name or navigate to it.