Etiquetado: JavaScript

Patos y código C#

Swimmer duck on Surfboard
Durante los primeros días del año trajo mucha cola un artículo de Eric Lippert, uno de los padres de C#, sobre su visión de qué es el Duck Typing y qué representa para él en lenguajes de tipificado principalmente estático (digo principalmente porque ya sabemos que en .Net podemos hacer muchas cosas raras :P).

En mi opinión, más que criticar el concepto de duck typing, ese artículo es una queja sobre el bajo nivel y falta de coherencia de los artículos de Wikipedia y para demostrar que las modas confunden a la comunidad de desarrolladores. Deja claro que la entrada está mal escrita, usa mal algunos términos y se va contradiciendo según el párrafo.

En cualquier caso, para gustos los colores y yo os voy a contar mi visión al respecto del duck typing. No pretendo rebatir a Eric Lippert, no tengo ni los conocimientos ni la experiencia de Eric y otra gente como él que han hablado sobre el tema, sino que quiero dar mi opinión como desarrollador raso tras unos cuantos años utilizando el concepto en lenguajes como JavaScript y Python. Estoy de acuerdo en que el término es confuso, porque en realidad no tiene que ver directamente con el tipo que tengan los argumentos que enviamos, sino en cómo maneja el desarrollador los valores de los parámetros de sus métodos.

El nacimiento del pato tipificado (con lo bien que sonaba en inglés)

Para los que no conocéis el concepto, la frase que define el duck typing es, traducida, algo así:

Si anda como un pato y parpa como un pato entonces es un pato.

Detrás de esa frase tan simple veremos que hay que leer entre líneas y conocer un poco la historia. La discusión original era sobre cómo manejar el polimorfismo en Python y derivó en si se debía o no comprobar el tipo del objeto que llegaba en un parámetro de función. Los argumentos de Alex Martelli fueron que era mejor dejar de comprobar si el objeto que recibes es exactamente el tipo que te esperabas, sino que si el parámetro tiene las propiedades y métodos que necesitas, utilízalo sin más, si te falta algo lanza una excepción y que se preocupe el que envió al método un objeto inadecuado, todo esto acuñado bajo el símil del pato.

Yo añadiría que el Duck Typing es una convención a la hora de desarrollar porque el que utiliza el método o función debe saber que quien desarrolló ese método está utilizando duck typing, si no están los dos de acuerdo puede ser un auténtico desastre, sobre todo en lenguajes dinámicos (o en porciones dinámicas del código) donde nadie te avisa si estás haciendo algo “ilegal” hasta el momento de su ejecución.
Si pensáis que exagero, imaginad que pasaría si a una función JavaScript que se esperaba un pato le pasáis una bomba:

function vuelaAlto(pato){
    pato.volar(20);
}

var pato= {altura:0, volar: function(dif){this.altura+=dif; console.log(this.altura);}};
var bomba= {altura:0, volar: function(){console.log('¡booom!');},
            lanzar:function(dif){this.altura+=dif; console.log(this.altura);} };

vuelaAlto(pato);
vuelaAlto(bomba);

Aún así, tiene más ventajas que inconvenientes, tanto que, por ejemplo, en JavaScript lo utilizamos constantemente. Basta ver un ejemplo cualquiera de la librería jquery, donde enviamos un objeto anónimo con las propiedades que queremos enviar a la función, sin necesidad de crear una instancia de un tipo en concreto, basta que tenga la forma adecuada:

$.ajax({
  url: "test.html",
  context: document.body
}).done(function() {
  $( this ).addClass( "done" );
});

Duck typing en C#

Uno de los argumentos de Eric es que en realidad Duck Typing se refiere a Late Binding, y eso es algo que siempre hemos tenido en .Net y C#, e incluso en lenguajes más antiguos cuando surgió COM y podíamos ejecutar comandos de un Excel desde un programa hecho en Visual Basic. También dice que en cualquier otro caso tenemos los interfaces y que los lenguajes que lo implementan de forma estática en realidad a eso se le llama Structural Typing, como en Scala, Haskell o GO.

Otros autores opinan que duck-typing es bastante más que eso, por eso he dicho antes que el concepto me parece más una convención que otra cosa, veamos algunos casos que surgen en C# y si creo o no que tienen que ver con duck-typing:

Late Binding

En realidad, late-binding no es duck-typing ni creo que se le parezca. Cuando haces late-binding instancias de forma dinámica, normalmente por el nombre de la clase, un elemento que se corresponde con un tipo en concreto que está en un ensamblado al que no tenías acceso durante la compilación. Luego asignas ese objeto a una variable del tipo que tú sabes manejar y eso sólo puede pasar si el objeto es de ese tipo o hereda del mismo, al contrario que si usamos duck typing donde no tienen por qué ser del mismo tipo, sólo necesitas que se parezcan en las formas.

Normalmente en C# hacemos late-binding usando Reflection con métodos como Activator.CreateInstance(t) y asignando el resultado a un tipo en concreto, mediante un typecasting, de forma que luego nuestro código pueda utilizar ese objeto, por ejemplo:

Assembly assembly = Assembly.Load("ClassLibrary1");
Type type=assembly.GetType("ClassLibrary1.Form1");
Form form = (Form)Activator.CreateInstance(type);
form.Show();

A partir del typecasting con el tipo Form pasamos a utilizar el objeto como lo que es, un Form y si no lo fuera obtendríamos un error en tiempo de ejecución. Aquí está la gran diferencia con duck-typing, pues si utilizáramos este concepto nos bastaría que el objeto tuviera un método Show y no nos haría falta que el objeto heredara de Form, aunque eso sí, eso tiene una penalización de rendimiento.

dynamic

Con la palabra reservada dynamic, que trajo C# 4.0, si que podemos hacer duck-typing, pues nos proporciona la flexibilidad de los lenguajes dinámicos al no hacer typecasting contra ningún tipo concreto, sino que realiza una búsqueda del método o propiedad que nosotros estamos invocando, todo esto en tiempo de ejecución, como cualquier otro lenguaje dinámico. Si os estáis preguntando si es más lento, estáis en lo cierto. En el caso del late-binding sólo ralentiza mientras busca cómo hacer el binding, una vez enlazado el objeto ya se comporta de forma completamente normal, tanto en funcionamiento como en rendimiento.
En el caso de dynamic la penalización de rendimiento es alta y sólo se debería usar si es imposible saber con antelación el tipo.
Es fácil de comprobar si hacemos un ILDASM de una llamada dynamic contra una llamada con interfaz. Veremos cómo la cantidad de código se multiplica por 5, además de acceder al método utilizando un string en lugar de una posición de la tabla virtual.

public class Duck
{
    public string Quack()
    {
        return "Quack!";
    }
}
class Program
{
    static void Main(string[] args)
    {
        var duck = new Duck();
        var ventriloquistDuck = new { Quack = (Func<string>)(() => "Woof") };

        doQuackStatic(duck);

        doQuack(duck);
        doQuack(ventriloquistDuck);

        var notADuck = "Do you quack?";
        try
        {
            doQuack(notADuck);
        }
        catch
        {
            Console.WriteLine("Cannot quack!");
        }
        try
        {
            doQuack(25);
        }
        catch
        {
            Console.WriteLine("Cannot quack!");
        }
    }

    private static void doQuack(dynamic duck)
    {
        Console.WriteLine("Dynamic: {0} says {1}", duck.GetType(), duck.Quack());
    }

    private static void doQuackStatic(Duck duck)
    {
        Console.WriteLine("Static: {0} says {1}", duck.GetType(), duck.Quack());
    }
}

Aquí va el código IL de doQuackStatic tal como me lo da ILDASM tras compilar en modo Release:

.method private hidebysig static void  doQuackStatic(class DuckTyping.Duck duck) cil managed
{
  // Code size       23 (0x17)
  .maxstack  8
  IL_0000:  ldstr      "Static: {0} says {1}"
  IL_0005:  ldarg.0
  IL_0006:  callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
  IL_000b:  ldarg.0
  IL_000c:  callvirt   instance string DuckTyping.Duck::Quack()
  IL_0011:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object,
                                                                object)
  IL_0016:  ret
} // end of method Program::doQuackStatic

Y aquí el del método dinámico, lo he colapsado porque ocupa 130 líneas, pulsa sobre el nombre para ver el código:

