Knockout: bindear booleano a radio button

Knockout JS es un framework realmente útil para hacer páginas web dinámicas. Últimamente he tenido la oportunidad de utilizarlo bastante y, siendo un total inexperto, puedo decir que facilita mucho las cosas y tiene una excelente documentación.

Sin embargo, recientemente me encontré en una situación en la que tuve que invertir bastante tiempo para conseguir hacer funcionar un binding entre un par de radio buttons, una propiedad JavaScript del viewModel de Knockout y la correspondiente propiedad en el modelo MVC que recibía mi acción del controlador.

El error

El error se manifestaba simplemente no funcionando el binding entre mi propiedad JS del viewModel y los correspondiente radio buttons. En un primer momento el binding parecía ir bien, pero al enviar la página de vuelta al servidor y retornar al mismo punto por existir algún error en la validación del modelo MVC, el binding no saltaba y los radio buttons no se marcaban según los valores que el usuario hubiera elegido.

La razón

Los radio buttons definen sus valores como strings mientras que la propiedad del modelo MVC era booleano. Entre medias Knockout intentaba “lidiar” entre ambas, pero al recibir el modelo MVC de vuelta tras la validación, su valor booleano no bindeaba correctamente con los radio buttons por ser sus valores cadenas en lugar de booleanos también.

La solución

Probablemente haya múltiples, pero en mi caso la más sencilla fue utilizar un binding custom entre los radio buttons con valores string y la propiedad JS del viewModel.

https://gist.github.com/javierholguera/7bad4fa6e6a4e1aa2ffb

Utilizando un interceptor entre los valores que llegan de los radio buttons y la propiedad, podemos convertir en ambos sentidos entre booleanos y strings convenientemente.

El custom binding se aplica sobre el binding “checked” habitual de los radio buttons, de forma que podemos reaprovechar todo el mecanismo ya existente.

Referencias

StackOverflow – Knockoutjs (version 2.1.0): bind boolean value to select box

Silverlight–FallbackValue y TargetNullValue

En estos días me encuentro inmerso en un proyecto con Silverlight. Uno de los últimos problemas que me he encontrado involucraba el siguiente escenario:

  1. Control cuya visibilidad está enlazado a una propiedad de un objeto (en este caso, un campo del Content de un NavigationFrame)
  2. Este frame, en un primer momento, tiene null en esta propiedad Content, hasta que navega.

El problema es que esta navegación se producía un instante después de que el control se creara, por lo que durante ese instante, el binding fallaba y la propiedad de visibilidad se establecía a Visible, que supongo que es el valor por defecto. Después de darle muchas vueltas encontré estas dos propiedades interesantes para este tipo de escenarios.

FallbackValue

Nos sirve para establecer un valor para el binding, cuando el binding no es capaz de resolverse y obtener un valor. Por ejemplo, en mi escenario, hasta que el NavigationFrame no navegaba por primera vez a alguna pantalla, su Content era null. Al estar mi binding enlazado a una de las propiedades del objeto que esperaba encontrar en Content, y ser éste un null, internamente el Binding estaba fallando con NullReferenceException.

Para esta situación de fallo, FallbackValue es perfecta. Básicamente estás diciéndole al binding: “si no eres capaz de calcular un valor, ponme éste directamente”.

TargetNullValue

Esta otra propiedad nos ayuda en otro escenario típico: el valor al que estamos enlazando, es null. Si por ejemplo la visibilidad de nuestro control dependiera de una propiedad string con valores “VISIBLE” y “NOVISIBLE”, podríamos crear un sencillo conversor que se encargara de devolver Visibility.Visible para el primer valor, y Visibility.Collapsed para el segundo.

¿Qué pasaría si la propiedad string contuviera un null? En tal caso, podríamos o bien modificar nuestro conversor para devolver Visibility.Collapsed también con un valor null. Pero, ¿y si no hemos creado ningún conversor? ¿No es un poco tedioso tener que crear uno sólo para gestionar los valores null?

Para eso precisamente existe TargetNullValue, que nos permite indicarle al Binding qué valor por defecto queremos que se asigne a la propiedad bindeada, en caso de que el origen del binding (source) tenga un valor null.

 

Espero que le resulte útil a alguien. A mí ya me hizo perder una hora y pico…

 

Bibliografía

MSDN: BindingBase.FallbackValue

