Q & A Blazor: Two-way data binding with Radio Button - c#

I am writing a blazor app, where the user has to select a group of options in the form of a radio buttons. This selection needs to be done inside a component. Which also meant, I needed to pass data from a radio group in a child component to a parent component.
The Test
Below are code snippets for both child and parent components. The test involves selecting one of the four seasons (spring, summer, autum, and winter) in the child component. When the user selects any of the four seasons, that selection needs to be updated in the parent component as well.
Attempt
First attempt was to use an Editform.
This is the child component. Pages\Child.razor
<h1>Child</h1>
EditForm Model="model">
<InputRadioGroup #bind-Value="SelectedValue">
#foreach (var revOption in (Season[])Enum
.GetValues(typeof(Season)))
{
<InputRadio Value="#revOption" #onchange="#UpdateSelection"/>
<text> </text>
#revOption <text> </text>
}
</InputRadioGroup>
<p>Selected Season inside Child: #SelectedValue</p>
</EditForm>
#code {
[Parameter]
public string SelectedValue { get; set; } = "No seasons selected.";
[Parameter]
public EventCallback<string> TestEvent { get; set; }
Model model = new Model();
class Model
{
public Season Season { get; set; }
}
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
private async Task UpdateSelection()
{
SelectedValue = model.Season.ToString();
await TestEvent.InvokeAsync(SelectedValue);
}
}
and the parent component index.razor
<h1>Parent</h1>
<Child TestEvent=#UpdateChildSeason />
<p>Season data from Child: #ChildSeason</p>
#code
{
string ChildSeason;
void UpdateChildSeason(string value)
{
ChildSeason = value;
}
}
This does not work properly. I not pass the data to the parent
p.s.
At the same time, if I select any of the radio buttons, all of the radio buttons will be cover in green buttons.
I have no idea why that happens.

It's only a Css issue.
If you inspect the element you'll see:
<input class="modified valid" type="radio" name="81920f03ae5d4345bf72369b58ea59e1" value="Autumn" _bl_7ea09091-cc5a-4fb1-977c-dd45c318c204="">
Note the valid which gives a green box around normal input elements when the entered data is valid. It's part of the Css formatting created in InputBase.
The problem is the Css that styles this is (site.css: line 29) :
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
It doesn't exclude Radio.
There are various ways to solve this. This works:
.valid.modified:not([type=checkbox, radio]) {
outline: 1px solid #26b050;
}

So, the solution I have is to not use EditForm. Well, since I am not worried about data validation, I can do away with EditForm for now.
Here is my solution.
This is the child component. Pages\Child.razor
<p>Please select your choice.</p>
#foreach (var seasonOption in (Season[])Enum
.GetValues(typeof(Season)))
{
<label>
<input type="radio"
name="seasonOption"
value="#seasonOption"
onchange="#OnValueChanged" />
#seasonOption.ToString()
</label>
}
<br />
<p>SelectedValue: #SelectedValue</p>
#code {
[Parameter]
public string SelectedValue { get; set; } = "No season selected.";
[Parameter]
public EventCallback<string> SelectedValueChanged { get; set; }
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
private Task OnValueChanged(ChangeEventArgs e)
{
SelectedValue = e.Value.ToString();
return SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
and the parent component index.razor
<Child2 #bind-SelectedValue="childData" />
<p>Data from Child: #childData</p>
#code
{
string childData = "No child data.";
}
Now, the data is finally passed to the parent. This will work for me now. Does anyone have a solution for the EditForm version and how to remove that green boxes from the radio buttons?

Related

(Blazor) Select value not updating

I have a <select> element in my page, of which I want to get the value. To do that, I've added the #bind= property. Here's the code:
<form target="#">
<label for="subject">Vak</label>
<select #bind="#selectedSubject" id="subject">
#foreach (Subject subject in Auth.User.Subjects)
{
<option value="#subject.Uuid">#subject.Name</option>
}
</select><br />
<!-- (more input fields) -->
<button class="button btn-primary mt-3" #onclick="addNote">Toevoegen</button>
</form>
#code {
private String selectedSubject { get; set; }
private String inputTitle { get; set; }
private String inputContent { get; set; }
private void addNote()
{
Console.WriteLine(selectedSubject);
}
}
This doesn't output anything, and trying to use selectedSubject results in a NullReferenceError.
The other values (inputTitle, inputContent) work as expected.
Yes, the <select> is properly being filled with <option>s.
I've tried switching to whatever <EditForm> is, but it kept giving me warnings and failed to build.
First of all, you don't need a form at all. The main purpose of EditForm is to validate inputs-- like, making sure a password is the correct format.
Also, unless you want to programmatically SET the value of the dropdown, you don't need to bind to it-- instead, use #onchange:
<select #onchange=ProcessSelection>
. . .
#code {
Subject SelectedSubject {get; set;}
async Task ProcessSelection (ChangeEventArgs args){
SelectedSubject = Auth.User.Subjects.Single(s=>s.uuid = args.Value.ToString();
// Process the selection.
}
}
This will (1) give you a place to breakpoint for debugging. (2) Let you do useful things with the SelectedSubject object more easily (like add / remove it from a list, pass it as a parameter to a display component, etc.).
My approach to the problem is different from the other answer.
Here's everything in one Razor component.
Separate your data out into a class. Note that the fields can be null (you may not want them to be).
Use EditForm with normal binding. You can then do validation.
Logic in select list to determine if no value is selected and display a select message.
Separate out the Select code into a separate RenderFragment block to keep the code cleaner. Not necessary if you like all your markup in one place.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<EditForm EditContext="editContext">
<div class="col-12 mb-3">
<label class="form-label small muted">Subject</label>
<select class="form-select" #bind="model.Subject">
#this.SubjectList
</select>
</div>
<div class="col-12 mb-3">
<label class="form-label small muted">Title</label>
<input class="form-control" #bind="model.Title">
</div>
</EditForm>
<div class="alert alert-info m-3">
<div class="m-2">
Subject : #model.Subject
</div>
<div class="m-2">
Title : #model.Title
</div>
<div class="m-2">
Content : #model.Content
</div>
</div>
#code {
//private Note model = new() { Subject="Portugal"};
private Note model = new();
private EditContext? editContext;
protected override void OnInitialized()
=> this.editContext = new EditContext(model);
private IEnumerable<string> Subjects = new List<string> { "France", "Portugal", "Spain" };
private bool noSubjectSelected => !Subjects.Any(item => item == model.Subject);
// Separates out the code into a separate render block
private RenderFragment SubjectList => (__builder) =>
{
if (noSubjectSelected)
{
<option disabled selected value="">-- Select a Subject --</option>
}
foreach (var subject in Subjects)
{
<option value="#subject">#subject</option>
}
};
// Normally separate out into own class file
public class Note
{
public string? Subject { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
}
I've left the subject as a string as that's what you have it as. If it's a guid you will need to adjust the Select Code to handle a Guid/String pair.
Bear in mind that source code is an instruction of rendering intent so it isn't always clear how your code will react in all cases of operation.
You have to decide whether there will be a selection at all times, or whether no material selection is also valid. For example, if you want an option of "Select a Subject" at the top of the list which is the default, so set am option with that text an a value of Guid.Empty (all zeros). When processing values - just check that the selected Guid is not equal to Guid.Empty if you want to know if a "real" subject option is selected
I use the following two approaches with InputSelect. Note that the Microsoft renderer may set the text of the select to your bound option but may not set that option to "selected"
<InputSelect #bind-Value=MyData>
<option selected value="#Guid.Empty">Select a Subject</option>
#foreach (var i in Subjects)
{
<option value=#i.Id>#i.SubjectName</option>
}
</InputSelect>
If you want finer grained control over the select, there is this binding option. As well as a value to which you bind, you also need a function which is called on change - SelectChanged - where you can run other code. you should set the bound value within this method. If the InputSelect is inside a component, the bound value should also have an EventHandler of the same name with the "Changed" text appended - you should Invoke this in your SelectChanged function. Any parent control binding to the child will have its data automatically updated and the SetStateChanged is called for you
<InputSelect Value="#MyValue" ValueChanged="#((Guid value) => SelectChanged(value))" ValueExpression="#(() => MyValue)" >
#foreach (var i in MyValues)
{
if (TheSelectedValue == i.TheValue)
{
<option selected value=#i.TheValue>#i.ValueDisplay</option>
}
else
{
<option value=#i.TheValue>#i.ValueDisplay</option>
}
}
</InputSelect>
To solve your issue with things not changing, there are two ways you can do it:
Start with a null object for your SelectedSubject, do a null check in your click, and have a dummy select option that forces you to select an item:
.
<select #onchange=ProcessChange>
<option selected disabled>Pick a subject. . . </option>
#foreach (var subject in Auth.User.Subjects) {
<option>. . . </option>
}
</select>
<button #onclick=AddSubject />
#code {
List<Subject> SelectedSubjects = new();
Subject SelectedSubject {get; set; } = null;
async Task ProcessChange (ChangeEventArgs args){. . . }
async Task AddSubject{
if (Subject is not null) SelectedSubjects.Add (SelectedSubject);
}
}
Preselect your first option in your data initialization, and then you don't NEED the dummy <option> or the null check.
.
#code{
async Task OnInitializedAsync (bool IsFirstRender){
Auth.User.Subjects = await LoadSubjectsForUser (Auth.User);
SelectedSubject = Auth.User.Subjects.First();
}
}
In your use case, I think #1 is probably better.