.method private hidebysig static void  doQuack(object duck) cil managed
{
  .param [1]
  .custom instance void [System.Core]System.Runtime.CompilerServices.DynamicAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       284 (0x11c)
  .maxstack  13
  .locals init ([0] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000,
           [1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0001,
           [2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0002)
  IL_0000:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
  IL_0005:  brtrue.s   IL_005c
  IL_0007:  ldc.i4     0x100
  IL_000c:  ldstr      "WriteLine"
  IL_0011:  ldnull
  IL_0012:  ldtoken    DuckTyping.Program
  IL_0017:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_001c:  ldc.i4.4
  IL_001d:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  IL_0022:  stloc.0
  IL_0023:  ldloc.0
  IL_0024:  ldc.i4.0
  IL_0025:  ldc.i4.s   33
  IL_0027:  ldnull
  IL_0028:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                             string)
  IL_002d:  stelem.ref
  IL_002e:  ldloc.0
  IL_002f:  ldc.i4.1
  IL_0030:  ldc.i4.3
  IL_0031:  ldnull
  IL_0032:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                             string)
  IL_0037:  stelem.ref
  IL_0038:  ldloc.0
  IL_0039:  ldc.i4.2
  IL_003a:  ldc.i4.0
  IL_003b:  ldnull
  IL_003c:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                             string)
  IL_0041:  stelem.ref
  IL_0042:  ldloc.0
  IL_0043:  ldc.i4.3
  IL_0044:  ldc.i4.0
  IL_0045:  ldnull
  IL_0046:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                             string)
  IL_004b:  stelem.ref
  IL_004c:  ldloc.0
  IL_004d:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
                                                                                                                                                               string,
                                                                                                                                                               class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                                                                                                                               class [mscorlib]System.Type,
                                                                                                                                                               class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
  IL_0052:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
  IL_0057:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
  IL_005c:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
  IL_0061:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>>::Target
  IL_0066:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site3'
  IL_006b:  ldtoken    [mscorlib]System.Console
  IL_0070:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0075:  ldstr      "Dynamic: {0} says {1}"
  IL_007a:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
  IL_007f:  brtrue.s   IL_00b3
  IL_0081:  ldc.i4.0
  IL_0082:  ldstr      "GetType"
  IL_0087:  ldnull
  IL_0088:  ldtoken    DuckTyping.Program
  IL_008d:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0092:  ldc.i4.1
  IL_0093:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  IL_0098:  stloc.1
  IL_0099:  ldloc.1
  IL_009a:  ldc.i4.0
  IL_009b:  ldc.i4.0
  IL_009c:  ldnull
  IL_009d:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                             string)
  IL_00a2:  stelem.ref
  IL_00a3:  ldloc.1
  IL_00a4:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
                                                                                                                                                               string,
                                                                                                                                                               class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                                                                                                                               class [mscorlib]System.Type,
                                                                                                                                                               class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
  IL_00a9:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
  IL_00ae:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
  IL_00b3:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
  IL_00b8:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Target
  IL_00bd:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site4'
  IL_00c2:  ldarg.0
  IL_00c3:  callvirt   instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>::Invoke(!0,
                                                                                                                                                    !1)
  IL_00c8:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
  IL_00cd:  brtrue.s   IL_0101
  IL_00cf:  ldc.i4.0
  IL_00d0:  ldstr      "Quack"
  IL_00d5:  ldnull
  IL_00d6:  ldtoken    DuckTyping.Program
  IL_00db:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_00e0:  ldc.i4.1
  IL_00e1:  newarr     [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
  IL_00e6:  stloc.2
  IL_00e7:  ldloc.2
  IL_00e8:  ldc.i4.0
  IL_00e9:  ldc.i4.0
  IL_00ea:  ldnull
  IL_00eb:  call       class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
                                                                                                                                                                             string)
  IL_00f0:  stelem.ref
  IL_00f1:  ldloc.2
  IL_00f2:  call       class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
                                                                                                                                                               string,
                                                                                                                                                               class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>,
                                                                                                                                                               class [mscorlib]System.Type,
                                                                                                                                                               class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
  IL_00f7:  call       class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
  IL_00fc:  stsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
  IL_0101:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
  IL_0106:  ldfld      !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>>::Target
  IL_010b:  ldsfld     class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>> DuckTyping.Program/'<doQuack>o__SiteContainer2'::'<>p__Site5'
  IL_0110:  ldarg.0
  IL_0111:  callvirt   instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,object>::Invoke(!0,
                                                                                                                                                    !1)
  IL_0116:  callvirt   instance void class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite,class [mscorlib]System.Type,string,object,object>::Invoke(!0,
                                                                                                                                                                                           !1,
                                                                                                                                                                                           !2,
                                                                                                                                                                                           !3,
                                                                                                                                                                                           !4)
  IL_011b:  ret
} // end of method Program::doQuack

Sin entrar a mirar qué hace el código, podemos imaginar que entre 14 y 130 líneas de código debe haber alguna diferencia de rendimiento.

Interfaces y librerías para Duck Typing

Cuando desarrollas en unos cuantos lenguajes siempre echas de menos cosas de uno y de otro en el que estás utilizando en ese momento. Cuando paso de JavaScript a C# no suelo echar muchas cosas de menos, pero un sistema de duck-typing o structural-typing si que vendría muy bien, sobre todo cuando utilizas clases anónimas que tienes que devolver a alguien.

public interface IProcess
{
    string Name { get; set; }
    int Id { get; set; }
}
static void Main(string[] args)
{
    var procList = (from x in System.Diagnostics.Process.GetProcesses().AsQueryable()
            select new { Name = x.ProcessName, Id = x.Id }).ToList();
    printProcList(procList);
}

private static void printProcList(List<IProcess> procList)
{
    procList.ForEach((p) => Console.WriteLine("{0}: {1}", p.Id, p.Name));
}

El código anterior no funciona, porque tendremos que crear una clase que implemente el interfaz y rellenarla con los datos que nos llegan. En nuestro caso podríamos hacerlo, pero a veces no nos es posible pues usamos librerías complicadas de las que no tenemos el código o si lo tenemos tampoco le podemos meter mucha mano.

Si queremos forzar duck typing en C# contra métodos que no están pensados para duck typing inicialmente, existen algunas librerías para poder hacer esto en nuestras aplicaciones .Net, aunque pueden penalizar mucho el rendimiento. Una de ellas es impromptu que nos permite hacer cosas como usar un tipo anónimo y convertirlo a un interfaz para poder utilizarlo en la aplicación:

    using ImpromptuInterface;

    public interface ISimpleClassProps
    {
        string Prop1 { get;  }

        long Prop2 { get; }

        Guid Prop3 { get; }
    }

...

    var tAnon = new {Prop1 = "Test", Prop2 = 42L, Prop3 = Guid.NewGuid()};

    var tActsLike = tAnon.ActLike<ISimpleClassProps>();

Como ya sabréis, los interfaces explícitos nos proporcionan una forma de comprobar en tiempo de compilación que un objeto cumple con una firma en concreto, pero eso también nos limita mucho, sobre todo con el uso de clases anónimas, pues estas no pueden implementar un interfaz. Para solucionar ésto algunos lenguajes han añadido el concepto de Structural Typing, que permite el uso de interfaces implícitos, pero que el compilador podrá comprobar, al contrario que lo que ocurre con duck typing.

Al final, creo que en el propósito de aprender un nuevo lenguaje cada año, este va a ser alguno con Structural Typing… a ver si conseguimos que lo añadan a C# algún día.

Enlaces interesantes sobre duck-typing y C#:

Anuncios

Consumir SOAP desde WinJS

soap
No todos los servicios proporcionan una API Restful para acceder a ella de forma sencilla desde nuestras Apps, así que es posible que tengamos que pelearnos con servicios SOAP, que son más pesados y complicados, pero a la vez nos permiten trabajar operaciones y conjuntos de datos más complejos.
Consumir SOAP desde JavaScript no es complicado, sólo que es tedioso. Hoy vamos a ver dos maneras que podremos utilizar en nuestras aplicaciones de la tienda Windows.

Descarga el código de ejemplo de Codeplex

Consumir SOAP con xhr

