Compartir a través de


JavaScript

Desarrollo y uso de controles en las aplicaciones de la Tienda Windows con JavaScript

Chris Sells
Brandon Satrom

Descargar el ejemplo de código

En un artículo anterior, "Enlace de datos en una aplicación para la Tienda Windows con JavaScript", nos sumergimos en la biblioteca de Windows para JavaScript (WinJS) y su compatibilidad con enlace de datos. Los enlaces de datos se usan generalmente en el contexto de un control, esta es la razón por la cual pasamos tanto tiempo involucrándonos en el cuidado y atención necesaria del control ListView (podrá ver ese artículo en msdn.microsoft.com/magazine/jj651576). En este artículo, voy a realizar una presentación rápida de los controles disponibles para usted como programador de JavaScript construyendo aplicaciones de la Tienda de Windows. Si esos controles no satisfacen sus necesidades, le mostraremos cómo crear los propios.

Al usar JavaScript para desarrollar controles en una aplicación de la Tienda Windows, puede tener acceso a controles en varias familias:

  • Elementos HTML5: los elementos HTML5 son controles en el sentido de que son fragmentos y comportamiento de UI reutilizables, por ejemplo, <progress /> y <a />.
  • Controles de WinRT: controles expuestos como clases de Windows en tiempo de ejecución (WinRT) proyectados en JavaScript, como Windows.UI.Popups.PopupMenu.
  • Controles de WinJS: controles implementados como clases de JavaScript, tales como WinJS.UI.ListView.
  • Estilos CSS: CSS proporciona varios estilos que le permiten disponer de sus elementos de contenido como si fuesen contenedores de control, por ejemplo, column-count: 4.

En este artículo nos concentraremos en las primeras tres categorías de controles.

Elementos HTML5

Dado que las aplicaciones de la Tienda de Windows creadas con JavaScript se crean en base de tecnología web, todos los elementos HTML5 funcionan bien, tal como muestra la Figura 1.

HTML5 Controls Available to Your Windows Store Apps Built with JavaScript
Figura 1 Controles HTML5 disponibles para sus aplicaciones de Tienda de Windows creadas con JavaScript

Los detalles de los elementos HTML5 van más allá del alcance de este artículo, pero le recomendamos que recurra a su documentación HTML5 de cabecera para obtener más información. Además, la muestra usada para crear la Figura1 se proporciona con el código fuente de descarga acompañante.

Controles de WinRT

Windows en tiempo de ejecución proporciona toda clase de funcionalidad en todo tipo de áreas, pero, en el caso de los controles, solo proporciona dos:

  • Diálogo de mensaje: un mensaje con un título opcional.
  • Menú emergente: un menú limitado a seis o menos elementos.

El diálogo de mensaje se invoca usando la función MessageDialog:

var popups = Windows.UI.Popups;
var mb = new popups.MessageDialog("and welcome to my message box!", "Hello!");
mb.showAsync();

La clase MessageDialog tiene un método showAsync que regresa una promesa, tal como todas las otras operaciones de async que una aplicación de Tienda de Windows creada con JavaScript tiene a su disposición. Sin embargo, en este caso estamos ignorando la promesa, puesto que no nos solemos preocupar cuando un diálogo de mensaje desaparece. La Figura 2 muestra el resultado (usando "MessageBox", la terminología anterior de MessageDialog).

The WinRT Message Dialog
Figura 2 Diálogo de mensaje de WinRT

La clase PopupMenu se usa de forma similar:

var popups = Windows.UI.Popups;
var menu = new popups.PopupMenu();
menu.commands.push(new popups.UICommand("one", null, 1));
menu.commands.push(new popups.UICommand("two", null, 2));
menu.showAsync({ x: 120, y: 360 }).done(function (e) {
  // Do something with e.label and/or e.id
  ...
});

En este ejemplo, después de crear un objeto PopupMenu, proporcionamos dos objetos UICommand, cada uno con una etiqueta y devoluciones de llamadas y parámetros id opcionales. No usaremos la llamada de devolución para cada uno de los comandos de este ejemplo, porque capturaremos el parámetro de evento en el método de finalización "listo". Un menú emergente tendrá la apariencia que esperaría que una tuviera, como se muestra en la Figura 3.

The WinRT Pop-up Menu
Figura 3 El menú emergente WinRT

Recuerde que, en el momento que se escribió esto, el menú contextual se limitará a solo seis elementos.

Controles de WinJS

