Add element dynamically in MVC form - c#

I am working on a asp.net mvc-5 project. I have following c# model:
public class Settings
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public int ParentId { get; set; }
public List<Settings> SubSettings { get; set; }
}
I am rendering this model on razor view by using EditorTemplates as explained here. Now i want to add remove Settings node dynamically like:
When we click on red button the element should get deleted (which is quite simple and i already implemented it) and when we click on green button the node should get added to its appropriate place (here i want your help).
The node should added is such a way that default model binder can map it to its right place, can anyone please explain me how can i do this?

The key to ensuring dynamically added elements post back correctly for binding is to use an indexer. One way to do this is to create the elements representing the new object, giving them a fake indexer (so this one does not post back) and then cloning the elements and updating the index. For example
View
<div id="NewSetting" style="display:none">
<tr>
<td><input type="text" name="Settings[#].ID value /></td>
<td><input type="text" name="Settings[#].Name value /></td>
<td>.....</td> // more properties of Settings
<td><input type="hidden" name="Settings[#].Index" value ="%"/></td>
</tr>
</div>
Note the use of a dummy indexer to prevent this one being posted back
Script
$(AddButton).click(function() {
var index = // assign a unique value
var clone = $('#NewSetting').clone();
// Update the index of the clone
clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
clone.html($(clone).html().replace(/"%"/g, '"' + index + '"'));
$(SomeElement).append(clone);
}
Obviously change AddButton and SomeElement selectors to suit your needs.
You will also need to ensure your editor template for creating existing Settings include the Settings[].Index property.

I would recommend you not to add/delete the nodes but hide/show them.
Include all the possible nodes in your model and use jquery to show and hide when the buttons are clicked.

You can do this via Javascript. You can use jQuery to clone the an existing dom element then change the name. Or you could you a Ajax request to get the new HTML see this post for an example.
So you HTML/View need to contain a index for each dynamic item e.g.
<input type="checkbox" name = "Foo[0].Skills" value="true"/>
<input type="checkbox" name = "Foo[1].Skills" value="true"/>
<input type="checkbox" name = "Foo[2].Skills" value="true"/>
Alternatively you could use a JavaScript template engine to dynamically as new HTML form elements such as http://handlebarsjs.com/

Related

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.

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

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

Click Checkbox by Value Selenium

I have google around a bit trying to finds some way you can click check boxes by entering a value, similar what you can use to select values in Drop Down List.
But havent found a way yet.
I have two check boxes with Yes and No
div class="radio">
<label>
<input type="radio" name="LongTermContract" value="Yes" autocomplete="off" checked="">Yes
</label>
<label>
<input type="radio" name="LongTermContract" value="No" autocomplete="off">No </label>
</div>
I'm also using PageObjects,
[FindsBy(How = How.Name, Using = "LongTermContract")]
public IWebElement radioBtnLongTermContract { get; set; }
This is the Method I have.
public static void SelectOptions(this IWebElement element, string value)
{
PropertiesCollection.driver.FindElement(By.XPath("//input[#value='" + value + "']")).Click();
}
Now if im trying this
public void SelectValue(){
Reporting("NO");
}
I get an error that it does not find the element
Additional information: no such element: Unable to locate element:
{"method":"xpath","selector":"//input[#value='NO']"}
Any ideas tips?
This is the code snippet is in java. You need to pass value either Yes or No it will select respective radio button as the same value used for <input> tag's attribute value
public static void SelectOptions(String value)
{
driver.findElement(By.xpath("//input[#value='"+value+"']")).click();
}

Generating a list of RadioButtons with the same name in a for-loop in an ASP.NET MVC3 view

I'm working on an ASP.NET MVC3 project, and I have a problem with RadioButtons name generated by a for loop in a PartialView.
First, here is my code, I'll explain the exact problem just after :
Model FileUploadModel
public class FileUploadModel
{
public HttpPostedFileBase File { get; set; }
public bool IsMainFile { get; set; }
}
Model MyModel
public class MyModel
{
// Some properties
public List<FileUploadModel> Files { get; set; }
}
How the PartialView _UploadFiles is called
#Html.Partial("_UploadFiles", Model) // Model is a MyModel model
PartialView _UploadFiles - HERE IS THE PROBLEM
#model MyModel
#{
var nbFiles = (Model.Files != null ? Model.Files.Count : 0);
const int NB_DEFAULT_UPLOAD_FIELDS = 3;
}
#for (int i = 0; i < NB_DEFAULT_UPLOAD_FIELDS; i++)
{
#Html.TextBoxFor(m => m.Files[nbFiles].File, new { type = "file", name = "Files", id = "Files" })
#Html.RadioButtonFor(m => m.Files[nbFiles].IsMainFile, "Main ?", new { id = "rb" + i + "_File_IsMain" })
}
So this is the last snippet the problem. I'd like to create 3 upload fields with an associated RadioButton indicating if the file is the "main file". However, with the above snippet, the displayed view is OK, but when I validate my form, my posted model only have the first picture uploaded (and the corresponding boolean IsMainFile , the others are just ignored). That means the List Files contains only the first TextBox and first RadioButton data
So I tried with #Html.RadioButtonFor(m => m.Files[i].IsMainFile, but the RadioButtons have a different name so user can check all RadioButtons, which is NOT the desired behavior. MVC doesn't authorize me to override the name of the RadioButtons, so I can't give them my own name.
How can I generate these fields is order to only ONE Radiobutton can be checked AND my MyModel.Files property contains all chosen files ?
Thank you
I think your best bet here is to use editor templates.
First, you create a view named FileUploadModel, and place it a folder named EditorTemplates which should exist under your controller's Views folder. Note that the name of the view must match the name of your view model's class.
Its contents would look something like:
#model FileUploadModel
#Html.TextBoxFor(m => m.File, new { type = "file" })
#Html.RadioButtonFor(m => m.IsMainFile, true)
Then, back in your "_UploadFiles" view, your markup for the input field and radio button would change to:
#model MyModel
using (Html.BeginForm(...
#Html.EditorFor(m => m.Files)
...
Note that this will automatically iterate through and apply that editor template for each FileUploadModel object, and automatically name them as required. This of course assumes that the Files property in your model is populated.
Your action that accepts the post should accept type MyModel as its sole parameter, and everything should bind up automatically at post.
There are ways to avoid editor templates and build and name those fields programmatically, but this is really the preferred way, and much cleaner.
More specific to your issue... (In other words, I forgot that the code above won't group radio buttons.)
For the grouping of radio buttons to work, you should set the attribute name as follows:
#Html.RadioButtonFor(m => m.IsMainFile, true, new { Name = "IsMainFile" })
Case matters here. Note that Name must be capitalized for this to work. I'm not sure why this is the case.
The problem is that once you change that attribute, this field will no longer automatically bind in the post. This is unfortunate but understandably is as designed since, if you look at the request, I believe you will see that only the selected radio button value has been posted.
So, you could change the value of the radio button to something recognizable since just that one instance of IsMainFile will show up in the request, if I'm correct.
Perhaps you can use this specific knowledge to tweak your existing code.

Categories