WinJS.xhr es un wrapper del objeto XMLHttpRequest para facilitarnos las llamadas asíncronas, así que utilizar SOAP desde xhr va a ser tan sencillo o complicado como con el XMLHttpRequest original.

Para el siguiente código vamos a utilizar uno de los ejemplos de servicios web SOAP que hay en http://www.webservicex.net/, en concreto el servicio ChangeLengthUnit que realiza conversión de unidades.

En una aplicación de la tienda Windows creamos una página y en el tag body ponemos un cuadro de texto, un botón y un div como estos:

Introduce los centímetros: <input type="number" id="mmValue" value="0.0"/>
<button id="xhrTest">Conexión con WinJS.xhr</button>
<div id="result"></div>

En default.js, añadimos el código que sigue tras la línea donde se llama a WinJS.UI.ProcessAll(), creando el código XML que enviaremos al servicio SOAP para llamar a la acción SOAP ChangeLengthUnit. Podéis ver un ejemplo en la página de descripción de la operación, necesita tres parámetros: el valor numérico, las unidades origen y las unidades destino. Para el ejemplo convertiremos de centímetros a pulgadas:

var data = '<?xml version="1.0" encoding="utf-8"?>' +
            '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' + 
            ' xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
            ' xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
            '  <soap:Body>' +
            '    <ChangeLengthUnit xmlns="http://www.webserviceX.NET/">' +
            '      <LengthValue>'+mmValue.value+'</LengthValue>' +
            '      <fromLengthUnit>Centimeters</fromLengthUnit>' +
            '      <toLengthUnit>Inches</toLengthUnit>' +
            '    </ChangeLengthUnit>' +
            '  </soap:Body>' +
            '</soap:Envelope>';

var options = {
    url: "http://www.webservicex.net/length.asmx",
    type: "post",
    headers: {
        "Content-Type": "text/xml; charset=utf-8",
        "SOAPAction": "http://www.webserviceX.NET/ChangeLengthUnit"
    },
    data: data
};

Fijaos que he configurado la llamada xhr para llamar por POST y he incluido dos cabeceras específicas para la llamada, el Content-Type, que debe ser xml y la SOAPAction, que debe indicar a qué servicio estamos llamando. Ahora llamamos a la función xhr con las opciones que hemos configurado y esperamos la respuesta, que vuelve a ser un XML del que podremos extraer la información.

WinJS.xhr(options)
.done(
    function (request) {
        var doc = request.responseXML.documentElement;
        var output = doc.getElementsByTagName("ChangeLengthUnitResult");

        result.innerHTML = "La longitud es "+ window.toStaticHTML(output[0].textContent)+" pulgadas";
        result.style.backgroundColor = "#00A000";
    },
    function (error) {
        result.innerHTML = "Status: " + error.status + " : " + error.statusText;
        result.style.backgroundColor = "#FF0000";
    },
    function (progress) {
        result.innerText = "Ready state is " + progress.readyState;
        result.style.backgroundColor = "#0000A0";
    });

Todo esto para un servicio web bastante sencillo. Si empezamos a añadir tipos de datos complejos la cosa se complica.
Como estamos en Windows 8 / 8.1, tenemos una gran ventaja, podemos realizar la conexión con el servicio web mediante C#, que es capaz de leer la descripción WSDL del servicio y generar un proxy con las clases tipificadas, para luego utilizar esa clase desde JavaScript a través de las proyecciones WinMD.

Puente SOAP con C#

Como ya comenté en un artículo anterior, podemos utilizar librerías escritas en C# o C++ desde nuestros proyectos JavaScript, sólo tenemos que crear una librería tipo Windows Runtime Component y respetar unas normas básicas. Una vez creada la librería y referenciada desde nuestro proyecto JavaScript, utilizar servicios SOAP es muy fácil, basta con pulsar botón derecho sobre el proyecto C# y añadir una referencia al servicio. El Visual Studio nos creará las clases y llamadas que necesitamos para comunicarnos con el mismo.

Add WebService

Con la referencia al servicio ya tenemos todo lo necesario, sólo nos falta crear un método dentro de una clase C# para publicarlo y que se pueda utilizar desde JavaScript. Como las llamadas al servicio son asíncronas creamos un método que devuelve un tipo IAsyncOperation; podemos convertir la Task del cliente SOAP directamente a este interfaz con el método de extension AsAsyncOperation:

public sealed class LengthUnits
{
    LengthUnitService.lengthUnitSoapClient client;
    public LengthUnits()
    {
        client = new LengthUnitService.lengthUnitSoapClient();
    }
    public IAsyncOperation<double> GetAsync(double number,
        LengthUnitService.Lengths from, LengthUnitService.Lengths to)
    {
        return client.ChangeLengthUnitAsync(number, from, to).AsAsyncOperation();
    }
}

Et voilà, ya lo podemos utilizar desde JavaScript sin ensuciarnos las manos parseando XML:

csTest.onclick = function (e) {
    var cl = new SoapClient.LengthUnits();
    cl.getAsync(mmValue.value, SoapClient.LengthUnitService.Lengths.millimeters, SoapClient.LengthUnitService.Lengths.inches).done(
        function (c) {
            result.innerHTML = "La longitud es " + c + " pulgadas";
            result.style.backgroundColor = "#00a000";
        },
        function (err) {
            result.innerHTML = window.toStaticHTML(err);
            result.style.backgroundColor = "#FF0000";
        },
        function (progress) {
            result.innerHTML = window.toStaticHTML("percent: " + progress + "%");
            result.style.backgroundColor = "#0000a0";
        }
        )
}

Progreso

Un momento, ¿ahora no está llegando nunca a la función de progreso? Pues no, porque para tener progreso necesitamos utilizar el interfaz IAsyncOperationWithProgress. Nos llevará un poco más de trabajo, la clase AsyncInfo y las palabras async y await nos lo van a poner fácil:

    public Windows.Foundation.IAsyncOperationWithProgress<double, string> GetWithProgressAsync(double number,
        ServiceReference1.Lengths from, ServiceReference1.Lengths to)
    {
        return AsyncInfo.Run(async delegate(CancellationToken cancellationToken,
            IProgress<string> progress)
        {
            double result;
            progress.Report(client.State.ToString());
            await client.OpenAsync();
            progress.Report(client.State.ToString());
            result = await client.ChangeLengthUnitAsync(number, from, to);
            progress.Report(client.State.ToString());
            await client.CloseAsync();
            progress.Report(client.State.ToString());
            return result;
        });
    }

Seguro que hay alguna manera más, cuando la encuentre os la contaré!

Descarga el código de ejemplo de Codeplex

Mejorar el rendimiento del canvas HTML5 desde C#

IMG_4102
Desarrollar aplicaciones para la Tienda Windows en JavaScript tiene sus ventajas e inconvenientes. Aunque en la mayoría de los casos no lo vamos a notar, cuando tratamos grandes cantidades de datos y tenemos que realizar modificaciones sobre estas, lo más probable es que se penalice el rendimiento.

Estoy desarrollando la nueva versión de Fingerpaint y quiero añadir una herramienta de relleno de color.
No es complicado de hacer, hay bastantes algoritmos bien optimizados y el context tiene los métodos getImageData y putImageData que nos dan el array de datos para modificar el contenido del canvas a nivel de pixel:

width = context.canvas.width;
height = context.canvas.height;
imageData = context.getImageData(0, 0, width, height);
data = imageData.data;
for(var i=0;i<data.length;i++){
  //tratar los píxeles de data uno por uno
}

El problema de hacer esto en JavaScript es que el lenguaje no está optimizado para este tipo de operaciones e intentar hacer un relleno un poco inteligente (estilo cubo de pintura) va a tardar mucho más de lo que estamos acostumbrados con cualquier aplicación de dibujo moderna.

Componentes Windows Runtime (WinRT)

Por suerte, mi aplicación FingerPaint es para Windows 8 (en realidad 8.1) y puedo usar librerías desarrolladas en otros lenguajes, como C# o C++, que me permitirán realizar estas operaciones mucho más rápido.
Es muy fácil tener código en otros lenguajes dentro de una aplicación JavaScript de la tienda Windows, sólo tenemos que añadir una nueva librería de tipo Windows Runtime Component en el lenguaje que prefiramos. Con este tipo de proyectos estamos indicando que es una librería que puede utilizarse desde cualquier otro lenguaje.