Passing reference of component to its ChildContent components

To add some context, I'm trying to create a Dropdown select Blazor component. I've managed to create a concept of this entirely with CSS, #onclick, and #onfocusout.
I'm trying to pass a reference of the DropDown component to its children, DropDownItem. The only way I know how to achieve this, is by using the #ref and passing it as a parameter to the DropDownItem component.
<DropDown #ref="DropDownReference">
<DropDownItem ParentDropDown=#DropDownReference>Hello</DropDownItem>
<DropDownItem ParentDropDown=#DropDownReference>World</DropDownItem>
</DropDown>
There has to be a cleaner approach here that does not require manually passing the reference down to each child instance. I suppose I could use CascadingValue but that will still require me to store the DropDown reference.
I'm trying to notify DropDown parent when a click event occurs in DropDownItem. This will signal the parent to changes it selected value - as it would traditionally work in a select.
Here is an example of how you could do it using CascadingValue. The DropDownItem component will accept a [CascadingParameter] of type DropDown. There is nothing wrong in doing that, this is how it's done in most (if not all) component libraries.
DropDown.razor
<CascadingValue Value="this" IsFixed="true">
#* Dropdown code *#
<div class="dropdown">
#ChildContent
</div>
</CascadingValue>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
private string selectedItem;
public void SelectItem(string item)
{
selectedItem = item;
StateHasChanged();
}
}
DropDownItem.razor
#* Dropdown item code *#
<div class="dropdown-item" #onclick="OnItemClick">...</div>
#code {
[CascadingParameter] public DropDown ParentDropDown { get; set; }
[Parameter] public string Name { get; set; }
private void OnItemClick()
{
ParentDropDown.SelectItem(Name);
}
}
Usage:
<DropDown>
<DropDownItem Name="Hello">Hello</DropDownItem>
<DropDownItem Name="World">World</DropDownItem>
</DropDown>

Save components in list or array?

