Front End

29 nov, 2012

Estendendo dijits do Dojo para criar widgets customizados

Publicidade

O Dojo Toolkit é uma eficiente biblioteca JavaScript™ que permite aos desenvolvedores da web criar Rich Internet Applications usando widgets orientados a objetos, com tempo de desenvolvimento e esforços mínimos. Ele vem com quatro pacotes, chamados Dojo (o principal), Dijit (a estrutura de UI), dojox (a extensão Dojo) e util. É possível usar a funcionalidade do kit de ferramentas sem alterações, ou estendê-lo e criar widgets próprios. As funcionalidades fornecidas incluem manipulação de DOM, desenvolvimento com AJAX, eventos, armazenamentos de dados e mais.

O pacote Dijit (dojo widget), a biblioteca de UI própria do Dojo, contém uma coleção de classes Dojo que permitem aos desenvolvedores criar interfaces web 2.0 eficientes e multiplataformas com esforço mínimo. Esses widgets Dijit, ou dijits, são suportados por temas de fácil manipulação. Exemplos dos dijits nesse pacote são botões, campos de texto, editores, barras de progresso e muito mais.

Usando esses dijits, por exemplo, é possível criar um formulário de envio que inclui campos de texto para nome, endereço de email e números de telefone, além de campos de data, caixas de seleção, botões e validação, tudo em questão de minutos, com conhecimento mínimo de JavaScript.

Um dos dijits mais valiosos é o Calendar, que permite exibir um calendário no contexto de um mês. Os usuários podem navegar facilmente de mês a mês, ano a ano ou pular para qualquer mês no mesmo ano para selecionar datas específicas.

Ao trabalhar no desenvolvimento de um Rich Internet Application (RIA), geralmente é possível usar o dijit inalterado. No entanto, às vezes pode ser necessário um estilo diferente (como alteração de cores ou tema), ou alterações mais complexas que podem exigir uma combinação de mudanças na funcionalidade, modelo e estilo. Esses requisitos podem ser atendidos pela criação de um widget customizado do zero, ou pela criação de um widget customizado que estende um dijit existente.

Este artigo apresenta um exercício no qual é necessário usar uma variação diferente do widget Calendar no seu website. Você criará uma classe para atender esse requisito. Este exercício usa Dojo versão 1.7 e é uma oportunidade de explorar o dijit Calendar e as maneiras de reutilizar um dijit existente com modificações mínimas, para economizar tempo de desenvolvimento. Você também verá um exemplo funcional de uma nova classe declarada no Dojo 1.7 e irá explorar algumas das funções básicas do Dojo, como manipulação de datas, hitching, publicação e assinatura e mais.

O problema

Neste exercício, você irá trabalhar em uma versão customizada do dijit Calendar com estes requisitos:

  • Calendar deve exibir apenas os dias do mês corrente (ocultar e desativar os dias dos outros meses);
  • Calendar deve exibir apenas o ano corrente (sem anos anteriores ou seguintes) na parte inferior do calendário;
  • Calendar deve exibir o nome do mês corrente na sua parte superior;
  • Os usuários não podem ir para qualquer outro mês (desativar o botão suspenso de mês na parte superior);
  • Extrair as setas exibidas na parte superior do calendário para mover de mês a mês (para a frente e para trás) e exibir setas ao lado do calendário como botões dijit. Esses dois novos botões são a única maneira de o usuário alterar o mês;
  • Há datas de limite máximo e mínimo, o que significa que todas as datas fora desse limite serão desativadas e inacessíveis;
  • Desativar o botão de navegação de mês apropriado quando uma data limite for atingida;
  • Incluir estilo especial em alguns dias do calendário;
  • Quando o usuário selecionar uma data, passá-la para uma função que processará o novo valor selecionado.

A solução é criar um widget customizado, desenvolvido pela edição do dijit Calendar usando JavaScript e CSS. A Figura 1 mostra o widget Calendar antes (esquerda) e depois (direita) de os requisitos acima terem sido aplicados.

