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.
Related
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 .
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!)
This question already has answers here:
Blazor TwoWay Binding on custom Component
(4 answers)
Closed 2 years ago.
In my Blazor Webassembly (client-side) 3.2.0 application, I have a lot of the following select elements on a page, which create a combo box listing available items of an enum:
<select #bind="SomeEnumValue">
#foreach (var element in Enum.GetValues(typeof(SomeEnum)))
{
<option value="#element">#element</option>
}
</select>
While this works, I want to prevent repeating myself over and over for every such combo box, and thought of creating the following EnumSelect component:
#typeparam T
<select #bind="Value">
#foreach (var element in Enum.GetValues(typeof(#T)))
{
<option value="#element">#element</option>
}
</select>
#code {
[Parameter]
public T Value { get; set; } = default(T)!;
[Parameter]
public EventCallback<T> ValueChanged { get; set; }
}
And then use it like this:
<EnumSelect T="SomeEnum" #bind-Value="SomeEnumValue" />
However, while the display of the SomeEnumValue property and enum items works, the property is not written back to upon selecting another item from the combo box.
I'm not sure why this doesn't work. I kinda suspect the EnumSelect component storing its own copy of that value, not writing it back, but I thought the binding solves this for me.
Is there any (easier) way to get this combo box component working? Where is my mistake (in understanding bindings)?
Found my answer by rewording my question a bit (sorry, not yet used to all the Blazor terminology): Blazor TwoWay Binding on custom Component
In case of my problem, the final working component looks like this: The check if the value actually changed inside the property was required, or I hung my app (possibly because it wanted to set it over and over again?):
#typeparam T
<select #bind="Value">
#foreach (var element in Enum.GetValues(typeof(#T)))
{
<option value="#element">#element</option>
}
</select>
#code {
private T _value = default(T)!;
[Parameter]
public T Value
{
get => _value;
set
{
if (_value.Equals(value))
return;
_value = value;
ValueChanged.InvokeAsync(_value);
}
}
[Parameter]
public EventCallback<T> ValueChanged { get; set; }
}
I'm getting started with Razor pages and I have the following problem:
I have this model
public class Order
{
public int OrderId { get; set; }
public string Customer { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderItemId { get; set; }
public string Item { get; set; }
public decimal Price { get; set; }
}
I'm binding it like this on the Edit.cshtml.cs
public class EditModel : PageModel
{
[BindProperty]
public Order Order { get; set; }
public void OnPost()
{
}
}
And in my Edit.cshtml I use it this way
#for (byte i = 0; i <= 5; i++)
{
<input asp-for="Order.OrderItems[i].OrderItemId" />
<input asp-for="Order.OrderItems[i].Item" />
<input asp-for="Order.OrderItems[i].Price">
}
My loop has to be always from 0 to 5, but the OrderItems collection can have even less than 6 items in it.
Now, this works fine in the New.cshtml page where the Order is a new object and the OrderItems is empty. But when I'm trying to edit an existing record I'm getting an error:
ArgumentOutOfRangeException: Index was out of range.
Must be non-negative and less than the size of the collection.
Is there a way to overcome this error without having to manually fill in the OrderItems collection to match the loop's length?
First of all, I believe you should not use a For loop with, let's say 5 iterations, without knowing for sure that you have to iterate 5 times. Maybe you could use a ForEach loop insteed or rething something in your view.
That's said, even if it is not a clean way, you can simply add a surrounding if statement around your inputs. Something like :
#for (byte i = 0; i <= 5; i++)
{
#if(Order.OrderItems[i] != null)
{
<input asp-for="Order.OrderItems[i].OrderItemId" />
<input asp-for="Order.OrderItems[i].Item" />
<input asp-for="Order.OrderItems[i].Price">
}
}
But again, using a ForEach loop would be a far better choice.
EDIT
Based on your comment, here is what you can do :
#if(Order.OrderItems[i] != null)
{
<input asp-for="Order.OrderItems[i].OrderItemId" />
<input asp-for="Order.OrderItems[i].Item" />
<input asp-for="Order.OrderItems[i].Price">
}
else
{
// new form to post the 3 inputs, allowing to reload the page with the new non null values ...
}
Based on comments it sounds like you're indicating that your model must have (at least) 6 elements in its list. If that's the case then that logic belongs on the model. Perhaps something like this:
public class Order
{
public int OrderId { get; set; }
public string Customer { get; set; }
private List<OrderItem> _orderItems;
public List<OrderItem> OrderItems
{
get { return _orderItems; }
set
{
_orderItems = value;
if (_orderItems == null)
_orderItems = new List<OrderItem>();
while (_orderItems.Count < 6)
_orderItems.Add(new OrderItem());
}
}
public Order()
{
// invoke the setter logic on object creation
this.OrderItems = null;
}
}
There are of course a variety of ways to organize the logic, this is just one example. But the point is that if the model must have at least 6 elements in its list then the model is where you would guarantee that. (Alternatively, if you feel that in your domain the view should be where this logic is guaranteed and the model can have other list lengths in other places that it's used, then I guess you could put the same logic on the view. But that seems unlikely. Logic generally belongs in models, views should just bind to those models.)
Is there a way to overcome this error without having to manually fill in the OrderItems collection to match the loop's length?
No. Well, depending on how you define "manually". Do you need to write code somewhere to perform your custom logic? Yes. Do you need to repeat the same code everywhere that you use it? No, that's why it belongs on the model.
Or, to put it another way: "Smart data structures and dumb code works a lot better than the other way around." -Eric Raymond
public class RegisterSubscription
{
public string ID { get; set; }
public string Description { get; set; }
public int SubscrSelected { get; set; }
public string Image { get; set; }
public string InfoUrl { get; set; }
}
private List<RegisterSubscription> activePlans = new List<RegisterSubscription>();
In my controller I have ViewBag.ActivePlans = activePlans; and the ViewBag fills with the correct data. Now in my view I have:
#foreach (var item in ViewBag.ActivePlans)
{
<li class="popupmenu" id="service_#(item.ID)" >
<div>
#Html.Image(item.Image, new { style="border-style:none; text-align:left; vertical-align:middle; width:64px; height:64px" })
</div>
</li>
}
but I get the following error:
'System.Web.Mvc.HtmlHelper' has no applicable method named 'Image' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
Any help will be greatly appreciated,
thank you in advance.
Warning: I haven't done any MVC stuff, so this is guesswork.
My guess is that ActivePlans is dynamic in some form... so you basically need to make item strongly typed. It may be just as simple as this:
#foreach (RegisterSubscription item in ViewBag.ActivePlans)
... which would basically cast each item to RegisterSubscription as it got it out of the ViewBag. After that point, I can't see that you've got anything dynamic, so the extension method should be okay. I think. Maybe. When the wind is right.