So I am building a component library in blazor and I am adding support for the input components to have Blazor built in validation.
#inherits WindyInputTextBase;
#if(!string.IsNullOrEmpty(Label))
{
<label for="#Id" class="block text-sm font-medium">#Label</label>
}
<input class="mt-3 p-2 focus:outline-none focus:ring focus:ring-primary-light focus:border-primary block w-full shadow-sm sm:text-sm border border-gray-300 rounded-md #CssClass" id="#Id" name="#Id" #bind="#CurrentValue" />
<!--Div is empty when validation is not done.-->
<div class="text-danger text-sm mb-3">
<ValidationMessage For="#ValidationFor" />
</div>
The base class for the input component:
public class WindyInputTextBase : WindyInputBase<string>
{
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out string result, [NotNullWhen(false)] out string? validationErrorMessage)
{
result = value is null ? string.Empty : value;
validationErrorMessage = null;
return true;
}
}
And the base class of that one:
public abstract class WindyInputBase<T> : InputBase<T>
{
[Parameter]
public string? Label { get; set; }
[Parameter]
public Expression<Func<T>>? ValidationFor { get; set; }
}
The component (and others using the same base) work fine, but I get this error when using them outside an EditForm component:
Unhandled exception rendering component: WindyUI.Form.EditForm.WindyInputText requires a cascading parameter of type EditContext. For example, you can use WindyUI.Form.EditForm.WindyInputText inside an EditForm.
System.InvalidOperationException: WindyUI.Form.EditForm.WindyInputText requires a cascading parameter of type EditContext. For example, you can use WindyUI.Form.EditForm.WindyInputText inside an EditForm.
This is fine and expected, but forces me to write two sets of Input components, one that has validation and one that doesn't. I know MudBlazor does what I am looking to do, but I don't know how they do it exactly and their source code isn't all to clear for me since there is no inheritance from InputBase.
Is there a way to create a component with blazor built-in validation that can be used both inside and outside an EditForm component? Of course, this means that no built in validation is fine when not usig the components inside an EditForm.
Thanks,
Related
I have a base component PetTemplate and a second PetDog that inherits and uses the template of PetTemplate. PetTemplate has a method named ToggleDisplay. My goal is when I click the button on the Index page that invokes the PetDog.ToggleDisplay method and show/hide the PetDog details on the page.
The "Inside" button in the sample code below works but "Outside" button don't. How can I invoke the ToggleDisplay method from a page or a parent component correctly?
Index.razor
#page "/"
<button #onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
PetDog.razor
#inherits PetTemplate
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
PetTemplate.razor
<div class="mt-3">
<button #onclick="ToggleDisplay">Show Details (Inside)</button>
<h3>Pet Name: #Name</h3>
<div style="display:#display">
#ChildContent
</div>
</div>
#code {
string display = "none";
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
StateHasChanged();
}
}
When you use
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.ToggleDisplay();
}
}
You actually create a reference to the PetDog component, and then try to call a derived method, dog.ToggleDisplay(), on object you have no reference to ( the instance of the PetTemplate). In order to make it work, you'll have to get a reference to the parent component (PetTemplate), and provide it to the derived component (PetDog), like this:
PetTemplate.razor
<div class="mt-3">
<button #onclick="ToggleDisplay">Show Details (Inside)</button>
<h3>Pet Name: #Name</h3>
<div style="display:#display">
#ChildContent
</div>
</div>
#code {
string display = "none";
string val;
[Parameter]
public string Name { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
InvokeAsync(() => StateHasChanged());
}
}
PetDog.razor
#inherits PetTemplate
<PetTemplate #ref="petTemplate" Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
#code
{
PetTemplate petTemplate;
public PetTemplate PetTemplateProp { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if(firstRender)
{
PetTemplateProp = petTemplate;
}
base.OnAfterRender(firstRender);
}
}
Index.razor
#page "/"
<button #onclick="ShowPetDetails">Show Details (Outside)</button>
<PetDog #ref="dog" />
#code {
PetDog dog;
void ShowPetDetails()
{
dog.PetTemplateProp.ToggleDisplay();
}
}
Note: Though Razor components are C# classes, you cannot treat them as normal classes. They behave differently. As for instance, you can't define a variable instance, and set its parameters, etc. outside of the component. At best, you can capture a reference to a component as well as call public methods on the component instance, as is done in the current sample. In short, component objects differ from normal classes.
It's also important to remember that each component is a separate island that can render independently of its parents and children.
But just wondering how can I change a component parameter value from outside of it, that inherited/uses a template. I tried the methods in the documentation or the resources I found, but it didn't work for my case
You should not (it was a warning) and probably cannot ( it may be now an error) change a component parameter's value outside of the component. As for instance, you can't capture a reference to a component and assign a value to its parameter property:
<PetTemplate #ref="petTemplate">
<div>Someone's best friend!</div>
</PetTemplate>
PetTemplate petTemplate;
This is not allowed: petTemplate.Name="Dog" as this is changing the parameter outside of its component. You can only do that like this:
<PetTemplate Name="Dog">
<div>Someone's best friend!</div>
</PetTemplate>
Furthermore, modification of a parameter property from within the component itself is deprecated ( currently you should get a warning, at least that is what Steve Sanderson suggested to the Blazor team).
To make it clear, you should not modify the parameter property Name from within the PetTemplate component. A parameter property should be automatic property; that is, having a get and set accessors like this: [Parameter] public string Name { get; set; }
And you should not use it like this:
private string name;
[Parameter]
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
// Code to a method or whatever to do something
}
}
}
This is deprecated as it may have side effects. Component parameters should be treated as DTO, and should not be modified. If you wish to perform some manipulation of the parameter value, then copy it to a local variable, and do your thing.
As pointed out by #enet Blazor component inheritance doesn't behave exactly as one would intuitively expect. This is a cleaner approach when you want to control a UI functionality that can be controlled both internally and externally:
Declare an event in the base component that is raised when the UI state is changed from within the component. Also let the variable that controls the state be a parameter. In you case, something like
PetTemplate.razor:
[Parameter]
public EventCallback OnToggleRequested {get;set;}
[Parameter]
public string Display {get;set;}
protected async Task RaiseToggle()
{
await OnToggleRequested.InvokeAsync();
}
In your PetDog, simple call the toggle method when inside click is raised
PetDog.razor:
<button #onclick="RaiseToggle">Show Details (Inside)</button>
In your container (in this case, index.razor) listen to the event and make changes. Also wire the outside button to the same method:
Index.razor:
<button #onclick="ToggleDisplay">Show Details (Outside)</button>
<PetDog OnToggleRequested="ToggleDisplay" Display="#display"/>
string display = "block";
void ToggleDisplay()
{
display = display == "none" ? "block" : "none";
}
Note that the event can be used at level of hierarchy and you don't need to capture any references anywhere.
I'm new to Blazor and trying to work through some of the basics of how to construct my components and pages. Right now I have a MainLayout that looks like this:
<CascadingValue Value="#layoutVals" Name="LayoutVals">
<Leftbar />
<div class="content-page">
<Topbar />
<div class="content">
<div class="container-fluid">
#Body
</div>
</div>
</div>
</CascadingValue>
#code {
public LayoutValues layoutVals = new LayoutValues()
{
Title = "Dashboard",
Breadcrumbs = new Dictionary<string, string>()
{
{ "Dashboard", "/" }
}
};
}
I'm trying to use the cascading value to allow a child page/component to overwrite values in the Topbar component (i.e. page title, breadcrumb, some other display values that I want consistent across views but dynamically replaced based on the page).
I'm able to access the object in the Topbar component and they are set properly based on what they're initialized to in MainLayout (shown above), and I'm able to override them within that component. However, if I set them in a page the change doesn't seem to make it's way up and then back down the chain to the Topbar component where I want them displayed.
I'm sure I could eliminate the Topbar component and inline everything in my MainLayout but I'd prefer to keep the code clean and separate if it's possible.
The problem you are facing is, that the <Topbar /> is not re-rendered after the value has changed in some component down below the tree (page, etx). You have to tell the Topbar that to render again:
public class LayoutValues
{
public string Title {get;set;}
public Action ValuesChanged;
}
Subscribe to ValuesChanged in Topbar:
protected override void OnInitialized()
{
LayoutVals.ValuesChanged += () => StateHasChanged();
//dont forget to unsubscribe somewhere
}
[CascadingParameter] public LayoutValues LayoutVals {get;set;}
And call it whenever you change the value (or you can do this in setter of LayoutValues):
//some page
private void ButtonClicked()
{
LayoutVals.Title="Changed title";
LayoutVals.ValuesChanged.Invoke();
}
[CascadingParameter] public LayoutValues LayoutVals {get;set;}
Working demo.
This solution has a performance advantage - the app doesn't have to re-render the whole tree, just the Topbar has called the StateHasChanged.
I have a Blazor app with many components, and nested components that I would like to share data among, but I cannot figure out how to do this efficiently. Here is, sort of, what I have:
MyProject.Pages.Index.razor:
#page "/"
<div>
#if(some_state) {
<ComponentAlpha #bind-something=Something />
else if(some_other_state){
<ComponentBeta Something="Something" />
} else {
<ComponentGamma Something="Something" />
}
</div>
#code {
String Something { get; set; }
}
MyProject.Shared.ComponentAlpha.razor:
<div>
Stuff here ...
</div>
#code {
[Parameter]
public String something { get; set; }
[Parameter]
public EventCallback<String> somethingChanged { get; set; }
private async Task MyTask() {
await somethingChanged.InvokeAsync(something);
}
}
This all works fantastic for getting data from ComponentAlpha.razor back to Index.razor, and from Index.razor to ComponentBeta.razor and ComponentGamma.razor. My question comes in for beyond ComponentBeta.razor and ComponentGamma.razor.
MyProject.Shared.ComponentBeta.razor:
<div>
Stuff here ...
<ComponentDelta />
<ComponentEpsilon />
</div>
#code {
[Parameter]
public String Something { get; set; }
}
MyProject.Shared.ComponentGamma.razor:
<div>
Stuff here ...
<ComponentZeta />
<ComponentEta />
</div>
#code {
[Parameter]
public String Something { get; set; }
}
MyProject.Shared.ComponentDelta.razor:
MyProject.Shared.ComponentEpsilon.razor:
MyProject.Shared.ComponentZeta.razor:
MyProject.Shared.ComponentEta.razor:
<div>
Stuff here ...
<MoreAndMoreComponents />
</div>
#code {
// I want to use variable "something" here as well.
}
In order to be able to share the string something amongst all my components and embedded components, do I need to jump through all the elaborate hoops that I did for just Index.razor, ComponentAlpha.razor, and ComponentBeta.razor or is there some better way?
I saw THIS out there and thought option 3. State Container would be my best bet, However, when I follow their example, I always end up with this Exception:
Error CS0119 'AppState' is a type, which is not valid in the given context
So, what is the way we are supposed to use to efficiently share data amongst all components and nested components?
You should consider using a service, probably Scoped. You then inject the service into each component, you can use an Interface and/or base abstract class to boilerplate the code. You can also use this same service for events - signalling that data has changed.
See the MS Docs here on services and how to use/provision them.
One option you can consider is using cascading parameters, which will allow a top level component to pass itself down to any child components regardless of component tree depth. It's very easy to set up.
In your top level component:
<CascadingValue Value="this">
#*Note Component Alpha placement*#
<ComponentAlpha />
</CascadingValue>
#code {
private string something = "initial value";
// this is a getter only for the property, but you can use
// a setter for 2 way binding also. Just make sure
// to modify the setter to run 'StateHasChanged()'
// after updating the value, and then
// you can then set the property directly
public string Something => something;
// This will allow child components to set the new value
public Task SetNewValue(string value)
{
something = value;
StateHasChanged();
return Task.CompletedTask;
}
}
Note that the CascadingValue is passing this down the tree to it's children, so you can capture it where needed.
In the mid level (alpha) component
<div>
<div #onclick="SetNewValue">#ValueFromTopLevel</div>
#*Note Component Beta placement*#
<ComponentBeta />
</div>
#code {
// Capture the top level component here
[CascadingParameter]
public Component TopLevelComponent { get; set; }
// pull out the value you need here
public string ValueFromTopLevel => TopLevelComponent.Something;
// use this to set a new value
void SetNewValue()
{
TopLevelComponent.SetNewValue("Hello from Alpha component!");
}
}
Notice the beta component nested inside the alpha component.
In the low level (beta for my example) component
<div>
<div #onclick="SetNewValue">#ValueFromTopLevel</div>
</div>
#code {
// still capture the top level component
[CascadingParameter]
public Component TopLevelComponent { get; set; }
// use the value the same as Alpha
public string ValueFromTopLevel => TopLevelComponent.Something;
// set the value the same as Alpha
void SetNewValue()
{
TopLevelComponent.SetNewValue("Hello from Beta component!");
}
}
With this setup, by clicking on the text of either the Alpha or the Beta component, the value of something is updated at the top level and then the new value is cascaded down again with a fresh render. All the state management is at the top and the child components are just hooking in where needed.This setup will allow you to use methods and properties from a top level component as you see fit. For example I created a top level Toast Notification component once that wraps the whole app and can be captured and used anywhere. Simply pass it a message and a type (info, error, warning) and it displays the flash message for a few seconds and then hides again.
There are some downsides though, notably that your top level component needs to be responsible for all state management. This works for simpler scenarios, but as the app and the interactions become more complex this can get out of hand. However, if you combine this with Enet's answer and get the state management happening at the top, you can then hook in where needed down the component tree.
Also note that any child components become coupled to the top level parent as they will require being up the tree somewhere to work properly. Maybe Ok for you, maybe not. Either way, it's a good tool to be aware of.
Official docs for cascading parameters can be found here
It depends how permanent you need your info to be, and to what degree you're willing to hold global variables on the parent.
1. One trick is to pass the parent control to the children-- give them full access to its variables:
ParentControl
<CascadingValue Value="this">
<ChildControl />
</CascadingValue>
ChildControl
[Parameter]
ParentControl Parent;
#code {
Parent.SomeVariable = Something;
}
2. If you don't want to do that, then you can pass data to its parent using an EventCallback<T>
ChildControl
#code
{
[Parameter]
EventCallBack<MyCustomClass> OnDataReady { get; set; }
MyCustomClass ActiveObject { get; set; }
void DoSomething()
{
OnDataReady.InvokeAsync(ActiveObject);
}
}
And on ParentControl:
<ChildControl OnDataReady=HandleData />
#code {
async Task HandleData (MyCustomClass data){
// Do stuff
}
}
3. If you REALLY want highly persistent data, then consider saving state in a database. Since Blazor requires no postbacks, there's really no penalty for saving or loading information from a database whenever you want.
4. Using a service as per #enet's answer
Just hoping to get some help with some of this Blazor functionality. I'm building a true SPA app with no navigation, which means I'm going to need a fair bit of linkage.
Im going to follow some fundamentals of say Winforms or UWP were you have access to controls. And treat Blazor components as if they were controls. Do as much work in C# as I can.
So far I know:
For a child component, I can create an event callback, and register to it in the parent.
I can store a reference to the child component with the #ref tag. Then access functions of the child component after OnRender has been completed.
I can dynamically build a component using the builder.
But how do I pass a reference of the parent to the child? Like set a parameter of the child and pass "this".
The idea is that every child component of Index has a reference to the Index component. And the Index component has a reference to every child of Index.
So that way I can do things like when I click a button in the Header component.
I can call parent.PopupComponent.Show("Title");
I know it can be achieved with callbacks, but I would like to be able to make any call I need, access any variable etc. Through the linkage of components. Without needing to set up an extra callback function for each step.
Thank you in advance :)
You can pass a reference to the parent component to a child component as a regular parameter, as for instance:
Child.razor
#code {
[Parameter]
public Parent Parent { get; set; }
}
Parent.razor
#* Passing a parent reference to the child component*#
<Child Parent="this" />
#code {
}
You can also pass a CascadingParameter to child components. This is useful when you want to pass a reference to the parent component to all the children, as for instance:
Child.razor
#code {
[CascadingParameter]
public Parent Parent { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Parent.razor
#* Passing a parent reference to the child components in the form of
CascadingValue*#
<CascadingValue Value=this>
#ChildContent
</CascadingValue>
#code {
}
And this is bonus:
The following code snippet illustrate how to add a child component to a parent component, from which you can call properties of the child component :
protected override void OnInitialized()
{
Parent.AddChild(this);
}
Note: The OnInitialized method is implemented on the child component, right ?
And the word this refers to the current object; that is the child component, right ?
Hope this helps...
I just wanted to add this in as some people may find it useful when trying to build a Blazor app the same way I am.
Here is how I found a way to control what content is being loaded into a component, in my case I made an empty Flyout/Window component, with the content set as a render fragment. Note that the render fragment is private, the is because the content will be a BlazorComponent, defined in the ShowPopup() call.
Also worth noting that the component builder stuff will most likely become obsolete as they build on Blazor. This is low-level API, and as mentioned by the Developers. They will have something more useful in the future.
Child Component Code
<div>
...
#childContent
</div>
#code
{
private RenderFragment childContent { get; set; }
public void ShowPopup(Type ComponentType)
{
childContent = CreateDynamicComponent(ComponentType);
//Show popup logic....
}
RenderFragment CreateDynamicComponent(Type T) => builder =>
{
builder.OpenComponent(0, T);
builder.CloseComponent();
};
}
Then the parent Code
<div>
...
<button #onclick="#(e => PopupWindow.ShowPopup(typeof(BlazorComponentX)))">Load BlazorComponentX into Popup</button>
<PopupWindow #ref="PopupWindow"></PopupWindow>
...
</div>
#code
{
Public PopupWindow PopupWindow {get; set;}
}
I have created my own cached JSON string localizer by overriding IStringLocalizerFactory and IStringLocalizer and it works like a charm.
The only problem is that I can not localize data annotations because they seem to not work with a custom string localizer. I tried using the ResourceType property which should be used for exactly this case (at least as far as I am concerned), but instead of using my IStringLocalizer it tries to access properties. This might work with the default resx-localizer, but not with a custom one.
I could create properties in my class which reroute to my IStringLocalizer but thats a hassle and only want to do it this way if I have to.
My view model:
public class RegisterViewModel
{
[Required]
[Display(Name = "test", ResourceType = typeof(Lang))]
public string Username { get; set; }
}
is used inside of my cshtml like this:
<div class="form-group">
<label asp-for="Username" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
</div>
which should autogenerate the label and the textbox. But instead:
InvalidOperationException: Cannot retrieve property 'Name' because localization failed. Type 'Test.Models.Lang.Lang' is not public or does not contain a public static string property with the name 'test'.
booom. Thats my Lang class:
public class Lang
{
}
Instead of getting a IStringLocalizer<Lang> and searching for a test property via indexer (thats what I am trying to make him do), it tried to find the non-existing property test.
Is there any way to change the way the lookup works? I tried to changing the MvcDataAnnotationsLocalizationOptions via services.Configure(), but to no effect.
Or if it is possible to directly change the way the HTML-code is generated from those data-annotations?
Thank you for your help.
EDIT: This of course doesn't work because the CreateDisplayMetadata() is only called once and then cached, causing the first "translation" become the only one! I do have another solution extending the DefaultModelMetadataProvider or adding a IDisplayMetadataProvider (still not sure it's the right or best way to go...), so if anyone still needs help with this, please let me know :)
OLD AND WRONG ANSWER:
Don't know if there is any "more correct" (please let me know if so :) solution, but here is one that works in RC2:
public class LocalizedDisplayMetadataProvider : IDisplayMetadataProvider
{
public String Translate(String key)
{
if(key != null && key.StartsWith("#"))
{
return key.TrimStart('#').ToUpper(); // Todo: Replace with real localization code (e.g. fetch from json)...
}
else
{
return key;
}
}
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
foreach(var attr in context.Attributes.OfType<DisplayAttribute>().Where(a => a.ResourceType == null))
{
attr.Name = this.Translate(attr.Name);
attr.Description = this.Translate(attr.Description);
attr.GroupName = this.Translate(attr.GroupName);
attr.Prompt = this.Translate(attr.Prompt);
attr.ShortName = this.Translate(attr.ShortName);
}
}
}
And then register in ConfigureServices method:
services.Configure<MvcOptions>(m =>
{
m.ModelMetadataDetailsProviders.Add(new LocalizedDisplayMetadataProvider());
});
This will localize all strings starting with '#' (and not ResourceType set) on the DisplayAttribute, and can easily be expanded to handle the ValidationAttribute as well.