Para isso, será necessário criar três arquivos:

  • Modelo Dijit: Uma marcação que exibirá os componentes do widget customizado;
  • Classe Dijit: Uma classe de widget criada usando declaração (JavaScript);
  • Arquivo CSS: Contendo todas as classes de folha de estilo necessárias.

A Figura 2 mostra a estrutura de arquivo e localização do widget customizado. O ponto de partida é index.html, que será o controlador do widget nesse exemplo. O arquivo simple.css conterá todos os estilos.

Criando o widget

O modelo dijit

Crie três elementos div JavaScript: um para o calendário e dois para os botões de seta para navegar mês a mês, para a frente e para trás (Listagem 1). Os divs usarão pontos de conexão (data-dojo-attach-point) para referência. Usar pontos de conexão é melhor que usar IDs, pois isso permite ter mais de uma instância do mesmo widget na mesma página sem precisar se preocupar com conflitos de ID.

<div>
<div data-dojo-attach-point="calendarPreviousMonthButtonAP"></div>
</div>

<div>
<span data-dojo-attach-point="calendarMonthOneAttachPoint"></span>
</div>

<div>
<div data-dojo-attach-point="calendarFollowingMonthButtonAP"></div>
</div>

A classe dijit

Segundo os requisitos do aplicativo, é necessário definir estas variáveis:

  • selectedDate: Valor inicial do calendário;
  • currentFocusDate: O valor que o calendário consulta para saber qual mês exibir; definido inicialmente como igual a selectedDate;
  • calendarInstance: Instância de calendário Dijit;
  • bookingWindowMaxDate: Último dia permitido no calendário;
  • bookingWindowMinDate: Primeiro dia permitido no calendário;
  • onValueSelectedPublishIDString: Cadeia de caractere que representa o canal de publicação/assinatura (ou tópico).

As funções JavaScript

Comece modificando estes elementos da folha de estilo:

  • constructor

Substitua o construtor para copiar as variáveis vindas do controlador. Use dojo/_base/lang/mixin, que irá associar os nomes de variáveis e copiar os valores nas variáveis do widget customizado (Listagem 2).

constructor: function (args){
if(args){
lang.mixin(this,args);
}
}
  • postCreate

Todas as datas serão passadas como uma cadeia de caractere no formato curto en-us de mm/dd/aaaa. Converta todas as sequências de datas em objetos de datas usando a função dojo/date/locale para selectedDate, bookingWindowMaxDate, bookingWindowMinDate (Listagem 3).

this.bookingWindowMinDate = locale.parse(this.bookingWindowMinDate, {formatLength:
'short', selector:'date', locale:'en-us'});

Crie uma instância do objeto de calendário (Listagem 4). A lógica para a criação está na função createCalendar. Você cria uma instância de um calendário dijit programaticamente e conecta-o a um div que será criado usando dojo/dom-construct (equivalente a dojo.create em uma versão anterior do Dojo). Essa é uma boa prática em geral, pois permite destruir o calendário sem perder o ponto de conexão.

return new Calendar({ value : selectedDate, currentFocus : selectedDate }, domConstruct.create("div", {}, calendarAttachPoint)); }

Observe que você está configurando o valor de currentFocus no dijit calendar. O dijit Calendar sempre mostra a data corrente do local na sua primeira tela, portanto, se você quer que o calendário exiba uma tela (data) diferente, é necessário configurar currentFocus. Portanto, para o widget customizado, é necessário configurar o valor inicial do calendário e o currentFocus como selectedDate (segundo os requisitos). Para este exemplo, o valor é um dia em agosto de 2012.

Para atender os outros requisitos, é necessário substituir estas três funções do dijit Calendar:

  • isDisabledDate

Quando o dijit Calendar está carregando uma visualização, ele passa pelos dias da visualização atual um por um (todos os 42 dias) e chama as funções isDisabledDate e getClassForDate (texto coberto) para cada dia.

