A Receita para a 3 YUI Aplicação

01 de abril de 2011 às 02:52 por Satyam | Em Desenvolvimento | 18 Comments

YUI 3 foi projetado para criar aplicações em módulos. Eu não vou discutir o que é um módulo, uma vez que tem sido bem descrito por Nicholas Zakas em sua apresentação Application Architecture Scalable JavaScript . Eu vou ficar com a forma de construir esses módulos. A maioria do que vou dizer pode ser encontrada na documentação on-line, junto com várias outras alternativas, afinal, esse é o ponto de boa documentação: a informá-lo sobre todas as formas possíveis de fazer as coisas. É por isso que esta é uma receita, apenas uma maneira de fazê-lo entre muitos outros. Ela também assume um aplicativo pequeno, não com tantas camadas como Nicholas 'sugere, uma vez que este é apenas um artigo e não um livro.

Identificar os módulos

O primeiro passo é identificar os módulos que vai precisar. Uma boa abordagem é começar a cortar o desenho da tela do aplicativo em seções individuais: barra de título, barra de menu, conteúdo, painéis laterais ou qualquer outra coisa que poderia haver ali. Então dê uma olhada no que a biblioteca tem para oferecer. Por exemplo, YUI 3 não tem Menu, mas não é o plugin do Node-MenuNav , que tem uma estrutura de menu básico feito de elementos aninhados <UL> lista não ordenada e as transforma em um menu ativo. Ou você pode querer verificar a Galeria YUI para componentes básicos. Enfim, você vai finalmente chegar ao ponto onde você tem uma caixa em que o layout que você tem que preencher em si mesmo, então vamos fazer isso.

Eu recomendo colocar cada módulo em seu próprio arquivo e seu próprio diretório de mesmo nome. Assim, um weather módulo seria no weather/weather.js . A razão para isso é porque o módulo é provável que requerem algum estilo, alguns arquivos CSS e de imagem, torna-se fácil para o built-in loader se você colocá-los onde ele pode facilmente encontrá-los, neste caso, a folha de estilo principal estaria em weather/assets/skins/sam/weather.css , com os demais ativos, imagens e assim por diante, ao lado. Isso supõe que você não está usando o Construtor de YUI que já fazem as coisas desta maneira qualquer maneira, mas isso é outra história. Nomes da pasta assets e skins são mais ou menos auto-explicativo, sam no entanto, não é bastante óbvio. É o valor padrão para a skin de propriedade do carregador porque essa é a skin padrão fornecido com YUI, em homenagem ao designer, Sam Lind. Como este sugere, você é livre para colocar seu nome em sua própria pele ea skin propriedade permite que você diga YUI para carregá-los, mas para mantê-lo simples, vamos apenas ir com o padrão.

Modelo de arquivo de módulo

Esta é a estrutura do arquivo que eu uso com mais freqüência, o que vou descrever em um momento:

  / * JSLint devel: true, undef: true, newcap: true, rigorosa: true, MAXERR: 50 * / 
 / * YUI globais * / 
 / ** 
  * O módulo de nome-módulo cria o blá, blá 
  * @ Módulo nome do módulo- 
  * / 
 YUI.add ('nome-módulo', function (Y) { 
     "Use strict"; 
     / / Constantes acessível e atalhos usados ​​no módulo 
     var = Y. Lang Lang, 
         CBX = 'Contentbox', 
         BBX = 'BoundingBox ",
         NAME = 'xxxx'; 

 
     / ** 
      * A classe Xxxx faz .... 
      * @ Class Xxxx 
      * @ Estende Widget 
      * @ Usa WidgetParent 
      * @ Construtor 
      * Cfg @ {} objeto atributos de configuração 
      * / 
     Y. Xxxx = Y.Base.create ( 
         NAME, 
         Y. Widget, 
         [Y. WidgetParent], 
         { 
             / / Instância membros aqui 
         }, 
         { 
             / / Membros estáticos aqui, especialmente: 
             ATTRS: { 
             } 
         } 
     ); 

 
 }, '0 0,99 ', { 
     requer: ['widget', 'widget-pai'], 
     skinnable: true 
 }); 

As duas primeiras linhas de comentários são para o benefício de JSLint , a ferramenta de verificação de JavaScript que eu realmente recomendo. Se você vai para a versão web , há uma caixa para definir opções. Na parte inferior da caixa de opções que você pode ver a forma de codificar essas opções no próprio arquivo. Se você usar o Construtor de YUI, ele será executado JSLint para você e definir estas opções para você, mas você ainda pode sobrepor-los para qualquer arquivo individual, se desejar.

Os comentários são para o doc API YUI docs construtor. É menos uma dor de cabeça se você incluir o modelo inicial para os docs API, eventualmente, você irá preenchê-los. Como a aplicação cresce e você não consegue lembrar de tudo, você vai precisar deles.

Agora vem a primeira linha de código real, o YUI.add() comunicado. Esta é a maneira de dizer o carregador YUI o nome eo conteúdo do módulo e várias outras peças de informação. Nomes dos módulos são geralmente nomeados com todas as letras minúsculas e hífens entre as palavras. Esses são os nomes que você vê na API Docs no índice mais alto na esquerda. Você pode ver a convenção não é estritamente seguido, alguns nomes de módulos não têm qualquer hífens. Enfim, esta é principalmente de você, enquanto você usá-lo de forma consistente.

