Marcadores de posición Html5 con .NET MVC 3 Razor Editor

92

¿Hay alguna manera de escribir el marcador de posición Html5 usando @ Html.EditorFor, o debería simplemente usar la extensión TextBoxFor?

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

¿O tendría sentido escribir nuestra propia extensión personalizada que tal vez pueda usar el atributo de visualización 'Descripción' a través de DataAnnotations (similar a esto )?

Por supuesto, la misma pregunta se aplica también al "enfoque automático".

buscar
fuente

Respuestas:

68

Puede echar un vistazo al siguiente artículo para escribir un archivo DataAnnotationsModelMetadataProvider.

Y aquí hay otra forma más ASP.NET MVC 3 de proceder con la interfaz IMetadataAware recién introducida .

Comience creando un atributo personalizado que implemente esta interfaz:

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

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

Y luego decora tu modelo con él:

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

A continuación, defina un controlador:

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

Una vista correspondiente:

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

Y finalmente la plantilla del editor ( ~/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
fuente
¡Gracias por la información (y un gran ejemplo) de la interfaz IMetadataAware!
seekay
4
¿Sigue siendo válido para MVC3? Noté un nuevo [Display (Prompt = "type watermark here")] en MVC3 pero no pude hacerlo funcionar. ¿alguna idea?
smnbss
2
@smnbss Tienes razón. Vea mi respuesta para ver cómo hacer que Promptfuncione.
Daniel Liuzzi
6
¿Qué tanto trabajo para hacer un marcador de posición? tiene que ser algo más simple: S
krilovich
Hay, mira algunas de las respuestas a continuación. El Pax tiene uno bueno.
Termato
121

Como smnbss comenta en la respuesta de Darin Dimitrov, Promptexiste exactamente para este propósito, por lo que no es necesario crear un atributo personalizado . De la documentación:

Obtiene o establece un valor que se usará para establecer la marca de agua para las solicitudes en la interfaz de usuario.

Para usarlo, simplemente decore la propiedad de su modelo de vista así:

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

Luego, este texto se coloca convenientemente en ModelMetadata.Watermark. Fuera de la caja, la plantilla predeterminada en MVC 3 ignora elWatermark propiedad, pero hacer que funcione es realmente simple. Todo lo que necesita hacer es ajustar la plantilla de cadena predeterminada para decirle a MVC cómo renderizarla. Simplemente edite String.cshtml, como lo hace Darin, excepto que en lugar de obtener la marca de agua ModelMetadata.AdditionalValues, la obtiene directamente de ModelMetadata.Watermark:

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

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

Y eso es todo.

Como puede ver, la clave para que todo funcione es la placeholder = ViewData.ModelMetadata.Watermark bit.

Si también desea habilitar la marca de agua para cuadros de texto de varias líneas (áreas de texto), haga lo mismo para MultilineText.cshtml:

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

@Html.TextArea("", ViewData.TemplateInfo.FormattedModelValue.ToString(), 0, 0, new { @class = "text-box multi-line", placeholder = ViewData.ModelMetadata.Watermark })
Daniel Liuzzi
fuente
6
@Brett La hay, de hecho. EditorFor () es un ayudante con plantilla introducido en MVC 2. A primera vista, puede parecer que hace lo mismo que TextBox (), pero le brinda la gran ventaja de permitirle controlar exactamente cómo desea que se genere su HTML. Mi respuesta se basa en esta función para "enseñar" a MVC qué hacer con el Promptatributo. Para obtener más información sobre estas plantillas, puede consultar esta excelente publicación de Brad Wilson: bradwilson.typepad.com/blog/2009/10/…
Daniel Liuzzi
2
@DotNetWise No estoy seguro de por qué dice eso; todos los parámetros de cadena de DisplayAttribute(incluido Prompt) son localizables. Sólo tiene que especificar el TipoRecurso en su anotación: [Display(ResourceType = typeof(PeopleResources), Prompt = "AgePrompt")]. Y eso es. El texto de la marca de agua ahora proviene de AgeGroup clave en el recurso PeopleResources .
Daniel Liuzzi
1
¿Qué sucede si no está utilizando recursos .resx sino el sistema de localización .po i18N?
Adaptabi
3
La EditorTemplatescarpeta @FrancisRodgers no está allí de forma predeterminada; simplemente crea en su Views\Sharedcarpeta (o Views\{ControllerName}si desea que sea específico para un controlador determinado). Luego coloque sus plantillas .cshtml dentro de esta carpeta y debería estar listo para comenzar.
Daniel Liuzzi
2
@RobertIvanc He editado la respuesta y revertí la edición realizada por Raleigh Buckner que causó los problemas que tú y Ted informaron. Gracias.
Daniel Liuzzi
22

De hecho, prefiero usar el nombre para mostrar para el texto del marcador de posición la mayor parte del tiempo. A continuación, se muestra un ejemplo del uso de DisplayName:

  @Html.TextBoxFor(x => x.FirstName, true, null, new { @class = "form-control", placeholder = Html.DisplayNameFor(x => x.FirstName) })
La Pax Bisonica
fuente
1
Hay un aviso de anotación de datos especial para una marca de agua. Y DisplayName es para etiqueta de campo. Es una mala idea mezclarlos. Utilice las cosas adecuadas para las tareas adecuadas. Mira mi respuesta.
Mike Eshva
1
gracias, eso es lo que estaba buscando, simple y hasta el punto en que podemos obtener un nombre para mostrar, entonces ¿por qué agregar más clases?
saqibahmad
Esto hará doble escape al texto proporcionado por DisplayName; no es una buena solución, por ejemplo, idiomas con acentos como el francés.
marapet
3

Escribí una clase tan simple:

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

El uso como tal:

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

Y propiedad en un modelo de vista:

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

Observe el parámetro Prompt. En este caso, uso cadenas de recursos para la localización, pero puede usar solo cadenas, simplemente evite el parámetro ResourceType.

Mike Eshva
fuente
Acabo de descompilar el método DisplayNameFor y crear un análogo para la marca de agua.
Mike Eshva
Hola, ¿puedes cambiar tu método MvcHtmlString WatermarkFor () para usar el valor del atributo DisplayName si no se especifica Display -> Prompt value?
Sasa Tancev
¿Dónde guarda su clase WatermarkExtension para que luego pueda usarse como lo describió? Html.WatermarkFor (model => model.AddressSuffix)
Craig Gjerdingen
3

Yo uso de esta manera con el archivo de recursos (¡ya no necesito Prompt!)

@Html.TextBoxFor(m => m.Name, new 
{
     @class = "form-control",
     placeholder = @Html.DisplayName(@Resource.PleaseTypeName),
     autofocus = "autofocus",
     required = "required"
})
xicooc
fuente
1

Aquí hay una solución que hice usando las ideas anteriores que se pueden usar para TextBoxFor y 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
fuente
0

Creo que crear una plantilla de editor personalizada no es una buena solución, ya que debe preocuparse por muchas plantillas posibles para diferentes casos: cadenas, números, cuadros combinados, etc. Otra solución es la extensión personalizada a HtmlHelper.

Modelo:

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

Extensión de ayuda 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);

}

Una vista correspondiente:

@Html.BsEditorFor(x => x.Title)
Sel
fuente