Html5 Placeholder with .NET MVC 3 Razor EditorFor rozszerzenie?

92

Czy istnieje sposób na napisanie symbolu zastępczego Html5 przy użyciu @ Html.EditorFor, czy powinienem po prostu użyć rozszerzenia TextBoxFor, tj.

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

A może sensowne byłoby napisanie własnego rozszerzenia niestandardowego, które może używać atrybutu wyświetlania „Opis” za pośrednictwem adnotacji danych (podobnie do tego )?

Oczywiście to samo pytanie dotyczy również „autofokusa”.

seekay
źródło

Odpowiedzi:

68

Możesz zapoznać się z następującym artykułem, aby napisać niestandardowy DataAnnotationsModelMetadataProvider.

A oto inny, bardziej ASP.NET MVC 3ish sposób postępowania z wykorzystaniem nowo wprowadzonego IMetadataAware interfejs .

Zacznij od utworzenia niestandardowego atrybutu implementującego ten interfejs:

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

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

A następnie udekoruj nim swój model:

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

Następnie zdefiniuj kontroler:

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

Odpowiedni widok:

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

I na koniec szablon edytora ( ~/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
źródło
dzięki za informacje (i świetny przykład) o interfejsie IMetadataAware!
seekay
4
czy to nadal obowiązuje dla MVC3? Zauważyłem nowy [Display (Prompt = "wpisz znak wodny tutaj")] w MVC3, ale nie mogłem go uruchomić. dowolny pomysł?
smnbss
2
@smnbss Masz rację. Zobacz moją odpowiedź, aby zobaczyć, jak Promptpracować.
Daniel Liuzzi,
6
wow, tyle pracy na zrobienie symbolu zastępczego? musi być coś prostszego: S
krilovich
Jest, spójrz na niektóre z poniższych odpowiedzi. Pax ma dobry.
Termato
121

Jak komentuje smnbss w odpowiedzi Darina Dimitrova, Promptistnieje dokładnie w tym celu, więc nie ma potrzeby tworzenia niestandardowego atrybutu . Z dokumentacji:

Pobiera lub ustawia wartość, która będzie używana do ustawiania znaku wodnego dla monitów w interfejsie użytkownika.

Aby go użyć, po prostu udekoruj właściwość modelu widoku w następujący sposób:

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

Ten tekst jest następnie wygodnie umieszczony w ModelMetadata.Watermark. Po wyjęciu z pudełka domyślny szablon w MVC 3 ignoruje tę Watermarkwłaściwość, ale sprawienie, by działało, jest naprawdę proste. Wszystko, co musisz zrobić, to dostosować domyślny szablon ciągu, aby powiedzieć MVC, jak go renderować. Po prostu edytuj String.cshtml, tak jak robi to Darin, z tym wyjątkiem, że zamiast ModelMetadata.AdditionalValuespobierać znak wodny z , otrzymasz go bezpośrednio z ModelMetadata.Watermark:

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

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

I to wszystko.

Jak widać, kluczem do działania jest plik placeholder = ViewData.ModelMetadata.Watermark bit.

Jeśli chcesz również włączyć znak wodny dla wielowierszowych pól tekstowych (obszarów tekstowych), zrób to samo dla 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
źródło
6
@Brett Rzeczywiście jest. EditorFor () jest pomocnikiem opartym na szablonach wprowadzonym w MVC 2. Na pierwszy rzut oka może się wydawać, że robi to samo co TextBox (), ale daje dużą zaletę, ponieważ pozwala dokładnie kontrolować sposób generowania kodu HTML. Moja odpowiedź opiera się na tej funkcji, aby „nauczyć” MVC, co zrobić z Promptatrybutem. Więcej informacji na temat tych szablonów można znaleźć w tym świetnym poście autorstwa Brada Wilsona: bradwilson.typepad.com/blog/2009/10/…
Daniel Liuzzi
2
@DotNetWise Nie jestem pewien, dlaczego tak mówisz; wszystkie parametry łańcuchowe DisplayAttribute(w tym Prompt) są lokalizowalne. Trzeba tylko określić ResourceType w swojej adnotacji: [Display(ResourceType = typeof(PeopleResources), Prompt = "AgePrompt")]. I to wszystko. Tekst znaku wodnego pochodzi teraz z klucza AgeGroup w zasobie PeopleResources .
Daniel Liuzzi,
1
A co jeśli nie używasz zasobów .resx, ale systemu lokalizacji i18N .po?
Adaptabi
3
EditorTemplatesFolder @FrancisRodgers domyślnie nie istnieje; po prostu tworzysz w swoim Views\Sharedfolderze (lub Views\{ControllerName}jeśli chcesz, aby był on specyficzny dla określonego kontrolera). Następnie umieszczasz szablony .cshtml w tym folderze i powinieneś być gotowy.
Daniel Liuzzi,
2
@RobertIvanc Zredagowałem odpowiedź i cofnąłem edycję dokonaną przez Raleigh Buckner, która spowodowała problemy zgłoszone przez Ciebie i Teda. Dzięki.
Daniel Liuzzi
22

W rzeczywistości wolę używać nazwy wyświetlanej dla tekstu zastępczego przez większość czasu. Oto przykład użycia DisplayName:

  @Html.TextBoxFor(x => x.FirstName, true, null, new { @class = "form-control", placeholder = Html.DisplayNameFor(x => x.FirstName) })
Pax Bisonica
źródło
1
Istnieje specjalna adnotacja dotycząca danych Pytaj o znak wodny. DisplayName jest dla etykiety pola. To zły pomysł, aby je mieszać. Używaj właściwych rzeczy do właściwych zadań. Spójrz na moją odpowiedź.
Mike Eshva
1
dzięki, właśnie tego szukałem, proste i do tego stopnia, że ​​możemy uzyskać wyświetlaną nazwę, to po co dodawać więcej klas
saqibahmad
Spowoduje to podwójną zmianę znaczenia tekstu dostarczonego przez DisplayName - nie jest to dobre rozwiązanie, na przykład w językach z akcentami, takimi jak francuski.
marapet
3

Napisałem takie proste zajęcia:

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);
    }
}