A função isDisabledDate é usada para desativar algumas datas no calendário (Listagem 5) Se a função retornar true, o dia será desativado. A cada vez que o calendário é atualizado, essa função é chamada e cada dia do calendário é passado para ela. Para o widget customizado, é necessário:

  1. Desativar dias que não pertençam ao mês corrente: Para isso, você usará a função dojo/date/difference, que compara dois objetos de datas, baseados em um intervalo, e retorna 0 se forem iguais. Você irá comparar a variável currentFocusDate com cada dia na visualização atual usando o intervalo de mês e retornar true se não forem iguais, para desativar o dia.
  2. Desativar dias fora das datas limite: Use dojo/date/difference novamente, mas o intervalo definido em “day”. Se o valor de retorno for menor que bookingWindowMinDate ou maior que bookingWindowMaxDate, retorne verdadeiro para desativar a data.
isDisabledDate: function(date) {
//disable any day that doesn't belong to current month
if(dojoDate.difference(parent.currentFocusDate, date, "month")!==0){
return true;
}
if(dojoDate.difference(parent.bookingWindowMinDate, date, "day" ||
dojoDate.difference(parent.bookingWindowMaxDate, date, "day")<0){
return true;
}
else {
return false;
}
}
  • getClassForDate

Embora você tenha desativado os dias que não pertencem ao mês corrente com isDisabledDate, é necessário ocultá-los também. A função getClassForDate é usada para retornar o nome de uma classe CSS para marcar o dia de forma diferente no calendário. Para o widget customizado, é necessário indicar a selectedDate incluindo uma caixa azul com borda preta nessa data (Listagem 6). Também é necessário indicar as datas fora dos limites mínimo e máximo em cinza e ocultar os dias que não pertencem ao mês atual.

Para identificar a data que precisa ter um estilo diferente, é possível usar dojo/date/compare, que toma dois valores de data (objetos de data) e porção (cadeia de caractere) e retorna 0 se forem iguais. Aqui você irá passar currentFocusDate, o dia na iteração, e “date” como porção, pois estamos interessados apenas em comparar a data sem o registro de data e hora. Se a comparação retornar 0, essa função retornará a classe “Available”, definida no arquivo CSS (Listagem 7. Você usará seletores .class de CSS para os elementos específicos que queremos mudar.

getClassForDate: function(date) {	
	if ( dojoDate.compare(date,selectedDate,"date") === 0) {
		return "Available";
	} // apply special style
}
.AvailabilityCalendars .Calendars .CalendarDijit .Available 
	.dijitCalendarDateLabel
{
        background-color: #bccedc !important;
        border: 1px solid #000000 !important;
}

Você usará as mesmas condições se para isDisabledDate, para identificar os dias fora de limite e os dias que não pertencem ao mês atual, mas retornará o nome da classe CSS (Listagens 8 e 9).

if(dojoDate.difference(parent.currentFocusDate, date, "month")!==0){ 
	return "HiddenDay";
}
if(dojoDate.difference(parent.bookingWindowMinDate, date, "day")0){
	return "Disabled";
}
 .AvailabilityCalendars .Calendars .CalendarDijit .HiddenDay 
	.dijitCalendarDateLabel
{
    background-color: #ffffff !important;
    border-color: #ffffff;
    color: #ffffff;
}

 .AvailabilityCalendars .Calendars .CalendarDijit .Disabled 
	.dijitCalendarDateLabel
{
	background-color: #9c9c9c;
}
  • onChange

Essa função é chamada apenas quando um novo valor é definido para o calendário ou quando um dia ativado é selecionado no calendário (Listagem 10). Essa função retorna um objeto de data do dia selecionado. Você usará isso para publicar a data para outro método que irá processá-la. Chame uma função definida no widget customizado (onValueSelected) na qual será realizado o processamento necessário antes de publicar para o controlador (Listagem 11). Nesse exemplo, você apenas publicará a data para o controlador usando dojo/_base/connect/publish. A cadeia de caractere de canal (ou tópico) é armazenada na variável onValueSelectedPublishIDString.

onChange : lang.hitch(this, function(date){
this.onValueSelected(date);
})
onValueSelected : function (date){
	connect.publish(this.onValueSelectedPublishIDString, [date]);
}

Observe que você usou dojo/_base/lang/hitch para dar escopo para chamar a função onValueSelected (Listagem 10). O controlador (neste cenário, index.html) terá um assinante do canal para processar a data (Listagem 12). Neste exemplo, você apenas faz log. É possível substituir isso por qualquer outra lógica necessária.

connect.subscribe("selectedValueID", function(date){
//Do some processing
console.log("New Selected Date: ", date);
});

O dijit Calendar inclui um monthDropDownButton no cabeçalho. Esse botão exibe uma lista de todos os meses e permite que o usuário salte para qualquer mês. Para atender aos requisitos, é necessário configurar monthWidget como “disabled” para desativar o botão (Listagem 13).

this.calendarInstance.monthWidget.set("disabled", true);

Da perspectiva da usabilidade, também é necessário ocultar a seta, para que o usuário não se sinta motivado a clicar nela. Para isso, inclua classes CSS voltadas para os elementos que devem ser manipulados (Listagem 14).

.AvailabilityCalendars .Calendars .CalendarDijit .dijitDropDownButton
.dijitArrowButtonInner
{
visibility: hidden;
}

Em seguida, use classes CSS para impedir que os dígitos dos anos anterior e posterior sejam exibidos na parte inferior (Listagem 15).

.AvailabilityCalendars .Calendars .CalendarDijit .dijitCalendarPreviousYear,
.dijitCalendarNextYear
{
padding: 1px 6px;
visibility: hidden;
}

Você também irá ocultar as setas na parte superior, que permitiriam que um usuário passasse de um mês a outro (Listagem 16).

.AvailabilityCalendars .Calendars .CalendarDijit .dijitCalendarArrow
{
visibility: hidden;
}

Em seguida, crie os dois botões para que o usuário navegue pelos meses. Use dijit/form/Button e crie-os programaticamente. Para o primeiro botão (para trás), configure o rótulo como “<<” e substitua a função onClick (Listagem 17). A lógica do onClick estará na função goToPreviousMonth.

this.calendarPreviousMonthButton = new Button({
label: "<<",
onClick: lang.hitch(this, function(){
this.goToPreviousMonth(this.calendarInstance);
})
}, this.calendarPreviousMonthButtonAP);

O calendário deve ir um mês para trás sempre que o usuário clicar no botão. Em goToPreviousMonth, é necessário primeiro alterar currentFocusDate para currentFocusDate – 1 mês e depois atualizar a visualização do calendário. Por fim, é necessário verificar se esse é o último mês para exibir e, se for, desativar o botão.

Use a função dojo/date/add, que toma um objeto de data, intervalo (cadeia de caractere) e quantia (número inteiro). Nessa situação, a data será o objeto currentFocusDate, o intervalo será “mês” e a quantia será -1 (Listagem 18).

this.currentFocusDate = dojoDate.add(this.currentFocusDate,"month",-1);
calendarInstance.set("currentFocus",this.currentFocusDate);

Para definir a nova visualização do calendário, defina currentFocus com o novo valor de data. (Isso automaticamente atualiza o calendário e exibe a nova visualização).

Por fim, para verificar se essa é a última visualização do mês, compare currentFocusDate com o limite mínimo. Se for, desative o botão de retroceder. Além disso, verifique se é necessário ativar o botão para frente (caso tenha desativado antes, mas agora o usuário está saindo do limite máximo).

if(this.isLastCalendarMonth(this.bookingWindowMinDate, this.currentFocusDate)){
this.calendarPreviousMonthButton.set("disabled", true);
}
if(!this.isLastCalendarMonth(this.bookingWindowMaxDate, this.currentFocusDate)){
this.calendarFollowingMonthButton.set("disabled", false);
}

O segundo botão funciona da mesma forma. O rótulo será “>>” e onClick chama goToNextMonth, que usa a mesma função, mas adicionando um mês (Listagem 20).

goToNextMonth : function (calendarInstance){
this.currentFocusDate = dojoDate.add(this.currentFocusDate,"month",1);
calendarInstance.set("currentFocus",this.currentFocusDate);
if(this.isLastCalendarMonth(this.bookingWindowMaxDate, this.currentFocusDate)){
this.calendarFollowingMonthButton.set("disabled", true);
}
if(!this.isLastCalendarMonth(this.bookingWindowMinDate, this.currentFocusDate)){
this.calendarPreviousMonthButton.set("disabled", false);
}
}

Por fim, a Listagem 21 mostra um exemplo de como seria a classe do controlador, que faz a chamada para instanciar o novo customCalendar.

require(["myUtil/customCalendar","dojo/_base/connect"], function(myCalendar, connect){
var params = {
"bookingWindowMinDate":"10/9/2011",
"bookingWindowMaxDate":"10/9/2012",
"selectedDate":"8/15/2012",
"onValueSelectedPublishIDString":"selectedValueID"
};
var myTest = myCalendar(params);
myTest.placeAt("nodeId", "last");

connect.subscribe("selectedValueID", function(date){
//Do some processing
console.log("I got: ", date);
});

});

Como se pode ver, você está criando um objeto params com os valores necessários para passar ao widget de calendário customizado e assinando o canal.

Mais funções

Há algumas outras funções e propriedades que podem ser úteis nesse cenário:

  • Quando dojo/date/locale/isWeekend recebe um objeto de dados e um código de idioma, ele retorna true se o dia é no fim de semana (sábado e domingo para o código de idioma en-us). Isso pode ser usado para desativar os fins de semana ou colocá-los em um estilo diferente, se necessário;
  • O dijit Calendar também contém uma propriedade dayWidth que toma uma cadeia de caractere como valor. Por padrão, está configurado para “narrow”, que encurta o dia de calendário exibido, por exemplo, para “S” em vez de Segunda-feira. Outros valores são “wide” para exibir o nome completo do dia e “abbr” para abreviação (como “Seg”).

Uma variação desses requisitos de widget customizado poderia pedir para o widget exibit mais de um calendário e exibir que ambos os calendários avançassem quando o usuário clicasse para visualizar o próximo mês (Figura 3). Isso pode ser conseguido facilmente, bastando alterar as variáveis do widget para suportar um array em vez de uma variável de valor único.

Conclusão

Através de uma combinação de modificações em JavaScript e CSS, é possível criar facilmente um widget customizado para atender melhor aos requisitos de um projeto. Este artigo demonstrou essa prática usando Dojo 1.7 para declarar uma classe que estende o dijit Calendar, e explorou algumas das funcionalidades do Dojo, como manipulação de dados, hitching, publicação e assinatura, e outras funções Dojo básicas. Esperamos que você possa aplicar essas instruções para estender um dijit Dojo e criar seus próprios widgets.

 Download

Descrição Nome Tamanho Método de download
Sample application customCalendar17.zip 4 KB HTTP

 

 Recursos

Aprender

Obter produtos e tecnologias

***

Sobre o autor: Kareem Weller é engenheiro de software na IBM, atualmente parte do IBM Software Group e trabalhando em Orlando, Flórida. Ele trabalhou em várias organizações na IBM e tem cinco anos de experiência no desenvolvimento de aplicativo da web. Kareem trabalhou em vários projetos governamentais e comerciais usando diferentes tecnologias da web e produtos, como Dojo Toolkit, JSON, XML, IBM Web Content Management e Websphere Application Server.

***

Artigo original disponível em: http://www.ibm.com/developerworks/br/websphere/techjournal/1209_weller/1209_weller.html