Is there a way to update a binding variable attached to an Input text Item in Blazor when using Ctrl +V combination keys? - asp.net-core

I have this input which is to capture a phone number.
When the user enters a number and press the "Enter" Key the Method "KeyWasPressed" is triggered and some validation happens. this works as expected BUT...
When the user copies and pastes the number from excel, for example, the variable #Phone doesn't updates its value so when the user presses the "Enter" key the validation sends and empty value.
Is there a way to refresh/update #Phone variable when some text is pasted to the input control?
Here is a snipped of my code:
<input type="number" class="form-control" #bind="#Phone" #onkeypress="#(async e => await KeyWasPressed(e))" placeholder="Client Phone Number" />
#code {
string Phone { get; set; }
private async Task GetClientInfo()
{
if(String.IsNullOrWhiteSpace(Phone))
{
notificationMessages = $"Add a phone number";
}
else
{
showSpinner = true;
clientInfo = await ApiHelper.GetClientInfoByPhone(Phone);
if(clientInfo != null)
{
var singleViewId = clientInfo?.SingleViewId;
var customerNumber = clientInfo?.Accounts?.FirstOrDefault().CustomerNumber;
var status = clientInfo?.Accounts?.FirstOrDefault().Status;
showClientInformation = true;
var CrossSell = clientInfo?.Accounts[0]?.CrossSell;
}
else
{
showClientInformation = false;
notificationMessages = $"No client data for this phone ({Phone})";
}
showSpinner = false;
}
}
private async Task KeyWasPressed(KeyboardEventArgs args)
{
if(args.Key == "Enter")
{
//await GetClientInfo();
}
}
}

Direct solution:
Just use #bind-value="#Phone" #bind-value:event="oninput":
<input type="number" #bind-value="#Phone" #bind-value:event="oninput"
#onkeyup="#OnUserFinish"/>
<p>#clientInfo</p>
#code {
protected string Phone { get; set; }
protected string clientInfo {get; set;}
private async Task OnUserFinish(KeyboardEventArgs e)
{
if (e.Key == "Enter")
clientInfo = await Fake_ApiHelper_GetClientInfoByPhone(Phone);
}
private async Task<string> Fake_ApiHelper_GetClientInfoByPhone(string phone)
{
await Task.CompletedTask;
return $"Client phone: {phone}";
}
}
Bonus track:
Move to a user friendly debounce version:
#using System.Timers;
<input type="number" #bind-value="#Phone" #bind-value:event="oninput"
#onkeyup="#HandleKeyUp"/>
<p>#clientInfo</p>
#code {
protected string Phone { get; set; }
protected string clientInfo {get; set;}
private System.Timers.Timer aTimer;
protected override void OnInitialized()
{
aTimer = new System.Timers.Timer(250);
aTimer.Elapsed += OnUserFinish;
aTimer.AutoReset = false;
}
void HandleKeyUp(KeyboardEventArgs e)
{
// remove previous one
aTimer.Stop();
// new timer
aTimer.Start();
}
private void OnUserFinish(Object source, ElapsedEventArgs e)
{
InvokeAsync( async () =>
{
clientInfo = await Fake_ApiHelper_GetClientInfoByPhone(Phone);
StateHasChanged();
});
}
private async Task<string> Fake_ApiHelper_GetClientInfoByPhone(string phone)
{
await Task.CompletedTask;
return $"Client phone: {phone}";
}
}

The Reason
I could reproduce the same issue. Turns out the reason is when we copy sth & paste into the input, and then press the Enter key, the enter key event is triggered before the change event.
See the event sequence:
Because the Enter KeyPress event is triggered before the change event, the Phone property has not been updated yet.
How to fix
One possible walkaround is to listen the paste event. But unfortunately, there's currently a limitation when using Blaozr's native onpaste (See https://github.com/aspnet/AspNetCore/issues/14133#issuecomment-533198522).
Since the Team member suggests that we should use jsinterop, we can add a interHelper.handlePaste function:
<script>
var interHelper={
handlePaste: function (){
var node = document.getElementById('phoneInput');
return node.value;
},
}
</script>
<script src="_framework/blazor.server.js"></script>
And then refresh the latest value manually when pasting:
<input id='phoneInput' type="number" class="form-control" #bind="#Phone"
#onpaste="HandlePaste"
#onkeypress="#(async e => await KeyWasPressed(e))"
placeholder="Client Phone Number" />
#code {
...
private async Task HandlePaste(ClipboardEventArgs e){
var str = await jsRuntime.InvokeAsync<string>("interHelper.handlePaste");
this.Phone= str;
}
}
Demo
Copy "107783" & Paste into the input & Press the Enter Key:

Related

Blazor - change text inside input

I am building the number input component in blazor an i just can't figure it out how to prevent the input to change on mousewheeel up/down. I have parameter 'DisableMouseWheel' in if true i want to prevent the number input for going up or down if mouse wheel is turned. If 'DisableMouseWheel' is true and the mouse wheel is turned it does skip the StepUp/StepDown methods but still change the value. Is there any options to solve this without javascript
I use also the 'Disabled' and 'Readonly' parameters for the input
My code
<input type="number"
step="1"
disabled="#Disabled"
readonly="#Readonly"
#onmousewheel="#OnMouseWheel"
#onwheel="#OnMouseWheel"
#bind-value="#_value" />
protected async Task OnMouseWheel(WheelEventArgs args)
{
if (DisableMouseWheel == false)
{
if (args.ShiftKey || Disabled || Readonly)
return;
if (args.DeltaY > 0)
{
await StepUp();
}
else
{
await StepDown();
}
} else
{
args.DeltaY = 0;
}
}
EDIT --> SOLUTION:
In this case i need to disable the #onkeydown:preventDefault and then handle everything in onkeydown event
You can use #oninput event of the checkbox. I implemented your code with that, and it works correctly.
<input type="checkbox" #oninput="ChangeMouseWheel"/>prevent the input to change on mouse wheel
<br/>
<input type="number"
step="1"
disabled="#Disabled"
readonly="#Readonly"
#onmousewheel="#OnMouseWheel"
#onwheel="#OnMouseWheel"
#bind-value="#_value"/>
#code section:
#code
{
bool DisableMouseWheel,Disabled,Readonly;
int _value=0;
protected async Task ChangeMouseWheel(ChangeEventArgs e)
{
DisableMouseWheel = (bool)e.Value;
//StateHasChanged();
}
protected async Task OnMouseWheel(WheelEventArgs args)
{
if (DisableMouseWheel == false)
{
if (args.ShiftKey || Disabled || Readonly)
return;
if (args.DeltaY > 0)
{
await StepDown();
}
else
{
await StepUp();
}
} else
{
args.DeltaY = 0;
}
}
protected async Task StepUp()
{
_value++;
}
protected async Task StepDown()
{
_value--;
}
}

How to update a value on InputText [Blazor NetCore 5.0]