Lo que estamos haciendo es crear una librería con WinMD, el mismo sistema de metadatos que las librerías de sistema en Windows Runtime. En esta imagen creamos un nuevo componente en C#:
Windows Runtime Component

A continuación tenéis el ejemplo de código. Está dividido en dos métodos:

public sealed class Fill
{
   private byte[] _data;

   public void BeginFill(int x, int y, int width, int height, 
       [ReadOnlyArray] byte[] bitmap, string hexColor, byte tolerance)
   {
     _data=bitmap;
     //
     //aquí irá mi código de flood fill que modificará los datos
     //dentro de _data, pero al ser ReadOnly no volverán a JavaScript
     //modificados.
     //
   }

   public void EndFill([WriteOnlyArray] byte[] target)
   {
     _data.CopyTo(target,0);
   }
}

Si os fijáis, la clase es sealed y los parámetros array están decorados con los atributos Read/WriteOnlyArray. Estas dos características son requisito indispensable para que nuestra clase y métodos se puedan utilizar desde cualquier otro lenguaje.

Añadiremos el proyecto que hemos creado en las referencias de nuestra aplicación JavaScript y desde el código JavaScript las llamadas se verán así, como si fueran llamadas a cualquier otra librería JavaScript, pero tú y yo sabemos que está hecho en C#:

width = context.canvas.width;
height = context.canvas.height;
imageData = context.getImageData(0, 0, width, height);
data = imageData.data;
//creamos un objeto Fill
var helper=new ImageHelpers.Fill();
//calculamos el fill
helper.beginFill(x,y,width,height,data,"#00ff00",50);
//escribimos el resultado en imageData
helper.endFill(data);
//escribimos el resultado en el canvas
context.putImageData(imageData, 0, 0);

Para poder modificar el canvas necesitamos que nuestro método externo modifique directamente el array obtenido con getImageData, pero cuando trabajamos con componentes de WinRT y queremos pasar un array sólo podemos hacerlo en uno de estos dos modos:

  • ReadOnlyArray: trabajamos sobre una copia del array y cualquier modificación no se traspasará entre los contextos, ni siquiera si devolvemos el array como valor de retorno
  • WriteOnlyArray: podemos escribir sobre el array original… pero este siempre viene vacío, no veremos la información original

Adicionalmente, canvas no nos permite introducir un array de datos dentro de imageData que no haya sido creado por el propio canvas, así que si utilizáramos un parámetro de retorno tendríamos que copiar uno por uno los bytes del array, con lo que perderíamos el rendimiento que ganamos por hacerlo en C#.
Entonces, y por eso el artículo de hoy, tenemos que realizar la operación en dos pasos: en el primero obtenemos los datos originales y en el segundo escribimos sobre la memoria del canvas desde C#. Estas operaciones son bastante rápidas y casi no penalizan el rendimiento (entre 1 y 3 milisegundos en un i5), así que nos queda bastante margen para modificar el contenido del canvas y mejorar considerablemente la velocidad del fill con nuestro método C#.

Depuración híbrida

Para poder depurar nuestro código C# dentro de una aplicación JavaScript tendremos que indicar en las propiedades del proyecto qué tipo de lenguaje queremos depurar, a alguien se le olvidó poner Managed+Script así que por ahora sólo podremos depurar en uno o en otro, si hubiéramos elegido C++ como lenguaje no tendríamos este problema, pero tendríamos otros :P.
Script or Managed debug

Happy finger painting!

Optimizar el Canvas de Apps Windows en HTML5/JavaScript

En un artículo anterior sobre el patrón Memento en JavaScript introducía cómo podíamos ir guardando el estado de un elemento canvas para crear un sistema de deshacer/repetir en una app de dibujo. En dicha app había recibido algunas quejas sobre el rendimiento a partir de incluir esta funcionalidad, especialmente al ejecutar la aplicación en tabletas ARM con Windows RT.

Si observáis el código, obtenemos una copia de la imagen dibujada sobre el canvas con la siguiente llamada:

var img = this._ctx.getImageData(x, y, w, h);

En un principio pensé que el problema estaba en capturar toda la pantalla cada vez, pero intentar resolver eso complicaba mucho el desarrollo y tampoco se ganaba en rendimiento, sino todo lo contrario. Ya escribiré sobre ello en otro post.

En realidad el problema está en el rendimiento de los métodos getImageData y putImageData, que si buscáis un poco por internet os recomendarán utilizarlo lo menos posible, o sea, que ni se te ocurra hacerlo en un bucle. El caso es que aparte de algún artículo donde analizan el rendimiento de dicho método y de cómo algunos navegadores lo mejoran, para guardar la imagen de un canvas os dirán que tenéis que utilizar alguno de los siguientes métodos:

  • getImageData o getImageDataHD: copia todos los píxeles a un array, normalmente para poder modificar a nivel de píxel y luego volver a volcar al canvas con un putImageData.
  • toDataURL o toBlob: para guardar los datos, incluso con compresión png, para poder enviarlos a un servidor (aunque esto en Windows8 lo podemos hacer con métodos nativos).

El problema de estos métodos es que tienen un rendimiento bastante pobre, y si tenemos que copiar toda la pantalla cuando el usuario levanta el dedo de la puede que afecte al rendimiento de nuestras rutinas de dibujo, de hecho en mi App estaba afectando y mucho. Debemos recordar que muchos usuarios tendrán una versión RT con un chip ARM que no tiene el rendimiento de un desktop.

Canvas inception

Hay otra forma de guardar el estado de un canvas cuando no necesitamos actuar sobre píxeles individuales o guardar el contenido a un archvivo: pintando dentro de un canvas el contenido del canvas original.
Para guardar el canvas, basta crear uno nuevo de las mismas dimensiones y ejecutar el método drawImage que, además de pintar imágenes, si le damos otro canvas como parámetro pintará el contenido del canvas origen dentro del destino:

var newCanvas=document.createElement("canvas");
var newContext=newCanvas.getContext("2d");

var w=originalCanvas.width;
var h=originalCanvas.height;

newCanvas.width=w;
newCanvas.height=h;

newContext.drawImage(originalCanvas,0,0,w,h,0,0,w,h);

¿Tánto se gana?

Se puede ganar mucho rendimiento con este pequeño cambio. En algunos casos la mejora es tan espectacular que es difícil de creer, pero para que lo comprobéis vosotros mismos aquí os dejo este jsfiddle:
imagecopy

Luego, si queremos recuperar la imagen sólo tenemos que volver a pintar sobre el canvas original el canvas que hemos guardado. Aunque debemos ir con cuidado, no es conveniente ir creando infinitos canvas y esperar que el Garbage Collector vaya recogiendo lo que no necesitamos, aparte de que algunos navegadores no estarán demasiado contentos con eso. Es mucho mejor crear un pool de elementos canvas e ir reutilizándolos. Si queréis ver cómo ha quedado el nuevo patrón memento ahí tenéis el código, aunque por culpa del pool de canvas no está tan desacoplado como me gustaría:

(function mementoNS(global) {
    global.CanvasState = global.CanvasState || {};
})(this);

(function mementoInit(CanvasState) {
    CanvasState.memento = function (state) {
        this._state = state;
    };
    Object.defineProperty(CanvasState.memento.prototype, "state", {
        get: function () { return this._state; },
        set: undefined
    });
})(this.CanvasState);

//this is a buffer pool to avoid possible memory problems
(function bufferPoolInit(CanvasState) {
    CanvasState.bufferPool = {
        _buffer:[],
        addLevels: function (levels) {
            var buffer = CanvasState.bufferPool._buffer;
            for (var i = 0; i < levels; i++) {
                buffer.push([document.createElement("canvas"), false]);
            }
        },
        getBuffer: function () {
            var buffer = CanvasState.bufferPool._buffer;
            for (var i = 0; i < buffer.length; i++) {
                if (!buffer[i][1]) {
                    buffer[i][1] = true;
                    return { id: i, buffer: buffer[i][0] };
                }
            }
        },
        releaseBuffer: function (id) {
            CanvasState.bufferPool._buffer[id][1] = false;
        }
    }

})(this.CanvasState);

