miércoles, 29 de junio de 2011

Selectores personalizados en jQuery

Como te decía en un anterior post, Cómo NO complicarnos la vida con jQuery, quiero que mi código jQuery sea más fácil de leer, menos extenso, más manejable y sobre todo más independiente del DOM.

Algo que tarde o temprano echas de menos es algún selector para algo que, o bien jQuery no soporta, o bien es muy complicado y tampoco sabes cuál es la expresión correcta para seleccionar tus elementos.

En estas situaciones, la solución pasa por crear un selector personalizado. Por ejemplo, imagina que quiero seleccionar las celdas de una tabla que sean del color verde y además pertenezcan a filas impares. Para hacerlo a través de jQuery, la expresión sería la siguiente:

    <table id="Table1">

        <tr>

            <td style="background-color: #008000;">Verde</td>

            <td>Sin color</td>

            <td>Sin color</td>

        </tr>

        <tr>

            <td>Sin color</td>

            <td style="background-color: #008000;">Verde</td>

            <td style="background-color: #008000;">Verde</td>

        </tr>

        <tr>

            <td style="background-color: #008000;">Verde</td>

            <td style="background-color: #008000;">Verde</td>

            <td style="background-color: #008000;">Verde</td>

        </tr>

        <tr>

            <td style="background-color: #008000;">Verde</td>

            <td>Sin color</td>

            <td>Sin color</td>

        </tr>

        <tr>

            <td>Sin color</td>

            <td>Sin color</td>

            <td>Sin color</td>

        </tr>

    </table>

    <script type="text/javascript">

        $().ready(function () {

alert($("#Table1 tr:odd td[style*='rgb(0, 128, 0)']").size());

        });       

    </script>

 

Vamos a desgranar el selector #Table1 tr:odd td[style*='rgb(0, 128, 0)']

#Table1 está claro, es una selección por el atributo id

tr:odd son elementos tr (filas) que sean impares dentro de #Table1

td son elementos td (celdas) dentro de cada fila impar

[style*=’rgb(0, 128, 0)] son elementos td que tengan una atributo style que contenga la cadena especificada. Aquí, por ejemplo, IE ha traducido #008000 a rgb(0, 128, 0) con lo que hay que buscar por esta última cadena. Sin embargo FF sí respeta #008000, así que el selector en FF tiene que ser #Table1 tr:odd td[style*='#008000']. Sinceramente, me empieza a parecer un selector un poco lioso (más si cabe con la dicotomía de IE y FF).

La solución pasa por crear nuestro propio selector y encapsular esta complejidad en un selector sencillo. Para crear un selector personalizado hay que “extender” el objeto jQuery. La sintaxis es muy sencilla y el propio código es auto-explicativo.

        $.extend(

            $.expr[":"],

            {

              celdasVerdesEnFilasImpares: function (obj, index, meta, stack) {

                    /*

                    obj - Elemento DOM de la iteración actual

                    index - Índice de obj dentro de stack

                    stack - Array de todos los elementos DOM que se evaluarán

                    */

                    var el = $(obj);

                    if (!el.is("td")) {

                        return false; //el elemento no es una celda

                    }

                    if (el.parent().get(0).rowIndex % 2 == 0) {

                        return false; //la fila es par

                    }

                    var bgColor = el.css("background-color");

                    //Con jQuery, el color es rgb para IE y FF
                   
if (bgColor != "rgb(0, 128, 0)") {

                        return false; //el color de la celda no es verde

                    }

                    return true; //el elemento cumple nuestro selector

                }

            }

        );


A partir de aquí, su utilización es muy sencilla y hemos logrado encapsular toda la complejidad del caso anterior es un selector sencillo.

alert($("#Table1 td:celdasVerdesEnFilasImpares").size());


Para finalizar con el tema de los selectores personalizados, también es importante conocer que estos pueden recibir parámetros. Es decir, si quiero celdasRojasEnFilasImpares ¿Tengo que volver a hacerme el mismo selector para sólo cambiar una línea? Pues no, estaba claro, no se a cuento de que tanto misterio ;-)

En este caso lo que nos interesa es el argumento meta. Por ejemplo, vamos a cambiar un poco nuestro selector que ahora se llamará celdasEnFilasImpares. Ahora podremos llamar a nuestro selector de la siguiente forma:

alert($("#Table1 td:celdasEnFilasImpares(rgb(0, 128, 0))").size());


El parámetro meta es un Array con los siguientes elementos:

clip_image001

Si nos fijamos, el índice 3 es justamente el parámetro del selector, así que el único cambio (además del nombre del selector) que hay que hacer es el siguiente:

if (bgColor != meta[3]) {

return false; //el color de fondo no es el color especificado

}


Un saludo!.

Cómo NO complicarnos la vida con jQuery

Estoy liado con un rediseño de la capa de presentación de una aplicación web y está claro que voy a utilizar “intensamente” jQuery.

jQuery es maravilloso, eso no lo voy a descubrir yo, pero me pasa que cuando lo utilizo tiendo a acumular cantidades ingentes de código javascript para realizar todo tipo de tareas y al final, mi inocente código se convierte en todo un mini-programa dentro de mi aplicación… o peor, dentro de una sola página.

¡Basta! ¡Hay que poner puertas al campo! Esa es mi intención de hoy. Ver cómo puedo contener la cantidad de código y además escribir un código más eficiente y sobre todo, independiente en la medida lo posible de la estructura DOM de mi página (que ya estoy harto de que cualquier cambio en el diseño de la página, lleve al traste con la mitad de mi código jQuery).

Mi primera recomendación (o bien auto-imposición) va a ser lograr la independencia del diseño y de la programación. Es decir, que cambios en la estructura del DOM no supongan, o al menos no en exceso, cambios en mi código. Para ello, primero veremos cómo hacer “mal” algo y después cómo hacerlo bien.

Haciéndolo mal…

    <div id="padre">

        <div>

            Red</div>

        <div>

            Yellow</div>

        <div>

            Olive</div>

        <div>

            Blue</div>

    </div>

    <script type="text/javascript">

        $().ready(function () {

            $("#padre div").click(function (e) {

                $(this).parent().css("background-color", $(this).html());

            });

        });       

    </script>

 

Este super-programa lo que hace es que cuando se pulsa en un div hijo, establece el color del padre al color que dice el hijo. Lo cierto es que funciona, pero vamos a criticarlo y veremos como no ha resultado el mejor de mis programas ;-)