Aunque los controles de HTML5 son enriquecidos y variados, el conjunto no será extensible hasta que World Wide Web Consortium decida agregar una nueva etiqueta de elemento y los proveedores de exploradores decidan implementarlos. De igual manera, el conjunto de controles WinRT tampoco es extensible (aunque puede crear componentes de Windows en tiempo de ejecución que no son de la UI). Para el conjunto de controles extensibles que se crearon específicamente con aplicaciones de la Tienda de Windows en mente, el punto de equilibrio es el conjunto proporcionado por WinJS.

Un control WinJS es uno implementado en JavaScript que proporciona cierta firma en la función de constructor:

function MyControl(element, options) {...}

El argumento es el elemento de modelo de objeto de documento (DOM) HTML, que está orientado a actuar como el host del contenido del control, habitualmente esto es un div. El argumento de las opciones es un objeto de JavaScript que se usa para proporcionar argumentos de configuración opcional, como la propiedad itemDataSource de ListView.

Para ver a WinJS en acción, consideremos a un div orientado a actuar como el host de un control DatePicker:

<div id="datePickerDiv"></div>

Con esto en su lugar, podemos crear un DatePicker tan fácilmente como se indica a continuación:

var datePicker = new WinJS.UI.DatePicker(datePickerDiv);

Y la salida es un nuevo control DatePicker, como se muestra en la Figura 4.

Output of the DatePicker Control
Figura 4 Salida del control DatePicker

Si quisiéramos configurar un control, podemos pasar un conjunto de opciones:

var datePicker = new WinJS.UI.DatePicker(datePickerDiv, { current: "6/2/1969" });

En el caso de DatePicker, la opción actual nos permite usar la fecha que se muestra actualmente, como se indica en la Figura 5.

Setting the Currently Displayed DateFigura 5 configuración de la fecha mostrada actualmente

Una vez que tenga un control asociado con su elemento HTML5, puede tomar el control usando la propiedad winControl:

var datePicker = datePickerDiv.winControl; // Magical well-known property name
datePicker.current = "5/5/1995"; // Now we're talking to the control

Es más, una vez que tenga el control, puede volver al elemento HTML5 asociado con la propiedad element:

var datePickerDiv = datePicker.element; 
datePickerDiv.style.display = "none"; 
// Now we're talking to the HTML element

Además de permitir reacción programática, cada control también permite creación declarativa a través de los atributos data-win-control y data-win-options, como vimos anteriormente:

<div id="datePicker2"
   data-win-control="WinJS.UI.DatePicker" data-win-options=
   "{current: '6/2/1969'}" ></div>

El atributo data-win-control es el nombre de la función constructora a llamar. Como pasa con el enlace de datos, que exige una llamada a WinJS.Binding.processAll que los atributos data-win-bind se analicen (consulte nuestro artículo anterior), la propiedad data-win-control necesita una llamada a WinJS.UI.processAll para analizarlo y los controles para crearlo. Esta es la razón por la que verá una llamada a WinJS.UI.processAll en todos los códigos de plantilla de proyecto generados.

el subproceso data-win-options se analiza como una sintaxis de inicialización de objeto menos poderosa. Por ejemplo, observará que, en lugar de configurar la opción actual para el control de DatePicker para crear un objeto date, pasamos el subproceso directamente. Esto se debe a que el analizador de opciones no entiende la palabra clave "nueva", este solo funciona con datos estáticos. Sin embargo, debido a que DatePicker y los otros controles de WinJS esperan que se cree en una forma declarativa, ellos hacen concesiones especiales por las limitaciones del analizador de opciones, lo cual, en el caso de DatePicker, significa tomar un subproceso y analizarlo como objeto Date para usted.

cada control tiene un conjunto distinto de opciones, le instamos a que revise la documentación para ver cuáles controles tienen cuáles opciones. La Figura 6 muestra una lista de los controles WinJS integrados.

Figura 6 Los controles WinJS