(function originatorInit(CanvasState) {
    CanvasState.originator = function (canvas) {
        this._canvas = canvas;
        this._ctx = canvas.getContext("2d");
    };
    CanvasState.originator.prototype.saveToMemento = function (x, y, w, h) {
        if (x === undefined)
            x = 0;
        if (y === undefined)
            y = 0;
        if (w === undefined)
            w = this._canvas.width;
        if (h === undefined)
            h = this._canvas.height;
        var buffer = CanvasState.bufferPool.getBuffer();
        var bufferContext = buffer.buffer.getContext("2d");
        buffer.buffer.width = w;
        buffer.buffer.height = h;
        bufferContext.drawImage(this._canvas, x, y, w, h, 0, 0, w, h);
        var memento = new CanvasState.memento({ image: buffer, x: x, y: y, w: w, h: h });
        return memento;
    };
    CanvasState.originator.prototype.restoreFromMemento = function (memento) {
        var state = memento.state;
        this._ctx.clearRect(state.x,state.y,state.w,state.h);
        this._ctx.drawImage(state.image.buffer, state.x, state.y);
    };
})(this.CanvasState);

(function caretakerInit(CanvasState) {
    CanvasState.caretaker = function (maxLevels) {
        CanvasState.bufferPool.addLevels(maxLevels+1);
        this._undoStates = [];
        this._redoStates = [];
        this._maxLevels = maxLevels;
    };
    CanvasState.caretaker.prototype.addMemento = function (memento) {
        this._undoStates.push(memento);
        this._redoStates.forEach(function (s) {
            CanvasState.bufferPool.releaseBuffer(s._state.image.id);
        });
        this._redoStates = [];
        if (this._undoStates.length > this._maxLevels) {
            CanvasState.bufferPool.releaseBuffer(this._undoStates[0]._state.image.id);
            this._undoStates.splice(0, 1);
        }
    };
    CanvasState.caretaker.prototype.getUndoMemento = function () {
        if (this._undoStates.length > 1) {
            var state = this._undoStates.pop();
            this._redoStates.push(state);
            return this._undoStates[this._undoStates.length-1];
        }
        else
            throw "Undo not allowed, states array empty";
    };
    CanvasState.caretaker.prototype.getRedoMemento = function () {
        if (this._redoStates.length > 0) {
            var state = this._redoStates.pop();
            this._undoStates.push(state);
            return state;
        }
        else
            throw "Redo not allowed, states array empty";
    }
    Object.defineProperty(CanvasState.caretaker.prototype, "canUndo", {
        get: function () { return this._undoStates.length>1; },
        set: undefined 
    });
    Object.defineProperty(CanvasState.caretaker.prototype, "canRedo", {
        get: function () { return this._redoStates.length > 0; },
        set: undefined
    });
})(this.CanvasState);

Warning : DEP0810 : This app references Microsoft.WinJS.1.0 …

Si os aparece este warning en el Visual Studio 2012 cuando desarrolláis aplicaciones JavaScript para la Tienda Windows, significa que la versión de WinJS que tenéis en el VS no coincide con la que está instalada en el equipo. Lo más probable es que os falte una actualización de las extensiones de JavaScript.

El pasado día 8 actualizaron la versión de WinJS a 1.0.9200.20789. La podéis encontrar aquí: http://www.microsoft.com/en-us/download/details.aspx?id=30687

Esto no es lo que parece, el “this” en JavaScript

Leandro's Pool

JavaScript es un lenguaje muy dinámico, tanto que lo podemos utilizar como lenguaje orientado a objetos, como lenguaje funcional y redefinir una función de sistema, todo en la misma línea sin despeinarnos. Cuando estás acostumbrado a lenguajes orientados a objetos “clásicos”, algunas características de JavaScript se te hacen extrañas y pueden traerte más de un dolor de cabeza. Por eso es importante conocer bien el lenguaje en el que estás programando y no sólo algunas librerías útiles.

He visto bastante gente que lleva algún tiempo desarrollando en JavaScript y todavía no tiene claros algunos conceptos básicos del lenguaje, así que aquí va un artículo para intentar aclarar estos puntos; tampoco soy un experto en JavaScript, pero tener claros estos conceptos me ha ayudado mucho durante el desarrollo de algunas aplicaciones de Windows 8 y me ha evitado hacerme un lío con lo que ya sabía de C# y Java. Si vais a desarrollar una aplicación JavaScript moderna es necesario conocer cómo se escribe el JavaScript de hoy.

Las dos características que suelen despistar más al principio son: el ámbito de las variables y la palabra reservada this. Como es necesario comprender el primero para utilizar bien el segundo, vamos por orden.

Ámbito de variables

En JavaScript tenemos dos ámbitos para las variables:

  • El Global, visible desde toda la aplicación. En este contexto deberíamos evitar declarar variables y funciones, pues es muy fácil equivocarse y reescribir su valor, cambiando la funcionalidad de la aplicación por completo, aunque esto también se puede aprovechar a la hora de ofrecer objetos en una API. La mayoría de frameworks declaran dentro del ámbito global algunos objetos básicos, pero, para evitar conflictos con otras librerías, suelen utilizar una técnica de espacios de nombres.
  • El  Local es a nivel de función, todas las variables que se declaran dentro de una función no son visibles fuera de la misma, pero si son visibles a las funciones que declaremos internamente. Este pequeño truco nos vendrá muy bien para poder utilizar el concepto de closures.

Sin profundizar demasiado, hagamos un repaso de los efectos del ámbito en las variables. En el siguiente fragmento de código, vemos como la variable a es definida en el ámbito global y desde la función b podemos cambiar su valor.

var a=0;
function b(){
    a=5;
}

b();
log("ejecución b: "+a);

El resultado tras ejecutar b() será 5, hemos modificado el valor de la variable global. De hecho podríamos cambiar cualquier cosa de la variable, incluso asignarle el no-valor undefined. No probéis esto sin que os acompañe un adulto ;).

Si definimos una variable dentro de una función sólo es visible dentro de la misma y no entra en conflicto con las definiciones globales:

function c(){
    var a=10;
}
c();
log("ejecución c: "+a);

En este punto (supongamos que va a continuación del código anterior), el valor global de a no habrá cambiado y seguiremos teniendo el valor 5.

Las variables locales a una función se pueden declarar en casi cualquier lugar, por ejemplo dentro de un bloque if, dentro de un bucle, dentro de un bloque de llaves; el siguiente bloque de código nos muestra cómo el código tiene acceso a una variable definida dentro de un bucle, JavaScript no nos pondrá ningún problema, ni siquiera con la clausula “use strict”:

for(var i=0;i<50;i++){
    var x=i;
}
log("valor de x tras el bucle: "+x);

El resultado será:

valor de x tras el bucle: 49

Cuando tenemos una variable con ámbito de función también es visible a las funciones definidas dentro de la misma. Así:

function d(valor){
    function cuadrado(){
        return valor*valor;
    }
    return cuadrado();
}

log("ejecución d:"+d(a));

La función que definimos dentro de d no necesita que le enviemos el parámetro, pues puede acceder al mismo directamente.

Podemos tener efectos secundarios que no voy a explicar aquí, pero seguro que os parece interesante el artículo de Robert Nyman.

Debido a todo esto y con el objetivo de mantener el ámbito global lo más limpio posible, habréis visto en más de una ocasión la siguiente estructura o una de sus variantes:

(function(){
 var miVariable="incalculable";
 function miFuncion(){
  console.log("Mi variable tiene un valor " + miVariable);
 }
 miFuncion();
})();

Este código declara una función y dentro de la misma declara una variable, una función y llama a ésta última. La parte interesante de esta función es que está declarada entre paréntesis y tiene otro par de paréntesis al final.
Los primeros paréntesis convierten a la función en una expresión, al ser una expresión no crea un nombre de función, aunque lo pongamos, en el ámbito global y lo único que podemos hacer con la misma es ejecutarla, o asignarla a una variable para poder ejecutarla más tarde.
Los segundos paréntesis hacen esto mismo, ejecutar la expresión. Como la expresión es una función, todas las variables declaradas dentro se quedarán en el ámbito de la función y así evitamos que cualquier otro código pueda entrar en conflicto con el nuestro.
 

La palabra reservada this y el ámbito de ejecución

