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

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);
}
}

Related

how to bind select value and change event [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.

Dynamically change the Body for a component in Blazor

I'm trying to achive a search functionality for Blazor-server where the idea is to use it anytime on the site by typing on a search box which causes the page to change the #Body for a component that shows the results of the search.
Currently I'm able to do the search well on the MainLayout but this is by having already a list there and the Body component either below or on top. What I need is to only show the List AFTER I input something on the search bar and to replace it with the Body.
Here is what works but whithout the issue I am having.
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" #oninput="(ChangeEventArgs e)=>SearchHandler(e)" />
<BrowseListShared #ref="BrowseListShared" />
#Body
code{
public string Searched { get; set; }
protected BrowseListShared BrowseListShared;
public void SearchHandler(ChangeEventArgs e)
{
Searched = e.Value.ToString();
BrowseListShared.UpdadeMe(Searched);
}
}
And this is my attempt at trying to make the replacement which gives me the error "Object reference not set to an instance of an object.", the Error shows when I type something in the search box.
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" #oninput="(ChangeEventArgs e)=>SearchHandler(e)" />
#if (setVisible){
<BrowseListShared #ref="BrowseListShared" />
}else{
#Body
}
code{
public string Searched { get; set; }
protected BrowseListShared BrowseListShared;
private bool setVisible=false;
public void SearchHandler(ChangeEventArgs e)
{
if (e != null && e.Value.ToString() != ""){
setVisible = true;
}else{
setVisible=false;
}
Searched = e.Value.ToString();
BrowseListShared.UpdadeMe(Searched);
}
}
Hope someone can give me some direction to deal with this, thank you.
Edit:
Adding if(BrowseLit != null) to avoid error does make it work with some issues.
1- the first character makes so it shows just the list without the search because on the first character the code doesnt have the reference yet for the BrowseListShared so it skips the BrowseListShared.UpdateMe for the first tipe.
2- On deleting the text in the box completely until its blank and typing again will cause this error 'Cannot access a disposed object.'
There shouldn't be an issue to add a small if-block, the following is a basic concept that works for me:
<button #onclick="#( () => test = !test )">test</button>
#if (!test)
{
#Body
}
else
{
<span>some other search content - use a component here
and pass the data as a parameter to it, and its OnParametersSetAsync
can fetch needed data: #test</span>
}
#code{
bool test { get; set; }
}
I would also suggest you try using parameters for the search details instead of a reference.
If you want to show a particular page with search results, you can consider navigating the user to that page (e.g., pass the search query as a route parameter to it) - then it will render only what you want in the #Body - which can range from nothing, to search results, to a lot of other things.

Convert text to always be uppercase in Blazor

I am trying to make my input text always be uppercase in a blazor input text field. I tried creating a custom InputText like the below but it doesn't update the binding (appears as uppercase but doesn't bind as such). What am I doing wrong here? Is there a better way?
#inherits InputText
<input #attributes="AdditionalAttributes"
class="#CssClass"
value="#CurrentValue"
#oninput="EventCallback.Factory.CreateBinder<string>(
this, __value => CurrentValueAsString = __value.ToUpper(), CurrentValueAsString.ToUpper())" />
Simplicity works for me.
<input type="text" oninput="this.value=this.value.toUpperCase()" #bind=CurrentValueAsString />
For simplicity and (in my opinion) UX reasons, let's assume that the user is allowed to type lowercase letters, but the application will ToUpper the input after they leave that field.
First, make a new component so we can reuse the logic. I called mine UppercaseTextInput.razor
<input value="#UserInput"
#onchange="async e => await OnChange(e)" />
#code {
[Parameter]
public string UserInput { get; set; }
[Parameter]
public EventCallback<string> UserInputChanged { get; set; }
private async Task OnChange(ChangeEventArgs e)
{
var upperCase = (e.Value as string).ToUpperInvariant();
await UserInputChanged.InvokeAsync(upperCase);
}
}
The private method OnChange gets called whenever the input loses focus. Change #onchange to #oninput to make this happen every keystroke.
Now you can use this component in another page or component. For example, in the index page:
#page "/"
<h1>Hello, world!</h1>
<UppercaseTextInput #bind-UserInput="UserInput" />
<p>You typed: #UserInput</p>
#code {
public string UserInput { get; set; } = "Initial";
}
In this example, I have "Initial" as the starting text, which will be printed inside the text box. As soon as you leave the element, the text inside will be transformed to be uppercase.
The benefit of using a component is that you can do a standard #bind- with your properties.
I was using an old reference to the input which was causing the error. There was a bug in the code though (if the input was null). The corrected version can be seen here:
#inherits InputText
<input #attributes="AdditionalAttributes"
class="#CssClass"
value="#CurrentValue"
#oninput="EventCallback.Factory.CreateBinder<string>(
this, __value => CurrentValueAsString = __value?.ToUpper(), CurrentValueAsString?.ToUpper())" />
The answer to this was easier than all the answers: it can be solved using CSS text-transform: uppercase;. This is all you needed and it doesn't take any processing power because it's included in the browser's engine.
I'm using MudBlazor.
I scratched my head for a while why the heck adding
Style="text-transform: uppercase;"
to <MudInput> is not working, even with !important attribute the default user agent style (text-transform: none;) was applied.
C# or JS seemed overkill for the task in my opinion.
What finally worked for me was adding
input {
text-transform: uppercase;
}
to the site's CSS sheet (index.css in my case).
This eventually overwritten the default user agent style.

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

ASP.NET MVC checkbox always false

I made an asp.net website, but the checkbox is always false. Why is this so?
Model:
public string Username { get; set; }
public string Password { get; set; }
public bool Remember { get; set; }
CSHTML:
<div class="form-group">
#Html.Label("Remember me?")
#Html.CheckBoxFor(m => m.Remember)
</div>
The Remember property is always false, if the checkbox is checked then Rememberis still false.
I got the same issue, I fixed it by writing html checkbox tag, giving it the name same as property name, and value = true, if the checkbox is not checked no need to worry as it won't be submitted anyway,
in your case this will be it
<input type="checkbox" name="Remember" value="true" />
With Razor, I had the same problem. What worked for me was taking off the value="xxx" tag. Then it functioned normally.
Does not work:
<input class="form-check-input" value="true" asp-for="Answer.aIsCorrect" />
Works:
<input class="form-check-input" asp-for="Answer.aIsCorrect" />
The first parameter is not checkbox value but rather view model binding for the checkbox hence:
#Html.CheckBoxFor(m => m.SomeBooleanProperty, new { #checked = "checked" });
The first parameter must identify a boolean property within your model (it's an Expression not an anonymous method returning a value) and second property defines any additional HTML element attributes. I'm not 100% sure that the above attribute will initially check your checkbox, but you can try. But beware. Even though it may work you may have issues later on, when loading a valid model data and that particular property is set to false.
Source and additional info:
https://stackoverflow.com/a/12674731/3397630
Hope it will helpful for you ,kindly let me know your thoughts or feedbacks
Thanks
Karthik

Categories