how to bind select value and change event [duplicate] - c#

I need to be able to run a function after a selection in made in a <select>. The issue is I'm also binding with #bind and I get a error when I try to use #onchange stating that it is already in use by the #bind. I tried using #onselectionchange, but that does nothing(doesn't run the function). I could forget the #bind and just assign #onchange to a function, but I'm not sure how to pass the selected value to the function.
I have the following code:
<select #bind="#SelectedCustID" # #onchange="#CustChanged" class="form-control">
#foreach (KeyGuidPair i in CustList)
{
<option value="#i.Value">#i.Text</option>
}
</select>
Thanks.

#bind is essentially equivalent to the having both value and #onchange, e.g.:
<input #bind="CurrentValue" />
Is equivalent to:
<input value="#CurrentValue" #onchange="#((ChangeEventArgs e) => CurrentValue = e.Value.ToString())" />
Since you've already defined #onchange, instead of also adding #bind, just add value to prevent the clash:
<select value="#SelectedCustID" #onchange="#CustChanged" class="form-control">
#foreach (KeyGuidPair i in CustList)
{
<option value="#i.Value">#i.Text</option>
}
</select>
Source: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-3.1

<select #bind="MyProperty">
<option>Your Option<option>
</select>
#code {
private string myVar;
public string MyProperty
{
get { return myVar; }
set
{
myVar = value;
SomeMethod();
}
}
private void SomeMethod()
{
//Do something
}
}

Just add #bind-value and #bind-value:event="oninput" with #onchange as below. Note the lower case letters. This works for me without any issue.
<select #bind-value="variablenametokeepselectedvalue" #onchange="yourmethodname" #bind-value:event="oninput">
<option value="1">Test</option>
</select>

This seems to be a popular confusion. Firstly you cant use #onchange since it would internally be used by #bind. You should be able to access the selected value from the setter of your CustChanged property. Based on what you are trying to do with your CustChanged, you may not even need to manually check when this value is updated. For instance, if your intent is to use CustChanged in your UI directly or indirectly (within Linq or something), the UI would automatically update with CustChanged value when your <select> is changed. Hence, for most use cases I don't see the need to check when it was updated.
To use #onchange, you can bind it to a function something like this:
public void OnUpdated(ChangeEventArgs e)
{
var selected = e.Value;
}

You can avoid #bind altogether (if you're using a foreach):
<select #onchange=#(handleChange)>
#foreach (var option in _options){
<option value=#option.Id selected=#(SelectedId == option.Id)>#option.Name</option>
}
</select>
#code {
public record Person(int Id, string Name);
public int SelectedId { get; set; }
public List<Person> _options = new List<Person>() {
new Person(1,"A"),
new Person(2,"B"),
new Person(3,"C")
};
public void handleChange(ChangeEventArgs args) {
Console.WriteLine(args.Value);
SelectedId = Int32.Parse(args.Value.ToString());
}
}
Some sordid details: I was getting some weird behavior when trying to use F# with server-side Blazor. In short, setting the List of options to the result of an Entity Framework query (mapped to a list of records) wouldn't #bind properly, but using a dummy list of options that were C# classes and not F# records did work. It wasn't due to it being records though, because if I set the list to the EF query and then immediately set it to a dummy list of records, it still didn't #bind properly - but it did work if I commented out the EF line.

Please check this example. It is using #bind but upon setting the value it triggers #onchange event
<div class="form-group">
<label for="client">Client Name</label>
<select id="client" #bind="CheckSelected" class="form-control">
<option value="selected1">selected1</option>
<option value="selected2">selected2</option>
</select>
</div>
#code {
private string selectedItem {get; set;}
private string CheckSelected
{
get
{
return selectedItem;
}
set
{
ChangeEventArgs selectedEventArgs = new ChangeEventArgs();
selectedEventArgs.Value = value;
OnChangeSelected(selectedEventArgs);
}
}
private void OnChangeSelected(ChangeEventArgs e)
{
if (e.Value.ToString() != string.Empty)
{
selectedItem = e.Value.ToString();
}
}
}

Please check below code for reference how we use select tag with bind value and call function onselection value change in blazor
<InputSelect class="form-control mb-0" ValueExpression="#(()=>request.Id)" Value="request.Id"
ValueChanged="#((string value) => SelectedValueChange(value))">
#if (dropdownResponses != null)
{
#foreach (var item in dropdownResponses)
{
<option value="#item.Id.ToString()">#item.Name</option>
}
}
</InputSelect>
use <InputSelect> tag instead of <select> tag and use ValueChanged method for getting call on select value change
here is the code of ValueChanged function
internal void SelectedValueChange(string value)
{
string NewValue = value;
}

Starting from .NET 7 Preview 7, the recommended way to handle this issue is to use the bind:after modifier.
Here is a small example (partially borrowed from the docs):
<p>
<label>
Select one or more cities:
<select #bind="SelectedCities" multiple #bind:after="DoSomething">
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>
<span>
Selected Cities: #string.Join(", ", SelectedCities)
</span>
#code {
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };
// Run your logic here after a binding event
private void DoSomething()
{
Console.WriteLine(string.Join(',', SelectedCities));
}
}

My recommendation is, if possible, use an EditForm wrapper around your form elements. Then you can detect a change of any of the form elements, in one place. This is good for, for example, a bunch of search filters. Any change in any of the filters should trigger another query of the data, etc.
Example of how to trigger event on form changes is here:
blazor editform change events

According to Microsoft's documentation, this is their preferred way of handling this problem:
<InputText Value="#NewPaymentAmount" class="mdl-textfield__input"
ValueExpression="() => NewPaymentAmount"
ValueChanged="(string value) => ValidateAmount(value)" />
private void ValidateAmount(string amount)
{
NewPaymentAmount = amount;
// Do validation or whatever
}
Or the async way:
<InputText Value="#NewPaymentAmount" class="mdl-textfield__input"
ValueExpression="() => NewPaymentAmount"
ValueChanged="async (string value) => await ValidateAmountAsync(value)" />
private async Task ValidateAmountAsync(string amount)
{
NewPaymentAmount = amount;
// Do validation or whatever
}

<div class="form-group">
<label for="client">Client Name</label>
<select class="form-control" #onchange="#((e) => { myVar = e.Value.ToString(); MyMethod(); })">
<option value="val1">val1</option>
<option value="val2">val2</option>
</select>
</div>
This is how I was able to set the property while calling for a method in the #onchange event.
-Blazor Server
-Dotnet Core 3.1

I use oninput to essentially have bind and onchange.
oninput triggers when the input value is changed whereas bind/onchange triggers when the element loses focus and the input has changed.
This might not fit every scenario however as it wll trigger on every input while typing, depending on your need or for inputs such as selects, radios, etc. it should be suitable.

Related

Blazor #bind + #onchange for a select html tag [duplicate]

