How do i reuse the expression from HtmlHelper.EditorFor - asp.net-mvc-3

In my cshtml file i have the following call
#foreach( var EducationPlan in Model.Fields) {
#HtmlHelper.EditorFor(m => EducationPlan, "ViewFieldInputWithHidden")
}
and in ViewFieldInputWithHidden.cshtml i have
#HtmlHelper.TextBox(Model.MemberName, Model.Value, Model.HtmlAttributes)
#HtmlHelper.Hidden(Model.MemberName, Model.Value, new { id = Model.MemberName + "_Hidden" })
This gives me, with unimportant stuff removed
<input id="EducationPlan_ResponsiblePerson" name="EducationPlan.ResponsiblePerson" type="text">
<input id="ResponsiblePerson_Hidden" name="EducationPlan.ResponsiblePerson" type="hidden">
Is there any way to emulate/use the same value as the TextBox uses to get the "EducationPlan" string with me when i write the id? Or is there, even better, just a way to append the "_Hidden" to the id.
I found the solution and i'm leaving it here for other who runs into the same problem.
Replacing the hidden with the following code helped:
#Html.Hidden(Model.MemberName, Model.Value, new { id = Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Model.MemberName) + "_Hidden" })
So, basically the TemplateInfo class saves the expression in some way.

Related

Can one avoid multiple DOM elements with same id, when using Backbone/Marionette view instances?

When I create multiple view instances of the Marionette view which is linked with a template html with ids, these would get duplicated for multiple instances of these views.
While it works correctly, I feel that there ought to be more architecturally correct way of doing this.
The example code is like below.
Template:
<script id="myTemplate" type="text/template">
<div id="myDiv">
<input type="text" id="myText"/>
<input type="button" id="myBtn" value="Click me!"/>
</div>
</script>
View:
MyView = Backbone.Marionette.ItemView.extend({
template: '#myTemplate',
events: {
'click #myBtn' : 'myFunc' //Correctly identifies its own 'myBtn'
},
myFunc : function() {
alert($('myText').val()); //Again, picks own 'myText'
}
});
var v1= new MyView();
v1.render();
var v2= new MyView();
v2.render(); //Duplicate IDs now present in DOM
I need some unique identification of these DOM elements and hence the ids.
Even when tying the model to this view, we need some way to identify these DOM elements.
What is the correct way of doing this without duplicating the ids.
Just pass the id to the view when you create it:
Template:
<script id="myTemplate" type="text/template">
<input type="text" class="js-myText"/>
<input type="button" class="js-myBtn" value="Click me!"/>
</script>
View def:
MyView = Backbone.Marionette.ItemView.extend({
template: '#myTemplate',
events: {
'click #myBtn' : 'myFunc' //Correctly identifies its own 'myBtn'
},
myFunc : function() {
alert($('myText').val()); //Again, picks own 'myText'
}
});
Instanciation:
var v1= new MyView({ id: "view" + number});
v1.render();
Then you can provide dynamic id values for your views (e.g. by using a model id).
That said, when using Marionette you shouldn't need to call render: you should instead show a view within a region. Take a look at the free sample to my Marionette book to get you up to speed.
If you must go for unique IDs to make sure no one accidentally duplicates a class name inside a view, you can use:
Underscore's uniqueId method to generate a unique ID for each DOM element inside the view, like: <input type="text" id= <%= _.uniqueId("myText_") %> /> This will just make sure that IDs are not duplicated. But they're not very helpful if you need to identify the elements by these IDs.
Marionette's TemplateHelpers which allow you to use helper functions from inside the templates:
//Define this inside your view:
templateHelpers: function() {
var that = this;
return {
getIdSuffix : function() { return that.idSuffix; }
/*Where idSuffix is passed to the view during instantiation
and assigned to this.idSuffix */
};
}
//In the template:
<input type="text" id= <%= "myText_" + getIdSuffix() %> />
You now know before runtime what DOM IDs you will have, provided care is taken not to give the same idSuffix to more than one view instance.
Simply put, don't use an id if it's not unique. Use a class or some other way of identifying the element.
You can use any jQuery selector to locate the element you want, ranging from the insane and brittle:
this.$('div > input:first'); // don't actually do this!
to the slower but semantically better:
this.$('[data-element-type="some-text-box-descriptive-name"]');
Although in reality, using a class is best, because that's what a class is for - for identifying a type of element. I can see that a maintainer might not know not to change your class in the template, so a data-attribute might be acceptable, or maybe even (in this case):
this.$('input[type=text]');

AngularJS Form Validation inside an ng-repeat