Cuando hablamos de orientación a objetos, habitualmente tenemos una manera de acceder a la instancia propietaria de la función que se está ejecutando con alguna clave como this o self. Es por eso que cuando vemos que JavaScript tiene la palabra clave this pensamos que lo tenemos todo controlado y nos creamos un objeto que utiliza this para todo:

function objeto(v){
    this.valor=v;
    this.desplaza=function(cantidad){
                log(this.valor+" + "+cantidad+" = "+  (this.valor+cantidad));
                log("");
            }
}

Después, llamamos a nuestra función desplaza perteneciente al objeto y parece que funciona…

var o=new objeto(5);
//llama a la función definida en el objeto
log("o.desplaza(10) >>>");
o.desplaza(10);

Y el resultado es:

o.desplaza(10) >>>
5 + 10 = 15

Como sabréis, acceder a propiedades de objetos en JavaScript es considerablemente más lento que el acceso a una variable, así que un día decidimos optimizar nuestro código guardando la llamada en una variable. Al cabo de unas horas de depuración nos volvemos a dar cuenta que JavaScript, además de orientado a objetos, es un lenguaje dinámico y funcional:

//llama a la función definida en el objeto, pero cambia el contexto al asignar
//la función a una variable
log("var desp=objeto.desplaza >>>");
var desp=objeto.desplaza;
desp(5);

El resultado puede ser mucho más alucinante si alguien ha metido algo con el mismo nombre dentro del objeto global (window):

//llama a la función definida del objeto, pero utiliza el objeto global como this
log("window.valor=100 >>>");
window.valor=100;
var desp=o.desplaza;
desp(5);

¿Qué ha pasado aquí? Al colocar la función en una variable hemos cambiado el ámbito de la llamada y ahora se ejecuta en el global (window en un navegador). Para evitar esto, habitualmente se utiliza el concepto de closure para empaquetar el this y que sea visible al código que se ejecuta.

//redefinición del objeto, guardando el this...
function objetoMejor(v){
    var that=this;
    this.valor=v;
    this.desplaza=function(cantidad){
                log(that.valor+" + "+cantidad+" = "+  (that.valor+cantidad));
                log("");
            }
}
var o2=new objetoMejor(5);
//llama a la función definida en el objeto
log("o2.desplaza(10) >>>");
o2.desplaza(10);
log("var desp=o2.desplaza >>>");
var desp2=o2.desplaza;
desp2(10);

Pero este método nos obliga a definir todo el objeto en el constructor; en ocasiones eso no nos vendrá bien, pero no todo está perdido.

Dominando el ámbito de this con call, apply y bind

Además del método pedestre that=this, JavaScript cuenta con tres funciones que nos ayudarán a dominar el ámbito de ejecución como nosotros queramos. Las llamadas call,apply y bind.

Las dos primeras funciones nos servirán para inyectar el ámbito en la llamada que nosotros queramos.

//llama a la función definida en el objeto, pero cambia el valor de this
log("o.desplaza.apply({valor:10},[5]) >>>");
o.desplaza.apply({valor:10},[5]);
log("o.desplaza.call({valor:10}5) >>>");
o.desplaza.call({valor:10},5);

En ambos casos, el primer parámetro es el objeto que queremos que haga de this en la llamada, podemos poner el propio objeto (o) o bien inyectarle el que nosotros queramos, en el ejemplo un nuevo objeto anónimo con un valor. La única diferencia entre las dos funciones es que la primera recibe un array como parámetro, la segunda utiliza todos los parámetros que le pasemos a partir del segundo.

Estas funciones nos pueden ser útiles, pero la que más me gusta es la función bind, que en lugar de ejecutar la llamada, nos devuelve una nueva función pero con el valor que nosotros le pasamos como this.

function objetoMejorMasClaro(v){
    this.valor=v;
    this.desplaza=function(cantidad){
                log(this.valor+" + "+cantidad+" = "+  (this.valor+cantidad));
                log("");
            }.bind(this);
}
var o3=new objetoMejorMasClaro(5);
log("o3.desplaza>>>");
o3.desplaza(10);
var d3=o3.desplaza;
d3(10);

Como he dicho en el párrafo anterior, bind utiliza el valor. Esto lo hace muy diferente de una closure pues en esta última se toma la variable. Observad la diferencia de resultados:

var fclosure, fbind;
(function(){
    var x=5;
    fclosure=function (){
        return x*x;
    }
    
    function cuadrado(){
        return this*this;
    }
    fbind=cuadrado.bind(x);
    
    x=3;
    
})();

log("cuadrado closure="+fclosure());
log("cuadrado bind="+fbind());

Los resultados son:

cuadrado closure=9
cuadrado bind=25

Así que tened cuidado con el bind!

El bind también nos será muy útil cuando definimos funciones de objetos, funciones dentro de prototype y cuando enviemos funciones a parámetros de callback, como en la ejecución de un setTimeout:

log("setTimeout sin bind y con bind >>>");
setTimeout(o.desplaza,500,5);
setTimeout(o.desplaza.bind(o),600,15);

Espero que os haya servido para aclarar algunos conceptos. Como lo mejor para conocerlos bien es jugar con el código, aquí os dejo un jsfiddle: http://jsfiddle.net/jmservera/WNWaM/

¡Disfrutad!

Reducir el tamaño de las aplicaciones JavaScript de la Tienda Windows

Compressed Car
Cuando utilizo librerías externas en mis aplicaciones de la tienda Windows suelo tirar de NuGet, es la forma más cómoda de encontrar e instalar las librerías más comunes, pero tiene dos pequeñas pegas que conviene saber antes de publicar las aplicaciones:

UTF-8

Muchas de las librerías vienen con un código de página diferente de UTF-8 + BOM. Resulta que el Kit de pruebas de Windows 8 detecta que si la página de código no es esta última baja el rendimiento y nos dará error de certificación. Este kit es el mismo que se pasa como proceso automatizado en la tienda, así que si no pasa en nuestro equipo tampoco pasará la certificación en la tienda. Resolverlo es muy sencillo, basta con volver a guardar el archivo .js como UTF8 + BOM. Para ello abrimos el archivo y en el menú de archivo seleccionamos Advanced Save Options…:
Advanced Save Options...

En las opciones avanzadas podremos cambiar el Encoding a UTF-8 with signature:
Unicode UTF-8 with signature

Archivos innecesarios

En el caso de librerías de JavaScript, los paquetes de NuGet suelen tener más archivos que la propia librería. Pueden incluir, por ejemplo, la versión completa para que podamos depurar, la versión minimizada para desplegar, algún Readme y otros archivos para que funcione mejor el intellisense de JavaScript. En el siguiente ejemplo tenemos la última versión de la librería jquery, en producción sólo necesitaremos el archivo jquery-2.0.3.min.js, el resto podemos eliminarlos del paquete, pero nos vendrá bien mantenerlos en la solución para poder depurar y tener intellisense:

Unused jquery files

Para evitar que esos archivos se incluyan en el paquete, sólo tenemos que cambiar la propiedad Package Action del archivo a None:
Package Action: none

Una vez hecho esto con todos los archivos que hemos importado con NuGet ya tendremos la solución limpia y mejor preparada para publicar en el Windows Store.

Las nuevas características de búsqueda en Windows 8.1

Windows 8.1 Preview presenta grandes novedades con respecto a la versión actual. Una que me ha chocado mucho es el nuevo sistema de búsqueda. Tiene cosas muy buenas y otras no tanto, pero tengamos en cuenta que todavía es una versión preliminar y puede cambiar mucho hasta el lanzamiento.

found dog

Desde el punto de vista del desarrollador, en la versión anterior era muy fácil y directo integrarse con el contrato de búsqueda; permitía a los usuarios buscar dentro de nuestra aplicación aunque ésta estuviera cerrada, haciendo que nuestra aplicación estuviera siempre presente.

Cookbook search old

A pesar de los esfuerzos de Microsoft por explicar las bondades del sistema de búsqueda, en el mundo real™ los usuarios no acababan de entender el sistema o simplemente no encontraban el botón de búsqueda dentro de la aplicación y pensaban que la aplicación no podía buscar. En las aplicaciones no había ninguna evidencia de que pudieran buscar y al final poníamos un botón dentro de la aplicación que abría el charm de búsqueda. Además, cuando empiezas a tener muchas aplicaciones que implementan el contrato de búsqueda la lista de posibilidades se vuelve demasiado larga para ser manejable.

