Diseño de listas heterogéneas en Windows 8

  • Por
  • 30 de julio de 2012
  • Valoración:
  • 1 Comentarios
  • Javascript, Windows
En las aplicaciones de estilo Metro se hace mucho hincapié en el diseño de las listas y colecciones de datos.
Las plantillas que nos proporciona Visual Studio Express for Windows 8 nos permiten representar listas de datos de forma muy visual y limpia. Se evitan las líneas de separación y se utilizan el espaciado y los tamaños de fuente para crear orden, grupos y que el aspecto visual sea muy limpio.

Estas plantillas vienen muy bien para empezar, pero luego nos surgen necesidades naturales, como por ejemplo ¿Cómo resaltar un elemento dentro de una lista? ¿Cómo representar elementos con información diferente dentro de una misma colección?

Si nos fijamos en algunas aplicaciones que ya tenemos en Windows 8, dentro de una colección podemos tener diferentes representaciones de los elementos, dependiendo de la importancia, tipo de elemento, etc. Por ejemplo, en la tienda de aplicaciones Windows vemos iconos más grandes para los elementos destacados:

Conseguir este efecto puede parecer muy complicado desde la plantilla, pues todos los elementos utilizan la misma. Por suerte, la librería WinJS nos proporciona las herramientas necesarias para poder controlar la representación de nuestros elementos mediante diferentes mecanismos:

  • Uso de CSS dinámico a través de bindings
  • Creación de plantillas dinámicas a partir de una función
  • Combinación de funciones y plantillas HTML

Primer paso, crear una aplicación base

Como primer paso para los tres ejemplos, vamos a crear una aplicación tipo Grid que ya tiene un elemento ListView y datos de ejemplo:


var sampleItems = [
{ group: sampleGroups[0], importancia:"alta", title: "Item Title: 1", su
{ group: sampleGroups[0], importancia:"baja", title: "Item Title: 2", su
{ group: sampleGroups[0], importancia: "baja", title: "Item Title: 3", s
{ group: sampleGroups[0], importancia: "media", title: "Item Title: 4",
{ group: sampleGroups[0], title: "Item Title: 5", subtitle: "Item Subtit

{ group: sampleGroups[1], importancia: "baja", title: "Item Title: 1", s
{ group: sampleGroups[1], importancia: "baja", title: "Item Title: 2", s
{ group: sampleGroups[1], importancia: "alta", title: "Item Title: 3", s

{ group: sampleGroups[2], importancia: "alta", title: "Item Title: 1", s
{ group: sampleGroups[2], title: "Item Title: 2", subtitle: "Item Subtit
{ group: sampleGroups[2], title: "Item Title: 3", subtitle: "Item Subtit
{ group: sampleGroups[2], title: "Item Title: 4", subtitle: "Item Subtit

Nota: Vamos a utilizar este proyecto con la modificación realizada en los datos para los tres ejemplos que siguen. Si queremos seguir el ejercicio es recomendable empezar desde el proyecto limpio cada vez.

CSS dinámico mediante enlaces de datos

En algunos casos la representación que queremos hacer de los datos es lo suficientemente sencilla como para aprovechar la misma plantilla, cambiando sólo el estilo CSS de la misma. Si ese es nuestro caso, una forma muy sencilla y directa es utilizar enlaces de datos para aplicar el CSS.

Como ahora tenemos un campo por el que realizar una selección, podemos ir al código HTML y cambiar la plantilla. Abrimos la página groupedItems.html y buscamos el elemento <div> que tiene la clase itemtemplate. En este elemento vamos a añadir un enlace de datos al elemento item-title que quedará de la siguiente manera:

<h4 class="item-title" data-win-bind="textContent: title; className: importancia"></h4>

La propiedad className se corresponde en JavaScript al atributo class de HTML. La hemos enlazado al campo importancia que hemos creado en algunos elementos de la lista anterior. De esta manera, dependiendo del elemento, la clase se redefinirá. Ahora sólo nos queda generar tantas clases CSS como necesitemos. En el archivo groupedItems.css añadimos estas tres clases:

.alta {
margin: -6px -15px -2px -15px;
padding: 6px 15px 2px 15px;
-ms-grid-row: 1;
overflow: hidden;
width: 250px;
background-color: red;
}
.media {
margin: -6px -15px -2px -15px;
padding: 6px 15px 2px 15px;
-ms-grid-row: 1;
overflow: hidden;
width: 250px;
background-color: blue;
}
.baja {
margin: -6px -15px -2px -15px;
padding: 6px 15px 2px 15px;
-ms-grid-row: 1;
overflow: hidden;
width: 250px;
background-color: green;
}

Y así conseguiremos un aspecto diferente según la importancia del elemento:


Plantillas dinámicas a partir de una función

El método anterior tiene bastantes limitaciones, pues sólo nos permite modificar los elementos ya existentes en la plantilla. Si necesitamos que los elementos de diseño sean diferentes dependiendo del ítem que estemos representando, podemos proporcionar la plantilla en forma de función. De esta manera podremos generar el HTML correspondiente para cada elemento.

En el ejemplo, vamos al script groupedItems.js y buscamos la función ready. Encontraremos la línea donde se asigna el itemTemplate al elemento listView, por ahora se le asigna el elemento a partir de la plantilla que tenemos en el HTML. Sustituimos esa línea por el siguiente código:

listView.itemTemplate = function (itemPromise) {
return itemPromise.then(function (item) {

var div = document.createElement("div");
div.style.height = "250px";
div.style.width = "250px";
var childDiv = document.createElement("div");
var title;
switch (item.data.importancia) {
case "alta":
div.style.height = "510px";
childDiv.style.padding = "10px";
title = document.createElement("h2");
div.style.backgroundColor = "red";
break;
case "media":
div.style.width = "510px";
childDiv.className = "item-overlay";
title = document.createElement("h3");
div.style.backgroundColor = "blue";
break;
case "baja":
childDiv.className = "item-overlay";
title = document.createElement("h4");
div.style.backgroundColor = "green";
break;
default:
div.style.msGridRows = "1fr 90px";
div.style.backgroundColor = "rgb(250,150,0)";
title = document.createElement("h5");
childDiv.style.backgroundColor = "rgba(0,0,0,0.5)";
childDiv.style.msGridRow = "2";
childDiv.style.padding = "10px";
var image = new Image();
image.src = "/images/logo.png";
image.style.width = "100%";
image.style.height = "100%";
image.style.msGridRowSpan = "2";
div.appendChild(image);
break;
}

title.className = "item-title";
title.innerText = item.data.title;
childDiv.appendChild(title);
div.appendChild(childDiv);

return div;
});
};

Vemos como la función asignada a la plantilla del elemento recibe un objeto Promise. Esto quiere decir que se ejecuta de forma asíncrona. Esta función recibirá el elemento que se está representado, en el campo data tendremos el ítem que se debe representar. Dentro de la función hemos creado un selector por el campo importancia; dependiendo de la importancia del ítem estamos generando elementos diferentes, con diferentes tamaños y contenido. Los diferentes tamaños se ven representados en la lista, pero no como nos esperamos que se vean:

Para que el elemento ListView sepa mejor como apiñar los elementos, tenemos que proporcionarle algo más de información. Debemos activar la capacidad de expandir los elementos entre celdas y proporcionarle el tamaño del elemento mínimo; el resto de elementos deberían ser un múltiplo de este. Para que el rendimiento sea aceptable, debemos buscar el elemento más grande que cumpla con los requisitos, pues si ponemos 1x1 funcionará, pero penalizará el rendimiento una barbaridad. La fórmula es la siguiente:

tamañoElemento = ((tamañoCelda + margen) x multiplo) - margen

Así, para nuestro caso, el tamaño mínimo es 250x250 (añadiendo el margen, el siguiente tamaño sería 510x250 o 250x510). Localizamos en la función initializeLayout la segunda inicialización del listview.layout, donde se asigna el GridLayout y lo modificamos como sigue:

listView.layout = new ui.GridLayout({
groupHeaderPosition: "top",
groupInfo: function () {
return {
enableCellSpanning: true,
cellWidth: 250,
cellHeight: 250
};
}

});

Ahora al ejecutar la aplicación ya podemos ver los elementos bien colocados.


Múltiples plantillas HTML

Utilizando una función hemos conseguido el efecto que queríamos, pero a nuestro diseñador no le alegrará la noticia, pues le tendremos que explicar que debe escribir JavaScript para crear el HTML. Por suerte, tenemos un método que nos permitirá mantener el buen rollo con nuestro compañero diseñador: podemos seleccionar por código la plantilla adecuada de entre las que tengamos definidas en la página.

Para conseguir el mismo efecto con plantillas y evitar a las hordas de diseñadores enfandados, vamos a crear una plantilla para cada estado en la página groupedItems.html:

<div class="verybigitemtemplate" data-win-control="WinJS.Binding.Template">
<div class="verybigitem">
<div class="iteminfo">
<h2 class="itemtitle" data-win-bind="textContent: title"></h2>
</div>
</div>
</div>
<div class="bigitemtemplate" data-win-control="WinJS.Binding.Template">
<div class="bigitem">
<div class="iteminfo">
<h3 class="itemtitle" data-win-bind="textContent: title"></h3>
</div>
</div>
</div>
<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<div class="iteminfo">
<h4 class="itemtitle" data-win-bind="textContent: title"></h4>
</div>
</div>
</div>
<div class="undefineditemtemplate" data-win-control="WinJS.Binding.Template">
<div class="undefItem">
<img class="itemimage" src="/images/logo.png" />
<div class="iteminfo">
<h5 class="itemtitle" data-win-bind="textContent: title"></h5>
</div>
</div>
</div>

Ahora que tenemos una plantilla para cada estado, necesitamos definir por CSS el estilo de cada una. Como hemos creado una clase para cada elemento, podremos en el groupedItems.css definir tamaños, colores, etc, para cada tipo de elemento:

.verybigitem {
height:510px;
width:250px;
background-color:red;
}

.bigitem {
height:250px;
width:510px;
background-color:blue;
}

.item {
height: 250px;
width: 250px;
background-color: green;
}

.undefItem {
display: -ms-grid;
-ms-grid-rows: 1fr 90px;
height: 250px;
width: 250px;
background-color: rgb(250,150,0);
}

Para que estas definiciones de CSS tengan efecto, quitamos la definición para la clase .win-item, podemos eliminarla o simplemente comentarla, fijaos que la definición es muy parecida a la que tenemos para el .undefItem:

/*.groupeditemspage .groupeditemslist .win-item {
-ms-grid-columns: 1fr;
-ms-grid-rows: 1fr 90px;
display: -ms-grid;
height: 250px;
width: 250px;
}*/

Ahora necesitamos un poco de código en la asignación de la plantilla, pero tranquilos, nuestros diseñadores ni se enterarán:

listView.itemTemplate = function (itemPromise) {
var bigtemplate = element.querySelector(".bigitemtemplate").winControl;
var verybigtemplate = element.querySelector(".verybigitemtemplate").winControl;
var smalltemplate = element.querySelector(".itemtemplate").winControl;
var undefinedtemplate = element.querySelector(".undefineditemtemplate").winControl;
return itemPromise.then(function (currentItem) {
var item = currentItem.data; // aquí tenemos el elemento.
var itemTemplate;

switch(item.importancia){
case "alta":
itemTemplate = verybigtemplate;
break;
case "media":
itemTemplate = bigtemplate;
break;
case "baja":
itemTemplate = smalltemplate;
break;
default:
itemTemplate = undefinedtemplate;
break;
}

return itemTemplate.render(item);
});
};

El truco está en recuperar los elementos de tipo plantilla y luego llamar a la función "render" del mismo. Así será la propia plantilla que generará el HTML junto con los enlaces de datos que lleve hacia el elemento que le estamos pasando.

Finalmente, por si no lo habíamos hecho en el ejercicio anterior, comprobamos que el GridLayout está configurado para admitir tamaños diferentes de elemento:

listView.layout = new ui.GridLayout({
groupHeaderPosition: "top",
groupInfo: function () {
return {
enableCellSpanning: true,
cellWidth: 250,
cellHeight: 250
};
}

});

Y aquí tenemos el resultado. Le falta un poco de diseño (márgenes, colores adecuados, etc...) pero eso ya se lo dejaremos al diseñador a cambio de haberle liberado de escribir JavaScript.


Conclusiones

El diseño de listas en Windows 8 es muy importante y con poco esfuerzo podemos conseguir resultados muy atractivos. Como siempre, hay muchas maneras de conseguir el mismo efecto y en este artículo hemos aprendido tres técnicas diferentes que podremos utilizar e incluso combinar según nos convenga.
 

Código de ejemplo

Como es habitual, puedes descargar el código de ejemplo desde Codeplex en http://ejemplosw8js.codeplex.com/releases/view/91699
 

Referencias (ing)

Autor

Juan Manuel Servera

Technical Manager en el Microsoft Innovation Center | Tourism Technologies

Comentarios

Federico Navarrete

24/12/2012
Con el Full de VS
No funciona bien falla bastante en general la apariencia.

Compartir