I need to be able to run a function after a selection in made in a <select>. The issue is I'm also binding with #bind and I get a error when I try to use #onchange stating that it is already in use by the #bind. I tried using #onselectionchange, but that does nothing(doesn't run the function). I could forget the #bind and just assign #onchange to a function, but I'm not sure how to pass the selected value to the function.
I have the following code:
<select #bind="#SelectedCustID" # #onchange="#CustChanged" class="form-control">
#foreach (KeyGuidPair i in CustList)
{
<option value="#i.Value">#i.Text</option>
}
</select>
Thanks.
#bind is essentially equivalent to the having both value and #onchange, e.g.:
<input #bind="CurrentValue" />
Is equivalent to:
<input value="#CurrentValue" #onchange="#((ChangeEventArgs e) => CurrentValue = e.Value.ToString())" />
Since you've already defined #onchange, instead of also adding #bind, just add value to prevent the clash:
<select value="#SelectedCustID" #onchange="#CustChanged" class="form-control">
#foreach (KeyGuidPair i in CustList)
{
<option value="#i.Value">#i.Text</option>
}
</select>
Source: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-3.1
<select #bind="MyProperty">
<option>Your Option<option>
</select>
#code {
private string myVar;
public string MyProperty
{
get { return myVar; }
set
{
myVar = value;
SomeMethod();
}
}
private void SomeMethod()
{
//Do something
}
}
Just add #bind-value and #bind-value:event="oninput" with #onchange as below. Note the lower case letters. This works for me without any issue.
<select #bind-value="variablenametokeepselectedvalue" #onchange="yourmethodname" #bind-value:event="oninput">
<option value="1">Test</option>
</select>
This seems to be a popular confusion. Firstly you cant use #onchange since it would internally be used by #bind. You should be able to access the selected value from the setter of your CustChanged property. Based on what you are trying to do with your CustChanged, you may not even need to manually check when this value is updated. For instance, if your intent is to use CustChanged in your UI directly or indirectly (within Linq or something), the UI would automatically update with CustChanged value when your <select> is changed. Hence, for most use cases I don't see the need to check when it was updated.
To use #onchange, you can bind it to a function something like this:
public void OnUpdated(ChangeEventArgs e)
{
var selected = e.Value;
}
You can avoid #bind altogether (if you're using a foreach):
<select #onchange=#(handleChange)>
#foreach (var option in _options){
<option value=#option.Id selected=#(SelectedId == option.Id)>#option.Name</option>
}
</select>
#code {
public record Person(int Id, string Name);
public int SelectedId { get; set; }
public List<Person> _options = new List<Person>() {
new Person(1,"A"),
new Person(2,"B"),
new Person(3,"C")
};
public void handleChange(ChangeEventArgs args) {
Console.WriteLine(args.Value);
SelectedId = Int32.Parse(args.Value.ToString());
}
}
Some sordid details: I was getting some weird behavior when trying to use F# with server-side Blazor. In short, setting the List of options to the result of an Entity Framework query (mapped to a list of records) wouldn't #bind properly, but using a dummy list of options that were C# classes and not F# records did work. It wasn't due to it being records though, because if I set the list to the EF query and then immediately set it to a dummy list of records, it still didn't #bind properly - but it did work if I commented out the EF line.
Please check this example. It is using #bind but upon setting the value it triggers #onchange event
<div class="form-group">
<label for="client">Client Name</label>
<select id="client" #bind="CheckSelected" class="form-control">
<option value="selected1">selected1</option>
<option value="selected2">selected2</option>
</select>
</div>
#code {
private string selectedItem {get; set;}
private string CheckSelected
{
get
{
return selectedItem;
}
set
{
ChangeEventArgs selectedEventArgs = new ChangeEventArgs();
selectedEventArgs.Value = value;
OnChangeSelected(selectedEventArgs);
}
}
private void OnChangeSelected(ChangeEventArgs e)
{
if (e.Value.ToString() != string.Empty)
{
selectedItem = e.Value.ToString();
}
}
}
Please check below code for reference how we use select tag with bind value and call function onselection value change in blazor
<InputSelect class="form-control mb-0" ValueExpression="#(()=>request.Id)" Value="request.Id"
ValueChanged="#((string value) => SelectedValueChange(value))">
#if (dropdownResponses != null)
{
#foreach (var item in dropdownResponses)
{
<option value="#item.Id.ToString()">#item.Name</option>
}
}
</InputSelect>
use <InputSelect> tag instead of <select> tag and use ValueChanged method for getting call on select value change
here is the code of ValueChanged function
internal void SelectedValueChange(string value)
{
string NewValue = value;
}
Starting from .NET 7 Preview 7, the recommended way to handle this issue is to use the bind:after modifier.
Here is a small example (partially borrowed from the docs):
<p>
<label>
Select one or more cities:
<select #bind="SelectedCities" multiple #bind:after="DoSomething">
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>
<span>
Selected Cities: #string.Join(", ", SelectedCities)
</span>
#code {
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };
// Run your logic here after a binding event
private void DoSomething()
{
Console.WriteLine(string.Join(',', SelectedCities));
}
}
My recommendation is, if possible, use an EditForm wrapper around your form elements. Then you can detect a change of any of the form elements, in one place. This is good for, for example, a bunch of search filters. Any change in any of the filters should trigger another query of the data, etc.
Example of how to trigger event on form changes is here:
blazor editform change events
According to Microsoft's documentation, this is their preferred way of handling this problem:
<InputText Value="#NewPaymentAmount" class="mdl-textfield__input"
ValueExpression="() => NewPaymentAmount"
ValueChanged="(string value) => ValidateAmount(value)" />
private void ValidateAmount(string amount)
{
NewPaymentAmount = amount;
// Do validation or whatever
}
Or the async way:
<InputText Value="#NewPaymentAmount" class="mdl-textfield__input"
ValueExpression="() => NewPaymentAmount"
ValueChanged="async (string value) => await ValidateAmountAsync(value)" />
private async Task ValidateAmountAsync(string amount)
{
NewPaymentAmount = amount;
// Do validation or whatever
}
<div class="form-group">
<label for="client">Client Name</label>
<select class="form-control" #onchange="#((e) => { myVar = e.Value.ToString(); MyMethod(); })">
<option value="val1">val1</option>
<option value="val2">val2</option>
</select>
</div>
This is how I was able to set the property while calling for a method in the #onchange event.
-Blazor Server
-Dotnet Core 3.1
I use oninput to essentially have bind and onchange.
oninput triggers when the input value is changed whereas bind/onchange triggers when the element loses focus and the input has changed.
This might not fit every scenario however as it wll trigger on every input while typing, depending on your need or for inputs such as selects, radios, etc. it should be suitable.

Two way data/event binding w/ non strings (Blazor)

Is it possible to two way bind or bind to an event in Blazor w/ non strings? I have done this with text without an issue but any other type of object is causing me issues.
For example, I have a method that executes when inputting text in a box which is based on the value inputted as well as several other inputs on the form.
<InputNumber step=".01" class="form-control form-control-xs" #bind-Value="#Salary" #bind-Value:event="onkeydown"/>
private decimal salary {get; set;}
public decimal Salary
{
get
{
return salary;
}
set
{
salary = value;
CalculationHere();
}
}
When I do this, I get the below error:
I have also tried passing it in as a parameter like so:
#oninput="#((ChangeEventArgs __e) => CalculationHere(Convert.ToDecimal(__e.Value)"
This also does not work as it causes an error when the textbox is empty and doesn't fire for all inputs (have tried on keydown as well). There are also a lot of parameters so if possible I'd like to avoid this.
I should also note that when I run this project, set a breakpoint in the method being called, and bind like the below, it DOES work. However, removing the breakpoint stops it from working. This has left me VERY confused.
<InputNumber step=".01" class="form-control form-control-xs" #bind-Value="#Salary" #oninput="(() => CalculationHere())"/>
Is there a best practice regarding this? I'm new to web development and Blazor itself is very new so I'm not sure what the best route to go here is... Any advice? Thanks!
When you tell Blazor that it should update the value of a variable via events such as onkeydown, it does not know what to do with the event args provided to it. To achieve a two-way binding in such a case, you need to do the binding manually.
Add an #oninput directive to your InputNumber Component with the value "#((e) => #employee.Salary = Convert.ToDecimal(e.Value))"
Your InputNumber Component should look like this:
<InputNumber step=".01" class="form-control form-control-xs" #bind-Value="#employee.Salary" #oninput="#((e) => #employee.Salary = Convert.ToDecimal(e.Value))" />
Now, whenever the input of your text box changes, the input event is triggered, and the ChangeEventArags is passed to you, you extract the value, convert it to decimal and assigned it to the #employee.Salary property.
This answer could be deduced from my first answer where I use
#oninput="#((e) => CalculationHere(e))"
to call the CalculationHere
Hope this helps...
The InputNumber component should be embedded in the EditForm Component whose Model attribute is set to your model employee
You should not add the #bind-Value:event="onkeydown". What for ? The default and the correct event for the binding is the onchange event and it deals appropriately with updating the Salary property.
Put this code in the Index page and run
<EditForm Model="#employee" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" #bind-Value="#employee.Name" />
<!-- <InputNumber step=".01" class="form-control form-control-xs" #bind-Value="#employee.Salary" /> -->
<InputNumber step=".01" class="form-control form-control-xs" #bind-Value="#employee.Salary" #oninput="#((e) => CalculationHere(e))" />
<button type="submit">Submit</button>
</EditForm>
<p>Salary: #employee.Salary</p>
<p>After calculation: #calculation</p>
#code{
decimal calculation;
Employee employee = new Employee() { Name = "Davolio Nancy", Salary = 234 } ;
public class Employee
{
public string Name { get; set; }
private decimal salary { get; set; }
public decimal Salary
{
get
{
return salary;
}
set
{
salary = value;
//CalculationHere();
}
}
}
private void HandleValidSubmit()
{
}
void CalculationHere(ChangeEventArgs e)
{
calculation = Convert.ToDecimal(e.Value) * Convert.ToDecimal(1.2);
}
}

