94

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.

seekay
  • 2,493
  • 3
  • 27
  • 33

7 Answers7

122

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 })
Daniel Liuzzi
  • 16,807
  • 8
  • 52
  • 57
  • 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() ? – Brett Sep 19 '11 at 20:21
  • 6
    @Brett 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: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html – Daniel Liuzzi Sep 20 '11 at 19:18
  • This cannot be localized. The above version can! – DATEx2 Jan 07 '12 at 01:32
  • 2
    @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_. – Daniel Liuzzi Jan 07 '12 at 11:00
  • 1
    What if you are not using .resx resources but i18N .po localization system? – DATEx2 Feb 17 '12 at 13:24
  • 1
    @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.) – Daniel Liuzzi Feb 21 '12 at 04:19
  • 1
    ...like this http://www.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. – Daniel Liuzzi Feb 21 '12 at 04:19
  • 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. – Raleigh Buckner Apr 11 '12 at 15:12
  • 1
    @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.) – Daniel Liuzzi Apr 14 '12 at 16:46
  • 1
    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. – Daniel Liuzzi Apr 14 '12 at 16:53
  • 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. – Francis Rodgers Jan 29 '13 at 15:57
  • 3
    @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. – Daniel Liuzzi Feb 05 '13 at 05:05
  • Great help guys! Also see this blog post: http://geekswithblogs.net/Aligned/archive/2012/12/19/mvc-4-string.cshtml-override.aspx Regarding localization, this works out well. Example: [Display(Name = "From", ResourceType = typeof(Resources.Website), Prompt = "PromptAddress")] public string StartAddress { get; set; } – jesperlind Mar 08 '13 at 00:02
  • Unfortunately, the solution presented by @81bronco, at least with ASP.NET MVC 4, includes *all* ViewData info as attributes in the . – Ted Jun 19 '13 at 19:27
  • 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) :) – Robert Ivanc May 22 '15 at 11:53
  • 2
    @RobertIvanc I have edited the answer and reverted the edit made by Raleigh Buckner that cause the problems you and Ted reported. Thanks. – Daniel Liuzzi May 22 '15 at 12:22
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>
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • thanks for the info (and a great example) of the IMetadataAware interface! – seekay Apr 28 '11 at 21:48
  • 4
    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? – smnbss Jun 16 '11 at 15:56
  • 2
    @smnbss You are correct. See my answer to see how to make `Prompt` work. – Daniel Liuzzi Jul 05 '11 at 06:04
  • 6
    wow that much work for doing a placeholder? has to be something simpler :S – krilovich Oct 08 '13 at 15:00
  • There is, look at some of the answers bellow. The Pax has a good one. – Termato Feb 05 '15 at 20:51
  • 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? – amp Dec 08 '15 at 10:49
  • omg is this serious? This is so much more code than just writing `` kind of annoying – Kellen Stuart Oct 17 '18 at 16:54
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) })
The Pax Bisonica
  • 2,154
  • 2
  • 26
  • 45
  • 1
    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. – Mike Eshva Oct 23 '14 at 22:09
  • 1
    thanks, that's what i was looking for, simple and to the point when we can get display name then why add more classes – saqibahmad Jan 08 '15 at 15:56
  • This will double-escape the text provided by DisplayName - not a good solution for example languages with accents like french. – marapet Apr 25 '17 at 14:06
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"
})
xicooc
  • 855
  • 7
  • 8
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.

Mike Eshva
  • 1,078
  • 15
  • 17
  • Just decompiled DisplayNameFor method and made an analog for the watermark. – Mike Eshva Oct 23 '14 at 22:10
  • Hi, can you please change your method MvcHtmlString WatermarkFor() to use DisplayName attribute value if Display -> Prompt value is not specified? – Sasa Tancev Mar 20 '15 at 11:59
  • Where do you save your WatermarkExtension class so that it can then be used as you described? Html.WatermarkFor(model => model.AddressSuffix) – Craig Gjerdingen Apr 17 '15 at 04:17
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));
    }
}
Vladimir
  • 1,425
  • 16
  • 31
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)
Sel
  • 1,934
  • 1
  • 22
  • 13