In my .razor page I have an InputText, what I want is to update that number as soon as it is being typed, specifically is to put a space every 4 characters, how am I trying to do it?
<InputText #bind-Value="oPagos.NumeroEnTarjeta" #onkeydown="#Tecleando" type="number"
onchange="()=>NumberChanged()" id="card-number" placeholder="1111 2222 3333 4444" class="input" maxlength="16" />
Then,
public void Tecleando(KeyboardEventArgs e)
{
//Console.WriteLine(e.Key);
oPagos.NumeroEnTarjeta = generateSpaces(oPagos.NumeroEnTarjeta);
Console.WriteLine(oPagos.NumeroEnTarjeta);
}
I have a function where I plan to take all the value from the bind, ie: oPayments.NumberOnCard, and every 4 spaces generate a space.
This does not work for me for two reasons.
the first number that I type is taken from the #Onkeydown event but the variable oPagos.NumeroEnTarjeta is empty.
I don't know how to update the value of the InputText, as I show in the following image I effectively modify the variable oPagos.NumeroEnTarjeta, but I can't get the user to see it rendered in the text box.
Should I take another way or how do I fix what I have? Thank you.
Update
I succeeded in doing something similar, but with two different events, onblur and onfocus.
I use onfocus to remove the spaces and I use onblur to add my spaces, however, what I would like to do is while I'm writing
I got some Problems with Dynamic Data using Bind-Value / Bind so i started using Blazorise and solve my problems, a possible solution is this one:
<Field>
<TextEdit Text="#opagos.NumeroEnTarjeta" TextChanged="#MethodThatBringSpaces"></TextEdit>
<Field>
Then in #code
Task MethodThatBringSpaces(string value){
opagos.NumeroEnTarjeta = generateSpaces(value);
}
Also you can use the data that you want (i use string in this case) and you can add the same things than blazor (id,placeholder,etc.)
Here's a set of code which I think does basically what you want. It was written to answer a similar question on here a few months ago! I've used dashes instead of spaces to show the space being filled. It's was coded in Net6.0 but should be Ok in Net5.0.
You will probably need to tweak it a little to fit your exact needs:
CreditCardCode.razor
#namespace StackOverflowAnswers.Components
#inherits InputBase<string>
<input #attributes="AdditionalAttributes"
class="#CssClass"
value="#stringValue"
#oninput="OnInput"
#onchange="this.OnValueChanged"
#onfocus="OnFocus"
#onblur="OnBlur"
/>
CreditCardCode.razor.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using System.Text.RegularExpressions;
namespace StackOverflowAnswers.Components;
public partial class CreditCardCode : InputBase<string>
{
private string stringValue = String.Empty;
private string _currentValue = String.Empty;
// Sets up the initial value of the input
protected override void OnInitialized()
{
_currentValue = this.Value ?? string.Empty;
this.stringValue = this.GetCodeValue(_currentValue);
base.OnInitialized();
}
private async Task OnInput(ChangeEventArgs e)
{
var rawValue = e.Value?.ToString();
stringValue = "";
await Task.Yield();
_currentValue = this.GetCodeValue(rawValue ?? string.Empty);
this.stringValue = this.FormatValueAsString(_currentValue);
}
private async Task OnFocus(FocusEventArgs e)
{
stringValue = "";
await Task.Yield();
this.stringValue = this.FormatValueAsString(_currentValue);
}
private async Task OnBlur(FocusEventArgs e)
{
stringValue = "";
await Task.Yield();
this.stringValue = this.GetCodeValue(_currentValue);
}
// We set the base CurrentValueAsString to let it handle all the EditContext changes and validation process
private void OnValueChanged(ChangeEventArgs e)
=> this.CurrentValueAsString = e.Value?.ToString() ?? string.Empty;
// Necessary override for InputBase
protected override bool TryParseValueFromString(string? value, out string result, out string validationErrorMessage)
{
result = value ?? string.Empty;
if (!string.IsNullOrEmpty(value) && value.Length == 19)
{
validationErrorMessage = string.Empty;
return true;
}
else
{
validationErrorMessage = "Value must be nnnn-nnnn-nnnn-nnnn";
return false;
}
}
protected override string FormatValueAsString(string? value)
=> value ?? string.Empty;
private string GetCodeValue(string value)
{
value = new string(value.Where(c => char.IsDigit(c)).ToArray());
value = value.Length > 16
? value.Substring(0, 16)
: value;
var reg = new Regex(#"([0-9]{1,4})");
var matches = reg.Matches(value);
var outvalue = string.Empty;
if (matches.Count > 0)
{
foreach (Match match in matches)
{
outvalue = $"{outvalue}-{match.Value}";
}
outvalue = outvalue.Trim('-');
return outvalue;
}
return string.Empty;
}
}
Test Page
#page "/"
#using StackOverflowAnswers.Components
<h3>EditForm</h3>
<div class="container-fluid">
<EditForm EditContext=editContext>
<div class="row">
<div class="col-2">
Credit Card No:
</div>
<div class="col-4">
<CreditCardCode class="form-control" #bind-Value="this.model.CreditCardNo"/>
</div>
<div class="col-4">
<ValidationMessage For="() => this.model.CreditCardNo" />
</div>
</div>
</EditForm>
<div class="row">
<div class="col-2">
Credit Card No:
</div>
<div class="col-4">
#model.CreditCardNo
</div>
</div>
</div>
#code {
private EditContext? editContext;
private ModelData model = new ModelData();
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(model);
return Task.CompletedTask;
}
class ModelData
{
public string CreditCardNo { get; set; } = string.Empty;
}
}

StateHasChange dont want to rerender Select with new seleted

