JavaScript

Compilando e usando controles em aplicativos da Windows Store com JavaScript

Chris Sells
Brandon Satrom

Baixar o código de exemplo

Em um artigo anterior, “Ligação de dados em um aplicativo da Windows Store com JavaScript”, nós abordamos a Biblioteca do Windows para JavaScript (WinJS) e seu suporte à associação de dados. A associação de dados geralmente é usada no contexto de um controle, e esse é o motivo pelo qual gastamos muito tempo nos aprofundando no cuidado e na manutenção do controle ListView (você pode ler esse artigo em msdn.microsoft.com/magazine/jj651576). Neste artigo, vamos fazer um tour rápido pelos controles disponíveis para você, um programador de JavaScript, que está compilando aplicativos da Windows Store. E, se esses controles não atenderem às suas necessidades, nós mostraremos a você como compilar o seu próprio controle.

Ao usar JavaScript para compilar controles em um aplicativo da Windows Store, você tem acesso a controles de diversas famílias:

  • Elementos do HTML5: os elementos do HTML5 são controles no sentido de que são partes reutilizáveis de interface de usuário e comportamento, por exemplo, <progress /> e <a />.
  • Controles do WinRT: controles expostos como classes do Tempo de Execução do Windows (WinRT) projetados em JavaScript, como Windows.UI.Popups.PopupMenu.
  • Controles do WinJS: controles implementados como classes JavaScript, como WinJS.UI.ListView.
  • Estilos CSS: o CSS fornece diversos estilos que permitem que você disponha os itens de conteúdo como se eles fossem contêineres de controle, por exemplo, column-count: 4.

Neste artigo, nos concentraremos nas primeiras três categorias de controles.

Elementos do HTML5

Como os aplicativos da Windows Store compilados com JavaScript baseiam-se em tecnologia da Web, todos os elementos do HTML5 funcionam adequadamente, conforme mostrado na Figura 1.


Figura 1 - Controles do HTML5 disponíveis para seus aplicativos da Windows Store compilados com JavaScript

Os detalhes dos elementos do HTML5 estão além do escopo deste artigo, mas recomenda-se a documentação sobre HTML5 de seu amigável ambiente para obter mais informações. Além disso, o exemplo usado para criar a Figura1 é fornecido com o download do código-fonte que acompanha este artigo.

Controles do WinRT

O Tempo de Execução do Windows fornece todos os tipos de funcionalidades em todos os tipos de áreas, mas no caso de controles, ele fornece apenas dois:

  • Caixa de diálogo de mensagem: uma mensagem com título opcional.
  • Menu pop-up: um menu limitado a, no máximo, seis itens.

A caixa de diálogo de mensagem é invocada por meio da função MessageDialog:

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

A classe MessageDialog possui um método showAsync que retorna uma promessa, da mesma maneira que todas as outras operações assíncronas que um aplicativo da Windows Store compilado com JavaScript tem à sua disposição. Nesse caso, entretanto, vamos ignorar a promessa porque geralmente não nos preocupamos quando uma caixa de diálogo de mensagem desaparece. A Figura 2 mostra o resultado (usando “MessageBox”, que é a terminologia anterior para MessageDialog).


Figura 2 - A caixa de diálogo de mensagem do WinRT

A classe PopupMenu é utilizada de maneira semelhante:

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
  ...
});

Nesse exemplo, depois de criar um objeto PopupMenu, dois objetos UICommand são fornecidos, cada um com um rótulo e parâmetros opcionais de retornos de chamada e id. Não estamos usando o retorno de chamada para todos os comandos nesse exemplo porque estamos capturando o parâmetro do evento no método de conclusão “done”. Um menu pop-up tem a aparência esperada, conforme exibido na Figura 3.


Figura 3 - O menu pop-up do WinRT

Lembre-se de que, até o momento da publicação deste artigo, o menu de contexto era limitado a apenas seis itens.

Controles do WinJS

Embora os controles do HTML5 sejam avançados e variados, o conjunto não será extensível até que o World Wide Web Consortium decida adicionar uma nova marca de elemento e o os fornecedores do navegador decidam implementá-la. Da mesma maneira, o conjunto de controles do WinRT também não é extensível, embora seja possível compilar componentes do Tempo de Execução do Windows que não sejam da interface do usuário. Para o conjunto de controles extensível que foi compilado considerando especificamente aplicativos da Windows Store, o ponto principal é o conjunto fornecido pelo WinJS.

