StateHasChange dont want to rerender Select with new seleted - blazor-server-side

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

Related

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

How to set new value to existing query string Blazor

I'm attempting to change a query string paramater without reloading the page. I have a DateTime Date value that I'm attempting to turn into a string and put into the url. Say, for the sake of this question that it is:
DateTime? Date = new DateTime(2020, 9, 4);
So, my url could begin looking like:
https://localhost:44346/Events?d=2020-10-18
and after I do whatever magic must be done, it ends up like:
https://localhost:44346/Events?d=2020-10-04
I have attempted using NavigationManager and QueryHelpers like the following, but I've had no luck:
QueryHelpers.AddQueryString(navManager.Uri, "d", Date?.ToString("yyyy-MM-dd"));
Please check out this demo
#page "/counter"
#using Microsoft.AspNetCore.WebUtilities
#using System.Web
#inject NavigationManager navManager
<button class="btn btn-primary" #onclick="ChangeUrl">Change url</button>
<br />
demonstrate lack of page reload
<br />
<button class="btn btn-primary" #onclick="AddItem">Add item</button>
#foreach (var item in list)
{
#item
}
#code {
private List<string> list = new List<string>()
{
"Test string"
};
private void AddItem()
{
var uri = navManager.ToAbsoluteUri(navManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("d", out var param))
{
list.Add(param.First());
}
else
{
list.Add("d is empty");
}
}
DateTime dateCounter = DateTime.Today;
private void ChangeUrl()
{
dateCounter = dateCounter.AddDays(1);
string url = RemoveQueryStringByKey(navManager.Uri, "d");
var query = new Dictionary<string, string> { { "d", dateCounter.ToString("dd-MM-yyyy") } };
navManager.NavigateTo(QueryHelpers.AddQueryString(url, query));
}
public static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url);
// this gets all the query string key value pairs as a collection
var newQueryString = HttpUtility.ParseQueryString(uri.Query);
// this removes the key if exists
newQueryString.Remove(key);
// this gets the page path from root without QueryString
string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path);
return newQueryString.Count > 0
? String.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
: pagePathWithoutQueryString;
}
}

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

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:

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.

View not binding correcty with the model

I can figure out why it's not binding. So I have a form where a ListBox is in a partial view which I reload everytime I click on a checkbox to fill the listbox.
The code of my ModelView for the form is :
<div class="row-fluid">
<div class="span3">
<label>Fonction(s):</label>
</div>
<div class="span9" id="ListeFonction">
#Html.Partial("ListerFonction", Model)
</div>
</div>
<div class="row-fluid">
<div class="span5 offset3">
<div class="fonctions_container">
#foreach (extranetClient.Models.Classes.FonctionContact fonction in ViewBag.Fonctions)
{
string coche = "";
if ((#Model.ListeFonctions).Any(c => c.IdFonction == fonction.IdFonction))
{
coche = "checked";
}
<input type="checkbox" #coche class="checkbox" value="#fonction.IdFonction" />#fonction.LibelleFonction <br />
}
</div>
</div>
</div>
So as you can see, I render a partial view just after the "Email" Textbox. The code for it is :
#Html.LabelFor(contact => contact.SelectedFonctionIds, "ListeFonctions")
#Html.ListBoxFor(contact => contact.SelectedFonctionIds, new MultiSelectList(Model.ListeFonctions, "IdFonction", "LibelleFonction"), new { disabled = "disabled")
The model associated to that view looks like that:
private List<int> _selectedFonctionIds;
public List<int> SelectedFonctionIds
{
get
{
return _selectedFonctionIds ?? new List<int>();
}
set
{
_selectedFonctionIds = value;
}
}
public List<FonctionContact> ListeFonctions = new List<FonctionContact>();
public MultiSelectList ListeFonctionsSelectList
{
get
{
return new MultiSelectList(
ListeFonctions,
"IdFonction", // dataValueField
"LibelleFonction" // dataTextField
);
}
}
public Contact() { }
public Contact( List<FonctionContact> listeFonctions, List<int> selectedFonctionIds)
{
this.ListeFonctions = listeFonctions;
this.SelectedFonctionIds = selectedFonctionIds;
}
public Contact(int idContact, string nom, string prenom, string email, string telephoneFixe, string telephonePort) {
this.IdContact = idContact;
this.Nom = nom;
this.Prenom = prenom;
this.Email = email;
this.TelephoneFixe = telephoneFixe;
this.TelephonePort = telephonePort;
}
public Contact(int idContact, string nom, string prenom, List<int> selectedFonctionIds, List<FonctionContact> listeFonctions, string email, string telephoneFixe, string telephonePort)
{
this.IdContact = idContact;
this.Nom = nom;
this.Prenom = prenom;
this.SelectedFonctionIds = selectedFonctionIds;
this.ListeFonctions = listeFonctions;
this.Email = email;
this.TelephoneFixe = telephoneFixe;
this.TelephonePort = telephonePort;
}
But the ListBox of the partial view is not binding with the model. I get well the other informations but not these in the listbox. Somebody has an idea ?
Why are you forcing the ListBox's id here:
#Html.ListBoxFor(contact => contact.SelectedFonctionIds,
new MultiSelectList(Model.ListeFonctions, "IdFonction", "LibelleFonction"),
new { disabled = "disabled", **id="idFonctions"** })
ListBoxFor helper is supposed to generate the ListBox's id for you, and the Id should be the same as the attribute it should bind with. Shouldn't it be SelectedFonctionIds?
Was the binding working before you started using the PartialView? Because from your previous question, I see that you had:
#Html.ListBoxFor(contact => contact.SelectedFonctionIds, Model.ListeFonctionsSelectList, new { disabled = "disabled" })
in your View (i.e., you didn't set the id attribute).