Custom Collections

Durante los meses en que he estado con GMaps 3.0, he trabajado en mejorar el código de versiones previas. Siempre se puede mejorar en eficiencia, limpieza y mantenibilidad.

Durante este artículo os voy a hablar de un Helper que hice con el que se mejorar mucho la mantenibilidad y la limpieza de código.

Con las custom collections vamos a poder crear colecciones de "items" o de "nombre-valor" que se convertirán en un string según nuestras necesidades.

La necesidad viene de la propia naturaleza del control de googlemaps para ASP.NET, que en su esencia es un "conversor" ASP.NET --> Javascript/API de Google.

En muchas ocasiones deberemos devolver colecciones de nombre-valor, donde cada una de ellas es opcional. Por ejemplo, las opciones que se le pasan al contructor de un GMarker pueden ser...

{clickable:false,title:'Titulo del GMarker',draggable:true,dragCrossMove:false,bouncy:false,bounceGravity:1}

... o pueden no ser nada.

En este caso se trata de una variable JSON, que responde a la definición de "Colección de nombre-valor".

Algo también muy común en el javascript de la API de Google Maps que también es una colección de nombre-valor, es una cadena QueryString:

?nombre1=valor1&nombre2=valor2&nombre3=valor3

Por otra parte, tenemos una coleccion de items (o de sólo valor, sin nombre) como por ejemplo los parámetros de un método javascript:

(item1, item2, item3)

Visto así, no se aporta nada que no pueda solucionarse con un StringBuilder y varios "if", pero creedme que las CustomCollections son brutalmente útiles.

Por ejemplo, para crear una variable JSON, sabiendo que todos sus parámetros son opciones, nos vale con el siguiente código:

JSONCollection jsonCollection = new JSONCollection();
jsonCollection.Add("clickable", clickable, !clickable, typeof(bool));
jsonCollection.Add("title", title, !string.IsNullOrEmpty(title), typeof(string));
jsonCollection.Add("draggable", draggable, draggable, typeof(bool));
jsonCollection.Add("dragCrossMove", dragCrossMove, draggable, typeof(bool));
jsonCollection.Add("bouncy", bouncy, draggable, typeof(bool));
jsonCollection.Add("bounceGravity", bounceGravity, draggable);
string jsonString = jsonCollection.ToString();

El primer parámetro se corresponde al "nombre", el segundo al "valor", el tercero es un booleano que indica si se va a insertar o no en la colección, y el cuarto es el tipo de dato javascript (que no tiene por qué coincidir con el tipo de dato de ASP.NET).

Hacer lo mismo con el método tradicional puede ser un infierno de "if-else", StringBuilder y tratamientos diferenciados según si hay o no items que añadir, o si diferenciar si son de tipo booleano, string o entero.


Tanto de lo mismo tenmos con la cadena QueryString. Un ejemplo de uso real sería la llamada al servicio Web REST de GeoCoding de Google Maps:

QueryStringParameterCollection queryStringParameterCollection = new QueryStringParameterCollection();
queryStringParameterCollection.Add("q", query);
queryStringParameterCollection.Add("output", output.ToString());
queryStringParameterCollection.Add("key", Key);
queryStringParameterCollection.Add("oe", "ISO-8859-1");
...
string queryString = queryStringParameterCollection.ToString();


Entrando en una faceta más técnica, tanto el JSONCollection, como el QueryStringParameterCollection, como el MethodAttributesCollection, heredan de la clase AdvancedCollection.

El AdvancedCollection tiene como propiedades internas un NameValueCollection (para las colecciones nombre-valor) y un StringCollection (para las colecciones de items) a las que se les añadirán elementos con los métodos Add(nombre, valor) y/o Add(item).

