I am trying to generate my own component with checkbox system to know if I need the attribute or not (of type int / float etc)
<input type="checkbox" #bind="isMinInt" />
#if (isMinInt == true) {
<input type="number" #bind="MinInt"/>
}
So I would like to replace this #if:
#if(isMinInt == true) {
<MyComponent #bind-Value="ValueInt" Min="#MinInt"/>
} else {
<MyComponent #bind-Value="ValueInt"/>
}
by something like
<MyComponent #bind-Value="ValueInt"
#if(isMinInt == true ? Min="#MinInt" : String.Empty />
because I will have many attributes on my component and I would like to make it simplier
EDIT + Solution
Now using the #attributes:
<input type="checkbox" #bind="isMinInt" />
#if (isMinInt == true) {
<input type="number" #bind="MinInt" />
}
<MyComponent #bind-Value="ValueInt" #attributes="GetAttributesInt()" />
#code {
private bool isMinInt = false;
private int MinInt;
private IDictionary<string, object> GetAttributesInt() {
var dict = new Dictionary<string, object>() { };
if (isMinInt)
dict.Add("Min", MinInt);
return dict;
}
}
EDIT + Solution 2
Now using the #attributes:
<input type="checkbox" #bind="isMinInt" />
#if (isMinInt == true) {
<input type="number" #bind="MinInt" />
}
<MyComponent #bind-Value="ValueInt" #attributes="GetAttributesInt()" />
#code {
private bool isMinInt = false;
private int MinInt;
private IDictionary<string, object> GetAttributesInt() {
var dict = new Dictionary<string, object>() { };
dict["Min"] = this.isMinInt ? MinInt : Int32.MinValue;
return dict;
}
}
The reason why I'm using Int32.MinValue it's because MyComponent correspond to an <input type="number"> where his min is bind to my MinInt, so if I use 0 in the place of Int32.MinValue it won't allow me to go for negative numbers.
I will have many attributes on my component and I would like to make it simplier
You can't do that directly. However, as a walkaround, you can use the #attributes to bind attributes dynamically. For example:
<MyComponent #bind-Value="ValueInt" #attributes="#a_dictionary_expression" />
Where #a_dictionary_expression is a C# expression that can be evaluated at runtime. What's more, you can even create a custom function to calculate the dictionary:
<MyComponent #bind-Value="ValueInt" #attributes="getAttributes()" /gt;
#code {
...
private IDictionary getAttributes()
{
var dict = new Dictionary(){};
if(isMinInt) {
dict.Add("Min", $"{MinInt}");
} // this will introduce a bug, see Edit2 for more details
return dict ;
}
}
[Edit]: Here's a way to render the attributes within a single line
<input #bind-Value="ValueInt" #attributes="#(isMinInt? new Dictionary<string,object>{ Min= #Min} : new Dictionary<string,object>{})" />
[Edit2]
The above code will introduce a bug that the MyComponent is not updated correctly. The reason is the parameters of <MyComponent> received from previous #attributes is not automatically cleared when a new #attributes received.
For example,
the first time #attributes is {Min=1}, and it results in a statement:
MyComponent.Min=1;
The second time #attributes is {}, because there's no attribute inside it, it won't assign parameters for it, thus the MyComponent.Min remains the same.
To fix that, change the above code as below:
private IDictionary<string, object> GetAttributesInt() {
var dict = new Dictionary<string, object>() { };
dict["Min"] = this.isMinInt ? MinInt : 0 ;
return dict;
}
I used this simple solution and it work, hope this help you too:
<input id="#InputId" aria-labelledby="#(!string.IsNullOrWhiteSpace(InputId) ? "this is test" : null)">
Related
Here's what I imagine I need:
<input type="text" #bind="this.ConceptSearch" #bind:event="oninput" />
#code {
private string _ConceptSearch = "";
private string ConceptSearch {
get {
return this._ConceptSearch;
}
set {
this._ConceptSearch = value;
this.PopulateSuggestions();
}
}
}
Is there a way to cut out the middle man (the property) and just directly call some code?
Using #onchange instead of #bind to solve it
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;
}
}
When using the built in Html.GetEnumSelectList() for the following Enum:
public enum Country {
[Display(Name="United States")] US,
[Display(Name="Canada")] CA
}
It generates the following html:
<select id="prefix" class="form-control">
<option value="0">United States</option>
<option value="1">Canada</option>
</select>
Is there a way to have the value set to the value of the enum instead of the index?
I wrote an extension for what I needed, but it is so basic that it feels odd that the C# team missed it, so I'm curious if I did
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace HotelMaven.Extensions {
public static class HtmlExtensions {
public static IEnumerable<SelectListItem> GetEnumSelectList<TEnum>(this IHtmlHelper html, TEnum selectedValue) where TEnum : struct {
var values = Enum.GetValues(typeof(TEnum))
.Cast<TEnum>();
return values.Select(eachValue => new SelectListItem {
Text = eachValue.GetType().GetField(eachValue.ToString()).GetCustomAttribute<DisplayAttribute>()?.Name,
Value = eachValue.ToString(),
Selected = eachValue.Equals(selectedValue)
});
}
public static IEnumerable<SelectListItem> GetEnumSelectList<TEnum>(this IHtmlHelper html, bool isValueUsedForValue) where TEnum : struct {
if (!isValueUsedForValue) return html.GetEnumSelectList<TEnum>();
var values = Enum.GetValues(typeof(TEnum))
.Cast<TEnum>();
return values.Select(eachValue => new SelectListItem {
Text = eachValue.GetType().GetField(eachValue.ToString()).GetCustomAttribute<DisplayAttribute>()?.Name,
Value = eachValue.ToString()
});
}
}
}
Which results in what I'm looking for:
<select id="prefix" class="form-control">
<option selected="selected" value="US">United States</option>
<option value="CA">Canada</option>
</select>
Using:
<select asp-items="Html.GetEnumSelectList<Country>(Country.US)" id="country" class="form-control"></select>
U can Use attribute for enum and get and use it, for example this code get the DisplayAttribute value.
public static string GetDisplayName(this Enum value)
{
if (value == null) return String.Empty;
if (Cache.ContainsKey(value))
return Cache[value];
var enumType = value.GetType();
var enumName = Enum.GetName(enumType, value);
var member = enumType.GetMember(enumName)[0];
var attributes = member.GetCustomAttributes(typeof(DisplayAttribute), false);
var outString = ((DisplayAttribute)attributes[0]).ResourceType != null
? ((DisplayAttribute)attributes[0]).GetName()
: ((DisplayAttribute)attributes[0]).Name;
Cache.Add(value, outString);
return outString;
}
I think u can define attribute for each enum value (by custom or available attribute like display attribute that u used) and store the value in this attribute and use it for value of option or any where.
You should define the extenstion with a different name than GetEnumSelectList which is default
public static class HtmlExtensions
{
public static IEnumerable<SelectListItem> GetEnumValueSelectList<TEnum>(this IHtmlHelper htmlHelper) where TEnum : struct
{
return new SelectList(Enum.GetValues(typeof(TEnum)).OfType<Enum>()
.Select(x =>
new SelectListItem
{
Text = x.GetType().GetField(x.ToString()).GetCustomAttribute<DisplayAttribute>()?.Name,
Value = x.ToString()
}), "Value", "Text");
}
}
And usage:
<select class="form-control" asp-items="Html.GetEnumValueSelectList<Country>()"></select>
you can solve it by JavaScript if you want
For Example:
<select id="status" name="TicketStatus" asp-items="Html.GetEnumSelectList(typeof (TicketSystem.Models.EnumStatus))" class="form-control" >
<option value="-1">All</option>
</select>
<script>
var st = document.getElementById('status');
for (x of st.options) {
x.value = x.innerText;
}
</script>
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).
I would like to be able to write attributes without values, such as autofocus. Now, I can do this:
#Html.TextBoxFor(m => m.UserName, new { autofocus = true })
But of course this writes:
<input id="UserName" type="text" value="" name="UserName" autofocus="True">
Is there a way to get the attribute written without the value?
Commenter alok_dida is correct.
Use:
#Html.TextBoxFor(m => m.UserName, new { autofocus = "" })
Here's a simple way to achieve what you want. Note that this could easily be adapted for multiple attributes, variable attributes etc.
public static class HtmlHelperExtensions
{
private static readonly FieldInfo MvcStringValueField =
typeof (MvcHtmlString).GetField("_value",
BindingFlags.Instance | BindingFlags.NonPublic);
public static MvcHtmlString TextBoxAutoFocusFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes = null)
{
if(htmlAttributes == null) htmlAttributes = new { }
var inputHtmlString =htmlHelper.TextBoxFor(expression, htmlAttributes);
string inputHtml = (string) MvcStringValueField.GetValue(inputHtmlString);
var newInputHtml = inputHtml.TrimEnd('>') + " autofocus >";
return MvcHtmlString.Create(newInputHtml);
}
}
Usage example
#Html.TextBoxAutoFocusFor(m => m.UserName)
#Html.TextBoxAutoFocusFor(m => m.UserName, new { data-val-foo="bob" })
I'm sure someone will mention reflection is slow, which it is, but if the performance of reading that string is something that matters to you. I doubt you'd be using the html helpers to begin with.