I have this dropdown that generates a couple of dropdowns.
it shows fine at first render. then i change a value that gives me back some new selected items for all the dropdowns.
problem is my StateHasChanged does not want to rerender correctly.
#foreach (Parameter parameter in #group.Parameters)
{
#parameter.Description
if (parameter.IsList == true)
{
//Create Dropdown List
<select class="form-control" #onchange="#((ChangeEventArgs args) => SelectValueChange(args, parameter.Name))">
#foreach (Element element in parameter.Domain.Elements)
{
<option selected=#element.IsSelected style="color:white; background-color:#element.State;" value=#element.Name>#element.Description</option>
}
</select>
}
<br />
}
SelectValueChange is what i call when a new option is selected.
private void SelectValueChange(ChangeEventArgs args, string selectName)
{
var selectedName = args.Value.ToString();
ValueChange(selectName, selectedName);
}
ValuChange is where im trying to force the rerender.
public void ValueChange(string param, string value)
{
Result newResult = config.Commit(param, value);
SetupPage(newResult);
//TODO: find a way to change these values newResult.Response.Changed or newResult.Response.AllChanged
ShowNotification(newResult.Response);
InvokeAsync(() => StateHasChanged());
InvokeAsync(StateHasChanged);
}
SetupPage is where im just settings the new values from my result. maybe i can set these in a different way?
private void SetupPage(Result result)
{
CurrentStep = null;
Headline = null;
Groups = null;
PrevSteps = null;
NextSteps = null;
CurrentStep = result.CurrentStep;
Headline = result.RootGroup.Description;
Groups = result.RootGroup.SubGroups;
PrevSteps = result.PreviousSteps;
NextSteps = result.NextSteps;
StateHasChanged();
}

RadioButton list Binding in MVC4

I have a radiobuttonList which is binding data from Enum Class and its working correctly in the view.
But my concern is how can I set inital value of radiobutton to CROCount.ONE.I have tried to set the initial value in the following way but couldnot get the desired result.
public enum CROCount
{
ONE = 1,
TWO = 2
}
ViewModel is
public class RegistraionVM
{
....
public EnumClass.CROCount CROCount { get; set; }
}
I generated the radio button list as follows.
<div>
#foreach (var count in Enum.GetValues(typeof(SMS.Models.EnumClass.CROCount)))
{
<label style="width:75px">
#Html.RadioButtonFor(m => m.RegistrationVenue, (int)count,
new { #class = "minimal single" })
#count.ToString()
</label>
}
</div>
Binding performed in the Controller is
public ActionResult Index(int walkInnId)
{
try
{
var _studentReg = new RegistraionVM
{
CROCount=EnumClass.CROCount.ONE
};
return View(_studentReg);
}
catch (Exception ex)
{
return View("Error");
}
}
Your binding your radio button to property CROCount (not RegistrationVenue) so your code should be
#Html.RadioButtonFor(m => m.CROCount, count, new { id = "", #class = "minimal single" })
Note that the 2nd parameter is count (not (int)count) so that you generate value="ONE" and value="TWO". Note also the new { id = "", removes the id attribute which would otherwise result in duplicate id attributes which is invalid html.

MessageDialog closes Popup

in my Popup windows (contains game options control) I have "Reset HighScores" Button. Button fire a MessageDialog with a TextBlock "Are you sure that ..." and two Buttons "Yes" and "No". However, when MessageDialog opens, Popup closes. Do you know how to make popup still alive?
I was able to get around this using an Action delegate as a callback for when the MessageDialog is closed.
The key is to call the Action after an await on MessageDialog's ShowAsync in an async function.
Another key is to Close and Open your popup to get the IsLightDismissEnabled to actually take hold.
XAML:
<Popup
IsLightDismissEnabled="{Binding IsLightDismiss, Mode=TwoWay}"
IsOpen="{Binding IsPopupOpen, Mode=TwoWay}">
ViewModel:
private bool isPopupOpen;
public bool IsPopupOpen
{
get { return this.isPopupOpen; }
set { this.SetProperty(ref this.isPopupOpen, value); }
}
private bool isLightDismiss;
public bool IsLightDismiss
{
get { return this.isLightDismiss; }
set { this.SetProperty(ref this.isLightDismiss, value); }
}
protected void ShowDialog()
{
this.IsLightDismiss = false;
this.IsPopupOpen = false;
this.IsPopupOpen = true;
Action showPopup = () => {
this.IsLightDismiss = true;
this.IsPopupOpen = false;
this.IsPopupOpen = true;
};
ShowMessageDialog("message", "title", showPopup);
}
private async void ShowMessageDialog(string message, string title, Action callback)
{
var _messageDialog = new MessageDialog(message, title);
await _messageDialog.ShowAsync();
callback();
}
set your Popup's IsLightDismissEnabled property to false to achieve that.
popup.IsLightDismissEnabled = false;