MSDN: BindingBase.TargetNullValue

Silverlight: Binding de textboxs a propiedades nullables

No descubro nada si digo que Silverlight tiene un magnífico sistema de binding, con el que nos podemos ahorrar muchísimo “code behind”. Sin embargo, me he encontrado un extraño comportamiento cuando se combina con propiedades nulables, como serían int?. Mi escenario era el siguiente:

  • Un textbox bindeado a una propiedad int? en una entidad que era el data context de mi control
  • Un botón que, al pulsarlo, lanzaba de forma manual el checkeo de los bindings de todos los textboxs, incluido el anterior (método UpdateSource del binding)
  • Todos los métodos set de las propiedades son correctamente invocados, menos el de mi propiedad nulable.

¿Por qué? Además, como corolario, todos aquellos que no estaban correctamente formateados, se marcaban con el correspondiente borde rojo, incluido el que aparentemente no había llegado a checkearse. Y por si esto no fuera suficiente misterio, el mensaje que acompañaba al borde rojo, aparentemente había salido de ninguna parte; yo no lo había definido.

 

MISTERIO DESENTREAÑADO

Después de buscar bastante por Internet, llegué a esta página en la que explicaban el porqué de la situación. Al parecer, los bindings de Silverlight no se actualizan cuando no son capaces de deducir si el valor del control, es “casteable” a la propiedad que se está intentando asignar.

En mi caso, el textbox, al estar vacío, provocaba que el binding no supiera convertir un string.Empty a un int?, algo lógico por otra parte. De ahí que el método set de la propiedad nunca llegara a llamarse. La última pieza del misterio, el mensaje salido de la nada, era responsabilidad del propio binding, avisando de que no es capaz de hacer la conversión.

 

La solución

Ahora que ya sabemos el porqué, lo mejor es saber cómo resolverlo. Es fácil, necesitamos que “algo” convierta los valores que el binding no va a ser capaz de resolver, a valores que sí sean asignables a nuestras propiedades nulables. Por ejemplo, que cada string.Empty o puñado de caracteres vacíos, se conviertan en un null, algo con lo que el binding sí va a poder manejarse y setear en nuestra propiedades. En definitiva, necesitamos un Converter. Un ejemplo de uno totalmente funcional lo tenéis a continuación:

/// <summary>
/// Class that converts a value from an UI control to a nullable property value.
/// </summary>
/// <remarks>This class intents to resolve a "gap" in the Silverlight binding model.
/// Currently, the binding model is not able to invoke the setter of a nullable property, if
/// the value inside the control is empty.
/// Silverlight binding mechanism always avoids to invoke a setter if it is not sure how to
/// represent the type of the property to which is binded. For example, in a textbox with an empty
/// value for Text property, it will not set this value to a int? property in the underlying model.
/// </remarks>
public class NullableValueConverter : IValueConverter
{
    #region IValueConverter Members

    /// <summary>
    /// Converts a value from the underlying entity into its UI control representation.
    /// </summary>
    /// <param name="value">Value to convert.</param>
    /// <param name="targetType">Target type.</param>
    /// <param name="parameter">Conversion parameter.</param>
    /// <param name="culture">Culture to use in the conversion.</param>
    /// <returns>Object converted.</returns>
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }

    /// <summary>
    /// Converts a value from a UI control to its representation in the underlying entity.
    /// </summary>
    /// <param name="value">Value to convert.</param>
    /// <param name="targetType">Target type.</param>
    /// <param name="parameter">Conversion parameter.</param>
    /// <param name="culture">Culture to use in the conversion.</param>
    /// <returns>Object converted.</returns>
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string castedValue = (string)value;
        if (string.IsNullOrEmpty(castedValue) == true || castedValue.Trim().Length == 0)
        {
            return null;
        }

        return value;
    }

    #endregion
}

 

Y ya lo único que nos faltaría sería utilizarlo en nuestro código XAML, en dos pasos. El primero, definirlo como recurso dentro del XAML en que vamos a usarlo:

<UserControl.Resources>        
        <controls:NullableValueConverter x:Key="NullableValueConverter"/>
    </UserControl.Resources>

 

Y el segundo, asociándolo al binding que hemos establecido entre el control y la propiedad nulable.

Text="{Binding MyNullableProperty, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, Converter={StaticResource NullableValueConverter}}"

Y esto es todo. Espero que os sea de utilidad.