Por este motivo y porque la funcionalidad del búsqueda dentro de cualquier aplicación sólo la utilizaba el 7% de los usuarios, el equipo de Windows decidió cambiar el sistema de búsqueda para mejorarlo y hacerlo más eficiente.

¿Dónde están las Apps?

El nuevo charm de búsqueda ya no muestra una lista de aplicaciones disponibles. En su lugar nos permite buscar dentro de todo el sistema, integrando los resultados del contenido indexado en nuestro equipo con otros datos devueltos por Bing, Xbox Music y otros.
Las aplicaciones antiguas que implementan el contrato de búsqueda de Windows 8 no perderán esa funcionalidad; podremos seleccionar la aplicación para que nos devuelva la página de resultados, pero sólo nos aparecerá esta opción cuando sea la aplicación activa y no estará seleccionada por defecto, pero esto último se puede cambiar.

cookbok search 0

Una vez seleccionada nuestra aplicación en lugar del “Search everywhere” podremos seguir usándola como antes, aunque no tendremos la posibilidad de cambiar a otra aplicación:

Cookbok search

¿Cómo haremos para que se pueda buscar dentro de nuestra aplicación desde cualquier sitio?

Está algo más escondido para nosotros, pero en realidad será mejor que antes, pues parece ser que podremos ofrecer resultados dentro de la búsqueda general. Todavía no está disponible en la preview que podemos probar ahora, si bien en esta charla del Build podemos ver cómo será.
Además, la funcionalidad “Search everywhere” permite encontrar nuestra aplicación si el usuario la tiene instalada y, como me suele ocurrir mucho, no recuerda exactamente el nombre:

search everywhere

¿Y cómo implemento ahora las búsquedas?

Podemos optar por seguir haciéndolo como antes o bien utilizar los nuevos controles y capacidades de búsqueda.

Charm de búsqueda

En el caso de que queramos seguir con la implementación antigua, lo recomendación es que pongamos un botón en la zona superior derecha que abra el charm de búsqueda, así nuestros usuarios sabrán que nuestra aplicación puede buscar. Al ejecutar el siguiente código, el charm estará enfocado en nuestra aplicación en lugar de en el “Search everywhere”, e incluso podemos poner un término de búsqueda por defecto:

Windows.ApplicationModel.Search.SearchPane.getForCurrentView().show("buscar...");

Todavía tenemos una opción mejor, hacer que el cuadro de búsqueda aparezca al empezar a escribir, tal como hacen las aplicaciones de calidad y la pantalla de inicio. El código para añadir esta funcionalidad es muy sencillo:

Windows.ApplicationModel.Search.SearchPane.getForCurrentView().showOnKeyboardInput = true;

El control SearchBox

Si queremos ajustarnos a las nuevas recomendaciones de búsqueda, el equipo de Windows nos lo ha puesto muy fácil. Tenemos un nuevo control de búsqueda que tiene eventos muy parecidos al contrato de búsqueda, pero es un control visual que podemos poner en nuestro UI, tanto para XAML como para HTML:

<div class="pagesearch" data-win-control="WinJS.UI.SearchBox"></div>

Una vez en nuestra página, podremos usar el código que utilizábamos antes en nuestro contrato de búsqueda que utilizaba la consulta:

var search = element.querySelector(".pagesearch").winControl;
search.onquerysubmitted = function (e) {
    console.log(e.detail.queryText);
};

Sólo que ahora tendremos que navegar nosotros a la página de resultados.

También podemos añadir sugerencias de búsqueda:

search.onsuggestionsrequested = function (e) {
    var query = e.detail.queryText.toLowerCase();
    var suggestionCollection = e.detail.searchSuggestionCollection;
    var suggestionList = ["Almería","Albacete", "Alicante"];
    for (var i = 0, len = suggestionList.length; i < len; i++) {
        if (suggestionList[i].substr(0, query.length).toLowerCase() === query) {
            suggestionCollection.appendQuerySuggestion(suggestionList[i]);
        }
    }
};

Activar el foco del control al usar el teclado en la aplicación:

search.winControl.focusOnKeyboardInput = true;

Y ofrecer resultados recomendados con imágenes y separadores para identificarlos del resto, lo que dará un toque de calidad a nuestra aplicación:

search.onsuggestionsrequested = function (e) {

    var queryText = e.detail.queryText,
        query = queryText.toLowerCase(),
        suggestionCollection = e.detail.searchSuggestionCollection;
    var suggestionList = [{
        name: "Almería",
        thumb: "http://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Bandera_de_Almer%C3%ADa.svg/200px-Bandera_de_Almer%C3%ADa.svg.png"
    },
        {
            name: "Albacete",
            thumb: "http://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Albacete-Bandera.svg/200px-Albacete-Bandera.svg.png"
        },
        {
            name: "Alicante",
            thumb: "http://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Flag_of_Alicante.svg/200px-Flag_of_Alicante.svg.png"
        }];

    for (var i = 0, len = suggestionList.length; i < len; i++) {
        if (suggestionList[i].name.substr(0, query.length).toLowerCase() === query) {
            if (suggestionCollection.size <2) {
                var reference = Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(
                    new Windows.Foundation.Uri(suggestionList[i].thumb));
                suggestionCollection.appendResultSuggestion(suggestionList[i].name,
                    "El mejor resultado!", "id1", reference, "mejor resultado");
            }
            else {
                if (suggestionCollection.size == 2)
                    suggestionCollection.appendSearchSeparator("Más sugerencias");
                suggestionCollection.appendQuerySuggestion(suggestionList[i].name);
            }
        }
    }
};

sugerencias

Por lo que podemos ver es un control muy completo, que además cachea las últimas búsquedas para ofrecerlas también como sugerencia.

El indexador de Windows

Además del nuevo control de búsqueda, podemos añadir y consultar elementos con el indexador de Windows mediante la clase ContentIndexer. Antes de que os frotéis las manos como hice yo, leed la explicación que aparece en la documentación:

Content is separated per app package, per user, and a package can’t query another package’s data.

Podemos utilizar la potencia del indexador de Windows, pero sólo para y por nuestra aplicación, los datos que añadamos al índice son privados para nuestra aplicación y usuario.

Dicho esto,

  • La clase ContentIndexer nos permite añadir elementos al índice de Windows, de manera que podremos aprovechar la potencia del indexador de Windows dentro de nuestra aplicación. Por ahora el contenido que añadamos al índice será sólo para nuestra aplicación, el resto de las aplicaciones no pueden verlo.
  • También podemos añadir contenido indexado utilizando carpetas locales y creando ficheros appcontent-ms.
  • Con las clases del espacio de nombres Windows.Storage.Search podremos realizar búsquedas dentro del contenido indexado utilizando Advanced Query Syntax, pero sólo del contenido propio.
  • Todavía no sabemos cómo podremos hacer para aparecer dentro de las búsquedas generales de Windows Search.

Conclusiones

Tras estudiar el uso que hacían de la búsqueda los usuarios de las aplicaciones de la tienda Windows, el equipo de Windows se dio cuenta que sistema necesitaba un cambio. Este cambio ha tenido algunas consecuencias pero tenemos la promesa de que va a mejorar. Mientras tanto, ya podemos aprovechar la potencia que nos proporcionan los nuevos controles y funcionalidades del sistema de búsqueda.

Referencias

Juegos sociales online con SignalR y Windows Azure (3 de 3)

Eight!
En los artículos anteriores de esta serie hemos visto cómo utilizar SignalR para proporcionar un canal de comunicaciones desde el servidor hacia el cliente. Además, modificamos el código para funcionar en Azure para mejorar la escalabilidad de nuestro servicio. Llegamos al final de la serie con un reto que me he puesto a mi mismo, utilizar lo que hemos hecho desde una aplicación de Windows 8 y que no se nos complique el artículo.

SignalR tiene librerías cliente tanto para .Net como JavaScript. Como os prometí un artículo sencillo, utilizaremos JavaScript, porque así aprovecharemos el código que ya tenemos de forma casi directa dentro de nuestra aplicación.

App de tres en raya