O segundo argumento do YUI.add() declaração é uma função que recebe um único argumento, convencionalmente chamado de Y . Essa função contém o corpo do módulo e Y é a referência a uma instância de modo seguro de YUI que é onde você pode encontrar todos os outros módulos YUI e Galeria você pediu. Saltando para a parte inferior dessa caixa de código, você pode ver o resto dos argumentos do .add() método, a versão ( '0.99′ ou não completamente lá ainda) ea configuração para este módulo, um objeto com aa série de propriedades. Aqui, digo o Loader que este módulo requer widget e widget-parent e que tem uma pele. Listagem widget é redundante, uma vez widget-parent já exige widget , mas não se incomode com isso, o carregador não vai carregar um módulo de duas vezes e, se em um momento posterior você deixar cair uma dependência, você não precisa verificar para outras hipóteses você poderia ter feito: estado-los todos e deixar o negócio Loader com ele. Você pode encontrar uma lista de todas as opções na documentação da API para o .addModule() método do Loader.

Dentro do corpo da função, a primeira coisa é o “use strict”; declaração. Isto é para o seu código em conformidade com os 5 padrão ECMAScript, que neste momento o coloca no lado seguro para garantir a compatibilidade com todas as plataformas que você é provável encontrar no futuro. Para os intérpretes mais velhos, esta declaração não é nada mais do que uma seqüência que não é atribuído a qualquer coisa e é ignorado. O “use” declaração tem o escopo de função e é mais seguro colocá-lo dentro do corpo da função que no início do arquivo, de modo que ela afeta apenas o módulo que você está definindo. Se você colocá-lo no topo do arquivo, ele também se aplicaria a qualquer arquivo JavaScript que você carregar mais tarde, e muitas delas podem não cumprir com ES5.

Em seguida, vêm os atalhos e constantes, que são constantes apenas em uso desde o JavaScript não tem o conceito de constantes. Nós nomeá-los em todas as-letras maiúsculas e sublinhados, como constantes, muitas vezes estão em outras línguas. Há duas boas razões para utilizar constantes, especialmente as constantes string. Primeira é que quando você escreve a mesma seqüência várias vezes, você pode digitar errado um deles e você só vai perceber quando um bug aparece. Se você usar constantes, JSLint irá avisá-lo quando você digita errado o nome da constante, uma vez que será indefinido. A segunda razão é que o YUI Compressor pode fazer um trabalho melhor, pois os nomes constantes podem ser comprimidos, enquanto literais string não. Bons candidatos para as constantes nomeadas são os nomes dos atributos de configuração e eventos.

Atalhos, como Lang para Y.Lang também são bons porque eles permitem que você digite menos, o intérprete tem menos a avaliar (cada ponto implica uma nova pesquisa para os membros do objeto) e podem ser comprimido pelo compressor YUI.

Após os comentários API docs para a classe, temos a declaração real. Temos de declarar a nossa classe como uma propriedade de Y , assim, Y.Xxxx . Minha sugestão é usar Y.Base.create() para criá-lo, como mostrado aqui. Ela só pode criar classes derivadas de Base (e Widget , que também é uma subclasse da Base ), mas que vai cobrir a maior parte dos módulos que você vai usar isso, é incomum a necessidade de fazê-lo de outra maneira. O primeiro argumento é o nome do módulo, o NAME propriedade descrita para a Base componente. Convencionalmente, o NAME propriedade é uma versão de camelo caso do nome da classe. Esse nome é usado como prefixo para eventos (a parte antes dos dois pontos como “io:success” ), para nomes de classes CSS gerado pelo Widget classe (ie: “yui3-xxxx-content” ) e para a implementação padrão do toString() , que muitas vezes você vai ver em vestígios pelo depurador. Aqui eu uso o valor da constante NAME para definir a classe NAME propriedade.

O segundo argumento é a classe que extensões. Muitas vezes você vai usar Y.Base , para os módulos de utilidade, que não terá nenhuma interface do usuário, Y.Widget para aqueles que têm uma interface do usuário, Y.Plugin.Base de plugins ou qualquer outra classe derivada de Y.Base , como qualquer você já pode ter criado usando Y.Base.create() .

O terceiro argumento tem as extensões que você irá usar. Extensões são classes cujas propriedades e métodos que você quer ter misturado em sua classe. Bons candidatos para as extensões são ArrayList para Base ou qualquer um dos Widget-Xxxx submódulos de Widget . Attribute , EventTarget e PluginHost já vêm misturados em Base para que você possa sempre contar com essas três estarem lá. Extensões são muito poderosos, se você olhar para o código fonte para Overlay , você pode ver não há nada além Widget e extensões. Várias extensões podem ser misturados em um componente para o terceiro argumento é um array.

Finalmente, chegamos ao código real. O quarto e quinto argumentos são literais tanto o objeto que contém a instância e membros estáticos da classe. Membros de instância são as propriedades e métodos que vão para a classe prototype , aqueles que cada instância irá obter uma cópia e geralmente precisam ser referências por this . Membros estáticos são aqueles que serão compartilhadas por todas as instâncias.

Atributos de configuração

O mais importante desses membros estáticos é o ATTRS propriedade. Esta lista os atributos de configuração de sua classe terá. Por exemplo, digamos que queremos ter um atributo de configuração chamado value para armazenar valores numéricos e inicialmente definido como 0. Dentro do quinto argumento, gostaríamos de declará-la assim:

  ATTRS: {
     valor: {
         valor: 0,
         validador: Lang.isNumber
     }
 } 

Podemos listar qualquer número de atributos na ATTRS propriedade e cada um pode ser configurado com várias opções, das quais duas já mostrei aqui. Você pode ler sobre o resto das opções no addAttr() método de Attribute . Como pode ser visto, tenho usado o Lang atalho que eu declarei no início da declaração de módulo. O validator deve ser uma função que assume o valor para verificar e retorna um Boolean. Todos os Y.Lang.isXxxx métodos de fazer exatamente isso que eles podem ser usados ​​diretamente. Para mais validadores elaborados, ou getters setters, você precisa definir funções. Eu recomendo fornecendo o nome da função como uma string, Attribute se encarrega de resolver o nome da função para a função real. Por exemplo, se eu fosse para definir um, digamos, validCodes atributo que pode ter um código único válido ou um conjunto de códigos válidos, mas sempre deve retornar um array, eu faria:

  ATTRS: {
     validCodes: {
         setter: '_setValidCodes'
     }
 } 

Precisamos declarar o _setValidCodes método ao longo dos membros de instância outro no quarto argumento de Y.Base.create() :

  _setValidCodes: function (value) {
     if (! Lang.isArray (valor)) {
         = valor [valor];
     }
     valor de retorno;
 } 

O melhor é declarar setters, getters e qualquer mas o mais trivial dos validadores como funções instância separada e deixe Attribute resolver o nome da função para a chamada de função real.

Em geral, use setters para normalizar o valor, como mostrado acima, para não produzir efeitos secundários. Todos os atributos de configuração será acionado antes e depois de eventos de alteração com o evento antes capaz de impedir o atributo de mudar. Use esses eventos de alteração de produzir quaisquer efeitos secundários, e não o setter. O evento depois é o melhor desde então você sabe que nada impedia o atributo de ser definido pois outro código pode ter subscrito o evento de alteração antes (on) e cancelou-lo.

Você pode fazer o seu atributo mais ou menos estrita, dependendo de como você define seu validador e setter. Se você fizer o validador muito restritiva, o atributo será muito rigorosa, aceitando apenas os valores válidos, neste caso, o setter pode ser desnecessário. Por outro lado, você não pode usar um validador em tudo e confiar completamente no setter para massagear qualquer valor recebido em algo aceitável. Por exemplo, você pode ter qualquer um destes dois:

  validador: Y.Lang.isBoolean, / / ​​para fazer o atributo aceita estritamente um Boolean
 setter: Boolean, / / ​​para fazer o atributo aceitar qualquer valor e ter Boolean transformá-lo em um 

Setters também pode servir como validadores. Setters deve retornar o valor a ser atribuído ao atributo mas também pode retornar Y.Attribute.INVALID_VALUE que vai deixar o atributo inalterada, como se um validador a rejeitaram.

Quando eu definir um atributo de configuração Costumo definir uma constante para ele, que eu coloco no topo do módulo (junto CBX , BBX e os atalhos), por exemplo:

  var VALUE = "valor",
     VALID_CODES = 'validCodes'; 

As chances são de que vou usar os atributos de configuração várias vezes dentro do módulo e isso vai me salvar alguns problemas. No entanto, tenha cuidado, não use essa constante ao declarar o atributo, não faça isso:

  ATTRS: {
     / / *** Não faça isso *** / /
     VALOR: {
         valor: 0,
         validador: Lang.isNumber
     }
 } 

Se você fizer isso, você teria um atributo chamado VALUE , em vez de value . Esta é uma questão JavaScript, e não um YUI um. Além disso, tenha cuidado para não ultrapassar em qualquer um dos atributos de configuração já declaradas para tanto a base de classe ou qualquer uma das extensões. Widget já tem um monte de atributos declarados (veja tabela ) e que você dificilmente adicionar um boundingBox atributo si mesmo, você poderia facilmente esquecer que visible , disabled , height ou width já estão definidos. Se você pretende usar o que corresponde Widget usa-los para, tudo vai ficar bem. Dito isso, você pode alterar a definição de qualquer um deles. Y.Base.create() funde a definição dos atributos de configuração da extensão com os da classe base assim, se você deseja alterar, por exemplo, o valor padrão para um atributo existente, pode fazê-lo, declarando que o atributo novo em sua subclasse.

Tenha cuidado se você quer dizer para inicializar um atributo com um array ou um objeto. Objetos (e arrays são objetos) são passados ​​por referência e se você inicializar um atributo com um objeto, que tudo pode acabar apontando para o mesmo objeto de modo que quando você altera parte dela (remover um item de uma série ou adicionar uma propriedade ao objeto) você acaba alterando todas as instâncias de uma vez. Base , no entanto, tem alguma lógica interna que lhe permitirá segurança inicializar um atributo com o objeto e literais de array. Se o valor de inicialização é um objeto ou uma matriz literal, em seguida, Base irá cloná-lo. Use o valueFn opção ou inicializá-lo no inicializador de classe para outros objetos.

Outros membros estáticos

Existem dois outros membros estáticos você pode definir no quinto argumento de Y.Base.create() . Se você estiver criando um plugin, você absolutamente deve declarar a NS propriedade, se não o fizer, o plugin não vai funcionar e falhar silenciosamente. A NS propriedade deve ser definida para uma string que será usada como o nome da propriedade para armazenar o plugin dentro do objeto de host, mantenha isso em mente quando você escolhe o nome para que você não substituir qualquer propriedade existente.

Se você está construindo um widget e você planeja para apoiar fortalecimento progressivo, então você vai usar o HTML_PARSER propriedade estática. Isso é definido para um objeto que contém propriedades nomeado após os atributos de configuração para definir de analisar o HTML existentes e ou seletores CSS3 ou funções que irá produzir seus valores. Ver Progressive Enhancement no manual do utilizador Widget.

Você também pode querer fornecer valores para ser usado pelos desenvolvedores usando sua classe. As constantes declaradas no início do arquivo são completamente invisíveis de fora do próprio módulo. Se você quiser fornecer constantes públicas, este é o lugar para fazê-lo. Exemplos de tais são os HEADER , BODY e FOOTER constantes de WidgetStdMod (para usá-los você realmente tem que usar o nome totalmente qualificado: Y.WidgetStdMod.BODY e tal).

Membros de instância

O quarto argumento de Y.Base.create() são as propriedades e métodos que irão para o prototype da classe criada. Normalmente, nós declaramos primeiras propriedades e métodos mais tarde. Eu não tenho qualquer razão para isso, a ordem é irrelevante na verdade, nem JavaScript nem YUI exigem que você faça desta maneira, mas torna mais fácil localizar as coisas no arquivo de origem. Embora propriedades de instância podem ser criados na mosca no inicializador, eu recomendo declarando-os de forma explícita e inicializar-las. Cada propriedade deve ser precedida por um comentário doc API.

Propriedades geralmente será privado e seu nome prefixado por um sublinhado. É melhor se a interface pública do objeto é exposto através de atributos de configuração e não propriedades. Propriedades são muito burros, atributos de configuração pode ter validadores, a conversão de tipo (via setter) e produzir efeitos secundários (via eventos de alteração) e muitas vezes não é muito tempo até você descobrir que você deseja que todos esses recursos.

Tal como acontece com os atributos de configuração, não inicializar as propriedades de objetos ou arrays, todos eles acabam apontando para o mesmo objeto e você ter problemas. É melhor definir as propriedades que são para segurar objetos para null . Além disso, não deixe unset propriedades, se você não sabe o seu valor, no entanto, defini-los como null em seu lugar. Mais tarde, quando a depuração, uma propriedade definida como undefined aponta para um erro, geralmente um erro de digitação.

Instância Métodos de base

Você deve ter notado que não declararam qualquer construtor para a nossa subclasse. Base faz a inicialização do módulo e, em seguida, chama um método chamado initializer , se existir, com os mesmos argumentos que recebeu quando instanciado assim, para todos os efeitos, você pode considerar que initializer é o seu construtor. Todas as classes derivadas de Base costumam levar um único argumento ao ser criado, um objeto contendo os atributos de configuração. Base (ou Widget , uma vez que é uma classe de Base ) lê este argumento e define os atributos de configuração antes de chamar initializer . Para um Widget , se houver um HTML_PARSER propriedade, ela também teria sido processado e os valores para os atributos de leitura a partir de marcação será definido também.

O initializer método tem várias tarefas. Primeiro, deve definir as propriedades que precisam ser inicializados com objetos ou arrays. Em seguida, ele irá publicar todos os eventos desta classe irá produzir. EventTarget permitirá que você acionar um evento que não tenha sido publicado pela primeira vez usando as configurações padrão para eventos, mas mesmo neste caso, eu sugiro que você declará-los de qualquer maneira. Este é um bom lugar para colocar os comentários API docs para os eventos mesmo que pareça um pouco estranho, estar no corpo de uma declaração de função, mas não há melhor lugar para fazê-lo.

O argumento recebido pelo initializer teria sido processado por então, mas às vezes você quer algumas opções extra para ser usado na inicialização e você não se importa de manter atributos reais para eles. Por exemplo, Base aceita os atributos on , after , bubbleTargets e plugins (ver Base ), embora ela não tem atributos de configuração para aqueles. Da mesma forma WidgetParent leva uma children atributo na inicialização, mas não tem atributo de configuração do mesmo nome. O initializer método é aquele que processa-los. Assim, embora a sua classe vai acabar tendo apenas um argumento na instanciação, este argumento só pode carregar todas as informações que possa precisar.

JavaScript não tem noção de destruidores. Base compensa isso que lhe permite declarar um destructor método onde você pode colocar o código para liberar os recursos o objeto poderia ter tomado. Esta é apenas uma solução parcial, o interpretador JavaScript não chamá-lo automaticamente quando um objeto caindo assim que você é ainda responsável pela destruição de um objeto antes de descartá-lo, mas pelo menos você sabe um destrutor vai estar lá.

Usuários de sua classe nunca vai chamar initializer e destructor diretamente. Base vai chamá-los quando necessário. initializer será chamado quando o objeto é instanciado, destructor será chamado quando o usuário de sua classe chama de destroy método.

Uma das coisas que muitas vezes produzem vazamentos de memória são ouvintes de eventos deixado para trás. Widget tenta desligar todos os ouvintes ligados aos elementos da interface do usuário contido dentro do elemento Caixa delimitadora, mas não pode retirar quaisquer outros. Base não pode retirar qualquer ouvinte de evento em todos os . Este é o código que eu uso para me ajudar com isso. Ao longo das outras propriedades privadas, instância declaro o _eventHandles propriedade:

  _eventHandles: null, 

Em seguida, no initializer método, eu defini-lo como um array:

  inicializador: function (cfg) {
     this._eventHandles = [];
     / / ... ...
 }, 

No mesmo initializer (também em bindUI se fosse um Widget ) Eu, então, anexar ouvintes fazendo:

  this._eventHandles.push (this.after ('someAttributeChange, this._afterSomeAttributeChange, this)); 

Então, em destructor , eu tenho:

  destructor: function () {
     Y.each função (this._eventHandles, (punho) {
         handle.detach ();
     });
 }, 

É aqui, no initializer , que você ligar os ouvintes de eventos para os atributos que devem produzir efeitos secundários (você pode diferir-lo para bindUI se este efeito secundário tem de lidar com a interface do usuário). Como eu disse anteriormente, as funções setter atributo só deve lidar com normalizar o valor do atributo. Que a definição deve produzir quaisquer efeitos além armazenar o valor, estes devem ser manuseados por ouvintes de evento. No exemplo acima, eu tenho definir o método de _afterSomeAttributeChange para ouvir qualquer mudança no someAttribute atributo. Os ouvintes de eventos receberão um único argumento, a fachada evento que eu costumo chamar ev , um objeto com várias propriedades, um deles, newVal contendo o valor a ser definido.

Instância Propriedades widget

Duas propriedades importantes que Widget usos são BOUNDING_TEMPLATE e CONTENT_TEMPLATE . Ambos são inicialmente definido como “<div></div>” que produz a estrutura padrão de dois recipientes um dentro do outro que a maioria dos widgets de uso. Isso, no entanto, pode não ser adequado para todos os widgets, por exemplo, um Button widget pode ser mais bem servido por um <span> elemento dentro de uma âncora ( <a> ) elemento em vez de dois aninhados <div> s. Na verdade, você não se importe de ter um contentBox em tudo, Widget não requer que você. Você pode definir essas duas propriedades exemplo, para qualquer marcação que você deseja. Por exemplo, para o Button de classe que eu poderia ter:

  BOUNDING_TEMPLATE: '<a>',
 CONTENT_TEMPLATE: null, 

Tendo CONTENT_TEMPLATE definida para null dirá Widget que você não quer um contentBox em tudo. Neste caso, o contentBox atributo de configuração irá apontar para o mesmo elemento como o boundingBox atributo de configuração faz.

Você não deve colocar esses modelos em todo o HTML para o widget, fazer estes dois simples elementos HTML e criar qualquer marcação extra via código em renderUI (que veremos mais adiante).

Widget irá adicionar um id atributo e as classes padrão que ele usa para qualquer marcação que você deseja, como yui3-xxxx , yui3-xxxx-visible ou yui3-xxxx-disabled , onde xxxx é o valor do NAME propriedade se transformou em minúsculas.

Instância Métodos widget

Widget divide sua inicialização em várias etapas. Além do initializer , chamado quando o objeto é instanciado, eo destructor , chamado por destroy , ambos os métodos manipulados por Base , Widget acrescenta renderUI , bindUI e syncUI para a fase de construção, que serão chamados em seqüência ao Widget 's render método é chamado.

O renderUI método cuida de produzir o HTML básico para o widget. Tanto o boundingBox e contentBox foram prestados neste momento. Se estiver usando melhoria progressiva, renderUI primeiro tem de verificar se os elementos já existentes na página. Se usamos o HTML_PARSER propriedade, em seguida, os atributos de configuração mantendo as referências aos elementos terá sido colocado pelo então, se não, nós precisamos criá-los.

Para isso, a maneira mais fácil (assumindo que não há aumento progressivo) é usar Y.Node.create , como este:

  renderUI: function () {
     var cbx = this.get (CBX);
     cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE, CLASS_NAMES)));
 }, 

