Html5 Placeholders with .NET MVC 3 Razor EditorFor extension?
Asked Answered
G

7

95

Is there a way to write the Html5 placeholder using @Html.EditorFor, or should I just use the TextBoxFor extension i.e.

@Html.TextBoxFor(model => model.Title, new { @placeholder = "Enter title here"})

Or would it make sense to write our own custom extension that can maybe use the 'Description' display attribute via DataAnnotations (similar to this)?

Of course, then the same question applies to 'autofocus' as well.

Gonta answered 28/4, 2011 at 19:49 Comment(0)
D
69

You may take a look at the following article for writing a custom DataAnnotationsModelMetadataProvider.

And here's another, more ASP.NET MVC 3ish way to proceed involving the newly introduced IMetadataAware interface.

Start by creating a custom attribute implementing this interface:

public class PlaceHolderAttribute : Attribute, IMetadataAware
{
    private readonly string _placeholder;
    public PlaceHolderAttribute(string placeholder)
    {
        _placeholder = placeholder;
    }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues["placeholder"] = _placeholder;
    }
}

And then decorate your model with it:

public class MyViewModel
{
    [PlaceHolder("Enter title here")]
    public string Title { get; set; }
}

Next define a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }
}

A corresponding view:

@model MyViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Title)
    <input type="submit" value="OK" />
}

And finally the editor template (~/Views/Shared/EditorTemplates/string.cshtml):

@{
    var placeholder = string.Empty;
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("placeholder"))
    {
        placeholder = ViewData.ModelMetadata.AdditionalValues["placeholder"] as string;
    }
}
<span>
    @Html.Label(ViewData.ModelMetadata.PropertyName)
    @Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { placeholder = placeholder })
</span>
Disunite answered 28/4, 2011 at 19:53 Comment(7)
thanks for the info (and a great example) of the IMetadataAware interface!Gonta
is this still valid for MVC3? I noticed a new [Display(Prompt = "type watermark here")] in MVC3 but could not make it work. any idea?Mute
@Mute You are correct. See my answer to see how to make Prompt work.Repeat
wow that much work for doing a placeholder? has to be something simpler :SCroce
There is, look at some of the answers bellow. The Pax has a good one.Pisa
I'm trying to do something like that for a DateTime property. I have 3 editor templates: DateTime, Date and Time. And I have a custom IMetadataAware attribute for each. But only the constructor is called, the OnMetadataCreated() is never executed. Do anyone know what could be the problem? Do I need to register something in Global.asax?Assemblage
omg is this serious? This is so much more code than just writing <input placeholder="something"> kind of annoyingHeracles
R
123

As smnbss comments in Darin Dimitrov's answer, Prompt exists for exactly this purpose, so there is no need to create a custom attribute. From the the documentation:

Gets or sets a value that will be used to set the watermark for prompts in the UI.

To use it, just decorate your view model's property like so:

[Display(Prompt = "numbers only")]
public int Age { get; set; }

This text is then conveniently placed in ModelMetadata.Watermark. Out of the box, the default template in MVC 3 ignores the Watermark property, but making it work is really simple. All you need to do is tweaking the default string template, to tell MVC how to render it. Just edit String.cshtml, like Darin does, except that rather than getting the watermark from ModelMetadata.AdditionalValues, you get it straight from ModelMetadata.Watermark:

~/Views/Shared/EditorTemplates/String.cshtml:

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { @class = "text-box single-line", placeholder = ViewData.ModelMetadata.Watermark })

And that is it.

As you can see, the key to make everything work is the placeholder = ViewData.ModelMetadata.Watermark bit.

If you also want to enable watermarking for multi-line textboxes (textareas), you do the same for MultilineText.cshtml:

~/Views/Shared/EditorTemplates/MultilineText.cshtml:

