I'm doing a Blazor app, although I have the impression the question is C# and independent to blazor.
I have a class with some strings (like Person.Name, Person.Surname, etc)
I have a person viewer component that shows those strings. And a "string editor" component so when the user clicks on the name of the patient, the string editor component will appear and allow you to edit the name (It is not exactly like that, but will help us simplify the scenario and find a solution)
The question is how to send a reference to Person.Name to the StringEditor component, so the original string is also edited when it is edited there. If I was an object is simple, you send the object and just a reference is sent so changes done in the component apply to the original source object but for strings... this does not work like this. A copy is sent, and the changes are not carried over to the original component.
The scenario would look a bit like :
Person class
public class Person{
public string Name {get;set;} = "";
public string Surname {get;set;} = "";
}
Main razor component
<div>
<div #onclick=()=>{OpenStringEditor(user.Name)}>#user.Name</div>
<div #onclick=()=>{OpenStringEditor(user.Surname)}>#user.Surname</div>
</div>
<div class=#(ShowEditor ? "ShowEditor" : "HideEditor")>
<StringEditor StringToEdit EditFinished=whenFinished/>
</div>
code{
[Parameter] pubic Person user {get; set;}
private void OpenStringEditor(string ToEdit)
{
StringToEdit = ToEdit; // This is where I'd need to keep a reference
// to the original source, so when the
// edit is finished I can store the end product
ShowEditor = true;
}
private void whenFinished()
{
Person.StoreToDB(); //whatever
ShowEditor = false;
}
}
StringEditor
<input #bind=ToBeEdited #onfocusout=EditFinished />
code
{
[Parameter] public string ToBeEdited;
[Parameter] public EventCallback EditFinished;
}
So you see, this would work for objects, but not for raw strings
I guess the bottom question would be: how to keep a reference of "where to store the changes when the edit is finished."
Do this fit the bill?
First you need a Modal Dialog Framework. I've included a simple one - the code is in appendix to this answer, but you can use any of those available that are suitable.
A "Text Editor" component which we will use in the modal dialog. If you're editing other data types you will need different ones. It interacts with the cascaded instance of the Modal Dialog wrapper.
TextEditor.razor
<div class="mb-3">
<label class="form-label small text-muted">#this.Label</label>
<div class="input-group">
<input class="form-control" value="#this.Value" #oninput=this.Changed />
<button class="btn btn-success" #onclick=Exit>Set</button>
<button class="btn btn-danger" #onclick=Cancel>Cancel</button>
</div>
</div>
#code {
[CascadingParameter] private IModalDialog? Modal { get; set; }
[Parameter] public string Label { get; set; } = "Field";
[Parameter] public string? Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public Expression<Func<string>>? ValueExpression { get; set; }
private string? _value;
protected override void OnInitialized()
=> _value = Value;
private void Changed(ChangeEventArgs e)
=> this.ValueChanged.InvokeAsync(e.Value?.ToString() ?? null);
private void Exit()
=> Modal?.Close(ModalResult.OK());
private async Task Cancel()
{
await this.ValueChanged.InvokeAsync(_value);
Modal?.Close(ModalResult.Cancel());
}
}
And then our "Special Format" page:
#page "/"
<PageTitle>Index</PageTitle>
<h1 class="mb-5">Hello, world!</h1>
<div class="mt-5 mb-5" >
Editor
</div>
<div class="mt-5 border border-1 border-dark p-1" style="cursor:pointer;" #onclick="() => Edit(CountryModalOptions)">
#model.Country
</div>
<div class="mt-2 border border-1 border-dark p-1" style="cursor:pointer;" #onclick="() => Edit(ContinentModalOptions)">
#model.Continent
</div>
<BaseModalDialog #ref=this.Modal />
#code {
private IModalDialog? Modal;
private Model model = new();
private async Task Edit(ModalOptions options)
{
if (Modal is not null)
{
var result = await Modal.ShowAsync<TextEditor>(options);
// do things
}
}
private ModalOptions CountryModalOptions
{
get
{
var options = new ModalOptions();
options.ControlParameters.Add("Label", "Field Name");
options.ControlParameters.Add("Value", model.Country);
options.ControlParameters.Add("ValueChanged", EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => model.Country = __value, model.Country)));
options.ControlParameters.Add("ValueExpression", RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<System.String>>>(() => model.Country));
return options;
}
}
private ModalOptions ContinentModalOptions
{
get
{
var options = new ModalOptions();
options.ControlParameters.Add("Label", "Field Name");
options.ControlParameters.Add("Value", model.Continent);
options.ControlParameters.Add("ValueChanged", EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => model.Continent = __value, model.Continent)));
options.ControlParameters.Add("ValueExpression", RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<System.String>>>(() => model.Continent));
return options;
}
}
public class Model
{
public string Country { get; set; } = "No Country Set";
public string Continent { get; set; } = "No Continent Set";
}
}
So what's going on.
We have our BaseModalDialog component registered on the page with a global variable reference. When you click on an editable field the onclick handler opens the modal dialog by calling ShowAsync. It sets the component as T and passes in a set of Parameters to apply to T when it's defined. The standard bind Parameters are mapped to the model field using some RuntimeHelpers.
The dialog opens and you can edit the value. I've wired it up to update on each keypress (oninput). Click on either Set or Cancel to exit the model as pass "control" back to the main page.
I've kept everything as basic as I can, there are all sorts of Css and formating options you can apply to improve the UX.
Appendix The Modal Dialog
ModalOptions
public sealed class ModalOptions : IEnumerable<KeyValuePair<string, object>>
{
public string Width { get; set; } = "50%";
public Dictionary<string, object> ControlParameters { get; } = new Dictionary<string, object>();
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
foreach (var item in ControlParameters)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
public T? Get<T>(string key)
{
if (this.ControlParameters.ContainsKey(key))
{
if (this.ControlParameters[key] is T t) return t;
}
return default;
}
public bool TryGet<T>(string key, [NotNullWhen(true)] out T? value)
{
value = default;
if (this.ControlParameters.ContainsKey(key))
{
if (this.ControlParameters[key] is T t)
{
value = t;
return true;
}
}
return false;
}
public bool Set(string key, object value)
{
if (this.ControlParameters.ContainsKey(key))
{
this.ControlParameters[key] = value;
return false;
}
this.ControlParameters.Add(key, value);
return true;
}
}
ModalResult
public enum ModalResultType { NoSet, OK, Cancel, Exit }
public sealed class ModalResult
{
public ModalResultType ResultType { get; private set; } = ModalResultType.NoSet;
public object? Data { get; set; } = null;
public static ModalResult OK() => new ModalResult() { ResultType = ModalResultType.OK };
public static ModalResult Exit() => new ModalResult() { ResultType = ModalResultType.Exit };
public static ModalResult Cancel() => new ModalResult() { ResultType = ModalResultType.Cancel };
public static ModalResult OK(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.OK };
public static ModalResult Exit(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Exit };
public static ModalResult Cancel(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Cancel };
}
IModalDialog
public interface IModalDialog
{
public ModalOptions Options { get; }
public bool IsActive { get; }
public bool Display { get; }
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent;
public void Dismiss();
public void Close(ModalResult result);
public void Update(ModalOptions? options = null);
}
BaseModalDialog.razor
#namespace Blazr.UI
#implements IModalDialog
#if (this.Display)
{
<CascadingValue Value="(IModalDialog)this">
<div class="base-modal-background">
<div class="base-modal-content" style="width:#this.Options.Width;" #onclick:stopPropagation="true">
<DynamicComponent Type=this.ModalContentType Parameters=this.Options.ControlParameters />
</div>
</div>
</CascadingValue>
}
#code {
public ModalOptions Options { get; protected set; } = new ModalOptions();
public bool Display { get; protected set; }
public bool IsActive => this.ModalContentType is not null;
protected TaskCompletionSource<ModalResult> _ModalTask { get; set; } = new TaskCompletionSource<ModalResult>();
protected Type? ModalContentType = null;
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent
{
this.ModalContentType = typeof(TModal);
this.Options = options ??= this.Options;
this._ModalTask = new TaskCompletionSource<ModalResult>();
this.Display = true;
InvokeAsync(StateHasChanged);
return this._ModalTask.Task;
}
public void Update(ModalOptions? options = null)
{
this.Options = options ??= this.Options;
InvokeAsync(StateHasChanged);
}
public async void Dismiss()
{
this._ModalTask.TrySetResult(ModalResult.Cancel());
await Reset();
}
public async void Close(ModalResult result)
{
this._ModalTask.TrySetResult(result);
await Reset();
}
private async Task Reset()
{
this.Display = false;
this.ModalContentType = null;
await InvokeAsync(StateHasChanged);
}
}
BaseModalDialog.razor.css
div.base-modal-background {
display: block;
position: fixed;
z-index: 101; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
div.base-modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 10px;
border: 2px solid #888;
width: 50%;
}
Related
I understand OnInitializedAsync is running once before the component is loaded. However, I am passing in a variable to the API and I would like to have it rerender the UI once the variable is changed. I am just not sure what the best way to go about it is.
#page "/"
#inject HttpClient Http
<h1>Pokemon</h1>
<p></p>
#if (pokemons == null)
{
<p><em>Loading...</em></p>
}
else
{
#foreach (var n in pokemons.results)
{
<div class="card" style="width: 18rem;">
<img src=#n.url class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">#n.name</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Go somewhere
</div>
</div>
}
#pokemons.count
<br />
#offset
<nav aria-label="Page navigation example">
<ul class="pagination">
#for (int b = 0; b < pokemons.count; b += 20)
{
int local_b = b;
<li class="page-item"><button class="page-link" #onclick="() => Paginate(local_b)">#b</button></li>
}
</ul>
</nav>
}
#code {
private PokemonList pokemons;
private PokemonDetail pokemonDetails;
private int limit = 20;
private int offset = 0;
public void Paginate(int value)
{
Console.WriteLine(value);
this.offset = value;
InvokeAsync(StateHasChanged);
}
protected override async Task OnInitializedAsync()
{
pokemons = await Http.GetFromJsonAsync<PokemonList>($"https://pokeapi.co/api/v2/pokemon/?offset={offset}&limit={limit}");
foreach (var p in pokemons.results)
{
pokemonDetails = await Http.GetFromJsonAsync<PokemonDetail>(p.url);
p.url = pokemonDetails.sprites.front_default;
}
}
public class PokemonList
{
public int count { get; set; }
public string next { get; set; }
public List<Pokemon> results { get; set; }
}
public class Pokemon
{
public string name { get; set; }
public string url { get; set; }
}
public class PokemonDetail
{
public PokemonSprites sprites { get; set; }
}
public class PokemonSprites
{
public string front_default { get; set; }
}
}
You can have a function for your API call and can call this function whenever you want
private async Task FetchPokemonList()
{
pokemons = await Http.GetFromJsonAsync<PokemonList>($"https://pokeapi.co/api/v2/pokemon/?offset={offset}&limit={limit}");
foreach (var p in pokemons.results)
{
pokemonDetails = await Http.GetFromJsonAsync<PokemonDetail>(p.url);
p.url = pokemonDetails.sprites.front_default;
}
}
And can call FetchPokemonList in OnInitializedAsync
protected override async Task OnInitializedAsync()
{
await FetchPokemonList();
}
I want to be able to take in a list of blazor components as a parameter in a declarative way.
Component 1:
#foreach(Component2 input in Inputs)
{
#input
}
#code{
[Parameter]
public List<Component2> Inputs {get; set;}
}
Component 2:
<h1>#Header</h1>
#code{
[Parameter]
public string Header {get; set;}
}
How I want to use it:
<Component1>
<Inputs>
<Component2 Header="hello" />
<Component2 Header="world" />
</Inputs>
</Component1>
Does anyone know if this is possible?
Thank you,
Travis
One option is to override BuildRenderTree. Here we define a MessagesComponent that is supplied with a list of MessageComponents, each of which has a single message:
// YourPage.Razor
<p>Here are some messages:</p>
<MessagesComponent MessageComponents="messageComponents" />
#code {
private readonly IEnumerable<MessageComponent> messageComponents = new List<MessageComponent>
{
new MessageComponent { Message = "Hello" },
new MessageComponent { Message = "World" }
};
}
// MessagesComponent.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace WebApplication3.Pages
{
public class MessagesComponent : ComponentBase
{
[Parameter]
public IEnumerable<MessageComponent> MessageComponents { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
// Render each component.
foreach (var component in MessageComponents)
{
builder.OpenComponent(0, typeof(MessageComponent));
builder.AddAttribute(1, nameof(MessageComponent.Message), component.Message);
builder.CloseComponent();
}
}
}
}
// MessageComponent.razor
#using Microsoft.AspNetCore.Components.Rendering
<p>This message says "#Message"</p>
#code {
[Parameter]
public string Message { get; set; }
}
This outputs like so:
While digging around the web I found a link to a Blazor University page that described this perfectly.
https://blazor-university.com/templating-components-with-renderfragements/creating-a-tabcontrol/
https://github.com/mrpmorris/blazor-university/tree/master/src/TemplatedComponents/CreatingATabControl
The gist of the solution is you send a reference of the parent to the child component. Then when the child is initialized you tell the parent to add the child to the list of components.
Parent Component:
<CascadingValue Value="this">
<div class="btn-group" role="group">
#foreach (Tab tabPage in Pages)
{
<button type="button"
class="btn #GetButtonClass(tabPage)"
#onclick=#( () => ActivatePage(tabPage) )>
#tabPage.Text
</button>
}
</div>
#ChildContent
</CascadingValue>
#code {
// Next line is needed so we are able to add <TabPage> components inside
[Parameter]
public RenderFragment ChildContent { get; set; }
public Tab ActivePage { get; set; }
List<Tab> Pages = new List<Tab>();
internal void AddPage(Tab tabPage)
{
Pages.Add(tabPage);
if (Pages.Count == 1)
ActivePage = tabPage;
StateHasChanged();
}
string GetButtonClass(Tab page)
{
return page == ActivePage ? "btn-primary" : "btn-secondary";
}
void ActivatePage(Tab page)
{
ActivePage = page;
}
}
Child Component:
#if (Parent.ActivePage == this)
{
#ChildContent
}
#code {
[CascadingParameter]
private Tabs Parent { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Text { get; set; }
protected override void OnInitialized()
{
if (Parent == null)
throw new ArgumentNullException(nameof(Parent), "TabPage must exist within a TabControl");
base.OnInitialized();
Parent.AddPage(this);
}
}
Usage:
<ParentComponent>
<ChildComponent Text="Tab 1">
<h1>This is a test</h1>
</ChildComponent >
<ChildComponent Text="2">
<h3>This is a test</h3>
</ChildComponent >
<ChildComponent Text="3 Tab">
<h5>This is a test</h5>
</ChildComponent >
</ParentComponent>
Here's a simplistic but more generic way to achieve what you want. It renders three instances of your HeaderComponent and one Counter.
#page "/"
<h3>MultiHeader</h3>
#MyComponents
#code {
private List<Tuple<Type, Dictionary<string, object>>> _components = new List<Tuple<Type, Dictionary<string, object>>>();
protected override void OnInitialized()
{
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 1" } }));
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 2" } }));
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 3" } }));
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(Counter), new Dictionary<string, object>()));
}
private RenderFragment MyComponents => builder =>
{
foreach (var component in _components)
{
builder.OpenComponent(0, component.Item1);
foreach (var parameter in component.Item2)
{
builder.AddAttribute(1, parameter.Key, parameter.Value);
}
builder.CloseComponent();
}
};
}
I've used a Tuple to keep things simple: you could have an object to hold your component information if you want to get a bit more complex.
I have a custom component InlineAutocomplete. No matter what I do my pic value and SelectedPIC value are always null when I do validation and unsure why it is.
The component does render correctly, but can't get my parameter back with the values set correctly.
Usage as follow:
<InlineAutocomplete TItem="PIC"
Value="#pic"
Selected="#SelectedPIC"
Data="#PICs"
Property="#((item) => item.PICNumber)" />
My component:
<div class="inline-autocomplete">
<input #oninput="(e)=> SuggestItem(e)" type="text" #bind-value="#Value" class="overlap inline-autocomplete-input " />
<input readonly class="overlap inline-autocomplete-label" value="#_suggestedValue" />
</div>
#code {
string _value;
string _suggestedValue;
[Parameter]
public string Value
{
get => _value;
set
{
if (value == null)
return;
_value = value;
}
}
[Parameter]
public List<TItem> Data { get; set; }
[Parameter]
public Func<TItem, string> Property { get; set; }
[Parameter]
public TItem Selected { get; set; }
void SuggestItem(ChangeEventArgs e)
{
if (string.IsNullOrEmpty(e.Value.ToString()))
return;
Value = e.Value.ToString().ToUpper();
if (string.IsNullOrEmpty(Value))
{
_suggestedValue = string.Empty;
}
else
{
var selectedItem = Data.Select(Property).FirstOrDefault(x => x.StartsWith(Value, StringComparison.OrdinalIgnoreCase));
_suggestedValue = !string.IsNullOrWhiteSpace(selectedItem) ? selectedItem : string.Empty;
Selected = Data.FirstOrDefault(x => string.Equals(Property(x), Value, StringComparison.OrdinalIgnoreCase));
StateHasChanged();
}
// State has changed
StateHasChanged();
}
}
The solution to this was to pass down a function that changes the property in the Parent instead of the Child.
Child:
[Parameter]
public Action<string, TItem> ValueChanged { get; set; }
...
#code {
void SomeFunction() {
ValueChanged(Value, Selected);
}
}
Parent:
<InlineAutocomplete ... ValueChanged="FireEvent" />
void FireEvent(string stringVal, MyClass c)
{
// ... Do Something
}
My situation is this: I'm trying to implement and Autocomplete.
The Autocomplete will have a Parameter that will receive a string and return a IEnumerable<TValue>.
Here is an example of what I'm trying to do
Autocomplete.razor
#code {
[Parameter]
public SOME_TYPE GetItems { get; set; }
async void Foo(){
IEnumerable<TValue> items = await GetItems(SomeString);
// do something with items
}
}
ParentComponent.razor
<Autocomplete TValue="SomeEntity"
GetItems="#GetItems" />
#code {
SOME_TYPE GetItems(string name) {
IEnumerable<SomeEntity> entity = await GetEntitys(name);
return entity;
}
}
The problem is that I don't know what to put in SOME_TYPE. Should I use EventCallback? Action? What should I use?
I tried using EventCallback but looks like I can't get a return value from EventCallback? I have no idea.
I just find out how to do it, I should use Func<string, Task<IEnumerable<TValue>>>.
[Parameter]
public Func<string, Task<IEnumerable<TValue>>> GetItems { get; set; }
And
public async Task<IEnumerable<Employee>> GetItems(string name) {
IEnumerable<SomeEntity> entity = await GetEntitys(name);
return entity;
}
You can also use argument of EventCallback for that:
public class CancelArg
{
public bool Value { get; set; }
}
[Parameter]
public EventCallback<CancelArg> OnFoo { get; set; }
async Task CallerInChild()
{
var cancel = new CancelArg();
await OnFoo.InvokeAsync(cancel);
if (cancel.Value)
{
// cancelled ...
}
}
void HandlerInParent(CancelArg cancel)
{
cancel.Value = true;
}
In a child component:
<div class="form-group">
<label for="ddSetStatus">Status</label>
<InputSelect class="form-control" id="ddSetStatus" #bind-Value="#Status" #oninput="#((e) => ChangedSelection(e))">
<option value="1">Draft </option>
<option value="2">Published </option>
<option value="3">Archived </option>
</InputSelect>
</div>
#code
{
[Parameter] public EventCallback<int> OnStatusSelected { get; set; }
[Parameter] public int Status { get; set; }
private async Task ChangedSelection(ChangeEventArgs args)
{
await OnStatusSelected.InvokeAsync(int.Parse(args.Value.ToString()));
}
}
and then consuming the selected value in a parent page or component:
create a component (called DocStatus in this example) and define the event handling method
in markup area:
<DocStatus Status="#Status" OnStatusSelected="StatusSelectionHandler"/>
in the code area:
private void StatusSelectionHandler(int newValue)
{
Status = newValue;
}
I have this "Alert" component:
#if (Show)
{
<div class="alert #Class" role="alert">
#Text
</div>
}
#functions
{
[Parameter]
private bool Show { get; set; } = false;
[Parameter]
private string Text { get; set; } = String.Empty;
[Parameter]
private string Class { get; set; } = String.Empty; //Success, Warning etc.
}
However, when I call this component on my page I still need to create atleast two variables - ShowError and ErrorText - to handle the state of this alert still clutters my code a lot as this alert exists on practically all pages.
My question is: Is it possible to de-clutter the code by calling a ShowMessage method in the child component?
An example would be something like this:
Page
#page "/my-page"
#inject HttpClient Http
<!-- A lot of HTML code here -->
<Alert/>
<!-- A lot of HTML code here -->
#functions {
protected override async Task OnInitAsync()
{
var response = await Http.PostJsonAsync<Response>("/api/sessions/create", null);
if (response.StatusCode == HttpStatusCode.OK)
{
}
else
{
myAlertComponent.ShowSuccessMessage(response.Message);
}
}
}
"Alert" component
#if (Show)
{
<div class="alert #Class" role="alert">
#Text
</div>
}
#functions
{
[Parameter]
private bool Show { get; set; } = false;
[Parameter]
private string Text { get; set; } = String.Empty;
[Parameter]
private string Class { get; set; } = String.Empty; //Success, Warning, Danger
public void HideAlerts()
{
Show = false;
}
public void ShowSuccessMessage(string message)
{
Show = true;
Text = message;
Class = "success":
}
public void ShowErrorMessage(string message)
{
Show = true;
Text = message;
Class = "danger":
}
}
To call component methods try adding a reference to the component with #ref and then adding the component declaration in the #code block. If the methods are public in the component you can use them outside of the component's scope.
Parent.razor
<Alert #ref="Alert" Text="I am showing" /> #*Notice the #ref tag;*#
<button #onclick="() => ShowAlert(true)">Show Success</button>
<button #onclick="() => ShowAlert(false)">Show Failure</button>
<button #onclick="HideAlert">Hide</button>
#code {
private Alert Alert { get; set; } // This is where the #ref is bound; no need to initialize, the markup does that for you.
private void ShowAlert(bool success)
{
if (success) Alert.ShowSuccessMessage("Success!!");
else Alert.ShowErrorMessage("Failed :(");
}
private void HideAlert() => Alert.Hide();
}
Alert.razor
#if (_show)
{
<div class="alert alert-#_class" role="alert">
<strong>#Text</strong>
</div>
}
#code
{
[Parameter] public string Text { get; set; } = string.Empty; // set as "I am showing", but will be updated if a message is passed to the show methods below
private bool _show { get; set; }
private string _class { get; set; } = string.Empty;
public void Hide() { _show = false; StateHasChanged(); }
public void ShowSuccessMessage(string message? = null)
{
_show = true;
Text = message ?? Text ?? string.Empty;
_class = "success";
StateHasChanged(); // StateHasChanged() informs the parent component that the child has updated and to update the dom
}
public void ShowErrorMessage(string? message = null)
{
_show = true;
Text = message ?? Text ?? string.Empty;
_class = "danger";
StateHasChanged();
}
}
See Section: Capture references to components