I have a index.razor, that has a for loop that generates let's say 100 components (those display text, and a couple more vlaues), now i want to hava a reference of every component, is there a way to save those in a List or an array?
I tryed it myself in many ways but I get the name of the component displayed in the html.
addPdfComp is executed by a button:
public void addPdfComp(int type)
{
var newComponent = new PdfComp();
newComponent.text = "some test text";
PdfComponentsList.Add(newComponent);
}
and to display the components:
#foreach (var comp in PdfComponentsList)
{
<div>#comp</div>
}
result:
see result
Edit:
The component itself:
<div class="row p-1 " style="border:solid; ">
<h5>TextComponent</h5>
<div>
<label>X</label>
<input type="number" class="col-2" #bind-value="_x"/>
<label>Y</label>
<input type="number" class="col-2" #bind-value="_y"/>
<label>Text</label>
<input type="text" class="col-4" #bind-value="text" />
</div>
#code {
[Parameter]
public string text { get; set; } = "Missing text";
[Parameter]
public string type { get; set; } = "noone";
[Parameter]
public int _x { get; set; } = 0;
[Parameter]
public int _y { get; set; } = 0;
}
The default string representation for an object is the fully qualified class name for that object, which is what you're seeing. This means that whatever PdfComp is, it doesn't have a meaningful .ToString() implementation.
And that's all Razor syntax really does in this case, it just emits output as a string. So #something will basically evaluate something as a string and write it to the output.
Based on a comment above:
My intention is [...] to display the component itself as if i just typed: <PdfComp></PdfComp> 100 times.
An instance of PdfComp in C# code is not the same thing as an instance of <PdfComp></PdfComp> in the markup. (Though I imagine they are very closely analogous somewhere in the depths of the framework.) What you want isn't a list of object instances, but a list of the data your view needs to render the markup.
So, structurally, more like this:
// PdfComponentsList here is a list of strings
PdfComponentsList.Add("some test text");
And then loop through it in the markup to display the elements:
#foreach (var pdf in PdfComponentsList)
{
<PdfComp text="#pdf"></PdfComp>
}
You could also make PdfComponentsList a list of some object to hold multiple properties, for example:
PdfComponentsList.Add(new SomeObject { Text = "some test text" });
And in the markup:
#foreach (var pdf in PdfComponentsList)
{
<PdfComp text="#pdf.Text"></PdfComp>
}

Event for click outside a div or element to close it on Blazor