So I am trying to validate the input of one item inside of an ng-repeat. For examples sake lets say that I have 5 items (1,2,3,4,5) and I only want to validate the form if the 4th item is selected.
I have used ng-pattern before to validate forms, but not one that had a dropdown menu to select item.name
I have included the regex I would like the 4th item to be validated with inside the ng-pattern.
<div>
<select name="name" ng-model="item.name" ng-options="item for item in items" required></select>
</div>
<div>
<input name="results" type="text" ng-model="item.results" ng-pattern="/^\d\d\d\/\d\d\d/" required>
</div>
Any suggestions as to the correct way to validate this situation would be greatly appreciated. I have thought about creating a directive to validate this, but that feels like is an overly complicated solution to this since I would not use the directive more than once in this app.
//////////////////////////////////////////////////
It wouldn't let me answer my own question so here is the answer I figured out.
What I ended up having to do was use ng-pattern and pass it a function.
<input name="results" type="text" ng-model="vital.results" ng-pattern="vitalRegEx()" required>
Here is the controller code
$scope.item4RegEx = /^\d{2,3}\/\d{2,3}$/;
$scope.itemRegEx = function() {
if($scope.item && $scope.item.name === "fourth item")
return $scope.item4RegEx;
else return (/^$/);
};
or else...
add ng-change directive on the select dropdown which calls a Controller method and that controller method sets a flag whether to validate form or not.
eg.
<select ng-change="checkIfFormShouldbeValidated()" ng-model="item.name"></select>
// Inside controller
$scope.checkIfFromShouldBeValidated = function(){
if( $scope.item.name == 4th Item ) $scope.shouldValidate = true;
else $scope.shouldValidate = false;
};
$scope.formSubmit = function(){
if(($scope.shouldValidate && form.$valid) || (!$scope.shouldValidate)){
// Submit Form
}
};
See if it helps.
I wrote this recursive function inside my controller to check the validity of all child scopes.
function allValid(scope) {
var valid = true;
if (scope.$$childHead) {
valid = valid && allValid(scope.$$childHead);
}
if (scope.$$nextSibling) {
valid = valid && allValid(scope.$$nextSibling);
}
if (scope.scorePlannerForm) {
valid = valid && scope.myForm.$valid;
}
return valid;
}
Then in my controller I check this with the controller scope.
function formSubmit() {
if (allValid($scope)) {
// perform save
}
}

Custom Editor template based on ViewModel Dataannotation attribute MVC4