Um controle do WinJS é um controle implementado em JavaScript que fornece uma determinada assinatura na função de construtor:

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

O argumento element é o elemento DOM (Document Object Model) HTML, cuja finalidade é agir como host para o conteúdo do controle, geralmente, um div. O argumento options é um objeto JavaScript usado para fornecer argumentos de configuração opcionais, como a propriedade itemDataSource de ListView.

Para visualizar um controle do WinJS em ação, vamos considerar um div destinado a agir como o host de um controle DatePicker:

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

Com isso em vigor, nós podemos criar um DatePicker tão facilmente quanto isto:

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

E o resultado é um novo controle DatePicker, conforme exibido na Figura 4.


Figura 4 - Saída do controle DatePicker

Se desejarmos configurar um controle, podemos passar em um conjunto de opções:

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

No caso de DatePicker, a opção atual permite definir a data em exibição no momento, conforme mostrado na Figura 5.


Figura 5 - Definindo a data exibida atualmente

Após associar um controle o seu elemento do HTML5, você pode capturar o controle usando a propriedade winControl:

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

Além disso, após obter o controle, você pode voltar ao elemento do HTML5 associado com a propriedade element:

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

Além de permitir a criação programática, cada controle também fornece criação declarativa por meio dos atributos data-win-control e data-win-options, conforme mostrado a seguir:

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

O atributo data-win-control é o nome da função do construtor a ser chamada. Da mesma maneira que na associação de dados, que exige uma chamada para WinJS.Binding.processAll para que os atributos data-win-bind sejam analisados (consulte nosso artigo anterior), a propriedade data-win-control exige uma chamada para WinJS.UI.processAll para que ela seja analisada e os controles sejam criados. É por isso que você verá uma chamada para WinJS.UI.processAll em todos os códigos de modelo de projeto gerados.

A cadeia de caracteres de data-win-options é analisada como uma sintaxe de inicialização de objeto JavaScript menos potente. Por exemplo, você perceberá que, em vez de definir a opção atual para o controle DatePicker criando um objeto Date, nós passamos a cadeia de caracteres diretamente. Isso acontece porque as o analisador de opções não compreende a “nova” palavra-chave — ele funciona apenas com dados estáticos. Entretanto, como o DatePicker e os outros controles do WinJS esperam ser criados de uma maneira declarativa, eles fazem permissões especiais para as limitações do analisador de opções que, nesse caso do DatePicker, significa obter uma cadeia de caracteres e analisá-la como um objeto Date para você.

Todo controle tem um conjunto de opções diferente e será indica a você a documentação para verificar as opções de cada controle. A Figura 6 exibe uma lista dos controles internos do WinJS.

Figura 6 - Os controles do WinJS

Name Description Class
App Bar Exibe comandos de nível de aplicativo em uma barra de ferramentas WinJS.UI.AppBar
Date Picker Exibe uma interface de usuário para selecionar uma data WinJS.UI.DatePicker
Flip View Passa por um conjunto de conteúdos, um item de cada vez WinJS.UI.FlipView
Flyout Exibe uma sobreposição com conteúdo arbitrário WinJS.UI.Flyout
Modo de Exibição em Lista Exibe um conjunto de itens em uma lista ou grade, agrupados ou não WinJS.UI.ListView
Classificação Exibe uma interface de usuário para classificar algo, por exemplo, um filme WinJS.UI.Rating
Zoom Semântico Fornece uma interface de usuário para aplicar zoom de um ListView para outro, por exemplo, um ListView reduzindo para uma lista de grupos WinJS.UI.SemanticZoom
Settings Flyout Fornece uma interface de usuário para a definição de configurações do aplicativo WinJS.UI.SettingsFlyout
Time Picker Exibe uma interface de usuário para selecionar um horário WinJS.UI.TimePicker
Toggle Switch Exibe uma interface de usuário para selecionar entre duas opções WinJS.UI.ToggleSwitch
Tooltip (Rich) Exibe uma dica de ferramenta com conteúdo HTML arbitrário WinJS.UI.Tooltip
View Box Fornece uma região com tamanho fixado logicamente dimensionado para o espaço disponível WinJS.UI.ViewBox

A Figura 7 mostra os controles do WinJS em ação.


Figura 7 - Os controles do WinJS em ação

Você deve ficar à vontade para misturar e combinar controles do HTML5, do WinRT e do WinJS em seu aplicativo da Windows Store.