In my Blazor server-side project, I need to close a pop-up menu by clicking outside the menu.
I use a simple If statement to show/hide the pop-up by triggering the onClick event. but there is no such event to close the pop-up by clicking outside the pop-up menu. so the user should only close it by click on the element with the onClick event.
so my question is how we can resolve this issue better without using JS?
Thank you in advance.
Here is what I do with a div to fire the onfocusout event where the ShowSelectBox variable is used by your if-statement to show or hide something:
<div tabindex="0" #onfocusout="#(() => ShowSelectBox = false)">
...
</div>
I used this in a blazor webassambly application, but should be the same for server-side.
Add a <div> element that is the size of the screen and is z-indexed 1 lower than the popup menu, but higher then the rest of the application. You will need to use position: fixed in the CSS class, and then width: 100%; height: 100%; top: 0%; left: 0%; so it will fill up the page. Initial CSS display value should be display: none;. When you open the popup, also change the display property of the div to display: flex to add it to the DOM, then add an #OnClick click handler on this floating div that will close the popup AND set the floating div display property back to display: none;. The div should be clear so you can still see the rest of the app behind it, but allows you to click outside the popup for the behavior you are looking for, as anywhere outside the popup will be on the Div covering the rest of the screen.
Note that on the popup, you will also need to set the floating Div display value back to 'none' when you close the popup from inside the popup, otherwise it will stick around and require an extra click to get it to go away.
You can also slightly shade the Div element with transparency to provide a highlighting effect for your popup when it opens.
I came up with a solution without using JS, using the onmouseover event we can set a value to a bool var as true and with a onmouseout event we can set as false and so, verifying if the mouse clicked in or out a tag element
<button #onclick="Show">Click</button>
<div #onmouseover="MouseIn" #onmouseout="MouseOut">...</div>
#code{
bool IsInside;
void Show()
{
if(!IsInside)
{
...
}
}
void MouseIn()
{
IsInside= true
};
void MouseOut()
{
IsInside= False;
}
}
I had the same situation and I ended up using css :hover state. Assuming you have a drop down menu popup, doing this way when you hover the div element the menu popup will get displayed and when you hover out the menu popup will be closed automatically.
or
You can do something like,
<div class="dropdown is-right is-hoverable #(PopupCollapsed ? "is-active" : null)"
#onclick="e => this.PopupCollapsed = !this.PopupCollapsed"
#onfocusout="e => this.PopupCollapsed = false">
</div>
#code {
public bool PopupCollapsed { get; set; }
}
The #onmouseout event inside of my div tag does work, but it was a bit too touchy for what I needed because my div has several children so it kept closing when I didn't want it to. I found this solution to be more reliable.
I created an event handler class
[EventHandler("onmouseleave", typeof(EventArgs), true, true)]
public class EventHandlers
{
}
and then inside my razor page:
<div class="menu" #onmouseleave="MouseLeave"></div>
#code {
private void MouseLeave(EventArgs args)
{
IsVisible = false;
}
}
My solution is use blazor only.
All dropdown is from one component which contols click on it. if user click on closed dropdown to open it then actualy opened dropdown is closed. Or if user click on main div of body page or any object on this div (not dropdown), then actualy opened dropdown is closed too. Attribute stopPropagation is set to true on dropdown for block main div click event to block close dropdown actualy opening itself.
Dropdown has its own click event because it also contains an input element for searching dropdown items and clicking on it would close the dropdown.
It should also work for DatePisker, ColorPicker and other elements that actually open a div.
I try this solution and it looks like it could work well
Create class for all dropdowns
public class DropDownState
{
public string OpenedDropDownId { get; private set; }
public event Action OnChange;
public void SetOpened(string Id)
{
OpenedDropDownId = Id;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
To Program.cs add
builder.Services.AddScoped<Data.DropDownState>();
Dropdown component looks like this, stop propagation is important for this solution
#inject Data.DropDownState DropDownStateService
#implements IDisposable
<div class="form-group" #onclick="ObjectClick"
#onclick:stopPropagation="true">
<div class="dropdown">
...
// each dropdown needs own unique id
[Parameter]
public string ComponentId { get; set; }
bool showResults = false;
protected override async Task OnInitializedAsync()
{
DropDownStateService.OnChange += DropDownOdherOpened;
}
public void Dispose()
{
DropDownStateService.OnChange -= DropDownOdherOpened;
}
async Task DropdownShow(bool _showResults)
{
showResults = _showResults
if (showResults)
{
// this informs other dropdowns that this dropdown is opened an other dropdown go to closed state
DropDownStateService.SetOpened(ComponentId);
}
}
public void DropDownOdherOpened()
{
if (DropDownStateService.OpenedDropDownId != ComponentId && showResults)
{
// close dropdown
DropdownShow(false);
StateHasChanged();
}
}
// this closes other dropdowns on click, this replaces main div click event functionality
public async Task ObjectClick()
{
DropDownStateService.SetOpened(ComponentId);
}
And in MainLayout.razor add onclick
#inject Data.DropDownState DropDownStateService
<div class="container-fluid" #onclick="ObjectClick">
#Body
</div>
public async Task ObjectClick()
{
DropDownStateService.SetOpened("Body");
}
I had this very same issue with a custom dropdown menu I made, and I felt that the standard behavior of anything that looks like a dropdown should be to close itself if you click outside of it.
I solved it with a custom component that puts a fullscreen div just below the dropdown (z-index wise), and added an onclick eventcallback to that div. It feels a bit unorthodox to do it like this, and it may not fit all cases, but hey, it works, and no JavaScript needed!
On my .razor page:
#if(showUserDD)
{
<OutsideClickDetector MethodToCallOnClick="#ToggleUserDD" LowestInsideComponentZIndex="1000" />
<div id="UserDDContent" style="z-index: 1000" class="list-group position-absolute shadow">
... some content ...
</div>
}
#code {
private async Task ToggleUserDD() => showUserDD = !showUserDD;
}
OutsideClickDetector.razor
<div #onclick="OnClick" class="vh-100 vw-100 position-fixed"
style="top: 0; left: 0; z-index: #(LowestInsideComponentZIndex-1);
background-color: #(TransparentOutside ? "none" : "rgba(0,0,0,0.5)");"
/>
#code
{
[Parameter] public int LowestInsideComponentZIndex { get; set; }
[Parameter] public bool TransparentOutside { get; set; } = true;
[Parameter] public EventCallback MethodToCallOnClick { get; set; }
private async Task OnClick() => await MethodToCallOnClick.InvokeAsync(null);
}
For debugging purposes you can set TransparentOutside to false if you want to see the fullscreen div.
It's a bit of a faff but managed it with some JS interop. I'm new to Blazor but managed to cadge this together from various other posts;
Add an id to your popup - called mine profile-popup
<div id="profile-popup">
<RadzenProfileMenu #ref="profileMenu" Style="width: 200px;" >
<Template>
<div class="row">...
Create a JS file to attach a handler to the click event of the document - if the source of the click is in your popup ignore it, otherwise fire a helper method from your helper class
window.attachHandlers = (dotnetHelper) => {
document.addEventListener("click", (evt) => {
const profileElement = document.getElementById("profile-popup");
let targetElement = evt.target;
do {
if (targetElement == profileElement) {
//return as not clicked outside
return;
}
targetElement = targetElement.parentNode;
} while (targetElement);
dotnetHelper.invokeMethod("InvokeAction");
});
};
Create the helper class
public class ProfileOutsideClickInvokeHelper
{
Action _action;
public ProfileOutsideClickInvokeHelper(Action action)
{
_action = action;
}
[JSInvokable]
public void InvokeAction()
{
_action.Invoke();
}
}
Attach the handler in the OnAfterRender override. I have a component containing the popup. You need to dispose of the object reference
public partial class TopBanner : IDisposable
{
[Inject]
IJSRuntime JSRuntime { get; set; }
public void CloseProfileMenu()
{
profileMenu.Close();
}
DotNetObjectReference<ProfileOutsideClickInvokeHelper> _objRef;
protected override void OnAfterRender(bool firstRender)
{
_objRef = DotNetObjectReference.Create(new ProfileOutsideClickInvokeHelper(CloseProfileMenu));
JSRuntime.InvokeVoidAsync("attachHandlers", _objRef);
}
public void Dispose()
{
_objRef?.Dispose();
}
}
Not sure if this is the best solution, or even a good one, but it seems to work ok.
Here is my solution. This is Blazor/CSS only and will work with most popular exsisting pop-up/slider/offcanvas type css (eg: bootstrap).
The button to open the pop-up/slider etc:
<a #onclick="ToggleUserPanel">Open</a>
The pop-up/slider etc:
<div id="quick_user" class="offcanvas offcanvas-right p-10 #UserPanelClass">
<a href="javascript:;" class="btn btn-primary" id="quick_user_close">
<i #onclick="ToggleUserPanel"></i>
</a>
</div>
Important part, the Overlay. Most base css libraries will use an Overlay with a pop-up, the beauty of this is you don't need to worry about styling a div for the "non" pop-up part of the screen. If you aren't using a library it's easy enough to write your own styles for a z-indexed overlay.
#if (UserPanelClass != "")
{
<div class="offcanvas-overlay" #onclick="ToggleUserPanel"></div>
}
BLAZOR STUFF
#code{
private string UserPanelClass = "";
//This will either show the User Panel along with it's overlay
//or close the User Panel and remove the overlay.
//It can be triggered by any buttons that call it or (the solution to this
//post) clicking on the "Non Pop-Up" part of the screen which is the
//overlay div
private void ToggleUserPanel()
{
if (String.IsNullOrEmpty(UserPanelClass))
UserPanelClass = "offcanvas-on";
else
UserPanelClass = "";
}
}
And that's it.
Found a solution in a Github repository.
<OutsideHandleContainer OnClickOutside=#OnClickOutside>
...
<div>Sample element</div>
...
</OutsideHandleContainer>
#functions{
void OnClickOutside()
{
// Do stuff
}
}
I was running into a similar, if not the same, issue. In my case, I have a shared component from an RCL, that exists as a child component on either a page or other component. I'm creating a custom dropdown menu and I needed a global event that would close all of those components if I clicked anywhere else on the page.
Originally, I resorted to creating a global click event with Javascript. I decided to look back into it and came up with a solution that has zero Javascript and I wanted to share and it's actually pretty simple.
I have my component
<div class="dropdown" #onclick="toggleDropdown" #onclick:stopPropagation>
#if(_item.Expanded)
{
<div class="dropdown-list">
<ul>
<li>Item</li>
</ul>
</div>
}
</div>
#code{
[CascadingParameter]
protected MyService Service { get; set; }
[Parameter]
protected Item Item { get; set; }
private Item _item;
protected override void OnParametersSet()
{
_item = Item;
if(!Service.Items.Contains(_item))
Service.Items.Add(_item);
base.OnParametersSet();
}
private void toggleDropdown() => _item.Expanded = !_item.Expanded;
}
Then I have my service
public class MyService : IMyService
{
private List<Item> _items;
public List<Item> Items
{
get => _items ?? new List<Item>();
set => _items = value;
}
public void CloseDropdowns()
{
if(Items.Any(item => item.Expanded))
{
foreach(var i in Items)
i.Expanded = false;
}
}
}
Finally, I have the MainLayout of my actual application
<CascadingValue Value="MyService">
<div class="page" #onclick="MyService.CloseDropdowns">
<div class="main">
<div class="content px-4">
#Body
</div>
</div>
</div>
</CascadingValue>
Hope this helps.
Update
I failed to mention an issue that occurs when there are multiple of the same component on a page, for example, two or more dropdowns. The above code works as designed since the mainlayout click event is bubbled up, but we don't want this to close everything every time, we could have one dropdown open and all the others closed.
For this, we need to stop the event bubbling to the mainlayout and provide a callback to the dropdown's parent component that will update the child component.
In the service
public void ToggleDropdown(bool expanded, Item item)
{
foreach(var i in _items)
i.Expanded = false;
item.Expanded = expanded;
}
In the component
[Parameter]
public EventCallback<(bool, DSCInputConfig)> ExpandCallback { get; set; }
private bool _shouldRender;
protected override bool ShouldRender() => _shouldRender;
private void toggleDropdown()
{
_expanded = !_expanded;
ExpandCallback.InvokeAsync((_expanded, _config));
_shouldRender = true;
}
Finally, in the parent component/page
<Dropdown Options="#component" ExpandCallback="dropdownCallback" />
<Dropdown Options="#component" ExpandCallback="dropdownCallback" />
#code{
private void dropdownCallback((bool expanded, Item config) item) => MyService.ToggleDropdown(item.expanded, item.config);
}