Zastosowanie jako takie:

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

I właściwość w modelu widoku:

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

Uwaga parametr Monit. W tym przypadku używam ciągów z zasobów do lokalizacji, ale możesz użyć tylko ciągów, po prostu unikaj parametru ResourceType.

Mike Eshva
źródło
Po prostu zdekompilowałem metodę DisplayNameFor i utworzyłem analog dla znaku wodnego.
Mike Eshva
Cześć, czy możesz zmienić metodę MvcHtmlString WatermarkFor (), aby używała wartości atrybutu DisplayName, jeśli wartość Display -> Prompt nie jest określona?
Sasa Tancev
Gdzie zapisujesz klasę WatermarkExtension, aby można było z niej korzystać zgodnie z opisem? Html.WatermarkFor (model => model.AddressSuffix)
Craig Gjerdingen Kwietnia
3

Używam tego w przypadku pliku zasobów (nie potrzebuję już monitu!)

@Html.TextBoxFor(m => m.Name, new 
{
     @class = "form-control",
     placeholder = @Html.DisplayName(@Resource.PleaseTypeName),
     autofocus = "autofocus",
     required = "required"
})
xicooc
źródło
1

Oto rozwiązanie, które stworzyłem, korzystając z powyższych pomysłów, które można wykorzystać dla TextBoxFor i 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
źródło
0

Myślę, że utworzenie niestandardowego szablonu EditorTemplate nie jest dobrym rozwiązaniem, ponieważ musisz zadbać o wiele możliwych tepmlatów dla różnych przypadków: ciągi, liczby, kombinacje i tak dalej. Innym rozwiązaniem jest niestandardowe rozszerzenie do HtmlHelper.

Model:

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

Rozszerzenie pomocnika HTML:

   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);

}

Odpowiedni widok:

@Html.BsEditorFor(x => x.Title)
Sel
źródło