Senad Meškin
Senad Meškin

Reputation: 13756

ASP.NET MVC 3 get Editor ID and Name in JS

I'm wondering is there possibility to get HTML attribute "ID" of generated element, link ASP.NET Web Forms Field.ClientID.

So what I'm trying to accomplish is to get id in JavaScript. Currently I'm working on form Validator so I have

RAZOR Template

@Html.TextEditorFor(m => m.aspnet_Membership.Email);

Then JS for validation

$('#MyForm').validate({
   rules : {
      //and this is the part where I need email ID
      //I can do this by manually typing aspnet_Membership_Email
      //but I'm asking is there a way to get it like 
      @Html.GetElementID(m => m.aspnet_Membership.Email): {
        required: true,
        email: true
     }
   }
});

There are lot's of fields so I need this in order to avoid user typing errors like apsnet.....

Remember JS is in template. not in external js file. (I'm not beginer :))

Please don't focus just on Validator, I have added it as example. If I add in the future prefix for each field in the form in order to have unique ID on page then I would run into problems like fixing my js if fields ids are fixed.

Thanks in advance.

Upvotes: 4

Views: 2015

Answers (4)

jwaliszko
jwaliszko

Reputation: 17064

I'd write my own extension method:

public static class Helper
{
    public static MvcHtmlString GetElementID<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        return MvcHtmlString.Create(GetPropertyName(expression));
    }

    private static string GetPropertyName(Expression lambdaExpression)
    {
        IList<string> list = new List<string>();
        var e = lambdaExpression;

        while (true)
        {
            switch (e.NodeType)
            {
                case ExpressionType.Lambda:
                    e = ((LambdaExpression)e).Body;
                    break;
                case ExpressionType.MemberAccess:
                    var propertyInfo = ((MemberExpression)e).Member as PropertyInfo;
                    var prop = propertyInfo != null
                                      ? propertyInfo.Name
                                      : null;
                    list.Add(prop);

                    var memberExpression = (MemberExpression)e;
                    if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
                    {
                        var parameter = GetParameterExpression(memberExpression.Expression);
                        if (parameter != null)
                        {
                            e = Expression.Lambda(memberExpression.Expression, parameter);
                            break;
                        }
                    }
                    return string.Join("_", list.Reverse());
                default:
                    return null;
            }
        }
    }

    private static ParameterExpression GetParameterExpression(Expression expression)
    {
        while (expression.NodeType == ExpressionType.MemberAccess)
        {
            expression = ((MemberExpression)expression).Expression;
        }
        return expression.NodeType == ExpressionType.Parameter ? (ParameterExpression)expression : null;
    }
}

and the usage, which results in aspnet_Membership_Email:

@Html.GetElementID(m => m.aspnet_Membership.Email);

This gives you concatenated properties chain based on lambda expression. Basically it is similar to the implementation of @Html.IdFor in MVC4 (I don't check their implementation however).

Upvotes: 1

Nick Larsen
Nick Larsen

Reputation: 18877

The class that the framework uses to build the names (not IDs per say) is a static class called ExpressionHelper and it is publicly accessible in MVC3. It has one function called GetExpressionText which takes in a lambda. That being said, you'll have to craft your own IdFor wrapper to get the syntax you want out of it.

This is the class as it exists in MVC4:

public static class NameExtensions
{
    public static MvcHtmlString Id(this HtmlHelper html, string name)
    {
        return MvcHtmlString.Create(html.AttributeEncode(html.ViewData.TemplateInfo.GetFullHtmlFieldId(name)));
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
    public static MvcHtmlString IdFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
    {
        return Id(html, ExpressionHelper.GetExpressionText(expression));
    }

    public static MvcHtmlString IdForModel(this HtmlHelper html)
    {
        return Id(html, String.Empty);
    }

    [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "1#", Justification = "This is a shipped API.")]
    public static MvcHtmlString Name(this HtmlHelper html, string name)
    {
        return MvcHtmlString.Create(html.AttributeEncode(html.ViewData.TemplateInfo.GetFullHtmlFieldName(name)));
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
    public static MvcHtmlString NameFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
    {
        return Name(html, ExpressionHelper.GetExpressionText(expression));
    }

    public static MvcHtmlString NameForModel(this HtmlHelper html)
    {
        return Name(html, String.Empty);
    }
}

Upvotes: 0

felipeclopes
felipeclopes

Reputation: 4070

The helper IdFor may help you in this task:

@html.IdFor(m => m.aspnet_Membership.Email)

Upvotes: 2

Heather
Heather

Reputation: 2652

I think you are right to encapsulate the ID generation logic, even though it is relatively simple. Your Html helper idea seems like an efficient way to do it (as opposed to say, exposing a bunch of ID values along with field values in the model).

However, I'm curious as to why you don't use the unobtrusive validation features and let it generate your validators for you?

If you add a [Required] attribute to your Email property and include the unobtrusive validator javascripts in the page you'd be drinking pina coladas by now.

In your model:

[Required]
[Display(Name = "First Name")]
[RegularExpression("([\r-~])*", ErrorMessageResourceType = typeof (AccountMessages),
    ErrorMessageResourceName = "FormFieldInvalid")]
[StringLength(50)]
public string FirstName { get; set; }

In your markup:

@Html.LabelFor(model => model.User.FirstName)
@Html.TextBoxFor(model => model.User.FirstName)  
@Html.ValidationMessageFor(model => model.User.FirstName, null, new { @class = "errorMessageField" })

Upvotes: 0

Related Questions