Razor EditorFor with Onclick Event - asp.net-mvc

I have nullable Boolean value that is being presented as a checkbox using the following code:
#Html.EditorFor(m => m.IsInitialStatus, new { htmlAttributes = new { #onclick = "InitialOrStarting()" } })
however the #onclick attribute is not being added to the HTML when the page is loaded. Am I missing something here? I had taken the example from an answer on this page.
I have also looked at changing this to a CheckBoxFor but keep getting an issue with the nullable Bool datatypes.
Any help on this would be appreciated! I just want a nullable bool checkbox with an onClick event firing to a Javascript function... I am not the most advanced user but this seems to be more difficult for me to do than maybe it should!?
EDIT
There appears to be an EditorTemplate for Boolean which contains:
#model bool?
#Html.CheckBox("", Model.GetValueOrDefault())

You are using the overload of EditorFor() where the 2nd parameter is additionalViewData. If you did not have a specific EditorTemplate for bool?, the method would generate the default template, which is a <select> with 3 values for null, true and false, and include the attributes.
But because you have an EditorTemplate, you need to add the attributes yourself by reading the value from the ViewDataDictionary (typically, an EditorTemplate includes multiple html elements, so the method cannot know which element you want to apply the attributes to).
Your template would need to be
#model bool?
#{ var attributes = ViewData["htmlAttributes"]; }
#Html.CheckBox("", Model.GetValueOrDefault(), attributes)
Having said that, your should not be doing this. A bool? has 3 states (null, true or false) but a checkbox has only 2 states - on or off (translates to true or false) so your EditorTemplate does not represent the possible values of your property.
If you only want to allow true or false, then your property should not be nullable. Alternatively, use the default template that does allow a null selection (or if you want an alternative UI, create a template that renders 3 radio buttons for example)
In addition, I recommend you stop polluting you markup with behavior and use Unobtrusive JavaScript - i.e. your script will be
$(yourCheckBox).click(function() {
... // do something
});

Onclick Event does not working for #HtmlEditorFor. But you can use class attribute.
<script>
$(".single-checkbox").on("change", function () {
if ($(".single-checkbox:checked").length > 2) {
this.checked = false;
alert ("Only 2 choice")
}
});
</script>
#Html.EditorFor(model => model.YourProperty, new { htmlAttributes = new { #class = "single-checkbox" } })
#Html.EditorFor(model => model.YourProperty, new { htmlAttributes = new { #class = "single-checkbox" } })
#Html.EditorFor(model => model.YourProperty, new { htmlAttributes = new { #class = "single-checkbox" } })
#Html.EditorFor(model => model.YourProperty, new { htmlAttributes = new { #class = "single-checkbox" } })

Related

Setting TAB Order on MVC View with DropDownListFor()/EditorFor()?