How i can iterate over items in form Asp.net MVC3

I have a problem in my application
I am using MVC 3 with razor and i want to get the value of checked box in the form
#using (Html.BeginForm("ConfirmItemAvilabilty", "Order", FormMethod.Post, new { id = "frmConfirmAvilabilty", name = "frmConfirmAvilability" }))
{
foreach (OrderItem orderItem in orderAction.Order.OrderItems)
{
<div class="product">
<ul>
<li>
<div class="productImg">
<img src="#orderItem.Product.Image" width="100px" height="100px"/>
</div>
<div class="centered">
Link <span>#orderItem.Product.TitleAr</span>
<span>#orderItem.Product.Price</span>
</div>
#if (currentUser.UserTypeEnum == UserTypeEnum.Reseller)
{
<div>
#Html.CheckBox("ChkConfirm", orderItem.IsAvailable, new { id="chkConfirm" ,#class="chkConfirm"})
#Html.Hidden("OrderItemId", orderItem.Id, new { id="hdnConfirm"})
</div>
}
</li>
</ul>
</div>
}
if (currentUser.UserTypeEnum == UserTypeEnum.Reseller)
{
<button>Confirm</button>
}
}
Simply i want to get the value of all checked checkboxs i tryed to create a model holding the value of my checkbox and the value of the hidden text below it
public class ItemOrderModel
{
public string ChkConfirm { get; set; }
public string OrderItemId { get; set; }
}
and in my controller i do the following but nothing happened
public ActionResult ConfirmItemAvilabilty(List<ItemOrderModel> OrderItems)
{
return View();
}
but orderItems always returns null, Can anyone help me in that?
----------------- Edit ------------------
Thank you Sam and Jesse
I Found a solution for my problem but I am facing another problem now first of all i solved my problem by changing in model view to be like that
public class ItemOrderModel
{
public List<bool> ChkConfirms { get; set; }
public List<string> OrderItemId { get; set; }
}
and change the checkbox name to be
#Html.CheckBox("ChkConfirms", orderItem.IsAvailable, new { id = "chkConfirm", #class = "chkConfirm" })
the problem now is
when i submit i found two values false that is the actual representation of my checkboxs and two ids that's also the actual representation of my hidden fields "Correct scenario"
when i check one of check boxs in the same form i found 3 result for the check box and 2 results for the hidden field Can any one help in that or have a solution
You need to look into Editor Templates:
How to create custom editor/display templates in ASP.NET MVC 3?
These allow you to do the exact thing you are talking about.

Categories

Resources