I'm using a Razorcomponent with a Blazor server app. The app polls for alertmessages on the server.
The server might send back several messages, which I loop over.
The class on the div has a "show" and "hidden" and that takes care of hiding elements.
The problem I have is that I want to be able to close each alertmessage and not all- which happens as described in the below simplified code:
--snip
#if(alert.valid == true){
#foreach(var alert in alerts){
#if(alert.type == "alert")
<div id="alertmessage" class="#show">
<button type="button" #onclick="#show">Hide this element</button>
</div>
}
}
#code{
private string value { get; set;} = "show";
private void Show() {
value = "hidden";
}
}
As per the above example, if there are several alerts, the method Show() will close all the boxes, and it produces x count of <div id="alertmessage" I get this, but is there a way to grab that specific element like alert.id or something? Appreciate all feedback.
Thanks.
In blazor you work everytime with objects , you should do a class for the alert and change its attribute on the for each.
The page has to contain a list of alert objects as attribute.
More less this:
#if(alert.valid == true){
#foreach(var alert in alerts){
<div id="alertmessage" class="#show">
<button type="button" hidden="#alert.hidden" #onclick="()=>show(alert)">Hide this element</button>
</div>
}
}
#code{
private string value { get; set;} = "show";
private List<Alert> alerts = new();
private void Show(Alert alert) {
alert.hidden = true;
alert.message= "whatever"
}
public class Alert{
public String message = "whatever"
public bool hidden = false;
//other stuff
}
}
If you want to separate the logic from the presentation you can declare alert logic in its own class -file .
Related
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 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.
So I'm new to Blazor and I've just found out about CascadingValue and their possible use for showing error messages. I am creating an Outlook Add In for saving mails and attachments in Sharepoint, so I'm dynamically retrieving the current Outlook mail using Office-js and JSInterop, such that my Add In always uses the selected mail.
But now that I'm trying to implement a cascading component for error messages I've run into problems with StateHasChanged.
Following code is my cascading error component:
<CascadingValue Value=this>
#ChildContent
</CascadingValue>
<div class="error-container #(ErrorMessage == null ? "hidden" : "")">
<span class="error-message">
#ErrorMessage
</span>
<a class="dismiss-error" #onclick="DismissError">x</a>
</div>
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
public string ErrorMessage { get; set; }
public void ProcessError(Exception ex)
{
ErrorMessage = ex.Message;
StateHasChanged();
}
public void DismissError()
{
ErrorMessage = null;
StateHasChanged();
}
}
And the component I'm having troubles with (atleast the important parts):
<div>
{Showing details from OutlookMail here}
</div>
#code {
[CascadingParameter] public Error Error { get; set; }
public OutlookMail OutlookMail { get; set; }
private static Action<OutlookMail> _receiveMailAction;
protected override async Task OnInitializedAsync()
{
_receiveMailAction = UpdateMail;
await base.OnInitializedAsync();
}
private void UpdateMail(OutlookMail mail)
{
InvokeAsync(() =>
{
OutlookMail = mail;
StateHasChanged();
});
}
[JSInvokable]
public static void ReceiveMail(OutlookMail mail)
{
_receiveMailAction?.Invoke(mail);
}
public async Task MethodWithError()
{
try
{
await DoStuffWithPossibleError();
}
catch (Exception e)
{
Error.ProcessError(e);
}
}
}
Everything works fine until Error.ProcessError gets called and the error message is shown, after that StateHasChanged stops rerendering my component which gives inconsistent data to the users.
I am probably doing something wrong, since I can't really find anyone else with the same problem, but I'm not seeing it, so I'm hoping someone on here has a better understanding of where things are going wrong, or maybe even a better way of going about showing error messages if the problem is with using CascadingValue like this.
All the help is appreciated.
Edit 1: After some more looking into this, it seems that it's actually not updating OutlookMail, even when I move it outside of InvokeAsync. So there might be something weird happening with instances of my component
After my first edit, I found out it was indeed a problem with instances, since I'm overwriting a static Action within OnInitialized, each time it creates a new instance, the old instances no longer works. And the code which returned the error happened to have a auth dialog with a redirect callback to the same page, it closes directly after, but loading the page actually creates a new instance of my component, which in turn invalidates the one my user works with. So I'll probably just have to create a singleton service that handles the incoming mail data, so that my component can always access the correct data.
I'm using blazor webassembly, and trying to do something really simple - fire an event once a form has been completed...
The code below doesn't work, I've tried every different combo of "onsubmit" I can think of... what am I doing wrong?
#page "/"
<h4>Add Group</h4>
<EditForm Model="#addGroupModel" onsubmit="#addGroup" >
<InputSelect #bind-Value="addGroupModel.CowCategoryId">
#if (CowCategories != null)
{
foreach (var cat in CowCategories)
{
<option value="#cat.ForagePlanCowCategoryId">#cat.ForagePlanCowCategoryName</option>
}
}
</InputSelect>
<InputText #bind-Value="addGroupModel.GroupName"></InputText>
<input type="submit" value="Add" />
</EditForm>
#code {
public class AddGroupModel
{
public int CowCategoryId { get; set; }
public string GroupName { get; set; }
}
public AddGroupModel addGroupModel = new AddGroupModel();
protected void addGroup()
{
var addModel = addGroupModel;
var cat = this.foragePlan.Categories.FirstOrDefault(c => c.ForagePlanCowCategoryId == addModel.CowCategoryId);
if (cat == null)
{
cat = this.CowCategories.FirstOrDefault(c => c.ForagePlanCowCategoryId == addModel.CowCategoryId);
}
this.foragePlan.ForagePlanCategoryGroups.Add(new ForagePlanCategoryGroup() { ForageUtilisationFactor = 100, ForagePlanCowCategoryId = cat.ForagePlanCowCategoryId, ForagePlanCowCategory = cat, GroupName = addModel.GroupName });
this.UpdateModel();
}
}
EditForm is a Blazor component which allow you to attach two event handlers to it. The first, OnValidSubmit is fired when you hit the "submit" button. Put code in this handler that as for instance, perform a Web Api call in order to save your form data in a database.
The second attribute property which is exposed by the EditForm component is OnInvalidSubmit. This is fired when you hit the "submit" button as well, but your data did not pass validation. You can put in the event handler some code that, as for instance, display a message to the user, perform some checks, etc.
Note that in the following code I've altered onsubmit="#addGroup" to OnValidSubmit="addGroup"
Note: I did not check the rest of your code...
Note that no submit action is ever taken place. Indeed, the "submit" event is triggered, but then canceled by the framework. Blazor is an SPA framework. No traditional post back, no post get delete, etc. Http requests.
#page "/"
<h4>Add Group</h4>
<EditForm Model="#addGroupModel" OnValidSubmit="addGroup" >
<InputSelect #bind-Value="addGroupModel.CowCategoryId">
#if (CowCategories != null)
{
foreach (var cat in CowCategories)
{
<option value="#cat.ForagePlanCowCategoryId">#cat.ForagePlanCowCategoryName</option>
}
}
</InputSelect>
<InputText #bind-Value="addGroupModel.GroupName"></InputText>
<input type="submit" value="Add" />
</EditForm>
#code {
public class AddGroupModel
{
public int CowCategoryId { get; set; }
public string GroupName { get; set; }
}
public AddGroupModel addGroupModel = new AddGroupModel();
protected void addGroup()
{
var addModel = addGroupModel;
var cat = this.foragePlan.Categories.FirstOrDefault(c => c.ForagePlanCowCategoryId == addModel.CowCategoryId);
if (cat == null)
{
cat = this.CowCategories.FirstOrDefault(c => c.ForagePlanCowCategoryId == addModel.CowCategoryId);
}
this.foragePlan.ForagePlanCategoryGroups.Add(new ForagePlanCategoryGroup() { ForageUtilisationFactor = 100, ForagePlanCowCategoryId = cat.ForagePlanCowCategoryId, ForagePlanCowCategory = cat, GroupName = addModel.GroupName });
this.UpdateModel();
}
}
OnSubmit actually works (but not onsubmit - it was all down to case) as per the documentation. I'm new to Blazor, and find this mixing of cases quite confusing, as sometimes things are camel case, sometimes lower and sometimes a mix it's a real head scratcher.
Doesn't help that the intellisense in visual studio doesn't really work, and not being able to debug, whilst making changes seems like madness (you can debug but changes aren't reflected in the browser, or you can "Run without Debugging" and then refreshing browser does include any changes - well, I only want to do that whilst debugging, seems obvious to me!)