Estendendo dijits do Dojo para criar widgets customizados

Este artigo descreve o que é possível fazer quando um dijit em particular do kit de ferramentas Dojo não atende aos seus requisitos e é necessário criar seu próprio widget. Ao final, usando um exemplo com um conjunto de requisitos e uma abordagem para atendê-los, você estará familiarizado com o uso de um dijit e outras funcionalidades básicas do Dojo e saberá como declarar seu próprio widget. Este conteúdo é parte do IBM WebSphere Developer Technical Journal.

Kareem Weller, Staff Software Engineer, IBM

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.



08/Out/2012

Introdução

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.

Figura 1. Comparação de um dijit Calendar padrão para um widget Calendar customizado
Comparação de um dijit Calendar padrão para um widget Calendar customizado

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.

Figura 2. Estrutura do arquivo
Estrutura do arquivo

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.

Listagem 1. O modelo dijit
<div class="CalendarArrow">
<div data-dojo-attach-point="calendarPreviousMonthButtonAP"></div>
</div>

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

<div class="CalendarArrow">
<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).

    Listagem 2. O construtor
    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).

    Listagem 3. Usando dojo/date/locale
    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.

    Listagem 4. Retornando uma instância de dijit/Calendar
    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:

    • 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.
    • 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.
      Listagem 5. Substituindo isDisabledDate
      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.

    Listagem 6. Substituindo getClassForDate
    getClassForDate: function(date) {	
    	if ( dojoDate.compare(date,selectedDate,"date") === 0) {
    		return "Available";
    	} // apply special style
    }
    Listagem 7. Classe CSS para marcar dias disponíveis
    .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).

    Listagem 8. Ocultando e desativando dias
    if(dojoDate.difference(parent.currentFocusDate, date, "month")!==0){ 
    	return "HiddenDay";
    }
    if(dojoDate.difference(parent.bookingWindowMinDate, date, "day")<0 || 
    	dojoDate.difference(parent.bookingWindowMaxDate, date, "day")>0){
    	return "Disabled";
    }
    Listagem 9. Classe CSS para marcar dias ocultos e desativados
     .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.

    Listagem 10. Usando onChange do Calendar e hitch
    onChange : lang.hitch(this, function(date){
    	this.onValueSelected(date);
    })
    Listagem 11. Usando publish
    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.

    Listagem 12. Assinante da nossa publicação
    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).

Listagem 13. Desative o botão suspenso no cabeçalho de Calendar
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).

Listagem 14. Classe CSS para ocultar a seta do botão suspenso
 .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).

Listagem 15. Classe CSS para ocultar os dígitos dos anos na parte inferior de Calendar
 .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).

Listagem 16. Classe CSS para ocultar as setas dos meses na parte superior de Calendar
 .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.

Listagem 17. Criando uma instância de dijit/form/bottom
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).

Listagem 18. Visualização do calendário diminuindo um mês
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) (Listagem 19).

Listagem 19. Verifique se os novos botões de navegação devem ser desativados
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).

Listagem 20. Função que controla o botão para passar para o mês seguinte
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.

Listagem 21. Captura instantânea de como criar uma instância do novo widget de calendário customizado
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.

Figura 3. Mais de um calendário por visualização
Mais de um calendário por visualização

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çãoNomeTamanho
Sample applicationcustomCalendar17.zip4 KB

Recursos

Aprender

Obter produtos e tecnologias

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Desenvolvimento móvel, WebSphere
ArticleID=839211
ArticleTitle=Estendendo dijits do Dojo para criar widgets customizados
publish-date=10082012