Ou, se não encontrar o controle desejado na lista fornecida pelo HTML5, pelo Tempo de Execução do Windows ou pelo WinJS, você pode compilar o seu próprio controle.

Controles personalizados

Conforme mencionado, um controle do WinJS é apenas uma função que fornece um construtor da seguinte maneira:

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

Compilar um controle como esse é uma questão de implementar uma função para criar o HTML sob o elemento pai transmitido como o primeiro argumento e usar o objeto options transmitido como o segundo argumento. Por exemplo, imagine que desejamos compilar um controle de relógio pequeno, conforme o exibido na Figura 8.


Figura 8 - Um controle de relógio personalizado

Suponha que temos um div todo configurado para conter nosso controle de relógio:

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

Da mesma maneira que os controles internos do WinJS, nós gostaríamos de poder criar uma instância de um controle personalizado, desta maneira:

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

O nome escolhido para o nosso controle personalizado é ClockControl a partir do namespace Samples.UI. Como antes, a criação do controle é uma questão de transmitir o elemento contido (clockControl1) e um conjunto opcional de pares de nome/valor para as opções. Se, posteriormente, no tempo de vida do controle, quisermos alterar uma das opções do controle, nós devemos conseguir fazer isso definindo um valor de propriedade individual.

Nós também desejamos conseguir criar controles personalizados de maneira 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 nossa implementação, gostaríamos de ter certeza de que as propriedades winControl e element estão configuradas, que os membros privados estão marcados de maneira apropriada e que os eventos podem ser tratados adequadamente. À medida que nos aprofundarmos na implementação do ClockControl, veremos como o WinJS nos ajuda a implementar esses recursos.

Classe de controle Primeiro, precisaremos nos certificar de que o ClockControl obteve êxito no namespace correto. A maioria das linguagens modernas tem o conceito de um namespace como uma maneira de separar tipos, funções e valores em diferentes áreas nomeadas para evitar colisões. Por exemplo, se a Microsoft fornece um tipo ClockControl no WinJS 2.0, ele estará no namespace WinJS.UI e, por isso, não colidirá com o Samples.UI. No JavaScript, um namespace é apenas outro objeto com construtores, funções e valores que podem ser preenchidos desta maneira:

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

Isso funciona muito bem. Entretanto, definir namespaces (e namespaces aninhados) é uma coisa tão comum de se fazer que o WinJS (como muitas bibliotecas do JavaScript) fornece um atalho:

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

A função define no namespace WinJS.Namespace permite a definição de um novo namespace, lidando adequadamente com a análise de nomes com pontos para você. O segundo argumento é um objeto para definir os construtores, as funções e os valores que desejamos expor a partir desse namespace, que, nesse caso, é o construtor ClockControl.

Propriedades e métodos de controle Em nosso tipo ClockControl, gostaríamos de expor métodos e propriedades, como a propriedade color. Esses métodos e essas propriedades podem ser instâncias ou estáticas e, também, podem ser públicas ou privadas (pelo menos, tão “privada” quanto o Java­Script permite que os membros de um objeto sejam). Todos esses conceitos têm suporte por meio do uso correto da propriedade de protótipo do construtor e do método Object.defineProperties novo para JavaScript. O WinJS fornece um atalho para isso também, por meio do método define no namespace 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) { ... },
  })
});

O método WinJS.Class.define obtém a função que age como construtor, mas também obtém o conjunto de propriedades e métodos. O método define sabe como criar uma propriedade a partir das funções get e set fornecidas. Além disso, ele sabe que as propriedades e os métodos prefixados com um sublinhado — por exemplo, _tick — são indicados para serem “privados”. O JavaScript não oferece suporte real a métodos privados no sentido tradicional, ou seja, nós ainda podemos chamar o método _tick. Entretanto, eles não aparecem no Visual Studio 2012 IntelliSense nem em loops for-in do JavaScript, o que é, pelo menos, uma maneira prática de sinalizar que eles não são indicados para o consumo público.

O construtor configura as propriedades necessárias para ser um controle do WinJS, conforme exibido na Figura 9.

Figura 9 - O construtor configura as propriedades necessárias para ser um controle do 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);
  },
  ...
});

A primeira coisa que o construtor faz é configurar o conhecido winControl e as propriedades element, de forma que um desenvolvedor possa avançar e voltar entre elemento de hospedagem do HTML5 e o controle do JavaScript.