Las colecciones se transformarán en strings, para lo cual deberemos definir algunos parámetros (pongo entre paréntesis el valor que asignan el JSONCollection, el QueryStringCollection y el MethodAttributesCollection:
  • StartOfCollection: inicio de la colección. JSONCollection= {  QueryStringCollection= ?  MethodAttributesCollection= ( 
  • StartOfNameValue: inicio del item o del par nombre-valor. JSONCollection= vacío  QueryStringCollection= vacío  MethodAttributesCollection= vacío 
  • NameValuesSeparator: separador de los diferentes nombre-valor y los items. Se distingue de StartOfNameValue y EndOfNameValue, porque el NameValuesSeparator no se inserta tras el último nombre-valor o item. JSONCollection= ,  QueryStringCollection= &  MethodAttributesCollection= , 
  • NameValueSeparator: separador entre el nombre y el valor. Cuando sólo tenemos items no tiene efecto. JSONCollection= QueryStringCollection= =  MethodAttributesCollection= Tiene items 
  • EndOfNameValue: fin del item o del par nombre-valor. JSONCollection= vacío  QueryStringCollection= vacío  MethodAttributesCollection= vacío 
  • EndOfCollection: fin de la colección. JSONCollection= }  QueryStringCollection= vacío  MethodAttributesCollection= )

Así pues, los tres tipos de colecciones son sólo ejemplos ampliables tanto como necesitemos.





Versión 3.0 del control de Google Maps para ASP.NET

Han sido más de dos meses y medio de trabajo en mi tiempo libre, pero por fin ha sido publicada la versión 3.0 del control de Google Maps para ASP.NET.

Esto creo que ya lo he dicho antes, pero puede que se trate del mayor cambio realizado desde que comencé con el control hace ya más de dos años.

Para los que no lo conozcan el control de Google Maps para ASP.NET permite tener un mapa de Google en tu Web. Si quieres algo simple no tienes más que arrastrarlo y ya está. Si quieres algo potente y complejo, no tienes más que programarlo con unas pocas líneas de ASP.NET y ya está :D

Es un control completamente gratuito, aunque dispone de licencias (muy baratas) si lo vais a utilizar a nivel comercial.

La web del control (googlemaps.subgurim.net) trae una sección "Cómo..." a modo de tutorial, una galería de código con ejemplos más específicos, y una galería de iconos.

Para conocer los cambios, lo mejor es que le echéis un vistazo al changelog.



UpdatePanel.IsInPartialRendering, ¿funciona?

He estado trabajando con el IsInPartialRendering del UpdatePanel y me he dado cuenta que NO FUNCIONABA... ¿cómo es eso posible?

He estado buscando, y todo lo que he visto eran quejas por este (aparentemente) tremendo bug. De hecho he visto piezas de código similares a obras de arte capaces de encontrar con un montón de recursividades e historias si nuestro UpdatePanel estaba en un PartialRendering.

Esas piezas de código funcionan bien, pero el problema de base es el que solemos cometer muchos programadores. Cuando algo no funciona, lo asignamos enseguida a bugs de terceros, en los que nada podemos hacer, y nos quedamos tan panchos.

Pues NO, esa es una práctica que debemos desestimar. Ese tipo de bugs es demasiado evidente para perdurar tanto tiempo, con lo que inmediatamente debemos pensar que algo estamos haciendo mal.

Y así es, porque el IsInPartialRendering funciona perfectamente bien. El único problema es no saber utilizarlo. Porque sólo nos da la información buena a partir de las últimas fases del ciclo de vida. Específicamente funciona a partir del "Render", algo, por otra parte, bastante lógico.

Así que ya sabéis, si en algún momento necesitáis saber si cierto UpdatePanel está en PartialRendering, debéis manejar el evento OnRender.



Diccionario javascript que se puede leer en ASP.NET

Ahora mismo estoy al 100% en la nueva versión de mi control de Google Maps para ASP.NET (la 3.0). Estoy tardando más de lo que tenía pensado (prueba de ello es el "abandono" que tiene este blog), pero es que la cantidad de mejoras es enorme.

Una de las pequeñas cositas que voy a añadir es un diccionario para javascript que puede leerse en ASP.NET. Es una pequeña "clase" javascript que me ha quedado muy chula y aunque en el control de GoogleMaps no se hace trabajo directo con ella, pues es una librería auxiliar, la comparto con vosotros porque puede llegar a ser útil.

El objetivo es que desde javascript podamos crear un sencillo diccionario con su Clave-Valor y su Get-Set, y que al hacer un PostBack tengamos acceso a éste.

Lo único que vamos a necesitar es un input-type-hidden en nuestra página. Por ejemplo:

        <input type="hidden" id="subgurim_Store" name="subgurim_Store" />

Aprovecho para recordar que los input-type-hidden deben tener el atributo "name", si no, desde ASP.NET no se podrá leer con el Request.Form.

Antes de enseñaros la libreria, aquí hay un ejemplo de uso.

Javascript
        var myStore = new Store('subgurim_Store');
        myStore.Set('key1', 'value1');
        myStore.Set('key2', 'value2');
        myStore.Set('key3', 'value3');

        alert(myStore.Get('key2'));
       
        myStore.SaveHidden();

ASP.NET
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack)
                string subgurim = Request.Form.Get("subgurim_Store");
    }

