Razor Webassembly using EditForm not working for me - c#

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!)

Related

Blazor templating component with inheritance

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.

Blazor #onclick event to close a single item inside a loop

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 .

Displaying enum Display Name when iterating over enum values in Razor page (in ASP.NET Core)

How do you display the Display name for an enum when you're iterating over the enum values on a Razor Page (in ASP.NET Core)?
Razor Page:
<label asp-for="Survey.ToldNotToTakeHormones"></label><br />
#foreach (var answer in Enum.GetValues(typeof(AnswerYND)))
{
<div class="custom-control custom-radio custom-control-inline ">
<label><input type="radio" asp-for="Survey.ToldNotToTakeHormones" value="#answer" />#answer</label>
</div>
}
Code behind razor page:
public class EditModel : PageModel
{
[BindProperty]
public Survey Survey { get; set; }
Survey class:
public class Survey
{
[Display(Name = "Have you ever been told by a medical professional not to take hormones?")]
public AnswerYND? ToldNotToTakeHormones { get; set; }
AnswerYND:
public enum AnswerYND
{
Yes,
No,
[Display(Name="Don't Know")]
DontKnow
}
So, I was able to utilize Description instead of Display Name and achieved the desired effect.
I had to create the following extension method:
public static class EnumHelper
{
public static string GetDescription<T>(this T enumValue)
where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
return null;
var description = enumValue.ToString();
var fieldInfo = enumValue.GetType().GetField(enumValue.ToString());
if (fieldInfo != null)
{
var attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (attrs != null && attrs.Length > 0)
{
description = ((DescriptionAttribute)attrs[0]).Description;
}
}
return description;
}
}
And then I was able to access the extension method in the Razor page by casting the result of Enum.GetValues(typeof(AnswerYND)):
<label asp-for="Survey.CurrenltyUsingBirthControl"></label><br />
#foreach (var answer in Enum.GetValues(typeof(AnswerYND)).Cast<AnswerYND>())
{
<div class="custom-control custom-radio custom-control-inline ">
<label><input type="radio" asp-for="Survey.CurrenltyUsingBirthControl" value="#answer" />#answer.GetDescription()</label>
</div>
}
There are better ways to go about creating what you are looking for, if you're interested in binding enums to controls or fields, and having the system display the name of the enum, rather than the underlying value. The important thing that makes this complicated is that under the hood, the system isn't handling an enum as a string that gets picked from the developer-defined list; rather, it's handled by default as an Int32.
I found some other answers on Stack Overflow that might help you to see some better possible solutions. This answer explains how you can create your own type-safe class that handles like an enum, but returns the string value that you're looking for. This answer is possibly a duplicate of the same question, but adds more perspectives to the topic. I used code samples from the first question to help you see that what you're looking for can be found, but might not be simple.
public class Survey
{
[Display(Name = "Have you ever been told by a medical professional not to take hormones?")]
public AnswerYND? ToldNotToTakeHormones { get; set; }
}
public enum AnswerYND
{
Yes = 1,
No = 2,
[Display(Name = "Don't Know")]
DontKnow = 3
}
class Program
{
static void Main(string[] args)
{
var survey = new Survey();
Console.WriteLine(survey);
var options = Enum.GetValues(typeof(AnswerYND));
for (int i = 0; i < options.Length; i++)
{
Console.WriteLine($"Option {i + 1}: {options.GetValue(i)}");
}
var choice = Console.ReadLine();
if (int.TryParse(choice, out int intChoice) && Enum.IsDefined(typeof(AnswerYND), intChoice))
{
AnswerYND enumChoice = (AnswerYND)intChoice;
var name = (enumChoice
.GetType()
.GetField(enumChoice.ToString())
.GetCustomAttributes(typeof(DisplayAttribute), false)
as DisplayAttribute[])
.FirstOrDefault()?
.Name;
Console.WriteLine($"You selected: {name}");
}
}

Blazor: Select all option in dropdown

So I pretty much have a dropdown (for filterting purposes) currently where you can select a fruit from a list which is pretty much a list which contains FruitDtos, and it works perfectly fine.
However, I would like to add an "All" option, but I'm unsure how to do that.
It looks like this in the .razor file:
<Addon AddonType="AddonType.Body">
<Select TValue="int" SelectedValue="#SelectedListValue" SelectedValueChanged="#OnFruitChanged">
#foreach (FruitDto item in _fruitsList)
{
<SelectItem Disabled="#(item.FruitId == 0)" Value="#item.FruitId">#item.Name</SelectItem>
}
</Select>
</Addon>
So _fruitsList contains all of the FruitDtos. Is there a way I can let the user select all of them at once? Tbh, I mostly want this option because right now you need to refresh the page in order to "deselect"/remove the filter.
int SelectedListValue { get; set; }
[Parameter] public FruitDto Fruit { get; set; }
private async Task OnFruitChanged(int newValue)
{
SelectedListValue = newValue;
Fruit = _fruitsList.Find(s => s.FruitId == newValue);
await FruitChanged.InvokeAsync(Fruit);
StateHasChanged();
}
FruitDto just contains a string Name and int FruitId.
Blazorise's Select component offers a "Multiple" option as well. That might work well for what you are asking. Note that I did remove the Disabled attribute as I'm not sure how that was being used.
.razor:
<Select TValue="int" Multiple="true" SelectedValuesChanged="#OnFruitChanged">
#foreach (FruitDto item in _fruitsList)
{
<SelectItem Value="#item.FruitId">#item.Name</SelectItem>
}
</Select>
.cs:
protected List<int> SelectedListValues { get; set; }
protected void OnFruitChanged(IReadOnlyList<int> newValues)
{
SelectedListValues = newValues.ToList();
}
If you want to clear the list, you could add a button "Clear Filters" which would have on OnClick to clear the list as well.

List of buttons and custom event Handlers in a shared blazor component

I'm at my wits end with this one, I've been pluggin away at all kinds of methods online but don't seem to be getting anywhere.
I have a shared component, in this case a component that mimicks a list view from the web forms era. I would like for any future developers to implement the list view in to projects and be able to build a custom set of buttons to be appended to each row (This bit I can get to work fine). The trouble I'm having is being able to assign a custom Action handler to each button to run a method on the parent component of the list view. I have a class like this:
using System;
namespace Speedy.Razor.SharedComponents.WebFormComponents.Shared
{
public class CustomAction
{
public string Name { get; set; }
public string Icon { get; set; }
public Action<int> OnClick { get; set; }
}
}
Which I am applying to the row like this:
foreach (var customAction in CustomActions)
{
<span class="#customAction.Icon" #onclick='() => customAction.OnClick(obj.Id)'></span>
}
Which works okay.
Then I'm trying to create the list of custom actions as below:
List<CustomAction> customActions = new List<CustomAction>()
{
new CustomAction {Name = "Edit", Icon="oi oi-pencil", OnClick = },
new CustomAction {Name = "Contacts", Icon="oi oi-person", OnClick = }
};
I've tried delegates but can't seem to fire the required methods unless it is static, which I don't want; or I'd need to create a reference to the class which causes issues in Blazor as a new reference to the class which is on the component causes StateHasChanged to fail.
Any ideas?
Thanks
assign a custom Action handler to each button to run a method on the parent component of the list view
Assuming that customActions is also a member of that parent component, just define a method at the same level:
List<CustomAction> customActions = new List<CustomAction>()
{
new CustomAction {Name = "Edit", Icon="oi oi-pencil", OnClick = ClickHandler },
new CustomAction {Name = "Contacts", Icon="oi oi-person", OnClick = ClickHandler }
};
void ClickHandler(int id)
{
...
}
This is working...copy and run it. Ask question if you have, as I am not sure what I should explain here.
Index.razor
#page "/"
#foreach (var customAction in customActions)
{
count++;
<span class="#customAction.Icon" #onclick="#(() =>
customAction.OnClick(count))"></span>
}
#code{
List<CustomAction> customActions;
private int count = 10;
private void myclick(int myint)
{
Console.WriteLine(myint.ToString());
}
private void myclick2(int myint)
{
Console.WriteLine(myint.ToString());
}
protected override void OnInitialized()
{
customActions = new List<CustomAction>()
{
new CustomAction {Name = "Edit", Icon="oi oi-pencil", OnClick = myclick
},
new CustomAction {Name = "Contacts", Icon="oi oi-person", OnClick =
myclick2} };
base.OnInitialized();
}
}
Thank you for the responses, it really was that simple and after looking at your example I realised the reason I was having such problems which was not obvious in my initial question. I was trying to initialise the List outside of a method which didn't have access to the non-static methods in the class.
Thank you again for helping me spot this!

Categories