Visually speaking, on one of my MVC Views I have about 20 fields in standard vertical order, with the first 8 or so having optional [Create] boxes in same <div> group over on the right.
My default tab order currently hits my first dropdown, then goes right to [Create], down to the next, then right, etc. What I would like to do set the TAB order to where it goes straight down my various fields and leave the [Create] boxes as optional for the user (or at the end of the tab order). While there seems to be a lot of discussion on this with a quick search, there seems to be inconsistent answers; a lot of them seemingly from a couple years ago regarding setting TAB Order in an EditorFor() but being forced to use Custom Editor Templates or switch to TextBoxFor()?
Hoping someone can weigh in on this. The below somewhat details my fields:
(8 of these DropDownListFor()):
#Html.DropDownListFor(model => model.STATUS_ID, (SelectList)ViewBag.Model_List, htmlAttributes: new { #class = "form-control dropdown", #id = "selectStatus" })
(12 of these EditorFor()):
#Html.EditorFor(model => model.NOTE, new { htmlAttributes = new { #class = "form-control" } })
To set the tab order, all you need to do is be able to add an extra attribute, tabindex to the generated field. That's easy enough with something like TextBoxFor or DropDownListFor, since they actually take an htmlAttributes parameter specifically for this purpose:
#Html.TextBoxFor(m => m.Foo, new { tabindex = 1 })
In the past, the same could not be said for EditorFor. Since it's a "templated" helper, the editor template, not the method call, effects what's generated. You can see this in the definition of EditorFor, as there's no htmlAttributes param like the other helpers have, but rather additionalViewData.
Starting with MVC 5.1, Microsoft made it possible to pass additional HTML attributes to EditorFor, via a specially named ViewData key, "htmlAttributes". As a result, you can achieve the same thing as when using something like TextBoxFor, although it's a little more verbose:
#Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
See, you're still actually passing additionalViewData here, but that additional view data contains an anonymous object keyed to htmlAttributes. The built-in editor templates, then, know how to utilize ViewData["htmlAttributes"] to add additional attributes to the generated element. However, this only applies to the default editor templates because Microsoft has specifically programmed them to use this. As soon as you add your own custom editor templates, you're right back to where you started.
There's a number of ways you could approach this with custom editor templates. First, you could just pass the tab index directly as view data, and utilize that in your template:
#Html.EditorFor(m => m.Foo, new { tabindex = 1 })
Then, in your editor template:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { tabindex = ViewData["tabindex"]})
Second, you could mimic EditorFor's behavior with the default templates:
#Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
Then, in your editor template:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, ViewData["htmlAttributes"])
However, that option doesn't allow you to have "default" attributes. It's an all or nothing approach. To truly be able to utilize ViewData["htmlAttributes"] as the built-in editor templates do, you'll need to combine the default attributes with the passed in ones, first, and then pass the whole shebang to htmlAttributes. I've got a blog post that discusses that in depth, but TL;DR: you'll need the following extension:
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
public static partial class HtmlHelperExtensions
{
public static IDictionary<string, object> MergeHtmlAttributes(this HtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
{
var concatKeys = new string[] { "class" };
var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;
RouteValueDictionary htmlAttributes = (htmlAttributesDict != null)
? new RouteValueDictionary(htmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);
RouteValueDictionary defaultHtmlAttributes = (defaultHtmlAttributesDict != null)
? new RouteValueDictionary(defaultHtmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject);
foreach (var item in htmlAttributes)
{
if (concatKeys.Contains(item.Key))
{
defaultHtmlAttributes[item.Key] = (defaultHtmlAttributes[item.Key] != null)
? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
: item.Value;
}
else
{
defaultHtmlAttributes[item.Key] = item.Value;
}
}
return defaultHtmlAttributes;
}
}
And then you'll need to add the following to top of your custom editor templates:
#{
var defaultHtmlAttributesObject = new { type = "date", #class = "form-control" };
var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
You'd change the defaultHtmlAttributesObject variable depending on what attributes the generated input should have by default for that particular template.

DropDownListFor HTML helper not selecting option for Enum model property

I have an ASP.NET MVC 4 site and a domain model that uses an Enum. I'm able to generate a list of SelectListItem objects, but the proper item is not selected.
Domain Model
public enum ApplicationStatus
{
Unknown = 0,
Incomplete = 1,
Submitted = 2,
Error = 4
}
public class Application
{
public ApplicationStatus Status { get; set; }
// ...
}
The "Edit" View
#model Application
#using (Html.BeginForm("Edit", "Applications", new { ... }, FormMethod.Post, new { role = "form", #class = "form-horizontal" }))
{
#Html.Partial("_Form", Model)
<p>
#Html.ActionLink("Cancel", "Details", new { ... }, new { #class = "btn btn-default" })
<button type="submit" class="btn btn-primary">Save</button>
</p>
}
The "_Form" Partial
#model BWE.Models.Entity.BitWeb.Application
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Status, new { #class = "col-sm-2" })
<div class="col-sm-10">
#Html.DropDownListFor(model => model.Status, SelectHelper.GetApplicationStatusOptions(Model.Status))
#Html.ValidationMessageFor(model => model.Status)
</div>
</div>
SelectHelper
public static class SelectHelper
{
public static IEnumerable<SelectListItem> GetApplicationStatusOptions(ApplicationStatus currentStatus)
{
var items = new List<SelectListItem>()
{
new SelectListItem()
{
Text = "Select",
Value = string.Empty
}
};
IEnumerable<ApplicationStatus> statuses = Enum.GetValues(typeof(ApplicationStatus)).Cast<ApplicationStatus>();
foreach (var status in statuses)
{
if (status == ApplicationStatus.Unknown)
continue;
items.Add(new SelectListItem()
{
Text = status.ToString(),
Value = ((int)status).ToString(),
Selected = status == currentStatus
});
}
return items;
}
}
The "Select" option is always selected in the dropdown even though I can step through the code and see one of the SelectListItem objects get their Selected property set to true.
I've tried the solution recommended in My templated helper using a DropDownListFor does NOT reflect the model of enum. Why?, but this solution was geared towards MVC 3. I tried the solution (passing a SelectList object as the second argument to Html.DropDownListFor) and all I got was a dropdown list with 4 options whose display text was "System.Web.Mvc.SelectListItem" and no values for the <option> tags.
Furthermore, I tried other solutions that created an #Html.EnumDropDownListFor(...) helper function, which behaved the same way. It seems that all though the proper SelectListItem is getting selected, maybe the current Model.Status value is overriding it?
Update #1
I added an integer property called StatusId which gets and sets the Status property as an integer, and this works when calling Html.DropDownListFor(model => model.StatusId, ...) however I was hoping for a solution that allows me to use the enum value directly, not as a proxy through another property.
For some crazy reason, enum values are rendered as their string-based names by Razor, rather than their integer value counterparts. Regardless, my guess is that your SelectHelper is returning options with values as integers, which is why converting your enum value to an int allowed the selection to work. Since this is a custom component you have anyways, I would suggest simply modifying your helper to return the enum string names as the option values instead of ints. Then the property value and the option values will line up properly.

MVC5: Can #Html.TextBoxFor (or EditorFor) get its MaxLength from the Model's Data Annotation?

It seems like there should be a way of specifying a Model annotation like:
[Display(Name="State")]
[MaxLength(2)]
public string State { get; set; }
so that when it is used in an EditorFor or TextBoxFor, like:
#Html.EditorFor(model => model.State)
or
#Html.EditorFor(model => model.State)
the generation of the html would set the input field's maxlength to 2. Right now, I need to do this:
#Html.TextBoxFor(model => model.State, new { maxlength = 2 })
Since this violates the DRY principle, I'm wondering if there is already a built in way to have this flow from the data annotations to the input field?
There's an answer here that describes a way to get hold of additional metadata values within the view. With this in mind, you can do something like this...
Annotate your model:
[AdditionalMetadata("MaxLength", 30)]
public string State { get; set; }
Define a custom editor template (String.cshtml):
#{
string maxLength = ViewData.ModelMetadata.AdditionalValues.ContainsKey("MaxLength")
? ViewData.ModelMetadata.AdditionalValues["MaxLength"].ToString() : null;
}
#Html.TextBox("", Model, new { maxlength = maxLength })
Then just do:
#Html.EditorFor(m => m.State)
This might need some tweaking and could be improved by defining a custom attribute instead of just using AdditionalMetadata but it should get you started.
I tend to wrap up calls to the model's additional metadata in a custom HTML helper, too.
Here is what I did to get around this.
Created a js file to handle adding in the required field marker and maxlength attribute:
var formUtility = function () {
return {
AddMaxLength: function () {
$("input[data-val-length-max]").each(function () {
$(this).attr("maxlength", $(this).attr("data-val-length-max"));
});
},
ShowRequiredFields: function () {
$("input[data-val-required]").each(function () {
$(this).prev().prepend("<span>* </span>");
});
}
}
}();
After linking it on the page where I need it, just call it like this:
<script>
formUtility.AddMaxLength();
formUtility.ShowRequiredFields();
</script>
This is not currently supported, you can vote for this feature.
I think by using ContainerType and PropertyName properties of ModelMetadata you can obtain the PropertyInfo, and from that query for the MaxLength attribute, all this in a custom editor template.
Use
[StringLength(2)]
instead of
[MaxLength(2)]

How can I set id using Html.EditorFor with MVC3

I am trying to set a field id as follows:
#Html.EditorFor(x => x.Order, new { id = string.Format("Order_{0}", Model.Row) })
but this results in the following and it seems my id is not being set:
<input type="text" value="334" name="item.Order" id="item_Order" class="text-box single-line">
Does anyone have an idea what I am doing wrong. I checked the allowable formats for EditorFor and looked on google for examples but I could not see anything that matched what I need.
You should change to
#Html.TextBoxFor(x => x.Order, new { id = string.Format("Order_{0}", Model.Row) })
The second parameter of #Html.EditorFor is for view data, not for html attributes
#Html.EditorFor(x => x.Order, null, string.Format("Order_{0}", Model.Row))
I know this question is pretty old, but all I needed to do was the following:
#Html.EditorFor(modelItem => item.EndDate,
new { htmlAttributes = new { #class = "form-control", #id = #endID } })
You can give it whatever classes or id that you want/need. I have this earlier in my page as well to create a different ID for each item:
string endID = "end-" + #item.ID;
Hopefully this helps someone in the future.
have you tried creating an editor template for your x.Order?
have this on your editor template:
<input type="text" id="#ViewData["id"]">
and use this on your view page:
#Html.EditorFor(x => x.Order, new { id = string.Format("Order_{0}", Model.Row) })
I'm not sure if you can override the ID attribute when using the strongly typed helpers. But you can use the other non-model type:
#Html.Editor(string.Format("Order_{0}", Model.Row), Model.Order)
This will use the first parameter as the ID and the second as the default value.
If you want to use EditorFor, you can do this:
#Html.EditorFor(model => model.Order, new { htmlAttributes = new { #id = "YOUR_ID_HERE"})
For the above example it would be:
#Html.EditorFor(model => model.Order, new { htmlAttributes = new { #id = $"Order_{0}" } })
Svetlosav's answer looks good but it will mess up your data binding. If you use that answer, check to see if your model is populating as expected and not with a default value (null, false, 0, etc..).

Enabling & disabling a textbox in razor view (ASP.Net MVC 3)

I want to Enable or Disable a textbox based on the value (Model.CompanyNameEnabled).
The below code is not working. Please rectify.
#{
string displayMode = (Model.CompanyNameEnabled) ? "" : "disabled = disabled";
#Html.TextBox("CompanyName", "", new { displayMode })
}
#{
object displayMode = (Model.CompanyNameEnabled) ? null : new {disabled = "disabled" };
#Html.TextBox("CompanyName", "", displayMode)
}
You should pass htmlAttribute as anonymous object, with property names = html attribute names, property values = attribute values. Your mistake was that you were passing string instead of name=value pair
<input id="textbox1" type="text" #{#((Model.CompanyNameEnabled) ? null : new { disabled = "disabled" })}; />
Haven't tested it, but should work
A simple approach:
#Html.TextBoxFor(x => x.Phone, new { disabled = "disabled", #class = "form-control" })
As is already mentioned in this thread the suggested answer doesn't work in MVC5 anymore. There's actually an easy two step solution to that problem.
Assign a class to the HTML inputs you want to be disabled / enabled (id will do for a single item just as fine of course). In the example below I assigned a class 'switch-disabled' to the input.
#Html.TextBox("CompanyName", "", new { htmlAttributes = new { #class = "form-control switch-disable" } })
Use javascript(jquery) to enable / disable the disabled parameter in HTML. In my example below I do this at the page load.
<script>
$(document).ready(() => {
if(#Model.CompanyNameEnabled)
{
$('.switch-disable').attr("disabled", false);
}else{
$('.switch-disable').attr("disabled", true);
}
});
</script>

Resources