Isso pressupõe um monte de coisas, que eu vou explicar de imediato. Primeiro, tenho a CBX constante declarada como mostrado na caixa de primeiro código neste artigo. Então ele assume Node é carregado, o que Widget usa por isso é seguro, mas também assume Y.substitute está lá, que é opcional. Você tem que adicionar 'substitute' para o requires lista para o seu módulo. Em seguida, ele espera que um modelo para o widget de estar em uma variável estática chamada TEMPLATE que é até você para definir junto com outros membros da classe estática (à direita por ATTRS e tal). Finalmente, ele assume que há uma constante CLASS_NAMES declarou em algum lugar.

Eu costumo declarar CLASS_NAMES na minha definição de módulo, juntamente BBX e CBX (veja o quadro primeiro código neste artigo), assim:

  var BBX = 'BoundingBox ",
     CBX = 'Contentbox',
     NAME = 'botão',
     / / Outras constantes e atalhos ....
     YCM Y.ClassNameManager.getClassName =
     GetClassName = function () {
         var args = Array Y. (argumentos);
         args.unshift (NOME);
         retorno YCM.apply (this, args) toLowerCase ().;
     },
     LABEL = 'label',
     PRESSED = 'pressed',
     ICON = 'ícone',
     CLASS_NAMES = {
         pressionado: GetClassName (pressionado),
         ícone: GetClassName (ICON),
         label: GetClassName (LABEL),
         noLabel: GetClassName ("não" LABEL)
     }; 

CLASS_NAMES será então uma constante contendo um objeto com propriedades criado por ClassNameManager (que também vem incluído com Widget ). No código acima, eu primeiro criar o atalho YCM fazer depois acessa mais rápido, então eu crio a função getClassName , uma função privada que só é acessível dentro da definição de módulo. A função funciona muito bem como o método do mesmo nome do Widget , mas é uma função estática que eu possa usar para definir os valores mais estático. Isso é exatamente o que eu faço, mais tarde, quando eu crio CLASS_NAMES como um objeto com os nomes das classes geradas como suas propriedades. Isso me permite escrever um TEMPLATE corda, tais como:

  TEMPLATE: '<label class="{label}"> <input/>', 

O que é muito burro até agora. Eu também gostaria de fundir-se este modelo de valores de outras fontes, especificamente, os atributos de configuração. É assim que eu conseguir fazê-lo:

  this.get (CBX). append (Y.Node.create (Y.substitute (TEMPLATE, CLASS_NAMES, Y.bind (function (chave, sugeriu, arg) {
     retorno (tecla === '_' this.get (arg): sugerido);
 }, Este )))); 

Acrescento um terceiro argumento para Y.substitute , uma função. Normalmente, espaços reservados para Y.substitute são feitos de personagens fechados entre chaves, no entanto, se houver um espaço, ele irá dividir o espaço reservado em dois, a parte até o espaço que está sendo a chave eo segundo argumento opcional. Este se torna útil quando o terceiro argumento é uma função, como aqui. A função vai receber três argumentos, o primeiro é a chave, o segundo é o valor encontrado no objeto de substituição, aqui CLASS_NAMES , se houver, eo terceiro é o argumento opcional. Assim, na declaração acima, eu posso usar um modelo como este:

  TEMPLATE: '<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" />', 

Y.substitute encontrará {label} e procure-o na CLASS_NAMES . Ele vai encontrá-lo e obter 'yui3-button-label' . Ela irá então chamar a função de substituição com argumentos 'label' , 'yui3-button-label' e undefined . Desde key não é igual a '_' ele irá retornar o valor no segundo argumento, o nome da classe original. Quando chega a {_ id} , não há valor para uma propriedade chamada _ em CLASS_NAMES por isso vai chamar a função de substituição com argumentos '_' , undefined e 'id' . Com key igual '_' , a função irá buscar o valor do 'id' atributo. Ele vai fazer o mesmo novamente para o {_ value} espaço reservado.

Todas as constantes declaradas no topo estão escondidos partir de qualquer código fora do módulo, mas você pode querer fazer algumas delas visíveis, como CLASS_NAMES . Para isso, na seção de membros estáticos, o último argumento para Y.Base.create , você poderia ter:

  CLASS_NAMES: CLASS_NAMES 

Então o objeto com todos os nomes de classe seria visível como Y.MyWidget.CLASS_NAMES .

Eu sugiro que você faça a formatação tanto quanto você pode com a seqüência de HTML que irá tornar o conteúdo do widget. Manipulação de strings em JavaScript é muito mais rápido do que acessar o DOM portanto, quanto mais você faz antes de chamar Y.Node.create com essa seqüência, mais rápido você vai conseguir.