Name Descripción Clase
Barra de aplicaciones Muestra los comandos de nivel de aplicación en una barra de tareas WinJS.UI.AppBar
Selector de fecha Muestra una UI para seleccionar una fecha WinJS.UI.DatePicker
Pasar vista Pasa a través de un conjunto de contenido, de uno en uno WinJS.UI.FlipView
Ventana flotante Muestra una superposición con contenido arbitrario WinJS.UI.Flyout
Vista de lista Muestra una colección de elementos en una lista o cuadrícula, agrupados o no agrupados WinJS.UI.ListView
Calificación Muestra una UI para calificar algo, una película, por ejemplo WinJS.UI.Rating
Zoom semántico Proporciona una UI para hacer un alejamiento de una ListView a otra, por ejemplo, un alejamiento de una ListView agrupada a una lista de grupos WinJS.UI.SemanticZoom
Ventana flotante de configuración Proporciona una UI para la configuración de configuraciones de aplicaciones WinJS.UI.SettingsFlyout
Selector de fecha y hora Muestra una UI para seleccionar una hora WinJS.UI.TimePicker
Alternar conmutador Muestra una UI para escoger entre dos opciones WinJS.UI.ToggleSwitch
Información sobre herramientas (enriquecida) Muestra información sobre herramientas con contenido HTML WinJS.UI.Tooltip
Ver casilla Proporciona una región de tamaño fijo lógico escalado al espacio disponible WinJS.UI.ViewBox

La Figura 7 muestra los controles WinJS en acción.

The WinJS Controls in Action
Figura 7 Controles WinJS en acción

Siéntase libre de mezclar a gusto los controles HTML5, WinRT y WinJS en su aplicación de la Tienda de Windows.

O, si no encuentra el control que deseaba en la lista proporcionada por HTML5, Windows en tiempo de ejecución o WinJS, puede crear uno propio.

Controles personalizados

Como mencionamos, un control WinJS es solo una función que proporciona un constructor del siguiente formulario:

function MyControl(element, options) {...}

Crear tal control es un asunto de implementar una función que cree el código HTML bajo un elemento principal que se pasa como el primer argumento, usando el objeto de opciones que se pasa como segundo argumento. Por ejemplo, imagine que queremos crear un pequeño control de reloj como el que se muestra a continuación en la Figura 8.

A Custom Clock Control
Figura 8 Un control de reloj personalizado

Suponga que tenemos un conjunto div all que contiene nuestro control de reloj:

<div id="clockControl1"></div>

Tal como con los controles WinJS integrados, nos gustaría poder crear una instancia de un control personalizado, de esta manera:

var clock = new Samples.UI.ClockControl(clockControl1, { color: 'red' }); 
clock.color = 'red'; // Can set options as part of construction or later

El nombre que escogimos para nuestro control personalizado es ClockControl del espacio de nombre Samples.UI. Tal como ocurrió antes, crear el control es un asunto de pasar el elemento contenedor (clockControl1) y un conjunto opcional de pares de nombre/valor como opciones. Si después, durante el período de vida del control, deseáramos cambiar una de las opciones del control, debiéramos ser capaces de hacerlo configurando un valor de propiedad individual.

También podríamos crear controles personalizados de forma declarativa:

<script src="/js/clockControl.js"></script>
   ...
   <div id="clockControl2"
     style="width: 200px; height: 200px;"
     data-win-control="Samples.UI.ClockControl"
     data-win-options="{color: 'red'}">  </div>

Como parte de nuestra implementación, sería bueno asegurarnos de que las propiedades de elemento y winControl estén configuradas, que los miembros privados estén marcados apropiadamente y que los eventos se controlen apropiadamente. En la medida que exploramos la implementación de ClockControl, veremos como WinJS nos ayuda a implementar estas características.

Clase de control Primero, debemos asegurarnos de que ClockControl llegue al espacio de nombre correcto. Muchos lenguajes modernos manejan el concepto de espacio de nombre como una manera de separar tipos, funciones y valores en áreas con nombres distintos para evitar conflictos. Por ejemplo, si Microsoft proporciona un tipo ClockControl en WinJS 2.0, será en el espacio de nombre WinJS.UI, de manera que no entrará en conflicto con Samples.UI. En JavaScript, un nombre de espacio tan solo otro objeto con constructores, funciones y valores, que se pueden rellenar de la siguiente forma:

// clockControl.js 
(function () {
   // The hard way
   window.Samples = window.Samples || {};
   window.Samples.UI = window.Samples.UI || {};
   window.Samples.UI.ClockControl = function(element, options) { ... };
 })();

Esto funciona perfectamente. Sin embargo, la definición de nombres de espacio (y nombres de espacio anidados) es un asunto tan habitual que WinJS (así como muchas bibliotecas de JavaScript) proporciona un método abreviado:

// clockControl.js 
(function () {
   // The easy way
   WinJS.Namespace.define("Samples.UI", {
     ClockControl: function (element, options) { ... };
   };
})();

La función define en el espacio de nombre WinJS.Namespace permite definir un nuevo espacio de nombre, controlar adecuadamente el análisis de nombres punteados por usted. El segundo argumento es un objeto para definir los constructores, funciones y valores que desearíamos exponer en este espacio de nombre, que es como el constructor ClockControl en nuestro caso.

Controlar propiedades y métodos En el tipo ClockControl, nos gustaría exponer métodos y propiedades, como la propiedad color. Estos métodos y propiedades pueden ser instancias o estáticas y pueden ser públicas o privadas (al menos tan "privado" como JavaScript permite que sean los miembros de un objeto). Se brinda compatibilidad a todos estos conceptos mediante el uso correcto de la propiedad prototype del constructor y el método Object.defineProperties, los cuales son nuevos para JavaScript. WinJS proporciona un método abreviado para esto también, mediante el método define en el espacio de nombre WinJS.Class:

WinJS.Namespace.define("Samples.UI", {
  ClockControl: WinJS.Class.define(
    function (element, options) {...}, // ctor
  { // Properties and methods
    color: "black",
    width: { get: function () { ... } },
    height: { get: function () { ... } },
    radius: { get: function () { ... } },
    _tick: function () { ... },
    _drawFace: function () { ... },
    _drawHand: function (radians, thickness, length) { ... },
  })
});

El método WinJS.Class.define toma la función que opera como constructor, pero también tima el conjunto de propiedades y métodos. El método define sabe cómo crear una propiedad desde las funciones get y set proporcionadas. Adicionalmente, sabe que las propiedades o métodos prefijados con una carácter de subrayado (por ejemplo, _tick) deben ser "privadas". JavaScript en realidad no es compatible con métodos privados estrictamente hablando (por lo tanto, aún podemos llamar al método _tick). Sin embargo, no se mostrará en los bucles for-in de Visual Studio 2012 IntelliSense o en JavaScript, lo cual es, al menos, una manera práctica de señalar que no están pensados para consumo público.

El constructor establece las propiedades exigidas a un control WinJS, como se muestra en la Figura 9.

Figura 9 El constructor establece las propiedades exigidas para ser un control WinJS

WinJS.Namespace.define("Samples.UI", {
  ClockControl: WinJS.Class.define(function (element, options) {
    // Set up well-known properties
    element.winControl = this;
    this.element = element;
    // Parse the options; that is, the color option
    WinJS.UI.setOptions(this, options);
    // Create the drawing surface
    var canvas = document.createElement("canvas");
    element.appendChild(canvas);
    this._ctx = canvas.getContext("2d");
    // Draw the clock now and every second
    setTimeout(this._tick.bind(this), 0);
    setInterval(this._tick.bind(this), 1000);
  },
  ...
});

Lo primero que hace el constructor es establecer las conocidas propiedades winControl y element, de manera que un desarrollador pueda ir y volver entre el hospedaje del elemento HTML5 y el control JavaScript.

A continuación, el constructor controla las opciones. Recordará que las opciones se pueden especificar como un conjunto de pares name/value o, al usar el atributo data-win-options de HTML5, un subproceso. WinJS controla el análisis del subproceso de opciones en un objeto JavaScript, de manera que pueda lidiar estrictamente con los pares name/value. Si lo desea, se pueden extraer propiedades individuales, por ejemplo, la propiedad color en nuestro caso. Sin embargo, si tiene una lista grande de opciones, el método setOptions en el espacio de nombre WinJS.UI recorrerá todas las propiedades del objeto options y los establecerá como propiedades bajo su control. Por ejemplo, los siguientes bloques de código son equivalentes:

// Setting each property one at a time
myControl.one = "one";
myControl.two = 2;
// Setting all properties at once
WinJS.UI.setOptions(myControl, {
  one: "one",
  two: 2,
});

Después de configurar las opciones de control, el constructor tiene que crear cualquier elemento secundario del elemento principal HTML5 que necesite para hacer su trabajo. En el caso de ClockControl, usaremos el elemento canvas de HTML5 y un temporizador. La implementación de este control es HTML y JavaScript puros, por lo que no se mostrará aquí (pero está disponible en la descarga de código que acompaña a esta columna).

Controlar eventos Además de hacerlo con métodos y propiedades, un control frecuentemente expone eventos. Un evento es una notificación de un control acerca de que algo interesante acaba de pasar, como que el usuario hizo clic en el control o el control llegó a algún estado que activa otro tipo de conducta en su programa. A partir del conjunto de ejemplo establecido por el DOM HTML, querrá métodos como addEventListener y removeEventListener para permitir a los desarrolladores que se suscriban a cualquier evento que su control exponga, además de propiedades onmyevent correspondientes.

Por ejemplo, si quisiera exponer un evento de nuestro ClockControl de muestra cada 5 segundos, esperaríamos que pueda suscribirse a él de forma programática:

// Do something every 5 seconds
window.clockControl_fiveseconds = function (e) {
  ...
};
var clock = new Samples.UI.ClockControl(...);
// This style works
clock.onfiveseconds = clockControl_fiveseconds;
// This style works, too
clock.addEventListener("fiveseconds", clockControl_fiveseconds);
Declaratively, we’d like to be able to attach to custom events, too:
<!-- this style works, three -->
<div data-win-control="Samples.UI.ClockControl"
  data-win-options="{color: 'white',
    onfiveseconds: clockControl_fiveseconds}" ...>
</div>

Habilitar estos tres estilos exige dos cosas: los métodos para administrar suscripciones de evento (y para despachar eventos cuando ocurren) y una propiedad para cada evento. Ambos son proporcionados por el nombre de espacio WinJS.Class:

// clockControl.js
...
WinJS.Namespace.define("Samples.UI", {
  ClockControl: WinJS.Class.define(...);
});
// Add event support to ClockControl
WinJS.Class.mix(Samples.UI.ClockControl, 
  WinJS.UI.DOMEventMixin);
WinJS.Class.mix(Samples.UI.ClockControl,  
  WinJS.Utilities.createEventProperties("fiveseconds"));

El método mix de WinJS.Class le permite mezclar propiedades y métodos proporcionados por objetos existentes. En este caso, DOMEventMixin de WinJS.UI proporciona tres métodos:

// base.js
var DOMEventMixin = {
  addEventListener: function (type, listener, useCapture) {...},
  dispatchEvent: function (type, eventProperties) {...},
  removeEventListener: function (type, listener, useCapture) {...},
};

Una vez que mezclamos los métodos de DOMEventMixin, podemos crear las propiedades de cada uno de los eventos personalizados usando el método mix con un objeto creado por el método createEventProperties de WinJS.Utilities. Este método produce el conjunto de métodos de evento para cada uno de los nombres de evento delimitado por coma que pase, al adjuntarle el prefijo "on". Con este conjunto de propiedades y métodos proporcionados por estas dos llamadas al método mix, aumentamos nuestro control personalizado para hacerlo compatible con el elemento fiveseconds. Para distribuir un evento de este tipo desde el interior del control, llamaremos al método dispatchEvent:

// clockControl.js
...
_tick: function () {
  var now = new Date();
  var sec = now.getSeconds();
  ...
  // Fire the 5 second event
  if (sec % 5 == 0) {
    this.dispatchEvent("fiveseconds", { when: now });
  }
},
...

Llamar a dispatchEvent requiere dos parámetros: el nombre del evento y el objeto details opcional que está disponible en el evento mismo. Estamos pasando un solo valor "when", pero dado que JavaScript es como es, podríamos pasar lo que quisiéramos. Obtener acceso al detalle del evento en el controlador es un asunto de extraer el valor detail del objeto de evento mismo:

// Do something every 5 seconds
window.clockControl_fiveseconds = function (e) {
  var when = e.detail.when;
  ...
};

 

Los principios de definir los controles de WinJS que le mostramos: definir una clase en un espacio de nombre, configurar las propiedades winControl y element, procesar el objeto options, definir propiedades y métodos, además de definir y distribuir eventos personalizados, todos ellos son las mismas técnicas que el equipo de WinJS en Microsoft usó para producir los controles WinJS mismos. Puede aprender mucho acerca de cómo se crearon sus controles favoritos escribir el archivo ui.js proporcionado con WinJS.

Chris Sells es el vicepresidente de la división de herramientas de desarrollador en Telerik. Es coautor de "Building Windows 8 Apps with JavaScript” (Addison-Wesley Professional, 2012), que es la fuente de donde se adaptó este artículo. Encontrará más información acerca de Sells y sus varios proyectos en sellsbrothers.com.

Brandon Satrom es un administrador de programa en la división de UI Kendo en Telerik. Es coautor de "Building Windows 8 Apps with JavaScript” (Addison-Wesley Professional, 2012), que es la fuente de donde se adaptó este artículo. Puede seguirlo por Twitter en twitter.com/BrandonSatrom.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Chris Anderson, Jonathan Antoine, Michael Weinhardt, Shawn Wildermuth y Josh Williams