De modo que el string subgurim tendrá el valor "key1=value1&key2=value2&key3=value3". A partir de ahí es muy sencillo obtener cada Key-Value. En mi control de GoogleMaps tengo una librería que lo hace sólo, pero eso queda para otro artículo :D

Así que os enseño la sencilla librería. La he testeado poquito, y seguro que es mejorable, pero por lo menos sabemos que por ahí van los tiros. Por cierto, hay algunas cosillas que pueden parecer innecesarias (por ejemplo que algunas funciones sean públicas cuando podrían ser privadas, pero es que está adaptado a mi control de GoogleMaps y ahí sí me hace falta.

    // le pasamos el identificador del input-type-hidden
    function Store(id)
    {
        if (!id) return;
       
        this.id = id;

        // En la variable "s" es donde está la magia. Aquí se guarda todo.
        this.s = {};  
            
        // Referenciamos el objeto hidden para poder usarlo en cualquier lado.
        // Faltaría comprobar que realmente existe, etc.
        this.hidden = document.getElementById(this.id);
       
        // Al inicializar la variable queremos rellenar this.s con lo que ya tiene el hidden
        // De esa forma, aunque hagamos PostBacks, nuestro this.s será persistente
        this.FillFromHidden();

        // Cuando rellenamos this.s queremos borrar el hidden, obligándonos a evitar
        // desincronizaciones innecesarias.
        this.hidden.value = '';
    }
   
    Store.prototype.Get = function(key) { return this.s[key]; } ;
   
    Store.prototype.Set = function (key, value) { this.s[key] = value; } ;
   
    // Recoge los valores de this.s y los guarda en el hidden.
    // Hay que llamar a este método siempre que terminemos de trabajar con el objeto Store.
    Store.prototype.SaveHidden = function ()
        {
            var hiddenValue = '';
            for (key in this.s)
            {               
                var value = this.s[key];              
            
                if (key && value)
                    hiddenValue += key + '=' + value + '&';
            }
           
            hiddenValue = hiddenValue.substring(0, hiddenValue.length - 1);
            this.hidden.value = hiddenValue;
        } ;
       
    // Recoge el valor del Hidden y lo mete en this.s
    Store.prototype.FillFromHidden = function ()
        {
            var hiddenValue = this.hidden.value;           
            var keyValueArray = hiddenValue.split('&');
           
            for (var i=0; i<keyValueArray.length; i++)
            {
                var keyValue = keyValueArray[i].split('=');
                var key = keyValue[0];
                var value = keyValue[1];
                this.s[key] = value;      
            }
        } ;



Hacer un RadioButton dentro de un Repeater

Estos últimos días me he estado peleando con una cosilla algo rara en Todoexpertos. Y es que a veces ASP.NET tiene algunas cosas que te dan ganas de tirarte de los pelos... o tirarle de los pelos a ScottGu (aunque no tenga muchos).

Mi objetivo era bien sencillo. En lugar de utilizar un control RadioButtonList, lo que yo quería era generar diferentes RadioButtons usando el control Repeater, de modo que cuando seleccionas en cualquier RadioButton, el que estaba seleccionado previamente (si lo había) se deseleccionase, y sólo quedara uno.

Esto es algo bastante típico y muy sencillo de llevar a cabo, sin más que ponerle a todos los RadioButton el mismo "GroupName", que se acaba convirtiendo en el atributo "name" en HTML.

Pero ahora viene el problema, y es que al estar dentro de un Repeater, ASP.NET va generando un prefijo único al name (al igual que hace con el ID), de modo que no es posible que todos tengan el mismo name, por mucho GroupName que especifiques.

Tan sencillo como lo quería, tan sencillo como ASP.NET hacía la puñeta.

Pero bueno, menos mal que javascript y yo somos bastante amigos y pronto Googleé una solución que no tuve más que modificar a mi gusto.

La función javascript que utilicé es el siguiente:

    function SetUniqueRadioButton(nameregex, rid)
    {
       re = new RegExp(nameregex);
       rb = document.getElementById(rid);
       var inputs = document.getElementsByTagName('input');
       for(i = 0; i < inputs.length; i++)
       {
          elm = inputs[i]
          if (elm.type == 'radio')
          {
             if (re.test(elm.name))
             {
                elm.checked = false;
             }
          }
       }
 
       rb.checked = true;
    }

Y al radiobutton hay que ponerle esto dentro de su atributo "onclick":

onclick="SetUniqueRadioButton('Repeater1.*', '" + RadioButton1.ClientID + "')"

Como vemos, por una parte le estamos pasando una expresión regular, y por otra el Identificador que Javascript sabrá leer.

Lo que hace la función javascript es leer todos los elementos "input" de la página, se asegura de que sean de type "radio" y, de ser así, que cumplan la expresión regular. Siempre se le pone el checked=false, para, posteriormente, poner el checked=true a nuestro RadioButton.

Ya sé la pregunta... ¿qué narices representa la expresión regular? Lo único que representa es el prefijo que el simpático de ASP.NET le va a poner a todo elemento que esté dentro del Repeater1. No tenéis más que poner el identificador del Repeater y ya está

Espero que os sea útil.



iWhere: aplicación para Facebook

Durante estas semanitas he estado trabajando en mis ratos libres en una aplicación muy chula para Facebook.

Bueno, lo de chula lo digo yo, jeje.

En realidad no se trata de una aplicación para Facebook sólo, sino que mi intención era usar "TodasLasCosasNuevas" que se me ocurrían, como todo tipo de mejoras de ASP.NET 3.5: expresiones lambda, el uso de "var", inicializadores, LINQ para SQL, y demás cosillas que han dejado una respetable cosecha de potenciales artículos para este blog.

En cuanto a programar para Facebook, he usado el Facebook Developer Toolkit, en detrimento del Facebook.NET. Prácticamente ambos hacen lo mismo, y por lo único por lo que me he decidido por el primero es porque tenían más descargas, más visitas y más actualizaciones (aunque eso no significa apenas nada).

Lo único que tengo claro es que programar aplicaciones para Facebook usando el Toolkit es absolutamente sencillo. Apenas un par de cambios con respecto a programar en ASP.NET y ya está.

En cuanto a la aplicación en sí, se trata de dar a conocer a tus amigos de dónde eres, sin más que elegir un punto en el mapa. Cuando has indicado de dónde eres, puedes ver de dónde son tus amigos en un mapa... obviamente un mapa de Google, y así he aprovechado para utilizar mi propio control de GoogleMaps para ASP.NET porque desde ya mismo comienzo con una nueva versión (por cierto, todavía estoy dudando en si sacar una pequeña nueva versión 2.8 o hacer un gran cambio con la versión 3.0).

Por cierto... no he dicho la url de la aplicación, ¿no? Podéis accederla desde http://apps.facebook.com/iwheere/

Sí, tiene repetida la letra "e" porque Facebook requiere que la url de sus aplicaciones tenga más de 6 letras. Además es case sensitive... manda narices... en PHP tenía que estar hecho el Facebook ese, jaja.


Si queréis me podéis añadir como amigo (buscad a Subgurim)



Validar varios RadioButton

Seguro que hay alguna forma de hacerlo más fácil, pero yo no la he encontrado... así que me puse manos a la obra y lo he hecho yo mismo... he de confesar que me gusta demasiado hacer las cosas por mí mismo, aunque ya esté hecho, simplemente por el hecho de aprender, por tener un control 100% de lo que sucede... y por haberlo hecho yo, jeje

El caso es el siguiente: tenemos varios RadioButton diferentes, todos ellos pertenecientes al mismo GroupName, de modo que hacer click sobre uno de ellos implica que se desmarque el previamente elegido.

Por ejemplo eso lo tenemos en Todoexpertos.com, cuando un usuario finaliza y puntúa la pregunta al experto puede darle una puntuación de 0 a 5 y para eso usamos 6 RadioButton's, y no queremos hacerlo usando el RadioButtonList.

Hasta ahora marcábamos por defecto una puntuación de 3 sobre 5... pero decidimos obligar al usuario a elegir una puntuación. Para ello requeríamos verificar, al hacer click sobre un Button, que se hubiera elegido una de las opciones.

Lo primero que se viene a la cabeza es usar un RequiredFieldValidator, pero yo no encontré la forma de hacerlo para RadioButton's separados... así que me puse manos a la obra con un pequeño Javascript.

Os muestro el Javascript que lo utiliza, comentándolo sobre la marcha.

    <script type="text/javascript">
   
    // Valida que alguno de los elementos del mismo "groupname" haya sido elegido
    function validateRadioButtonList(groupname, message)
    {
        // Recogemos todos los elementos "input" de nuestra página
        var inputs = document.getElementsByTagName("input");
        var hasItems = false;
       
        // Recorremos cada uno de los elementos,
        for (var i = 0; i < inputs.length; i++)
        {
            // y seleccionamos si hay alguno de tipo "radio"
            if (inputs[i].type == 'radio')
            {
                // Verificamos que ese RadioButton pertenece al groupname especificado
                var name = inputs[i].name;
                var isFromGroup = (name.lastIndexOf(groupname) + groupname.length ) == name.length;
               
                // En de encontrar un RadioButton con el groupname...
                if (isFromGroup)
                {  
                    // Marcamos que lo hemos encontrado
                    hasItems = true;
                   
                    // Si hay alguno checkeado, devolver true
                    if (inputs[i].checked)
                    {
                        return true;
                    }
                }
            }
        }
       
       
        if (hasItems)
        {
            // Si llegamos aquí es que hemos encontrado
            // RadioButton's con el groupname, pero ninguno checkeado
            if (!message) message = 'Debes elegir una opción';
            alert(message);
            return false;
        }
        else
        {
            // Si llegamos aquí es que no hemos encontrado
            // ningún RadioButton con el groupname buscado
            return true;
        }
    }
   
    </script>


Eso en cuanto al javascript. Un ejemplo que lo use:

RadioButton.aspx

    <div>
        <div>
            <asp:RadioButton ID="RadioButton1" runat="server" GroupName="SubgurimTest" Text="RadioButton1" />
        </div>
        <div>
            <asp:RadioButton ID="RadioButton2" runat="server" GroupName="SubgurimTest" Text="RadioButton2" />
        </div>
        <div>
            <asp:RadioButton ID="RadioButton3" runat="server" GroupName="SubgurimTest" Text="RadioButton3" />
        </div>
        <div>
            <asp:Button ID="Button1" runat="server" Text="Button" OnClientClick="if (!validateRadioButtonList('SubgurimTest')) return false;"
                OnClick="Button1_Click" />
        </div>
        <div>
            <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
        </div>
    </div>

RadioButton.aspx.cs

        protected void Button1_Click(object sender, EventArgs e)
        {
            string message = string.Empty;

            if (RadioButton1.Checked)
                message = "RadioButton1";
            if (RadioButton2.Checked)
                message = "RadioButton2";
            if (RadioButton3.Checked)
                message = "RadioButton3";
           
            Label1.Text = message;
        }

Vemos que tenemos tres RadioButton's cuyo GroupName es "SubgurimTest". Posteriormente tenemos un Button que maneja un evento OnClick cualquiera, y un OnClientClick así:

        if (!validateRadioButtonList('SubgurimTest')) return false;


De forma que vemos claramente que si no se valida el Grupo de RadioButton's, no se debe seguir con el Post.

Espero que os sea de utilidad, incluso si sabéis una forma mucho más sencilla de hacerlo




Google vs el RewritePath de ASP.NET

ASP.NET no es perfecto. Y punto.

Uno de los peores errores que se pueden tener, son aquellos que no ves. Aquellos que no te enteras si quiera de que están sucediendo. Aquellos que cuando te enteres que están sucediendo, no entiendas qué está pasando ni por qué.

Esto es lo que sucede cuando GoogleBot (y muchos otros rastreadores como Yahoo Slurp) visitan algunas de tus páginas ASP.NET a las que has aplicado el RewritePath. Por ejemplo, si usas este famoso Url Rewriter es casi seguro que te está afectando el problema (yo siempre aconsejo el UrlRewritingNET).

Lo que sucede es que ASP.NET, según quién le esté visitando, devuelve una cosa y otra. En el caso de que le visiten algunos de los crawlers buscadores más importantes cuando usamos el RewritePath, ASP.NET puede devolver "paths" que apuntan "muy hacia abajo". Por ejemplo, puede darse el caso de que estemos en www.subgurim.net/default.aspx, y ASP.NET devuelva enlaces con "../../", con lo que salta un feo error.

Tras darle muchas vueltas y conseguir soluciones feas, y dado que los buscadores no tienen la culpa y ASP.NET no tiene pinta de querer solucionar el error, la mejor solución que he encontrado es usar el directorio App_Browsers, añadiéndole los archivos que tenéis más abajo de forma que "engañamos" a ASP.NET para que nos devuelva un código bueno.

ASí que la solución es fácil, añades esos archivos al directorio web.config y todo solucionado.

Descargar App_Browsers