@Html.TextArea("", ViewData.TemplateInfo.FormattedModelValue.ToString(), 0, 0, new { @class = "text-box multi-line", placeholder = ViewData.ModelMetadata.Watermark })
Repeat answered 5/7, 2011 at 6:1 Comment(16)
I'd love to +1 this answer, but it just doesn't seem to pan out for me. Wonder if there's a difference in using EditorFor() vs Html.TextBox() ?Inverter
@Inverter There is, indeed. EditorFor() is a templated helper introduced in MVC 2. At first glance it might seem to do the same thing as TextBox(), but it gives you the big advantage of allowing you to control exactly how you want your HTML generated. My answer is based on this feature to "teach" MVC what to do with the Prompt attribute. For more information on these templates, you can refer to this great post by Brad Wilson: bradwilson.typepad.com/blog/2009/10/…Repeat
This cannot be localized. The above version can!Pash
@DotNetWise I'm not sure why you say that; all string parameters of DisplayAttribute (including Prompt) are localizable. You just need to specify the ResourceType in your annotation: [Display(ResourceType = typeof(PeopleResources), Prompt = "AgePrompt")]. And that's it. Watermark text now comes from key AgeGroup in resource PeopleResources.Repeat
What if you are not using .resx resources but i18N .po localization system?Pash
@DotNetWise You didn't mention .po before but, as usual, if the tools the framework offers are not enough for you, or you need to do anything even remotely custom, then you are on your own. In this case, resources (be it .resx files, a database, or whatever) are the tool the framework offers you. If you are using something else instead, then yes, you will have to resort to custom attributes and such. Honestly, I don't know anything about .po files as I have never had a need for them. But if I had to use them, I would at least try to handle them through a custom resource provider... (cont.)Repeat
...like this west-wind.com/presentations/wwdbResourceProvider Resources are the native way of handling i18n. They are used by not just Display but also by other attributes like StringLength, Range, Required, and many more. This way, you write the provider, wire it in, and then you just use existing attributes. IMO coding a custom resource provider once seems a more efficient (and definitely more standard) way of handling i18n than writing a bunch of customized attributes and ditching resource all together.Repeat
The only problem with this solution is that by passing the value directly with the new { ... }, you lose any values in the existing ViewData that would normally get sent along to the @Html.TextBox (or TextArea). By adding the placeholder value to the existing ViewData first, and then passing ViewData, you will retain any attributes set earlier in the process. For example, to set a custom CSS class, you could use Html.EditorFor(m => m.Foo, new { class = "css-class" }). With the method listed, the CSS class would be lost. I have added examples showing a method that retains the ViewData.Hedgcock
@81bronco Strictly speaking, it isn't really a problem in the solution (if it was, then the same problem would exist in the built-in templates that come with MVC, since they are 99% identical) I can see where it can be a problem for you, since you seem to be using the additionalViewData parameter to make up for the absence of an htmlAttributes parameters in EditorFor/DisplayFor, which BTW is a nice workaround, but it would only be a problem in that particular scenario. (cont.)Repeat
And as a little suggestion: if you instead do ViewData["placeholder"] = ViewData["placeholder"] ?? ViewData.ModelMetadata.Watermark; you add support for overriding the watermark. In other words, you can have a default watermark in your view model, with [Display(Prompt = "something")], and then if a particular view needs a different watermark, it can override it with new { placeholder = "something else" }. Cheers.Repeat
Hi. Ive seen the following ==> "~/Views/Shared/EditorTemplates/String.cshtml" <== a few times while looking for an answer to this question. In my project I don't have this folder or file (or they are hidden), how can I access it or am I missing something. I'm using VS2012 and MVC4.Tad
@FrancisRodgers EditorTemplates folder is not there by default; you just create in your Views\Shared folder (or Views\{ControllerName} if you want it to be specific to a certain controller). You then place you place your .cshtml templates inside this folder and you should be good to go.Repeat
Great help guys! Also see this blog post: geekswithblogs.net/Aligned/archive/2012/12/19/… Regarding localization, this works out well. Example: [Display(Name = "From", ResourceType = typeof(Resources.Website), Prompt = "PromptAddress")] public string StartAddress { get; set; }Calmas
Unfortunately, the solution presented by @81bronco, at least with ASP.NET MVC 4, includes all ViewData info as attributes in the <input>.Autopilot
Yep, everything in ViewData now get's added as attributes to input tags... I was staring at that tag for some type now, trying to figure out how on earth whole viewdata got added to every freaking input tag on the page. (a coworker copy/pasted solution from this answer) :)Willful
@RobertIvanc I have edited the answer and reverted the edit made by Raleigh Buckner that cause the problems you and Ted reported. Thanks.Repeat
D
69

You may take a look at the following article for writing a custom DataAnnotationsModelMetadataProvider.

And here's another, more ASP.NET MVC 3ish way to proceed involving the newly introduced IMetadataAware interface.

Start by creating a custom attribute implementing this interface:

public class PlaceHolderAttribute : Attribute, IMetadataAware
{
    private readonly string _placeholder;
    public PlaceHolderAttribute(string placeholder)
    {
        _placeholder = placeholder;
    }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues["placeholder"] = _placeholder;
    }
}

And then decorate your model with it:

public class MyViewModel
{
    [PlaceHolder("Enter title here")]
    public string Title { get; set; }
}

Next define a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }
}

A corresponding view:

@model MyViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Title)
    <input type="submit" value="OK" />
}

And finally the editor template (~/Views/Shared/EditorTemplates/string.cshtml):

@{
    var placeholder = string.Empty;
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("placeholder"))
    {
        placeholder = ViewData.ModelMetadata.AdditionalValues["placeholder"] as string;
    }
}
<span>
    @Html.Label(ViewData.ModelMetadata.PropertyName)
    @Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { placeholder = placeholder })
