I have an input text field, and I want to be able to restore the initial value after the user edits the text.
So, I would say to add a data-{something} attribute, for instance data-init, that will hold the initial value, so it can be restored later.
Result should look like:
<input type="text" val="Some value..." data-init="Some value..." />
Now, I know I can achieve this by using:
#Html.TextBoxFor(m => m.InputText , new { data_init = Model.InputText })
But it's awful, and I have a lot of input fields with this behavior.
Also, I can't create a custom HtmlHelper because I have many input types with this behavior, it will be quite messy if I go this way...
So, what I think should be the practical solution is to use Data Annotations.
public class ExampleVM
{
[HoldInitialValue]
public string InputText { get; set; }
}
The [HoldInitialValue] attribute will be responsible to add the data-init="{value}" to the input tag. The {value} will be taken from the property's value, with also an option to override it.
So, how do I implement this behavior?
Thanks for the helpers.
Even if you were to create a custom attribute, which would need to implement MetadataAware in order to add the value to the AdditionalValues property of ModelMetadata, you still then need to create your own extension methods to read that value and add it to the htmlAttributes.
For an example of how to implement it, refer CustomAttribute reflects html attribute MVC5.
However, that is unnecessary since HTML inputs already store the initial value. For a <input> (other than a checkbox) or <textarea> its defaultValue. For a <input type="checkbox" /> its defaultChecked and for <select> its defaultSelected.
So in the case of your #Html.TextBoxFor(m => m.InputText), your could use for example
using javascript
var element = document.getElementById ("InputText");
var initialValue = elem.defaultValue;
// reset to initial value
element.value = initialValue;
or using jQuery
var element = $('#InputText');
var initialValue = element.prop('defaultValue');
// reset to initial value
element.val(initialValue);
try this one , using htmlAttributes as IDictionary
#Html.TextBoxFor(m => m.InputText , new Dictionary<string,object>()
{
{ "data-init", Model.InputText}
})
Related
My model contains an array of zip code items (IEnumerable<SelectListItem>).
It also contains an array of selected zip codes (string[]).
In my HTML page, I want to render each selected zip code as a drop down with all the zip code options. My first attempt did not work:
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes", Model.ZipCodeOptions )
}
I realized that although that would produce drop downs with the right "name" attribute, it wouldn't know which element of ZipCodes holds the value for that particular box, and might just default to the first one.
My second attempt is what really surprised me. I explicitly set the proper SelectListItem's Selected property to true, and it still rendered a control with nothing selected:
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes", Model.ZipCodeOptions.Select( x => (x.Value == zip) ? new SelectListItem() { Value = x.Value, Text = x.Text, Selected = true } : x ) )
}
There, it's returning a new IEnumerable<SelectListitem> that contains all the original items, unless it's the selected item, in which case that element is a new SelectListItem with it's Selected property set to true. That property is not honored at all in the final output.
My last attempt was to try to use an explicit index on the string element I wanted to use as the value:
#{int zipCodeIndex = 0;}
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes[" + (zipCodeIndex++) + "]", Model.ZipCodeOptions )
}
That doesn't work either, and probably because the name is no longer "ZipCodes", but "ZipCodes[x]". I also received some kind of read-only-collection error at first and had to change the type of the ZipCodes property from string[] to List<string>.
In a forth attempt, I tried the following:
#for (int zipCodeIndex = 0; zipCodeIndex < Model.ZipCodes.Count; zipCodeIndex++)
{
var zip = Model.ZipCodes[zipCodeIndex];
Html.DropDownListFor( x => x.ZipCodes[zipCodeIndex], Model.ZipCodeOptions )
}
That produces controls with id like "ZipCodes_1_" and names like "ZipCodes[1]", but does not select the right values. If I explicitly set the Selected property of the right item, then this works:
#for (int zipCodeIndex = 0; zipCodeIndex < Model.ZipCodes.Count; zipCodeIndex++)
{
var zip = Model.ZipCodes[zipCodeIndex];
Html.DropDownListFor( x => x.ZipCodes[zipCodeIndex], Model.ZipCodeOptions.Select( x => (x.Value == zip) ? new SelectListItem() { Value = x.Value, Text = x.Text, Selected = true } : x ) )
}
However, the problem with that approach is that if I add a new drop downs in JavaScript and give them all the name "ZipCodes", then those completely override all the explicitly indexed ones, which never make it to the server. It doesn't seem to like mixing the plain "ZipCodes" name with explicit array elements "ZipCodes[1]", even though they map to the same variable when either is used exclusively.
In the U.I., user's can click a button to add a new drop down and pick another zip code. They're all named ZipCodes, so they all get posted to the ZipCodes array. When rendering the fields in the loop above, I expect it to read the value of the property at the given index, but that doesn't work. I've even tried remapping the SelectListItems so that the proper option's "Selected" property is true, but it still renders the control with nothing selected. What is going wrong?
The reason you first 2 snippets do not work is that ZipCodes is a property in your model, and its the value of your property which determines what is selected (not setting the selected value in the SelectList constructor which is ignored). Since the value of ZipCodes is an array of values, not a single value that matches one of the option values, a match is not found and therefore the first option is selected (because something has to be). Note that internally, the helper method generates a new IEnumerable<SelectListItem> based on the one you provided, and sets the selected attribute based on the model value.
The reason you 3rd and 4th snippets do not work, is due to a known limitation of using the DropDownListFor() method, and to make it work, you need to use an EditorTemplate and pass the SelectList to the template using AdditionalViewData, or construct a new SelectList in each iteration of the loop (as per your last attempt). Note that all it needs to be is
for(int i = 0; i < Model.ZipCodes.Length; i++)
{
#Html.DropDownListFor(m => m.ZipCodes[i],
new SelectList(Model.ZipCodeOptions, "Value", "Text", Model.ZipCodes[i]))
}
If you want to use just a common name (without indexers) for each <select> element using the DropDownList() method, then it needs to be a name which does not match a model property, for example
foreach(var item in Model.ZipCodes)
{
#Html.DropDownList("SelectedZipCodes",
new SelectList(Model.ZipCodeOptions, "Value", "Text", item))
}
and then add an additional parameter string[] SelectedZipCodes in you POST method to bind the values.
Alternatively, use the for loop and DropDownListFor() method as above, but include a hidden input for the indexer which allows non-zero based, non consecutive collection items to be submitted to the controller and modify you script to add new items using the technique shown in this answer
Note an example of using the EditorTemplate with AdditionalViewData is shown in this answer
I have an few editorfor in my view like following
#Html.EditorFor(Model => Model.CashBalance)
Now when i enter any value in to that editorfor,the value should change to currency value in textbox change event
For ex:
123 should display as 123.00
14.35 should display as 14.35
I want to do this in generic way so that I don't need to change it every where as my project has many editorfor which takes inputs from user.
As I am using an EditorTemplate for all these textboxes,i want to handle here itself.
My EditorTemplate for this is decimal.cshtml and it looks like the foll
#model decimal?
#{
string value = (Model.HasValue == false || Model.Value == 0) ? "" : string.Format("{0:0.00}", Model.Value);
}
#Html.TextBox(
"",
value,
new { #class="amountRightAlign"}
)
Will there be any textchange event i can write here so that it affects where ever there is decimal datatype?
Thanks in advance?
Html helpers are server side code used to generate the html which is sent to the client. In order to interact with user changes in the browser, you need to use javascript to handle events.
In your case you don't need an EditorTemplate. Instead, just the overload of TextBoxFor() that accepts a format string
#Html.TextBoxFor(m => m.CashBalance, "{0:0.00}", new { #class="decimalnumber" })
Then in the view, or in a separate script file
$('.decimalnumber').change(function () {
var num = new Number($(this).val());
if (isNaN(num)) {
// Its not a valid number
return;
}
$(this).val(num.toFixed(2));
})
I have converted my MVC3 application to MVC5, I had to change all views to razor. Having a challenge with a select list:
In ASPX view that works I am using the following:
<select id="Profession" name="Profession" style="width: 235px; background-color: #FFFFCC;">
<% List<string> allProfessions = ViewBag.AllProfessions;
string selectedProfession;
if (Model != null && !String.IsNullOrEmpty(Model.Profession))
selectedProfession = Model.Profession;
else
selectedProfession = allProfessions[0];
foreach (var aProfession in allProfessions)
{
string selectedTextMark = aProfession == selectedProfession ? " selected=\"selected\"" : String.Empty;
Response.Write(string.Format("<option value=\"{0}\" {1}>{2}</option>", aProfession, selectedTextMark, aProfession));
}%>
</select>
In Razor I am using:
<select id="Profession" name="Profession" style="width: 235px; background-color: #FFFFCC;">
#{List<string> allProfessions = ViewBag.AllProfessions;
string selectedProfession;}
#{if (Model != null && !String.IsNullOrEmpty(Model.Profession))
{selectedProfession = Model.Profession;}
else {selectedProfession = allProfessions[0];}
}
#foreach (var aProfession in allProfessions)
{
string selectedTextMark = aProfession == selectedProfession ?
"selected=\"selected\"" : String.Empty;
Response.Write(string.Format("<option value=\"{0}\" {1}>{2}</option>",
aProfession, selectedTextMark, aProfession));
}
</select>
The list shows up at the top of the page, I can't figure out where is the problem. Would appreciate your assistance.
Don't create your dropdown manually like that. Just use:
#Html.DropDownListFor(m => m.Profession, ViewBag.AllProfessions, new { style = "..." })
UPDATE
I tried your solution but got this error: Extension method cannot by dynamically dispatched
And, that's why I despise ViewBag. I apologize, as my answer was a little generic. Html.DropDownList requires the list of options parameter to be an IEnumerable<SelectListItem>. Since ViewBag is a dynamic, the types of its members cannot be ascertained, so you must cast explicitly:
(IEnumerable<SelectListItem>)ViewBag.AllProfessions
However, your AllProfessions is a simple array, so that cast won't work when the value gets inserted at run-time, but that can be easily fixed by casting it to a List<string> and then converting the items with a Select:
((List<string>)ViewBag.AllProfessions).Select(m => new SelectListItem { Value = m, Text = m })
There again, you see why dynamics are not that great, as that syntax is rather awful. The way you should be handling this type of stuff is to use your model or, preferably, view model to do what it should do: hold domain logic. Add a property to hold your list of profession choices:
public IEnumerable<SelectListItem> ProfessionChoices { get; set; }
And then, in your controller action, populate this list before rendering the view:
var model = new YourViewModel();
...
model.ProfessionChoices = repository.GetAllProfessions().Select(m => new SelectListItem { Value = m.Name, Text = m.Name });
return View(model);
repository.GetAllProfessions() is shorthand for whatever you're using as the source of your list of professions, and the Name property is shorthand for how you get at the text value of the profession: you'll need to change that appropriately to match your scenario.
Then in your view, you just need to do:
#Html.DropDownListFor(m => m.Profession, Model.ProfessionChoices)
Given that you don't have this infrastructure already set up, it may seem like a lot to do just for a drop down list, and that's a reasonable thing to think. However, working in this way will keep your view lean, make maintenance tons easier, and best of all, keep everything strongly-typed so that if there's an issue, you find out at compile-time instead of run-time.
I believe it's happening because of the Response.Write. Try this:
#Html.Raw(string.Format("<option value=\"{0}\" {1}>{2}</option>", aProfession,
selectedTextMark, aProfession))
I want to have a static list of data in a model that can be used in a viewmodel and dropdown on a view. I want to be able to use it in this way in my controller:
MaintenanceTypeList = new SelectList(g, "MaintenanceTypeID", "MaintenanceTypeName"),
and access it in my view like this:
#Html.LabelFor(model => model.MaintenanceTypeID)
#Html.DropDownListFor(x => x.MaintenanceTypeID, Model.MaintenanceTypeList, "-- Select --", new { style = "width: 150px;" })
#Html.ValidationMessageFor(x => x.MaintenanceTypeID)
I am currently using a repository pattern for data in the database, but don't want to put this data in the database because it will never change. I still want it in a model though. Basically, my dropdown list should offer the following:
Value Text
-------------------------------------
Calibration Calibration
Prevent Preventative Maintenance
CalibrationPrevent PM and Calibration
Any help or examples of static lists using models/oop is appreciated
You can use a list initializer:
public static SomeHelperClass{
public static List<SelectListItem> MaintenanceTypeList {
get {
return new List<SelectListItem>
{ new SelectListItem{Value = "Calibration", Text = "Calibration"}
,new SelectListItem{ Value = "Prevent", Text = "Preventative Maintenance" }
,etc.
};
}
}
}
Hopefully I didn't miss a curly brace somewhere. You can google "C# list initializer" for more examples. I don't remember off top of my head what the actual collection to is for a SelectListCollection is, but I know there is a overload of DropDownList that accepts List as I often just have a collection of keyvaluepairs or something else, and then in my view I convert it to SelectListItems: someList.Select(i => new SelectListItem { Value = i.Key, Text = i.Value })
Note that another option is to place your values in an enum. You can then use a Description attribute on each enum value:
enum MaintenanceType {
[Description("Calibration")]
Calibration = 1,
[Description("Preventative Maintenance")]
Prevent = 2
}
Then you can do things like
Enum.GetValues(typeof(MaintenanceType )).Select(m=>new SelectListItem{ Value = m, Text = m.GetDescription()} ).ToList()
That last line was a little off the top of the head, so hopefully I didn't make a mistake. I feel like an enum is more well structured for what you're trying to do.
I'm having a hard time figuring out how to collect the data from a FormCollection when submitted for my form which collects the answers for a survey. Specifically my problem is with questions that have multiple choice options(radio buttons) and an other textbox field if the options did not apply.
My survey has the following structure:
QUESTION : [ QuestionId, Text, QuestionType, OrderIndex]
MULTIPLE_CHOICE_OPTIONS: [ MC_OptionId, QuestionId, OrderIndex, MC_Text]
ANSWER: [AnswerId,QuestionId, MC_OptionId (can be null), UserTextAnswer]
QUESTION_TYPES are: [Multiple_Choice, Multiple_Choice_wOtherOption, FreeText or Checkbox]
My view is rendering the form as follows(pseudo code to simplify):
//Html.BeginForm
foreach( Question q in Model.Questions)
{
q.Text //display question text in html
if (q.QuestionType == Multiple_Choice)
{
foreach( MultipleChoice_Option mc in Model.MULTIPLE_CHOICE_OPTIONS(opt => opt.QuestionId == q.QuestionId)
{
<radio name=q.QuestionId value=mc.MC_OptionId />
// All OK, can use the FormCollectionKey to get the
// QuestionId and its value to get the selected MCOptionId
}
}
else if (q.QuestionType == Multiple_Choice_wOtherOption)
{
foreach( MultipleChoice_Option mc in Model.MULTIPLE_CHOICE_OPTIONS(opt => opt.QuestionId == q.QuestionId)
{
<radio name=q.QuestionId value=mc.MC_OptionId />
}
<textbox name=q.QuestionId />
// ****Problem - I can get the QuestionId from the FormCollection Key, but
// I don't know if the value is from the user entered
// textbox or from a MCOptionId***
}
}
<button type="submit">Submit Survey</button>
// Html.EndForm
I was doing it this way so back in the controller action that handles the post back I could read over the FormCollection by key to get the questionId, and the value for each index to get the MCOptionID.
But in the case of the question with radio buttons and a textbox all with the same name key how would I determine if the form data is from the radio button or the text box.
I can see the way I'm doing this breaks because their could be the case where a question (id=1) has a MCOption w/ Id=5 so the radio button has value of 5 and the user enters 5 in the Other textbox. When the form submits I see the formcollection[key="1"] has the value 5 and I can't tell if that is from usertext or the radioButton value referencing a MCOptionId.
Is there a better way to approach this problem, either the db structure, view rendering code or the way the form controls are named? Maybe form collection is not the way to go but I was stumped how to post back and make the model binding work .
Thanks for any help, been going around in circles for something that seems pretty simple.
Consider this small refactoring...
//you're always rendering the radios, it seems?
RenderPartial("MultipleChoice", Model.MULTIPLE_CHOICE_OPTIONS.Where(x =>
x.QuestionId == q.QuestionId));
if (q.QuestionType == Multiple_Choice_wOtherOption)
{
<textbox name="Other|" + q.QuestionId />
}
and within that strongly typed partial view:
//Model is IEnumerable<MultipleChoice_Option >
foreach (MultipleChoice_Option mc in Model )
{
<radio name=mc.Question.QuestionId value=mc.MC_OptionId />
}
It seems your question was around the textbox name; being tied to the question by ID. In your controller, you'll have to explicitly know when to look for any value in the textbox.
string userAnswer = Request.Form["OtherEntry|" + someQuestionID].ToString();