Como sabéis, Windows 8 te permite utilizar librerías estándar como jQuery, lo más probable es que lo que hicimos en la versión web nos funcione con muy pocos cambios. Así que… ¡Manos a la obra!
Empezamos creando una aplicación de la tienda Windows en HTML5/JavaScript:

3er.30.w8project

Para conectarnos a nuestro Hub necesitaremos las librerías cliente de SignalR. De nuevo Nuget nos ayudará a instalarlas:

3er.30.w8jsclient

HTML

El código HTML será casi el mismo que el que hicimos en nuestro default.html, sólo cambiaremos una cosa, pues no podemos utilizar la función prompt para pedir el nombre de usuario; mientras no tengamos un login, ponemos una caja de texto en pantalla. Recordad que estoy enfocando los ejemplos al uso básico de SignalR, conectarnos al sistema de autentificación ya lo haremos en otro momento:

<body>
    <label for="userName">Nombre del jugador: </label><input type="text" id="userName" />
    <div id="partida">        
...

Cliente del Hub

SignalR nos permite definir manualmente las llamadas al Hub directamente, pero también genera automáticamente un proxy con el código para nosotros en http://127.0.0.1/signalr/hubs. Si abrimos esta dirección con el Internet Explorer intentará descargarse el archivo JavaScript autogenerado:
descargarhub

Nos guardarmos el archivo en la carpeta js de nuestro proyecto para poder referenciarlo desde la página default.html. Cuando abramos el script veremos que en la línea donde se define el signalR.hub se utiliza un path relativo. Nos bastará cambiar el path por uno absoluto que apunte a nuestro servidor de Azure. Como por ahora seguimos usando el emulador, apuntamos a la dirección de bucle local 127.0.0.1:

signalR.hub = $.hubConnection("http://127.0.0.1/signalr", { useDefaultPath: false });

El script tresenraya.js también lo copiamos a la carpeta js y modificamos la llamada por la lectura en el campo username que hemos creado antes:

//prompt("Escribe tu nombre");
$username.change(null, function (e) {
    nombre = e.target.value;
});

Definimos la variable $username junto a las otras:

$username = $("#userName");

Y ya casi lo tenemos, sólo nos queda enlazar a los scripts y cambiar el tema a light para que se vea parecido a nuestra página. La etiqueta head de la página default.html de nuestra app queda como esta:

<head>
    <meta charset="utf-8" />
    <title>TresEnRapp</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0/css/ui-light.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

    <!-- TresEnRapp references -->
    <link href="/css/default.css" rel="stylesheet" />

    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <script src="Scripts/jquery.signalR-1.1.2.min.js"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="/js/hub.js"></script>
    <script src="js/tresenraya.js"></script>
    <script src="/js/default.js"></script>

</head>

Y aquí tenéis la aplicación en Windows 8 jugando contra otra en IE10:

3er.24.sidebysidew8

Forzar el uso de WebSockets

Nuestra aplicación Windows 8 ha funcionado bien, pero si miramos la salida de la consola de JavaScript veremos los siguientes mensajes:
3er.25.websockets
Tenemos dos mensajes de error. El primero proviene de una comprobación que hace jQuery para ajustarse a las capacidades de cada navegador. El segundo debe preocuparnos un poco más, nuestra aplicación no está utilizando WebSockets y podría hacerlo.
El mensaje nos lo dice claro, no hemos habilitado las llamadas cross-domain en SignalR, aunque ha sido lo suficientemente listo como para encontrar otra manera de funcionar sin WebSockets. Antes no necesitábamos habilitar las llamadas cross-domain pues el código se ejecutaba en la página proveniente del sitio, pero ahora hemos creado el front-end dentro de una app de la tienda Windows, así que la llamada es cross-domain. Para habilitarlo sólo tenemos que configurar SignalR antes de arrancarlo en el Global.asax.cs:

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableCrossDomain = true;
// Register the default hubs route: ~/signalr
RouteTable.Routes.MapHubs(hubConfiguration);

Al ejecutar de nuevo la aplicación ya habrá desaparecido el error y estaremos aprovechando todo el potencial que nos proporciona Windows 8.

¿Y ahora qué?

Tal como os prometí, hacer que nuestra app funcionara no ha sido complicado. Crear una aplicación válida para la tienda Windows y que además tenga éxito ya es otra historia.
Ahora es vuestro turno. Nos quedan, entre otras cosas, las siguientes tareas:

  • Autentificación: podemos utilizar los sistemas estándar de autentificación de usuarios y luego aplicar el atributo Authorize para permitir o denegar el acceso a métodos y servicios.
  • Control de conexión y desconexión de usuarios: SignalR nos avisa cuando ocurre una conexión/desconexión/reconexión de usuario, nosotros “sólo” tendremos que gestionar esos cambios de estado para saber si un usuario está online o no.
  • Windows 8: esta aplicación es sólo una prueba de concepto. Hacer que nuestra aplicación brille nos costará un poco más, para conseguirlo tenemos grandes consejos en el MSDN

Espero que os haya gustado. Podéis descargar el código completo del ejemplo en el siguiente enlace de Codeplex.

Formularios interactivos dentro de un ListView

En algunas aplicaciones de la Tienda Windows vemos una colección de elementos dentro de un ListView en las que uno de ellos contiene un formulario con el que podemos interactuar, por ejemplo en esta aplicación, que no sólo tiene uno sino dos:
booking

Vamos a ver cómo podemos crear un interfaz parecido en nuestras aplicaciones HTML5/JavaScript de la Tienda Windows.

Plantilla condicional

Podéis ver en el artículo sobre diseño de listas heterogéneas cómo escribir diferentes plantillas y cargarlas condicionalmente. En nuestro caso vamos a crear una plantilla adicional para el primer elemento de la lista e insertaremos un elemento select, sólo a efectos de demostrar la técnica, sirve con cualquier cosa que tengamos dentro del elemento.

Empezamos con la plantilla de Aplicación de Cuadrícula (Grid App), que ya tiene datos y un control ListView para poder realizar la demostración. Una vez creada, en la página groupedItems.html copiamos la plantilla itemTemplate justo debajo y la modificamos un poco, añadiendo la etiqueta select con tres opciones:

<div class="interactiveitemtemplate itemtemplate" data-win-control="WinJS.Binding.Template">
    <div class="item">
        <img class=" item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
        <div class="item-overlay">
            <!--<h4 class="item-title" data-win-bind="textContent: title"></h4>-->
            <select>
                <option>Opt 1</option>
                <option>Opt 2</option>
                <option>Opt 3</option>
            </select>
        </div>
    </div>
</div>

Tras añadir la plantilla adicional, podemos modificar la función ready del código de groupedItems.js para que se ejecute la nueva plantilla al crear el primer elemento del ListView (tal como comento en el artículo que he citado antes) y la plantilla general para el resto, sustituyendo el itemTemplate por una función que ejecuta una plantilla u otra según el índice:

var standardTemplate = element.querySelector(".itemtemplate").winControl;
var interactiveTemplate = element.querySelector(".interactiveitemtemplate").winControl;
listView.itemTemplate = function (itemPromise) {
    return itemPromise.then(function (item) {
        var template;
        if (item.index==0) {
            template = interactiveTemplate;
        }
        else {
            template = standardTemplate;
        }
        return template.render(item.data);
    });
};

El modo interactivo: win-interactive

Si ejecutamos el código anterior veremos el primer elemento con la plantilla que hemos aplicado pero no podremos interactuar con el selector. El comportamiento por defecto de los elementos de un ListView es que podamos pulsar sobre el elemento para realizar una navegación. Como nosotros queremos interactuar directamente con el contenido del elemento necesitamos desactivar ese comportamiento por defecto.

En este caso el truco es muy sencillo: basta con añadir la clase win-interactive al elemento que queremos activar, WinJS se encargará del resto de la magia:

<div class="interactiveitemtemplate itemtemplate" data-win-control="WinJS.Binding.Template">
    <div class="item win-interactive">
...

A partir de ahora todo el recuadro se volverá interactivo y dejará de responder como un elemento de ListView. Si queremos que el elemento contenedor siga actuando a la pulsación podemos poner la clase win-interactive sólo en el elemento select … o en el que queramos nosotros.

En otro post ya os contaré más trucos para crear efectos parecidos, pero sin un control ListView.