The #onkeypress doesn't get the updated value of Text input box?

I have the following code.
<input id="search" type="text" #bind="Search" #onkeypress="SearchChanged" />
#code {
string Search;
void SearchChanged()
{
var s = Search; // Search is always one character behind
}
}
Typing in the text box will trigger the function SearchChanged. However, the value it got is always one character before the typed text. For example, setting a break point in SearchChanged,
Typed Value of Search
===== ================
a null
ab a
abc ab
abcd abc
BTW, the #onkeypress doesn't work in Internet Browser?
Tried to change the code to
<input id="search" type="text" #bind="Search" />
#code {
string search;
string Search
{
get { return search; }
set {
search = value;
ResultList = GetNewList(search).Result.ToList();
}
}
void SearchChanged()
{
var s = Search; // Search is always one character behind
}
}
I set a break point in the set { }, however, the break point is hit just one or two times when typing the text box?
You are looking for #bind-value:event:
<input id="search"
type="text"
#bind-value="Search"
#bind-value:event="oninput"
/>
#code {
string search;
string Search
{
get { return search; }
set {
search = value;
ResultList = GetNewList(search).Result.ToList();
}
}
Quoting Data Binding docs:
In addition to handling onchange events with #bind syntax, a property or field can be bound using other events by specifying an #bind-value attribute with an event parameter (#bind-value:event).

Controlling a C# WebBrowser object in code to push a button which is initially disabled?