What I want to do is automatically add an image span after my input textboxes if the [Required] attribute decorates my ViewModel property be it an integer, double, string, date etc
For example, my ViewModel might look like
public class MyViewModel
{
[Required]
public string Name { get; set; }
}
and my View would look like
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
and the output would be something like
<input id="Name" class="text-box single-line" type="text" value="" name="Name" data-val-required="The Name field is required." data-val-length-max="20" data-val-length="The field Name must be a string with a maximum length of 20." data-val="true">
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Name"></span>
-- Note the automatically added span
<span class="indicator required" style="width: 11px;"></span>
I was intending to have some css that would show the image i.e.
span.required {
background-image: url("required.png");
}
Is this possible to do or do I need to create my own Helper method to implement this type of functionality?
Yes, it's possible, but in general I wouldn't recommend it, because templates are really there to customize type rendering, and you should be able to create templates without worrying if it overrides another template.
I would instead create a custom LabelFor helper, such as the one described here:
http://weblogs.asp.net/imranbaloch/archive/2010/07/03/asp-net-mvc-labelfor-helper-with-htmlattributes.aspx
or here:
http://weblogs.asp.net/raduenuca/archive/2011/02/17/asp-net-mvc-display-visual-hints-for-the-required-fields-in-your-model.aspx
A third option is to not do anything in MVC, but rather add some javascript that will add the indicator based on the standard MVC validation data attributes (if you're using unobtrusive validation). See the answer here:
https://stackoverflow.com/a/8524547/61164
What I did was to modify the jquery.validate.unobtrusive JS file to add a second container, specifically for your images, if there is a validation error.
var container2 = $(this).find("[data-valimg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replace = $.parseJSON(container.attr("data-valimg-replace")) !== false;
container2.removeClass("img-validation-valid").addClass("img-validation-error");
Then don't forget to bind it to the model:
error.data("unobtrusiveContainer", container2);
Finally, empty it in the if (replace) code block:
if (replace) {
container.empty();
container2.empty();
error.removeClass("input-validation-error").appendTo(container);
}
else {
error.hide();
}
On success, remember to hide it:
var container2 = error.data("unobtrusiveContainer"),
replace = $.parseJSON(container.attr("data-valimg-replace"));
if (container2) {
container2.addClass("img-validation-valid").removeClass("img-validation-error");
error.removeData("unobtrusiveContainer");
if (replace) {
container2.empty();
}
}
If you take a look at the onError and onSuccess functions in the file, you should be able to find out where you can put them in.
In your view, add the following line of code to each form input there's validation for:
<img class="img-validation-valid" data-valimg-replace="true" data-valimg-for="<replace with field name here, ie. Name>" src="required.png" />
I've only tested this with the [Required] attribute, but it works. I'm also pretty sure you can use this for generating other stuff as well, not just images.

MVC matching ModelState keys to ViewModel collection

Is it possible to match a ViewModel property to the matching ModelState.Key value when the ViewModel is a (has a) collection?
Example: To edit a collection of viewmodel items, I am using the extension found here.
That adds a GUID to the id of the fields on the page.
example:
class Pets
{
string animal;
string name;
}
For a list of Pets, the generated html source is like this:
<input name="Pets.index" autocomplete="off" value="3905b306-a9..." type="hidden">
<input value="CAT" id="Pets_3905b306-a9...__animal" name="Pets[3905b306-a9...].animal" type="hidden">
<input value="MR. PEPPERS" id="Pets_3905b306-a9...__name" name="Pets[3905b306-a9...].name" type="hidden">
<input name="Pets.index" autocomplete="off" value="23342306-b4..." type="hidden">
<input value="DOG" id="Pets_23342306-b4...__animal" name="Pets[23342306-b4...].animal" type="hidden">
<input value="BRUTICUS" id="Pets_23342306-b4...__name" name="Pets[23342306-b4...].name" type="hidden">
So when this gets bound on post, the ModelState gets loaded with all the form fields.
In ModelSTate.Keys, there is:
Pets[23342306-b4...].name
Pets[23342306-b4...].animal
Pets[3905b306-a9...].name
Pets[3905b306-a9...].animal
Everything good so far, but I am doing some business logic validation, things like, cant add new animal if one exists with the same name. In that case, I want to be able to highlight the input field that is in error.
So if my create function fails, it will return an error/key value pair like this:
{ error = "Duplicate Name", key="name" }
So I at least will now what property caused the problem.
But since my repository functions don't know about the view field ids, how can I match the key "name" to the appropriate ModelState key (in this case, either Pets[23342306-b4...].name or Pets[3905b306-a9...].name)?
If you used the built in functionality of MVC for displaying collections (Html.DisplayFor(m => m.Pets) or Html.EditorFor(m => m.Pets)) with appropriate display/editor template, MVC would render something like this:
Pets[0].name
Pets[0].animal
Pets[1].name
Pets[1].animal
This maps to IEnumerable<Pets> and you know that first item has index of 0, second item 1 etc.
So if the second item has an error, you can set error for the ModelState key "Pets[1].name" for example.
If you are using the Html.BeginCollectionItem extension method, like I was, I was able to get around this by not using the GUID. I need the dynamic add and delete, but I was always looking up known items, persons that have an ID, which I had in my editor. So instead of using the GUID, I just assign the ID (uniqueId) in the code below. I could then find the key because I knew it was Person[234232]. Of course if you are adding new items and not displaying selected items, it might not work for you.
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName, string uniqueId)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : uniqueId;
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}

HtmlHelper and htmlAttributes help

I'm fairly new to MVC 3 and am using the Razor view engine. I'm using the Html.Hidden extension method to output input elements of type hidden. What I woudl also like to do is add a custom attribute to hold a dynamic value. I was under the impression in HTML5 wee could write custom html element attributes that are prefixed with 'data-'. I'm trying to do something like below;
#Html.Hidden("hdnID", mymodel.somevalue, new { data-uniqueid = mymodel.somevalue })
hoping to render;
<input type="hidden" value="mymodel.somevalue" data-uniqueid="mymodel.somevalue"/>
The htmlAttributes part (new { data-uniqueid = mymodel.somevalue }) is giving the error,
"Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access".
Can I add user-defined attribute to html elements using the HtmlHelper classes?
Regards,
Use:
#Html.Hidden("hdnID", mymodel.somevalue, new { #data_uniqueid = mymodel.somevalue })
The underscore gets automatically converted to a dash.
Doh! I was being silly. You can't have '-' in the anon type declaration:
data-uniqueid = ...it must be
datauniqueid = ....
In that case, your best best is to write out the hidden input by hand:
<input type="hidden" value="#mymodel.somevalue" data-uniqueid="#mymodel.somevalue"/>
You can step around the member validation by constructing a dictionary object. As follows:
#Html.TextBoxFor(model => model.Phone, new Dictionary<string, object>
{
{
"data-call-results-target", "#search-results-area"
},
{
"data-action-path", "/Controler/Method"
}
})

Resources