Para seleccionar los hijos utilizo la expresión $(“#padre div”). Pues bien, como alguien (léase yo mismo o el diseñador de turno) quiera introducir más div debajo de #padre voy a tener un problema porque va a seleccionar todos (los que inicialmente eran hijos y también los que metimos después). La solución podría pasar por utilizar la expresión $(“#padre > div”), que seleccionará solo los hijos (no así los descendientes que no sean hijos) de #padre. Pero aun así, ¿Qué pasa si los nuevos div son realmente hijos (están justo debajo de #padre) y no quiero seleccionarlos?. Podríamos estar un rato largo discutiendo sobre esto, pero para abreviar mi solución pasa por agregar clases CSS de programación. ¡Uff!, suena raro, pero realmente lo que quiero decir es que utilizaré clases CSS para la selección con jQuery, pero serán clases que no tendrán un reflejo en la apariencia del documento (dicho de otro modo, no fueron pensadas para dar diseño, sino solamente para seleccionar elementos a través de jQuery). Vamos a ver cómo queda:

    <div id="padre">

        <div>

            Yo sólo pasaba por aquí, ¡no me selecciones!</div>

        <div class="slct_Hijo">

            Red</div>

        <div class="slct_Hijo">

            Yellow</div>

        <div class="slct_Hijo">

            Olive</div>

        <div class="slct_Hijo">

            Blue</div>

        <div>

            Yo soy el último de la fila, ¡nadie se acuerda de mí!

        </div>

    </div>

    <script type="text/javascript">

        $().ready(function () {

            $("#padre .slct_Hijo").click(function (e) {

                $(this).parent().css("background-color", $(this).html());

            });

        });       

    </script>

 

* Lo de slct_Nombre es simplemente una convención, en mi caso slct es la abreviatura de select

Ahora, el diseñador puede hacer lo que quiera y no habrá que cambiar el código (incluso metió 2 div hijos que quedaron excluidos de la selección). Igualmente tenemos un mismo problema con $(this).parent() ¿Qué pasa si cambia el padre?. Por otro lado, lo de tomar el color desde el contenido del div hijo tampoco es muy científico (imagínate que el diseñador decide meter un span o cualquier otra cosa dentro del hijo, para darle más vidilla al asunto?. Sin más dilaciones, solución final o cómo hacerlo bien:

    <div class="slct_Padre">

        <div>

            <div>

                Yo sólo pasaba por aquí, ¡no me selecciones!</div>

            <div class="slct_Hijo">

                Mi color es el <span class="slct_Color">Red</span></div>

            <div class="slct_Hijo">

                Yo sin embargo soy <span class="slct_Color">Yellow</span></div>

            <div class="slct_Hijo">

                El más bonito soy yo, que soy <span class="slct_Color">Olive</span></div>

            <div class="slct_Hijo">

                El color de cielo es <span class="slct_Color">Blue</span></div>

            <div>

                Yo soy el último de la fila, ¡nadie se acuerda de mí!

            </div>

        </div>

    </div>

 

    <script type="text/javascript">

        $().ready(function () {

            $(".slct_Padre .slct_Hijo").click(function (e) {

                $(this).closest(".slct_Padre").css("background-color", $(this).find(".slct_Color").html());

            });

        });       

    </script>

 

Por último, también es importante ver que utilizo .slct_Padre .slct_Hijo en vez de div.slct_Padre div.slct_Hijo. Si me está leyendo algún purista, podrá decir que el segundo selector es más óptimo (le costara menos) para jQuery. A cambio, yo le diría que si en vez de div, el diseñador decide que hablamos de p, pues ya tengo el lío. La moraleja de todo esto es que yo opino que el selector de jQuery tiene que ser lo más generalista posible (excepto en operaciones críticas donde quizás arañar un par de ciclos de CPU al navegador sea la diferencia entre la vida y la muerte).

A partir de aquí, hay otras consideraciones a tener en cuenta pero eso lo dejo para un siguiente post que tengo ya horneándose en la cocina.

Un saludo!

lunes, 13 de junio de 2011

Timeout en TransactionScope

Trabajando con TransactionScope, ¿Recuerdas “ha venido para quedarse”? nos han surgido problemas relacionados con el timeout de las transacciones.

Lo cierto es que no está muy claro cuál es el timeout por defecto de una nueva transacción, cual es el máximo valor que puede alcanzar una transacción, etc.

Lo primero a tener en cuenta es que podemos incluir la siguiente sección a nuestro fichero web.config para controlar el timeout por defecto de nuevas transacciones.

<configuration>

<system.transactions>

<defaultSettings timeout="00:01:00"/>

</system.transactions>

</configuration>

 

El atributo timeout determina el valor predeterminado para el timeout de una nueva transacción.

A nivel de machine.config, podemos también especificar el máximo valor de timeout disponible, maxTimeout (esto está para que los administradores de sistemas nos hagan la vida imposible a los pobres desarrolladores… bueno, y también para controlar a desarrolladores locos…). Ninguna transacción podrá tener un timeout superior al valor establecido (o al valor por defecto). Puedes saber exactamente qué fichero machine.config te está afectando consultando System.Runtime.InteropServices.RuntimeEnvironment.SystemConfigurationFile)

<configuration>

<system.transactions>

<machineSettings maxTimeout="00:10:00"/>

</system.transactions>

</configuration>

 

En caso de no existir ninguna de estas secciones, los valores predeterminados son de 1 minuto para la propiedad timeout y 10 minutos para la propiedad maxTimeout.

La referencia sobre estas secciones las puedes encontrar aquí.

En tiempo de ejecución, puedes ver que valores exactos tienes consultado las propiedades TransactionManager.DefaultTimeout y TransactionManager.MaximumTimeout.

Otra consideración es aclarar como la transacción toma el valor del timeout en función de las opciones y constructor que elijamos al crear nuestro nuevo objeto TransactionScope.

El siguiente código tomará el valor de timeout por defecto para una nueva conexión (el valor de TransactionManager.DefaultTimeout):

Using tran As New Transactions.TransactionScope

tran.Complete()

End Using

 

Sin embargo, este otro código tomará el valor 0, que significa infinito (aunque recuerda que nunca podrá irse más allá del valor de maxTimeout establecido en el machine.config).

' options se inicializa con Timeout igual a 00:00:00, luego infinito

Dim options As New TransactionOptions

Using tran As New TransactionScope(TransactionScopeOption.Required, options)

tran.Complete()

End Using

 

Un saludo!