I'm loading a web page into a WebBrowser object, and part of the web page has an HTML Form with 3 attributes, namely two Select attributes and one Input attribute of type Submit. The two Select attributes present different lists of choices, and having made a choice one presses the button that corresponds to the Input attribute which causes data to be loaded. When the program is running and the WebBrowser object is visible, I can manually use the mouse and the web page loads the data correctly, exactly as though it was running in a browser like e.g. Chrome. However, when I try to write C# to control the WebBrowser object, it doesn't work.
When the web page is first loaded, the Input attribute on the form is greyed out. However, using the mouse to make a choice in either of the two Select attributes causes the Input attribute to be enabled and it changes colour to green. The C# code I have to make a selection doesn't replicate that behaviour, because the Input attribute doesn't get enabled as a result of the code (below) which does the selection. It's not clear to me where the code which causes the Input attribute to be enabled is located, but I've been hoping that I don't need to work that out.
The HTML code for the form is as follows:
<form method="get" id="filter-nav">
<fieldset>
<label for="group-drop-down">Show me:</label>
<select id="drop-down-1" name="filter">
<option value="">drop-down-1-heading</option>
<optgroup label="optgroup-1-label" id="optgroup-1" >
<option value="optgroup-1-choice-1" > Choice-1 </option>
<option value="optgroup-1-choice-2" > Choice-2 </option>
</optgroup>
<optgroup label="optgroup-2-label" id="optgroup-2" >
<option value="optgroup-2-choice-3" > Choice-3 </option>
<option value="optgroup-2-choice-4" > Choice-4 </option>
</optgroup>
</select>
<span>OR</span>
<select id=""drop-down-2" name="filter">
<option value="">drop-down-2-heading</option>
<optgroup label="optgroup-3-label" id="optgroup-3" >
<option value="optgroup-3-choice-5" > Choice-5 </option>
<option value="optgroup-3-choice-6" > Choice-6 </option>
</optgroup>
<optgroup label="optgroup-4-label" id="optgroup-4" >
<option value="optgroup-4-choice-7" > Choice-7 </option>
<option value="optgroup-4-choice-8" > Choice-8 </option>
</optgroup>
</select>
<input id="filter-nav-submit" type="submit" value="Update" />
</fieldset>
</form>
and the C# code that I've been using to try and control it is the LoadData() method of the following class
private class WebBrowserHelper {
WebBrowser wb;
public void LoadData() {
HtmlElement select1 = getElement("select", "drop-down-1");
select1.Focus();
Application.DoEvents();
select1.SetAttribute("Value", "optgroup-1-choice-2");
Application.DoEvents();
select1.InvokeMember("change");
Application.DoEvents();
// at this point, the select1 dropdown shows the right value
// but the button corresponding to the input attribute is still greyed out
}
public HtmlElement getElement(string tagName, string IdName) {
HtmlElementCollection theElementCollection = wb.Document.GetElementsByTagName(tagName);
foreach (HtmlElement curElement in theElementCollection) {
object id = curElement.GetAttribute("id");
if (id != null && id.Equals(IdName)) return curElement;
}
return null;
}
}
What am I doing wrong?
The problem might with Application.DoEvents (which BTW is an evil even for UI automation). A single iteration of the message loop pump that it does may just be not enough. Ideally, you should be using async/await and some asynchronous delay:
public async Task LoadDataAsync() {
HtmlElement select1 = getElement("select", "drop-down-1");
select1.Focus();
await Task.Delay(200);
select1.SetAttribute("Value", "optgroup-1-choice-2");
await Task.Delay(200);
select1.InvokeMember("change");
await Task.Delay(200);
// ...
}
However, if you take this road, the code has to be async "all the way down".
If that doesn't help anyway, just try enabling the button manually:
HtmlElement button = getElement("input", "filter-nav-submit");
button.Enabled = true;

ASP.Net MVC View with RadioButtonList with Other freeformat selection

I am putting together questionairre for registering for an event.
One of the eventr registration questions is:
What is your main job function?
Director/MD/Owner
Manager
Team Leader
Other: Please state
I would like this to show as a drop down box, or a radio button list in my view, but also with a textbox underneath or next to the "Other" radiobutton, so that a free form "Other" job function can be entered. How would I bind this in my view?
I suspect my model will contain:
[Display(Name = "What is your main job function?")]
public string JobFunction { get; set; }
...where JobFunction get's populated by the selected item above. But how in my controller, would I override the selected item, if it's "Other" and substitute it with the text box on the view?
I'm sure this has been done many times before - so thank you for any help,
Mark
One approach of many would be this:
View:
<div id="sample-dropdown">
<select name="JobFunction " id="JobFunction" class="dropdown">
<option value="Director/MD/Owner">Director/MD/Owner</option>
<option value="Manager">Manager</option>
<option value="Team Leader" selected="selected">Team Leader</option>
<option value="Other">Other: Please state</option>
</select>
<input name="JobFunctionOther" id="JobFunctionOther" />
</div>
<script type="text/javascript">
$('input[name="JobFunctionOther"]').hide();
$(function() {
$("#JobFunction").change(
function() {
var val = $(this).val();
if (val =='Other') {
$('input[name="JobFunctionOther"]').show();
} else {
$('input[name="JobFunctionOther"]').hide();
}
}).change();
});
</script>
Controller:
public ActionResult DropDown(string jobFunction, string jobFunctionOther)
{
if (!string.IsNullOrEmpty(jobFunctionOther)) jobFunction = jobFunctionOther;
//do what you do
return View(jobFunction);
}

Categories