</span>
Disunite answered 28/4, 2011 at 19:53 Comment(7)
thanks for the info (and a great example) of the IMetadataAware interface!Gonta
is this still valid for MVC3? I noticed a new [Display(Prompt = "type watermark here")] in MVC3 but could not make it work. any idea?Mute
@Mute You are correct. See my answer to see how to make Prompt work.Repeat
wow that much work for doing a placeholder? has to be something simpler :SCroce
There is, look at some of the answers bellow. The Pax has a good one.Pisa
I'm trying to do something like that for a DateTime property. I have 3 editor templates: DateTime, Date and Time. And I have a custom IMetadataAware attribute for each. But only the constructor is called, the OnMetadataCreated() is never executed. Do anyone know what could be the problem? Do I need to register something in Global.asax?Assemblage
omg is this serious? This is so much more code than just writing <input placeholder="something"> kind of annoyingHeracles
D
23

I actually prefer to use the display name for the placeholder text majority of the time. Here is an example of using the DisplayName:

  @Html.TextBoxFor(x => x.FirstName, true, null, new { @class = "form-control", placeholder = Html.DisplayNameFor(x => x.FirstName) })
Diorite answered 17/7, 2014 at 22:6 Comment(3)
There is a special data annotation Prompt for a watermark. And DisplayName is for field label. It's a bad idea to mix them. Use right things for right tasks. Look at my answer.Williams
thanks, that's what i was looking for, simple and to the point when we can get display name then why add more classesHeads
This will double-escape the text provided by DisplayName - not a good solution for example languages with accents like french.Jabalpur
B
4

I use this way with Resource file (don't need Prompt anymore !)

@Html.TextBoxFor(m => m.Name, new 
{
     @class = "form-control",
     placeholder = @Html.DisplayName(@Resource.PleaseTypeName),
     autofocus = "autofocus",
     required = "required"
})
Bonaire answered 30/12, 2014 at 10:24 Comment(0)
W
3

I've wrote such a simple class:

public static class WatermarkExtension
{
    public static MvcHtmlString WatermarkFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        var watermark = ModelMetadata.FromLambdaExpression(expression, html.ViewData).Watermark;
        var htmlEncoded = HttpUtility.HtmlEncode(watermark);
        return new MvcHtmlString(htmlEncoded);
    }
}

The usage as such:

@Html.TextBoxFor(model => model.AddressSuffix, new {placeholder = Html.WatermarkFor(model => model.AddressSuffix)})

And property in a viewmodel:

[Display(ResourceType = typeof (Resources), Name = "AddressSuffixLabel", Prompt = "AddressSuffixPlaceholder")]
public string AddressSuffix
{
    get { return _album.AddressSuffix; }
    set { _album.AddressSuffix = value; }
}

Notice Prompt parameter. In this case I use strings from resources for localization but you can use just strings, just avoid ResourceType parameter.

Williams answered 23/10, 2014 at 22:2 Comment(3)
Just decompiled DisplayNameFor method and made an analog for the watermark.Williams
Hi, can you please change your method MvcHtmlString WatermarkFor() to use DisplayName attribute value if Display -> Prompt value is not specified?Peen
Where do you save your WatermarkExtension class so that it can then be used as you described? Html.WatermarkFor(model => model.AddressSuffix)Ernaline
H
1

Here is a solution I made using the above ideas that can be used for TextBoxFor and PasswordFor:

public static class HtmlHelperEx
{
    public static MvcHtmlString TextBoxWithPlaceholderFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        return htmlHelper.TextBoxFor(expression, htmlAttributes.AddAttribute("placeholder", metadata.Watermark));

    }

    public static MvcHtmlString PasswordWithPlaceholderFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        return htmlHelper.PasswordFor(expression, htmlAttributes.AddAttribute("placeholder", metadata.Watermark));

    }
}

public static class HtmlAttributesHelper
{
    public static IDictionary<string, object> AddAttribute(this object htmlAttributes, string name, object value)
    {
        var dictionary = htmlAttributes == null ? new Dictionary<string, object>() : htmlAttributes.ToDictionary();
        if (!String.IsNullOrWhiteSpace(name) && value != null && !String.IsNullOrWhiteSpace(value.ToString()))
            dictionary.Add(name, value);
        return dictionary;
    }

    public static IDictionary<string, object> ToDictionary(this object obj)
    {
        return TypeDescriptor.GetProperties(obj)
            .Cast<PropertyDescriptor>()
            .ToDictionary(property => property.Name, property => property.GetValue(obj));
    }
}
Haircloth answered 22/5, 2015 at 14:21 Comment(0)
U
0

I think create a custom EditorTemplate is not good solution, beause you need to care about many possible tepmlates for different cases: strings, numsers, comboboxes and so on. Other solution is custom extention to HtmlHelper.

Model:

public class MyViewModel
{
    [PlaceHolder("Enter title here")]
    public string Title { get; set; }
}

Html helper extension:

   public static MvcHtmlString BsEditorFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TValue>> expression, string htmlClass = "")
{
    var modelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    var metadata = modelMetadata;

    var viewData = new
    {
        HtmlAttributes = new
            {
                @class = htmlClass,
                placeholder = metadata.Watermark,
            }
    };
    return htmlHelper.EditorFor(expression, viewData);

}

A corresponding view:

@Html.BsEditorFor(x => x.Title)
Unshakable answered 18/10, 2018 at 12:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.