Establecer el foco automáticamente, versión declarativa

ASP.NET MVCHace poco escribía un post en el que mostraba cómo se podía conseguir establecer el foco inicialmente en un control de edición, algo que era posible con Webforms pero no directamente con las herramientas que ASP.NET MVC trae de fábrica.

La solución propuesta consistía en introducir en la vista código de script para desplazar el foco hasta el control indicado mediante una llamada al helper Html.SetFocusTo(), que implementábamos en el mismo post, aunque hay otras formas para conseguirlo.

Unos días después, un amigo de Variable not found preguntaba en un comentario si no había una forma de conseguir lo mismo utilizando una sintaxis más declarativa, al estilo de la especificación de metadatos o validadores basados en atributos o data annotations.

En primer lugar he de decir que no creo que sea muy conveniente hacerlo, puesto en casi todos los escenarios posicionar el foco en un elemento es pura presentación, no suele haber decisiones de negocio tras ello, y por tanto no es algo que debamos sacar de la Vista. Pero bueno, independientemente de eso, vamos a ver cómo podríamos conseguirlo.

Una forma relativamente sencilla de hacerlo sería utilizando atributos de metadatos personalizados de forma muy similar al ejemplo que vimos también en un post anterior.

Lo primero que necesitamos es definir un atributo que implemente IMetadataAware, lo que indica que incluye información sobre metadatos, y aprovechar el método OnMetadataCreated() para introducir en la colección de metadatos adicionales una señal que permita más adelante detectar la propiedad a “enfocar”:

public class FocusedAttribute : Attribute, IMetadataAware
{
    public const string MetadataKey = "Focused";
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues[MetadataKey] = true;
    }
}

De esta forma, para activar el foco inicialmente sobre el editor de una propiedad concreta, simplemente deberíamos marcarla con el atributo [Focused], como se muestra en el código siguiente:

public class Friend
{
    [Focused]
    public string Name { get; set; }
 
    public string Email { get; set; }
    public string Phone { get; set; }
    public string Fax { get; set; }
 
}

Una vez finalizados los preparativos, tenemos que ver cómo implementarlo en la vista. Lo más sencillo en este caso sería crear el helper Html.Autofocus(), que será encargado de localizar la propiedad con el atributo [Focused] y generar sobre la vista el script que mueva el foco a la misma:

public static class HtmlExtensions
{
    public static MvcHtmlString Autofocus(this HtmlHelper html)
    {
        var template = html.ViewData.TemplateInfo;
        var focusedProperty = 
             (from property in html.ViewData.ModelMetadata.Properties
              where property.AdditionalValues.Any(
                     a => a.Key == FocusedAttribute.MetadataKey && (bool) a.Value)
              select template.GetFullHtmlFieldId(property.PropertyName)
             ).FirstOrDefault();
        if (focusedProperty != null)
        {
            return createAutofocusScript(focusedProperty);
        }
 
        return null;
    }
 
    private static MvcHtmlString createAutofocusScript(string focusedProperty)
    {
        string script = "<script type='text/javascript'>" +
                        "$(function() { $('#" + focusedProperty + "').focus(); });" +
                        "</script>";
        return MvcHtmlString.Create(script);
    }
}

Por último, basta con introducir en la vista la siguiente llamada. O mejor aún, si queremos que esta funcionalidad esté activa en todas las vistas, podemos añadirla al final del Layout que estemos empleando para ellas:

@Html.Autofocus()

Hay que tener en cuenta que nada impide que el atributo Focused sea empleado en más de una propiedad, en cuyo caso el foco se establecerá en la primera ocurrencia localizada. Esto se podría evitar llevando el atributo a nivel de clase e indicando como parámetro el valor de la propiedad (algo así como [DefaultProperty("Name")] sobre la propia entidad). En el proyecto de demostración podréis encontrar también esta implementación.

Descargar proyecto de demostración (VS2010+MVC 3) desde Skydrive.

Publicado en Variable not found.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *