The use of tabindex seems to only work for htmlhelpers like Textboxfor and not EditorFor
For example;
<%: Html.TextBoxFor(model => Model.MyItem, new { #tabindex = "3" })%>
Produces a tabindex value.
However, if you use;
<%: Html.EditorFor(model => Model.MyItem, new { #tabindex = "3" })%>
Then the result is that the control is created as expected, but the tabindex is missing.
So...... How is it possible to set the tabindex for a given EditorFor control?
The main problem I was having is that I needed to create a EditorFor type mechanism in order to format the decimal like a currency (our system has multiple currencies so "C" would not have been appropriate), get a tab index working AND allow the system to maintain the standard validation.
I've managed to achieve that using the following. By creating my own custom editor control.
Create a file (mine is called decimal.ascx) within the Views/Shared/EditorTemplates directory of your project.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<decimal?>" %>
<% int intTabindex = 0;
decimal myVal = 0;
string strModelValue = "";
if (Model != null)
{
myVal = (decimal)Model;
strModelValue = myVal.ToString("#.00");
}
else
strModelValue = "";
if (ViewData["tabindex"] != null)
{
intTabindex = (int)ViewData["tabindex"];
}
%>
<%: Html.TextBox("", strModelValue, new { #tabindex = intTabindex })%>
Essentially, this code just overrides what would normally be presented in a "decimal" EditorFor method with the;
<%: Html.TextBox("", Model.ToString("#.00"), new { #tabindex = intTabindex }) %>
template.
My calling code now reads;
<%: Html.EditorFor(model => Model.MyItem, new { tabindex = 5 })%>
The result is the following code on the page.
<input id="Model_MyItem" name="Model.MyItem" tabindex="5" type="text" value="12.33" />
Which is exactly what I required.
Whilst this is only true for my particular circumstances, I would encourage anybody looking to solve this issue to attempt a custom control first for the task as it might save you a considerable amount of time.
If would of course be possible in the code to create a specific type of control required and adjust the results around that.
For example; we could simple add another item in the call to determine the text format.
new {tabindex = 12, numberformat=2}
Then simply create a handler for all the formats.
Since the EditorFor is just a template for a DataType it's only expecting a datatype as it's Model. There are a couple of ways of going about this I am guessing. You could possibly add the tabindex to an anonymous object that will be merged into the ViewData for the EditorTemplate like so.
Code in your View:
Html.EditorFor(m => m.Username, "test", new { tabindex = 3, style = "width: 400px;" })
EditorForModel Template Check for ViewData:
<%: Html.TextBoxFor(m => m.Username, ViewData)%>
This should render an text input with a tabindex of 3 and style="width: 400px;"
Happy coding.
Edited:
Here is exactly the markup I have inside of my test page:
<%: Html.EditorFor(m => m.DollarsAmount, "NullableDecimal", new { tabindex = 99 }) %>
I'm telling the EditorFor template to select the "NullableDecimal" EditorTemplate that I have created. (You could place a UiHint attribute on the property inside of the model also to tell it what editortemplate to use)
"NullableDecimal" EditorTemplate located in ~/Views/Shared/EditorTemplates:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<decimal?>" %>
<%: Html.TextBox(string.Empty, (Model.HasValue ? Model.Value.ToString("#.00") : null), ViewData) %>
What's more extensible about my implementation is the extra ViewData that I pass in via my anonymous object is merged into the ViewData dictionary to be used by the EditorTemplate. So if you don't pass any ViewData in to the EditorTemplate then it wont render your text inputs tabindex to 0 as your implementation currently will do. Plus your implementation will only account for tabindexes and not for any other input attributes. i.e. maxlength or style
Related
I find myself writing this a whole lot in my views:
<div class="form-group">
#Html.LabelFor(x => x.City)
#Html.EditorFor(x => x.City)
#Html.ValidationMessageFor(x => x.City)
</div>
I'd really like to put this in a Partial _Field.cshtml, something like this:
#model //what model type???
<div class="form-group">
#Html.LabelFor(Model)
#Html.EditorFor(Model)
#Html.ValidationMessageFor(Model)
</div>
That could then be called by:
#Html.Partial("_Field", x => x.City)
What would the #model type in my partial be if I wanted to accomplish something like this?
UPDATE This works, but I'd rather use a partial for ease of changing the template:
public static MvcHtmlString Field<TModel, TItem>(this HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
var h = "<div class='form-group'>";
h += $"{html.LabelFor(expr)}{html.EditorFor(expr)}{html.ValidationMessageFor(expr)}";
h += "</div>";
return MvcHtmlString.Create(h);
}
That's not possible. However, what you want is very similar to editor templates. Essentially, you just create a view in Views/Shared/EditorTemplates named after one of the following conventions:
A system or custom type (String.cshtml, Int32.cshtml, MyAwesomeClass.cshtml, etc.)
One of the members of the DataType enum (EmailAddress.cshtml, Html.cshtml, PhoneNumber.cshtml, etc.). You would then apply the appropriate DataType attributes to your properties:
[DataType(DataType.EmailAdress)]
public string Email { get; set; }
Any thing you want, in conjunction with the UIHint attribute:
[UIHint("Foo")]
public string Foo { get; set; }
Which would then correspond to a Foo.cshtml editor template
In your views, then, you simply use Html.EditorFor:
#Html.EditorFor(x => x.City)
Then, for example, you could have Views/Shared/EditorTemplates/String.cshtml as:
<div class="form-group">
#Html.Label("", new { #class = "control-label" })
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "form-control" })
#Html.ValidationMessage("")
</div>
(The empty quotes are placeholders. Razor will automatically fill in the appropriate property name, thankfully.)
Calling EditorFor, then, will print all of this, rather than just the default text input. You can take this much further, as well. I have some articles on my blog that goes into greater detail, if you're interested.
UPDATE
It's worth mentioning a few features of EditorFor:
You can pass a template directly to the call, meaning you can customize what template is used on the fly and per instance:
#Html.EditorFor(x => x.City, "MyCustomEditorTemplate")
You can pass additionalViewData. The members of this anonymous object are added to the ViewData dynamic dictionary. Potentially, you could use this to branch within your editor template to cover additional scenarios. For example:
#Html.EditorFor(x => x.City, new { formGroup = false })
Then in your editor template:
#{ var formGroup = ViewData["formGroup"] as bool? ?? true; }
#if (formGroup)
{
<!-- Bootstrap form group -->
}
else
{
<!-- Just the input -->
}
I have an ASP.NET MVC page that uses a lot of AJAX. At the most basic level, it contains a list of questions, with each question having a list of answers inside it. My view models (vastly simplified for the sake of this question):
View Models:
public class QuestionViewModel
{
....
public string QuestionText { get; set; }
public List<AnswerViewModel> AnswerList { get; set; }
public bool EditMode { get; set; }
....
}
public class AnswerViewModel
{
....
public string Answer { get; set; }
....
}
_View.cshtml:
#model QuestionViewModel
#if(Model.EditMode)
{
#Html.EditorFor(x => x)
}
else
{
...
}
QuestionViewModel.cshtml (Editor Template):
#model Project.Models.QuestionViewModel
....
#Html.EditorFor(x => x.AnswerList)
....
AnswerViewModel.cshtml (Editor Template):
#model KSUAdvising.Models.AnswerViewModel
....
#Html.TextBoxFor(x => x.Answer)
....
^^^This EditorFor call renders my answer list fine (along with the appropriate indices so the model binder will bind back to the list appropriately. i.e.:
<input id="AnswerList_0__Answer" name="AnswerList[0].Answer" type="text" value="Yes">
<input id="AnswerList_1__Answer" name="AnswerList[1].Answer" type="text" value="No">
However, on the client side I need to add items to this list when the user clicks a button. This needs to happen client side so no changes are made to the database until the user saves the question.
I see a few direction to go to solve this problem:
Make an AJAX call to the server to render back a new blank answer and add it to the DOM. However, with this being out of the context of a question it doesn't render the proper name (index) and it doesn't get picked up by the model binder.
Render a hidden HTML "template" to the client including the proper input names, then clone this template and modify the name indices to satisfy the model binder. This seems a bit hacky, and is hard to properly render this template.
This seems like it would be a common scenario, but I'm having trouble coming up with a good solution to this. What is the recommended approach for this case?
I vote for option 2.
Render a hidden HTML Item Template. Then on a client button click, clone the template and modify the index.
In the cshtml page, after the foreach, add a new hidden div for the template. Give it an index place holder (I use _ x0x_). Create a new empty item and then render it the same way as you did in the foreach. (You could also have the item render as a partial view and then call it inside the foreach and outside.)
Here is sample cshtml page:
#foreach (var role in roles)
{
int roleIndex = roles.IndexOf(role);
string rolePrefix = "CasePartyRoles[" + roleIndex + "].";
<div id="CasePartyRoleIndex_#roleIndex" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
#Html.Hidden(rolePrefix + "SequenceNo", role.SequenceNo)
#Html.Hidden(rolePrefix + "RowVersion", role.RowVersion)
#Html.DropDownListFormGroupFor(modelItem => role.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, role.PartyRoleCode, rolePrefix + "PartyRoleCode")
#Html.DropDownListFormGroupFor(modelItem => role.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, role.PartyStatusCode, rolePrefix + "PartyStatusCode")
#Html.EditorFormGroupFor(modelItem => role.SubFileNo, "col-md-3", "col-md-9", null, null, rolePrefix + "SubFileNo")
#Html.EditorFormGroupFor(modelItem => role.PartyRank, "col-md-3", "col-md-9", null, null, rolePrefix + "PartyRank")
</div>
</div>
}
<div id="CasePartyRoleTemplate" class="hidden">
#*Template for new Role*#
#{
var newRole = new CasePartyRole();
string newRolePrefix = "CasePartyRoles[_x0x_].";
}
<div id="CasePartyRoleIndex__x0x_" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
#Html.Hidden(newRolePrefix + "SequenceNo", newRole.SequenceNo)
#Html.Hidden(newRolePrefix + "RowVersion", newRole.RowVersion)
#Html.DropDownListFormGroupFor(modelItem => newRole.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyRoleCode, newRolePrefix + "PartyRoleCode")
#Html.DropDownListFormGroupFor(modelItem => newRole.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyStatusCode, newRolePrefix + "PartyStatusCode")
#Html.EditorFormGroupFor(modelItem => newRole.SubFileNo, "col-md-3", "col-md-9", null, null, newRolePrefix + "SubFileNo")
#Html.EditorFormGroupFor(modelItem => newRole.PartyRank, "col-md-3", "col-md-9", null, null, newRolePrefix + "PartyRank")
</div>
</div>
</div>
Here is a little jQuery function to add the item based on the template:
function createItemFromTemplate(templateId, indexNo, insertBeforeId) {
// Copy the template Element, replaces all the _x0x_ with the index no and add the new element
$(insertBeforeId).before($.parseHTML($(templateId).clone().prop('outerHTML').replace(/_x0x_/g, indexNo)));
}
It depends... For the sake of performance I wouldn't go with the first option. This slight delay to load up a new item via Ajax could be annoying.
Personally most times I would just build an HTML element completely on client side. I know it complicates maintanance as you have to remember to change your client side logic any time you make changes to your model collection but it's fast and straightforward.
Also have an idea how to improve your second option. Basicly you don't need to have a real object to build it.
#Html.TextBoxFor(model => model.Items[int.MaxValue], new { style= "display:none" })
the input rendered
<input name="Items[2147483647]" id="Items_2147483647_" style="display: none;" type="text" value="">
Please note the Items property is null in my case but it doesn't matter if you just want to build a template as xxxFor html helpers just use expressions to build html. Also it's very easy then to replace int.MaxValue while cloning the template with a real index. It's safe to use int.MaxValue as a placeholder, it's very unlickely you will have that much items on your page.
Hope it helps!
I am using ASP.NET MVC 5 for a project, and while I like the model binding capabilities of MVC, I do not find myself using them much because I am using javascript MVVM systems, and I find that it is hard to do these with the model binder.
But I do find myself re-using HTML code a lot for specific inputs that need to be replicated across multiple places.
Is there any way to use the #Html.EditorFor etc and instead of specifying a property, just call upon a specific template to use it like a small inline snippets engine?
Update
Hey guys, I went with the suggestion to use Partial Views, but I changed it up a bit. I'd like to post my solution on StackOverflow for feedback, as I want to know if this is a good practice or not, but for now I am accepting the answer.
You can use your own user interface.
do the following steps:
go to Views\Shared
and create [EditorTemplates] folder.
then create your own *.cshtml file for each type or UIHint attributes
for example Create Int32.cshtml as follow for int type:
#model int?
#{
int i;
if (!Model.HasValue)
{
i = 0;
}
else
{
i = Model.Value;
}
var htmlAttributes = new RouteValueDictionary();
if (ViewBag.#class != null)
{
htmlAttributes.Add("class", "form-control " + ViewBag.#class);
}
else
{
htmlAttributes.Add("class", "form-control");
}
if (ViewBag.placeholder != null)
{
htmlAttributes.Add("placeholder", ViewBag.placeholder);
}
}
<div class="form-group#(Html.ValidationErrorFor(m => m, " has-error"))">
#Html.LabelFor(m => m, new { #class = "control-label" })
<div class="controls">
#Html.TextBox(
"",
ViewData.TemplateInfo.FormattedModelValue,
htmlAttributes)
#Html.ValidationMessageFor(m => m, null, new { #class = "help-block" })
</div>
</div>
You could use partial views for that.
I have two editor templates:
UploadFiles.cshtml:
#model HttpPostedFileBase[]
#Html.EditorFor(m => Model, "UploadFile", new { multiple = true })
UploadFile.cshtml:
#model HttpPostedFileBase
#Html.TextBox("", Model, new { type = "file", multiple = Convert.ToBoolean(ViewBag.Multiple) ? "multiple" : "" })
<!-- Additional code here i don't wish to repeat in both controls -->
Notice how UploadFiles.cshtml template accepts an array and then calls the UploadFile.cshtml template and passes in multiple = true via the view data.
The problem i have is if i say:
#Html.EditorFor(m => Model.Files, "UploadFiles")
It doesn't render anything.
However if i say:
#Html.EditorFor(m => Model.File, "UploadFile")
It renders correctly.
I'd appreciate if someone could show me how this can be achieved.
Thanks
In UploadFiles.cshtml, you're passing Model, which is an array, to UploadFile.cshtml, which does not take an array. Did you mean to wrap that line in a foreach?
foreach (var file in Model)
{
#Html.EditorFor(x => file, "UploadFile", new { multiple = true })
}
I know there was few similar questions here about DropDownListFor, but neither helped me...
I use Entity Framework as ORM in my project. There's EF model called "Stete". Stete has Foreign on EF model called "Drustva"
Now I'm trying to make a form for editing the data, for Stete model. I managed to display everything, including Stete.Drustva.Naziv property, but I can't get this last property in my handler method [HttpPost]. It always return 0, no matter what I select in drop down list.
Here's the code:
DrustvaController:
public static IEnumerable<SelectListItem> DrustvaToSelectListItemsById(this KnjigaStetnikaEntities pEntities, int Id)
{
IEnumerable<Drustva> drustva = (from d in pEntities.Drustva
select d).ToList();
return drustva.OrderBy(drustvo => drustvo.Naziv).Select(drustvo => new SelectListItem
{
Text = drustvo.Naziv,
Value = drustvo.Id.ToString(),
Selected = (drustvo.Id == Id)? true : false
});
}
SteteController:
private IEnumerable<SelectListItem> privremenaListaDrustava(int Id)
{
using (var ctx = new KnjigaStetnikaEntities())
{
return ctx.DrustvaToSelectListItemsById(Id);
}
}
public ActionResult IzmijeniPodatkeStete(Int32 pBrojStete)
{
PretragaStetaModel psm = new PretragaStetaModel();
ViewData["drustva"] = privremenaListaDrustava(psm.VratiStetuPoBrojuStete(pBrojStete).Drustva.Id);
ViewData.Model = new Models.Stete();
return View("EditView", (Stete.Models.Stete)psm.GetSteta(pBrojStete));
}
EditView:
<div class="editor-label">
<%: Html.Label("Društvo") %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(m => m.Drustva.Naziv, ViewData["drustva"] as IEnumerable<SelectListItem>) %>
<%: Html.ValidationMessageFor(model => model.FKDrustvo) %>
</div>
I am sorry for not translating names of the objects into english, but they hardly have appropriate translation. If necessary, I can try creating similar example...
Did you include Html.BeginForm or Ajax.BeginForm in your view markup? That is a common oversight that can cause the behavior you are referring to. I can't tell from the code you pasted in your question.Cheers.