O método de instância próxima chamada para qualquer widget é bindUI . Este é o lugar onde você anexar ouvintes de eventos para todos os elementos criados por renderUI , por exemplo, o ouvinte para quaisquer alterações no valor do <input> caixa do TEMPLATE acima. O valor na caixa de texto e que, no atributo de configuração devem sempre ser mantidos em sincronia. O value atributo pode ser alterado por meio de código ou pelo usuário digitar na caixa de entrada. Se se trata de código externo, a caixa de texto deve ser atualizada, se se trata da caixa de texto, não deve, caso contrário corre o risco de entrar em um loop infinito: a mudança na caixa de texto define o value atributo que define o value na caixa de texto que depois muda e define o value atributo e assim por diante. Vamos ver como lidar com este caso. Montamos um ouvinte sobre o sintético valueChange evento na caixa de entrada. Para fazer isso precisamos adicionar o event-valuechange módulo para o requires lista deste módulo.

  this._eventHandles.push (this._inputEl.after ('valueChange, this._afterInputChange, this)); 

Assumimos o objeto tem uma referência para a caixa de texto salvos em _inputEl . O ouvinte faz isso:

  _afterInputChange: function (ev) {
     this.set (VALOR, ev.target.get (VALUE), {fonte: UI});
 }, 

Aqui nós assumimos que temos as constantes VALUE e UI declarado como 'value' e 'ui' , respectivamente. Nós simplesmente definir o atributo de value para o valor lido a partir da caixa de entrada. No entanto, estamos adicionando um terceiro argumento para o método set: {source:UI} . O set método pode levar um terceiro argumento, um objeto, cujas propriedades serão misturados na fachada do evento de alteração de atributo. Esta é a maneira que nós podemos dizer a diferença entre valor a ser definido a partir da caixa de texto ou de código externo. Em bindUI teríamos definido este ouvinte:

  this._eventHandles.push (this.after ('valueChange, this._afterValueChange)); 

Este é o ouvinte para uma mudança no value atributo do seu objeto, o outro foi para uma mudança no valor do <input> caixa, eles são chamados o mesmo, afinal de contas, ambos ouvir mudanças em algo chamado de valor, mas não são a mesma coisa. Geralmente, os ouvintes para alterações de atributo são definidos no initializer , mas já que esta afeta um elemento de interface do usuário, vamos colocá-la em bindUI para que possamos saber o textbox vai estar lá. O ouvinte terá:

  _afterValueChange: function (ev) {
     if (ev.source === UI) {
         retorno;
     }
     this._inputEl.set (VALOR, ev.newVal);
 }, 

A primeira coisa que fazemos é verificar a source do evento. Se se trata da UI então ignorá-lo. Tanto a propriedade do nome, source e seu valor, UI são arbitrários, esses são os que eu usei quando definir o value atributo para aqueles são os que eu verificar no ouvinte, mas qualquer nome / valor faria tão bem. Na verdade, Widget fornece uma constante para que, Y.Widget.UI_SRC , mas é o tipo de longa, então eu provavelmente usaria um atalho de qualquer maneira.

Outro petisco: você pode definir atributos declarados como somente leitura usando _set em vez de set . O _set método serve para ser protegida, para ser usado internamente, mas, como sabemos, o JavaScript não sabe nada sobre segurança para _set está aberto a qualquer mas, pelo menos, tentamos ao declarar o atributo com readOnly:true e documentá-lo como tal na documentação da API.

Finalmente, declaramos syncUI . While the first two, renderUI and bindUI are going to be called once and only once, syncUI will be called at least once by Widget itself and you might call it several times afterwards. Its purpose is to refresh the UI to reflect the current state of the object. Since the state might change, the UI might need to be refreshed over time. However, I can't provide a simple recipe for handling this. For a simple UI, syncUI might refresh everything in the screen and be called every time anything changes. For more complex UIs refreshing the whole UI might take time and cause flickering so you might want to refresh only the bits and pieces you need. If so, you will have separate methods to refresh each of these parts and syncUI will call each of them just once. Moreover, as I've shown in the example for renderUI , I set the value of the textbox right there, though that should be done in syncUI .

In the more general case, you will have a function for each UI element that can be set separately. That function will be called once from syncUI , when initializing, and any number of times from the after attribute change event listener. For example, we could have:

 _valueUIRefresh: function (value) {
    this._inputEl.set(VALUE, value);
 } 

Which could be called from syncUI along other similar setters:

 syncUI: function () {
    this._valueUIRefresh(this.get(VALUE));
    // other such refreshers 
 }, 

and by the after listener:

 _afterValueChange: function (ev) {
    if (ev.source === UI) {
         retorno;
     }
    this._valueUIRefresh(ev.newVal);
 }, 

Communicating with others

Once you have the logic of one of your modules finished, you want it to interact with other modules on your page. If you've seen Nicholas Zakas video, you already know what tight and loose coupling is. Calling methods and setting attributes from one module to another means having those modules tightly coupled and it is the traditional way, so I won't talk about it since you know how to do it. The other way to do it is to fire custom events. Base already includes everything you need to do that.

First, in initializer , you publish the custom events you want everybody to find out about.

 initializer: function (cfg) {
    this.publish('eventName', { /*… options … */});
 }, 

Normally, the name of the event will come from a constant, since you will use that same name every time you fire it and you don't want typos there.

Normally, when you have a reference to an object, such as:

 var myWidget = new Y.MyWidget({ /* .. attributes … */ }); 

you can listen to its events by doing:

 myWidget.after('eventName', this._eventNameListener, this); 

However, to do this, you need to have a reference to myWidget , which is not as tightly coupled as calling its methods directly but it is still quite tight: at least one module knows about the other or, perhaps, a supervisor module knows about both and sets the links in between them. Two options are important to get modules to communicate in between themselves, broadcast and emitFacade .

The first, broadcast , lets you set listeners for that event in other modules. When broadcast is left at 0, the default, you have to do as shown above. If you want the event to be listened to elsewhere, you will want broadcast set to 1, so events are broadcast within the same sandbox and sometimes 2, so they can go across sandboxes. In this context, a sandbox is what you get when you call:

 YUI().use( 'module1′, …, 'moduleN', function (Y) {
    // this is your sandbox
 }); 

You can have several such sandboxes in your page:

 YUI().use( 'module1′, …, 'moduleN', function (Y) {
    // this is your sandbox
 });
YUI().use( 'moduleX-1′, …, 'moduleX-N', function (Z) {
    // this is another sandbox
 }); 

If you set broadcast to 2, then an object in the second sandbox can listen to an event when fired in the first. You can see the details in the Event user guide . Lets just stick to the simple sandbox case.

To listen to an event fired from another module within the same sandbox you need to know the value of the NAME static property of that module and the name of the event. Remember, Y.Base.create takes, as its first argument, the value that it will use for its NAME property, thus, if you created a module in this way:

 Y.MyWidget = Y.Base.create(
    'xxxx',
    Y.Widget,
    // … and so on 

and then, in the initializer you published the 'help' event like this:

 initializer: function (config) {
    this.publish('help', {
        broadcast: 1,
        emitFacade: true
     });
 }, 

To listen to that event in any other module within the same sandbox, you do:

 Y.after('xxxx:help', function (ev) { … }, this); 

Here, I am calling Y.after , not myWidget.after , I don't need to have a reference to the module firing the event. This is the same method used to listen to DOM events or other synthetic events such as 'valueChange' the only difference being the prefix, the part before the colon. Base already takes care to prefix all events with the value of the NAME property so you don't have to take care of that when publishing them. You can do so, you can even use something else as a prefix; if one such prefix is there, Base will respect it, but usually you just want the default, which Base provides.

You also want to set emitFacade because you will want to have a reference to the instance that fired the event, which the event facade provides in ev.target . But wait, if the listener module gets a reference to the firing module, don't they become tightly coupled once again? Not quite, as long as you don't preserve that reference in the listening module, the coupling will be volatile. Still, we can do better.

When firing the event we may add all the information the listener needs in the facade, like this:

 this.fire('help', {helpTopic: 'Event Broadcasting'}); 

Method fire takes the name of the event being fired (which Base will further prefix with the NAME of the class) and an object containing any number of properties which will be merged into the event facade. The listener then doesn't need to query the firing module for any information, all that might be needed is there. This is as loose as it gets. The listener simply knows that some module, and there may be many such modules, is asking for help on 'Event Broadcasting' and that is really all it needs to know. It doesn't even care which module asked for it. New modules may be added later and the help system will also work for them.

Events and Default Behaviors

The usual solution to changing the behavior of a class is to sub-class it so you can override one of its functions and do whatever it is you want to do instead. You can still do that. You can use Y.Base.create to define a module based on, say Y.Widget and then use Y.Base.create again using your new module as the base to change a particular behavior. For example, I might have:

 Y.MySimpleWidget = Y.Base.create(
    'simpleWidget',
    Y.Widget,
    [],
     {
        // instance members here, amongst them:
        renderUI: function () {
            this.get(CBX).append(Y.Node.create(' … whatever goes into the widget … ' ));
         }
     },
     {
        ATTRS: {
            // configuration attributes
         }
        // other static members
     }
 ); 

and then:

 Y.MyFancyWidget = Y.Base.create(
    'fancyWidget',
    Y.MySimpleWidget,
    [],
     {
        renderUI: function () {
            Y.MyFancyWidget.superclass.renderUI.apply(this, arguments);
            this.get(CBX).append(Y.Node.create(' … add some bells and whistles … ' ));
     }
    // Presumably the fancy version does not need any further static members so I skip the last argument
 ); 

MyFancyWidget improves over MySimpleWidget by adding some bells and whistles. This might be too much of a trouble in some cases, you might plan for a base class more flexible and easier to change. Custom events can help with that.

Imagine you have a class that has a sort function. The sort function takes a key and direction argument and is declared like this:

 sort: function (key, direction) {
     // sorting happens here
 }, 

If you know that the behavior of that function might be changed in some circumstances, you might do the following. In the initializer method, you can have:

 initializer: function (config) {
    // amongst many other things:
    this.publish(SORT, {defaultFn: this._defSortFn});
 }, 

Where SORT is a constant containing 'sort' . Then, you declare the sort function like this:

 sort: function(key, direction) {
    this.fire(SORT, {key:key, direction:direction});
 }, 

The sort function simply transforms the standard function call into a fired event containing the same arguments. Though this is meant to provide alternatives, you still want the class to sort somehow, you do that through the default sort function:

 _defSortFn: function (ev) {
    var key = ev.key, direction = ev.direction;
    // same code as the original sort function
 }, 

The class will do sort as before, the body of _defSortFn might be just the same as the original one, once you have read the key and direction arguments from the event facade, but any other piece of code can set a listener for that same sort event and change it, for example:

 myObjectThatSorts.on('sort', function (ev) {
    var key = ev.key, direction = ev.direction;
    ev.preventDefault();
    // now do your own sort
 }); 

By calling preventDefault I tell myObjectThatSorts not to call _defSortFn . I could do this conditionally and decide, based on whatever I want, whether I may leave the original sort go ahead or unconditionally stop it, as I did here. I might not even care to stop it ever, I might listen to the after event and simply flip an arrow somewhere in the UI to signal which way the sort went.

I may also alter the event facade. There is only one copy of the event facade that gets built when the event is fired and it is propagated through all before (on) listeners, to the default function and then to the after listeners until finally it is dropped. You can change the values of its properties at any point. Of course, it hardly matters any changes you might do after the default function is called but any changes done in the before (on) listeners will reach the default function, for example:

 myObjectThatSorts.on('sort', function (ev) {
    ev.direction = (ev.direction==='desc'?'asc':'desc');
 }); 

This would get the sort done upside down.

YUI_config

The easiest way to get your module on your page is to include it in its own <script> tag or in a script tag pointing to a combo URL (via creating a file on the server that is a manual concatenation of files or a combo service is the server supports one). Integrating custom modules into the Loader is a more advanced option, though it might improve performance. The important point in this case is to make sure the YUI.add() includes the requires: [...] in the last parameter, so use() will apply the module and its dependencies in the proper order.

For small applications, you will probably have everything loaded from the start as outlined above. However, for larger applications, you might not want everything loaded from the start since it can take too long. You can call use() more than once to request extra functionality as needed. However, having the Loader find out about each module's dependencies when it loads each is time consuming since it might take several sequential requests until it finally gets everything it needs. Instead, you can forewarn the Loader of your modules and their dependencies so, when the time comes, it knows how to deal with them and can load them all in parallel.

To do so, you need to add the module description and requirements to the tables that the YUI Loader uses to fetch modules. The easiest way is to build a yui_config.js file (or whatever you want to call it) that contains all those definitions. That file will look like this:

 YUI_config = {
    filter:'raw',
    //combine:false,
    gallery: 'gallery-2011.02.18-23-10′,
    groups: {
        js: {
            base: 'build/',
            modules: {
                'myWidget': {
                    path: 'myWidget/myWidget.js',
                    requires: ['widget', 'widget-parent', 'widget-child', 'widget-stdmod', 'transition'],
                    skinnable: true
                 },
                // other modules here
             }
         }
        // other groups here
     }
 }; 

You include this file in a regular <script> tag in your HTML file before you issue the first YUI().use() statement. They replace those options you would otherwise place as the first argument to YUI().use() , as if you did YUI(YUI_config).use() , but YUI does it for you. You can use any of the options listed here .

The filter option can be set to 'min' for production code (the default so you would usually comment out), 'debug' for the fully expanded with log statements (which might overwhelm your console) and 'raw' for fully expanded without log statements, the last two used only in development. Likewise with the combine option, only used when you have really tough bugs and you want to find out what is going on and get lost in those huge combos. Then you put your gallery option, if you use any gallery modules, to freeze your gallery modules to a version you know it works.

The groups option is where you start describing your own modules. The first name, in this case js , can be anything, whatever you want to call your group of files. You could create one such group for each family of files in a common location. The first declaration in each group is the base location of the group of files relative to the home page or an absolute path. That is, basically, the criteria for grouping files, however, there are several more options, listed here .

Finally, in the modules section you start listing your modules. The key for each entry is the module name, the very same name that you have used as the first argument in the YUI.add in your file and the same that you will use in the module list when you issue the YUI().use() call in your application. Then you specify the location of the module file, relative to the previous base or the fullpath if located elsewhere, and the rest of the options that where at the very end of the YUI.add declaration and are listed here . The requires list can list YUI modules, gallery modules or modules of your own either within the same group or from other groups in your config file. Skins will be loaded automatically by setting skinnable:true if you locate them as I recommended at the beginning of this article.

To simplify things for myself, I created a Windows script file that builds the YUI_config options for me. It basically scans the folder with the module files and reads each of them and extracts the information from each YUI.add call by defining a fake YUI.add function that extracts the arguments for me. It makes plenty of quite simplistic assumptions but it works for me as it is, you use it at your own risk.

Conclusão

YUI3 is very flexible and you can build your modules in many ways. This is no more than one way to do that; I don't always do it this way, sometimes, not often, I don't need all of what Base provides so Y.Base.create is of no use, but this works most of the time.

Compartilhar e ampliar: Bookmark com del.icio.us | digg it! | reddit!

18 Comments »

RSS feed para comentários a este post. TrackBack URI

  1. Thanks a lot for this article. It is difficult to find informations about skins in the official doc. It is now much clearer for me.

    Comment by plv — April 1, 2011 #

  2. Artigo Awesome!

    Thank you, Satyam!

    Comment by John — April 1, 2011 #

  3. Wonderful article!

    Comment by Daniel Stockman — April 1, 2011 #

  4. “This is a JavaScript issue, not a YUI one.”

    That is NOT issue, but pretty normal behavior. :)

    Comment by John — April 3, 2011 #

  5. Agradecimentos para o artigo! Cleared a lot of doubts that I had.

    I have one question though :) I don't quite understand the use of syncUI method. You have stated its use as “Its purpose is to refresh the UI to reflect the current state of the object”. Aren't you doing that in bindUI() method when you register afterValueChange events for widget's attributes to refresh UI?

    Comment by Vignesh — April 5, 2011 #

  6. i am just start to learn YUI3, i think this article will help me to understand it deeply.

    Comment by lily — April 5, 2011 #

  7. [...] mobile support, a “Simple” theme, a new kitchen-sink-like Widget Browser, and more A Recipe for a YUI 3 Application – Satyen Desai of the YUI team goes into detail about how to organize a YUI application WebGL [...]

    Pingback by JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: iOS viewport fixes, Mobile Boilerplate, CommunityJS, Ender.js — April 8, 2011 #

  8. I've put it into Chinese.
    http://ued.taobao.com/blog/2011/04/14/a-recipe-for-a-yui-3-application/

    Comment by jayli — April 14, 2011 #

  9. @Vignesh:
    Regarding the syncUI method, the listeners you add in bindUI dont get fired during sync, because the object state at the time you bind the listeners has already been set, so there's no change in the state.

    I'll use pseudo code to help explain:
    var obj = new Y.Widget({attr1: 55});
    //attr1 state is now 55
    obj.render()
    So during render, since nothing changes, no change event is fired, hence why you may need to call the syncUI method.

    Ideally, you should have a _uiSetAttr1 method that is just called by the after change event, and can be called during sync that simply handles updating the ui in response to that attr changing.

    There are also properties that help make this glue wiring easier. Off the top of my head I don't remember if they're private still, but if you set them (I think it's either BIND_UI or BIND_UI_ATTRS and same for SYNC) all you have to do is set a method called _uiSetAttrname and list the ATTRS in that object, and it will automatically do all of that wiring for you.

    I hope that helps :)

    Comment by Nate Cavanaugh — April 16, 2011 #

  10. [...] Recipe for a YUI Application Building Reusable Widgets with YUI3 Alloy UI [...]

    Pingback by Build A Chrome App with YUI – (1) « Triptych — April 17, 2011 #

  11. Nate

    You are referring to property _UI_ATTRS which is an object with two properties, BIND and SYNC . As its initial underscore signals and the documentation states, they are private and unless you really know what you are doing, it is better keep your hands off them. (I mean 'you' in generic terms)

    Each of these has an array of attribute names which are already initialized by Widget so any changes need to be done carefully not to destroy their initial values.

    For those listed in the _UI_ATTRS.BIND array, an “after change” listener will be attached before our bindUI method which will call a method named like _uiSetXxxx , where xxxx is the name of the attribute.

    Likewise, configuration attributes named in _UI_ATTRS.SYNC will have this same _uiSetXxxx called right before our own syncUI method. Each of these _uiSetXxxx methods will receive the value of the attribute, either the initial value or the changed value.

    Indeed, this is a clever mechanism and it would be great if it was made public, or an alternative public equivalent were to be provided and thus supported in the long term. A formal statement by the YUI team regarding the long-term support for this mechanism would be welcome.

    Nevertheless, I should have mentioned it or suggested a similar custom approach. So, since the Pandora's box is open, let me explain it.

    Say you have an attribute “myAttr” which has an effect on the UI. You need to provide a method called _uiSetMyAttr (note the M in myAttr turned uppercase) which receives the new value to set and a second argument which might either be undefined (not there) or set to Widget.UI_SRC and affects the UI when the second argument is not set. You would define it in the instance member section like this:

    _uiSetMyAttr: function (value, src) {
    if (src === Y.Widget.UI_SRC) { return; }
    // set the UI element
    }

    To get the UI set initially, right before your own syncUI is called, push the name of the attribute into _UI_ATTRS.SYNC , usually in the initializer , like this:

    this._UI_ATTRS.SYNC.push("myAttr");

    To have it further called after any change in the attribute, push it also into _UI_ATTRS.BIND , like this:

    this._UI_ATTRS.BIND.push("myAttr");

    Obrigado pela dica.

    Comment by Satyam — April 17, 2011 #

  12. Here's the enhancement request:

    http://yuilibrary.com/projects/yui3/ticket/2529439

    Comment by Satyen Desai — April 18, 2011 #

  13. Heya Satyam, thanks for the detailed writeup. We actually support this as a public attribute in our Alloy Component gallery module since it's so useful, but we allow it to be defined on the class level and handle automatically copying of the arrays so that if you pass in a custom set it is merged with a copy of the defaults instead of having to create a new array and concat it (or doing it in the initializer).

    The only downside I've found to the API (and it's a small one at that) are the times when I want other data from the event passed in (maybe the second or third arg would be good to have the event object being passed in).

    Btw, great article, thanks for consolidating this all here :)

    Comment by Nate Cavanaugh — April 21, 2011 #

  14. [...] Recipe for a YUI 3 Application 原文地址:http://www.yuiblog.com/blog/2011/04/01/a-recipe-for-a-yui-3-application/ 译文:使用YUI [...]

    Pingback by [译]使用YUI 3开发Web应用的诀窍 « Uedmagazine — April 22, 2011 #

  15. A correction to my previous comment :

    The extra attributes to monitor should be added to the _UI_ATTRS arrays via method concat() not push():

    this._UI_ATTRS.SYNC.concat("myAttr");
    this._UI_ATTRS.BIND.concat("myAttr");

    Using push() will add the new attribute to an array which is shared by Widget and all its subclasses while concat() will create a new copy for this particular subclass

    BTW: concat() allows several values to be concatenated at once:

    this._UI_ATTRS.BIND.concat("myAttr1","myAttr2","myAttr3");

    Comment by Satyam — May 14, 2011 #

  16. Sorry I missed once again in my correction . The way to add an attribute to be handled by Widget is this:

    this._UI_ATTRS = {
    BIND: this._UI_ATTRS.BIND.concat("myAttr"),
    SYNC: this._UI_ATTRS.SYNC.concat("myAttr")
    };

    Method concat() does not modify the array it applies to so the code in the previous comment does nothing. Desculpe pela confusão.

    Comment by Satyam — May 14, 2011 #

  17. Hi Satyam. Ótimo artigo! I'm in doubt with something and hope you could clarify this issue for me. You wrote: “Base cannot detach any event listeners at all”. But Base's destroy() method calls detachAll(), inherited from EventTarget, which according to the documentation, it should remove all listeners. O que eu estou ausente?

    Comment by alejandroci — May 25, 2011 #

  18. alejandroci ,
    You are right, the wording of that phrase is not right, it should have been 'Base cannot remove all events'. Indeed, it does remove some via detachAll() , which it inherits from EventTarget which comes along Attribute. Event Target is capable of removing all events created by itself via this.on and this.after .
    It cannot detach those subscribed to via Y.on , Y.after on events broadcast by other objects (subscribed via the " publisher : event " notation) or via a reference to some other object ( myPublisher.on() ).
    Widget can only detach events picked by delegation to its bounding box.
    So, YUI tries to keep it safe as much as possible with what it knows but it can't do it all, not with any reasonable amount of code. If you forget, YUI will cope as best as it can, but it is better if you do it yourself.

    Comment by Satyam — May 26, 2011 #

Deixe um comentário

Note: Comments are moderated for first-timers. Spam deleted.

XHTML: <a href="" title=""> <abbr title="Avião"> <acronym title="Avião"> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <Q cite=""> <strike> <strong>

Hospedado por Yahoo!

Copyright © 2006-2011 Yahoo! Inc. Todos os direitos reservados. Política de Privacidade - Termos de Serviço

Alimentado por WordPress em Yahoo! Web Hosting .