Em seguida, o construtor trata das opções. Você se lembrará de que as opções podem ser especificadas como um conjunto de pares de nome/valor ou — usando o atributo data-win-options do HTML5 — uma cadeia de caracteres. O WinJS trata da análise da cadeia de caracteres de opções em um objeto JavaScript, de forma que você possa lidar apenas com os pares de nome/valor. Se desejar, você pode extrair propriedades individuais, por exemplo, a propriedade color em nosso caso. Entretanto, se tiver uma grande lista de opções, o método setOptions no namespace WinJS.UI percorrerá todas as propriedades no objeto options e as definirá como propriedades em seu controle. Por exemplo, os blocos de código a seguir são 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,
});

Após definir as opções do controle, é tarefa do construtor criar todos os elementos filho do elemento pai do HTML5 de que ele necessitar para realizar o trabalho. No caso de ClockControl, estamos usando o elemento canvas do HTML5 e um timer. A implementação desse controle é apenas o bom e velho HTML e o JavaScript e, por isso, ele não está exibido aqui, mas está disponível no código para download que acompanha este artigo.

Eventos de controle Além de métodos e propriedades, um controle geralmente expõe eventos. Um evento é alguma notificação de seu controle sobre algo interessante que ocorreu, como o usuário ter clicado no controle ou o controle ter atingido algum estado que aciona algum outro tipo de comportamento em seu programa. Com base no exemplo definido pelo DOM HTML, você desejará métodos como addEventListener e removeEventListener para permitir que os desenvolvedores assinem a todos os eventos que o seu controle expõe, bem como propriedades onmyevent correspondentes.

Por exemplo, se desejarmos expor um evento a partir de nosso ClockControl de exemplo a cada 5 segundo, esperamos que seja possível inscrever-se para isso de maneira 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 todos esses três estilos exige duas coisas: os métodos para gerenciar assinaturas de eventos (e para a expedição dos eventos quando eles ocorrem) e uma propriedade para cada evento. Ambos são fornecidos pelo namespace 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"));

O método mix de WinJS.Class permite que você misture propriedades e métodos fornecidos por objetos existentes. Nesse caso, o DOMEventMixin de WinJS.UI fornece três métodos:

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

Após misturar os métodos de DOMEventMixin, nós podemos criar as propriedades para cada um dos eventos personalizados usando o método mix com um objeto criado pelo método createEventProperties de WinJS.Utilities. Esse método gera o conjunto de métodos de eventos para cada um dos nomes de evento delimitados por vírgulas transmitidos por você, acrescentando o prefixo “on”. Com esse conjunto de propriedades e métodos fornecido por essas duas chamadas para o método mix, nós ampliamos nosso controle personalizado para dar suporte ao evento fiveseconds. Para expedir um evento desse tipo a partir do controle, nós chamamos o 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 });
  }
},
...

A chamada do método dispatchEvent utiliza dois parâmetros: o nome do evento e o objeto de detalhes opcionais disponíveis no próprio evento. Estamos passando um único valor “when”, mas com o JavaScript sendo Java­Script, é possível passar tudo o que desejarmos. Acessar detalhes do evento no manipulador é uma questão de extrair o valor de detail do objeto do próprio evento:

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

Os princípios de definição de controles do WinJS mostrados a você — definição de uma classe em um namespace, definição das propriedades winControl e element, processamento do objeto options, definição de propriedades e métodos e definição e expedição de eventos personalizados — são exatamente as mesmas técnicas que a equipe do WinJS na Microsoft utiliza para gerar controles do WinJS. Aprenda muito mais sobre como seus controles favoritos foram criados lendo o arquivo ui.js fornecido com o WinJS.

Chris Sells é vice-presidente da Divisão de ferramentas para desenvolvedores da Telerik. Ele é coautor de “Building Windows 8 Apps with JavaScript” (Addison-Wesley Professional, 2012), a partir do qual este artigo foi adaptado. Obtenha mais informações sobre Sells e seus vários projetos em sellsbrothers.com.

Brandon Satrom é gerente de programas na Divisão de Kendo UI da Telerik. Ele é coautor de “Building Windows 8 Apps with JavaScript” (Addison-Wesley Professional, 2012), a partir do qual este artigo foi adaptado. Você pode segui-lo no Twitter, em twitter.com/BrandonSatrom.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Chris Anderson, Jonathan Antoine, Michael Weinhardt, Shawn Wildermuth e Josh Williams