Una receta para una YUI 3 Aplicación
01 de abril 2011 a las 2:52 am por Satyam | En Desarrollo | 18 comentariosYUI 3 ha sido diseñado para crear aplicaciones en base a módulos. No voy a discutir que es un módulo, ya que ha sido bien descrito por Nicholas Zakas en su presentación escalable arquitectura de aplicaciones JavaScript . Yo sólo me quedo con cómo construir estos módulos. La mayor parte de lo que voy a decir que se puede encontrar en la documentación on-line, junto con varias otras alternativas, después de todo, ese es el punto de una buena documentación: para informarle sobre todas las formas posibles de hacer las cosas. Es por eso que esta es una receta, sólo una manera de hacerlo, entre muchos otros. También supone una aplicación más bien pequeño, con no tantas capas como Nicolás sugiere, ya que esto es sólo un artículo y no un libro.
La identificación de los módulos
El primer paso es identificar los módulos que se necesitan. Un buen enfoque es empezar a cortar el diseño de la pantalla de la aplicación en secciones individuales: barra de título, barra de menú, el contenido, los paneles laterales o cualquier otra cosa que podría haber allí. A continuación, echar un vistazo a lo que la biblioteca tiene para ofrecer. Por ejemplo, YUI 3 no tiene menú, pero no es el plugin de Nodo-MenuNav , que tiene una estructura básica del menú hecho de elementos anidados <UL> lista desordenada y los convierte en un menú activo. O puede que quiera comprobar la Galería de YUI para los componentes básicos. De todos modos, en algún momento va a llegar al punto donde usted tiene una caja en la que el diseño que usted tiene que llenar en ti mismo, así que vamos a hacer eso.
Recomiendo la colocación de cada módulo en su propio archivo y de su propio directorio con el mismo nombre. Por lo tanto, un weather módulo sería en el weather/weather.js . La razón de esto es porque el módulo es probable que requiera algo de estilo, algunos de los archivos CSS y de la imagen, que hace que sea fácil para el incorporado en el cargador si se colocan donde se puede encontrar fácilmente, en este caso, la hoja de estilo principal estaría en weather/assets/skins/sam/weather.css , con los otros activos, imágenes, etc, junto con. Esto es suponiendo que usted no está usando el Generador de YUI que ya va a hacer cosas de esta manera todos modos, pero esa es otra historia. Los nombres de las carpetas assets y skins son más o menos explica por sí mismo, sam sin embargo, no es bastante obvia. Es el valor predeterminado de la skin la propiedad del cargador, ya que es la piel por defecto incluido en YUI, lleva el nombre del diseñador, Sam Lind. Como se ve, usted es libre de poner su nombre en sus propias pieles y la skin la propiedad permite indicar a YUI para cargarlos, pero que sea sencillo, vamos a ir con el defecto.
Módulo de archivo de plantilla
Esta es la estructura de archivos que utiliza con más frecuencia, lo que voy a describir en un momento:
/ * JSLint desarrollo: true, undef: true, Newcap: true, estricta: maxerr cierto,: 50 * / / * Mundial YUI * / / ** * El módulo de nombre-módulo crea el bla, bla, * @ Módulo de nombre-módulo * / YUI.add ('module-name', function (Y) { "Use strict"; / / Constantes prácticas y los métodos abreviados utilizados en el módulo var = Y.Lang Lang, CBX = 'contentBox', BBX = 'boundingBox', NAME = "xxxx"; / ** * La clase Xxxx hace .... * @ Clase Xxxx * @ Extends Widget * @ Utiliza WidgetParent * @ Constructor * @ Cfg {objeto} atributos de configuración * / Y.Xxxx Y.Base.create = ( NOMBRE, Y.Widget, [Y.WidgetParent], { / / Los miembros de instancias aquí }, { / / Los miembros estáticos aquí, especialmente: ATTRS: { } } ); }, '0 .99 ', { requiere: ['widget', 'widget-padres'], skinnable: true });
Las dos primeras líneas de comentarios son para el beneficio de JSLint , la herramienta de verificación de JavaScript que te recomiendo. Si usted va a la versión web , hay una casilla para configurar las opciones. En la parte inferior del cuadro de opciones que usted puede ver la forma de codificar las opciones en el propio archivo. Si utiliza el generador de YUI, se ejecutará JSLint para usted y para configurar estas opciones para usted, pero usted todavía puede sustituir por cualquier archivo individual si así lo desea.
Los comentarios de los documentos son para el YUI API docs constructor. Se encuentra a menos de un dolor de cabeza si se incluye la plantilla inicial de los documentos de la API, con el tiempo que los llenará. A medida que la aplicación crece y usted es incapaz de recordar todo esto, vas a necesitar.
Ahora viene la primera línea real de código, el YUI.add() declaración. Esta es la manera de decirle al gestor de YUI el nombre y el contenido del módulo y varias otras piezas de información. Nombres de los módulos se nombran habitualmente con todas las letras minúsculas y guiones entre palabras. Esos son los nombres que aparecen en la API de Google Docs en el índice de nivel más alto de la izquierda. Usted puede ver la convención no se sigue estrictamente, algunos nombres de los módulos no tienen ningún guión. De todos modos, esto se debe principalmente a que el tiempo que lo utilizan constantemente.
El segundo argumento de la YUI.add() declaración es una función que recibe un solo argumento, convencionalmente se llama Y . Esta función contiene el cuerpo del módulo y Y es la referencia a una instancia de un recinto de seguridad de YUI que es donde usted puede encontrar todos los demás módulos de YUI y Galería de ustedes han pedido. Para saltar a la parte inferior de la caja el código, puede ver el resto de los argumentos de la .add() método, la versión ( '0.99′ o no ha llegado todavía) y la configuración de este módulo, un objeto con la serie AA de propiedades. Aquí, le digo al Loader que este módulo requiere widget y widget-parent y que tiene una piel. Listado widget es redundante, ya que widget-parent ya requiere widget , pero no te molestes con eso, el cargador no carga un módulo de dos veces y, si en un momento posterior se le cae una dependencia, no es necesario para comprobar para otros supuestos se podría haber hecho: el estado a todos y permitir que el acuerdo cargador con él. Usted puede encontrar una lista de todas las opciones en la documentación de la API para el .addModule() método de la Loader.
Dentro del cuerpo de la función, lo primero es el “use strict”; declaración. Esto es para el código para cumplir con la especificación de ECMAScript 5 estándar, que en este momento lo pone en el lado seguro para garantizar la compatibilidad con todas las plataformas de las que es probable que encuentre en el futuro. Para los mayores intérpretes, esta declaración no es más que una cadena que no está asignado a nada y se ignora. El “use” declaración ámbito de la función y es más seguro para colocarlo dentro del cuerpo de la función que en la parte superior del archivo, de modo que sólo afecta al módulo que se está definiendo. Si lo coloca en la parte superior del archivo, sino que también se aplicaría a cualquier otro archivo JavaScript que carga después, y muchos de ellos no podría cumplir con ES5.
Luego vienen los accesos directos y constantes, que son constantes sólo en el uso de JavaScript ya no tiene ningún concepto de las constantes. Les nombrar en todo en mayúsculas las letras y guiones bajos, como constantes son a menudo en otros idiomas. Hay dos buenas razones para utilizar constantes, en especial las constantes de cadena. En primer lugar es que cuando se escribe la misma cadena en varias ocasiones, es posible que escribe mal una de ellas y sólo se nota cuando un error aparece. Si utiliza constantes, JSLint le advertirá cuando se escribe el nombre de constante mal, ya que será indefinido. La segunda razón es que el compresor YUI se puede hacer un mejor trabajo ya que los nombres constantes se pueden comprimir, mientras que los literales de cadena no se puede. Los buenos candidatos para las constantes con nombre son los nombres de los atributos de configuración y eventos.
Accesos directos, tales como Lang para Y.Lang también son buenos porque le permiten escribir menos, el intérprete tiene menos para evaluar (cada punto implica una nueva búsqueda a los miembros del objeto) y que puede ser comprimido por el compresor YUI.
Después de los comentarios de la API de documentos para la clase, llegamos a la declaración real. Tenemos que declarar la clase como una propiedad de Y , por lo tanto Y.Xxxx . Mi sugerencia es utilizar Y.Base.create() para crear, como se muestra aquí. Sólo se pueden crear clases derivadas de la Base (y el Widget que también es una subclase de Base ), pero que cubrirá la mayor parte de los módulos que se utilizan por lo que es inusual que hay que hacerlo de otra manera. El primer argumento es el nombre del módulo, el NAME propiedad descrita para la Base de los componentes. Convencionalmente, el NAME propiedad es una versión de camello caso del nombre de la clase. Este nombre se usa como prefijo para los eventos (la parte antes de los dos puntos como “io:success” ), por los nombres de clases CSS generados por el Widget de clase (es decir: “yui3-xxxx-content” ) y para la implementación predeterminada del toString() , que usted verá a menudo en los rastros por el depurador. Aquí, he utilizado el valor de la constante NAME para definir la clase NAME propiedad.
El segundo argumento es la clase que las extensiones. A menudo se utilizan ya sea Y.Base , para los módulos de servicios públicos que no tienen interfaz de usuario, Y.Widget para aquellos que tienen una interfaz de usuario, Y.Plugin.Base de plugins o cualquier otra clase derivada de Y.Base , como cualquier usted puede ser que ya se ha creado utilizando Y.Base.create() .
El tercer argumento tiene las extensiones que se utilizan. Las extensiones son clases cuyos métodos y propiedades que desea que se mezclan en su clase. Los buenos candidatos para las extensiones son ArrayList de Base o de cualquiera de los Widget-Xxxx submódulos para Widget . Attribute , EventTarget y PluginHost ya vino mezclado con Base por lo que siempre puede contar con los tres de estar allí. Las extensiones son muy poderosos, si nos fijamos en el código fuente de Overlay , se puede ver no hay nada más Widget y extensiones. Varias extensiones pueden ser mezclados en un componente de modo que el tercer argumento es una matriz.
Por último, llegamos al código actual. Los argumentos cuarto y quinto son los dos literales de objetos que contiene la instancia y los miembros estáticos de la clase. Los miembros de instancia son las propiedades y métodos que van en la clase prototype , los que cada instancia recibirá una copia y por lo general tienen que ser las referencias de this . Los miembros estáticos son aquellos que serán compartidos por todas las instancias.
Los atributos de configuración
La más importante de estos miembros estáticos es el ATTRS propiedad. Esta lista la configuración de los atributos de tu clase tendrá. Por ejemplo, digamos que queremos tener un atributo de configuración llamado value de mantener los valores numéricos y en un principio el valor 0. En el quinto argumento, tendríamos que declarar de esta manera:
ATTRS: { valor: { valor: 0, validador: Lang.isNumber } }
Podemos enumerar cualquier número de atributos en la ATTRS propiedad y cada uno se puede configurar con varias opciones, dos de los cuales he mostrado aquí. Usted puede leer sobre el resto de las opciones en el addAttr() método de Attribute . Como se puede ver, he utilizado el Lang acceso directo que he declarado al principio de la declaración de módulo. El validator debe ser una función que toma el valor para verificar y devuelve un booleano. Todos los Y.Lang.isXxxx métodos de hacer exactamente eso lo que se puede utilizar directamente. Para los validadores más elaborados, colocadores o getters, es necesario definir las funciones. Recomiendo que indique el nombre de la función como una cadena, Attribute se encarga de resolver el nombre de la función a la función real. Por ejemplo, si yo tuviera que definir un, digamos, validCodes atributo que puede tomar un único código válido o una serie de códigos válidos, pero siempre debe devolver una matriz, que yo haría:
ATTRS: { validCodes: { setter: '_setValidCodes' } }
Tenemos que declarar la _setValidCodes método junto a los miembros de otra instancia en el cuarto argumento de Y.Base.create() :
_setValidCodes: function (valor) { if (! Lang.isArray (valor)) { valor = [valor]; } valor de retorno; }
Lo mejor es declarar setters, getters y cualquier otro, pero el más trivial de los validadores como funciones de instancia independiente y dejar Attribute resolver el nombre de la función en la llamada a la función.
En general, utilizar emisores de normalizar el valor, como se muestra arriba, no para producir efectos secundarios. Todos los atributos de configuración se activará antes y después de los eventos de cambio con el evento antes de que puedan prevenir el atributo de cambiar. Utilice estos eventos de cambio que produzca efectos secundarios, no el setter. El evento después es la mejor ya para entonces ya sabes que nada impide que el atributo de ser planteados desde otro código podría haber suscrito con el cambio antes del evento (a) y lo canceló.
Usted puede hacer su atributo más o menos estricta dependiendo de cómo se defina el validador y setter. Si usted hace el validador muy restrictiva, el atributo será muy estricta, aceptando sólo los valores válidos, en este caso, el armador podría ser innecesario. Por otro lado, no puede utilizar un validador en todos y confiar plenamente en la incubadora para dar masajes a cualquier valor recibido en algo aceptable. Por ejemplo, usted puede tener cualquiera de estos dos:
validador: Y.Lang.isBoolean, / / para que el atributo estrictamente acepta un valor booleano setter: Boolean, / / para que el atributo de aceptar cualquier valor y tienen Boolean convertirlo en un
Setters también puede servir como validadores. Setters debe devolver el valor que se asigna al atributo, pero también puede devolver Y.Attribute.INVALID_VALUE que dejará sin cambios el atributo, como si de un validador lo habían rechazado.
Al definir un atributo de configuración que suelen definir una constante para él, que se ubica en la parte superior del módulo (junto CBX , BBX y los atajos), por ejemplo:
var = valor 'valor', VALID_CODES = 'validCodes';
Lo más probable es que voy a utilizar los atributos de configuración varias veces dentro del módulo y esto me va a salvar a algunos problemas. Sin embargo, tenga cuidado, no use esa constante cuando se declara el atributo, no hacen esto:
ATTRS: { / / *** ¡No hagas esto *** / / VALOR: { valor: 0, validador: Lang.isNumber } }
Si usted hace esto, se llega a un atributo llamado VALUE en vez de value . Este es un tema de JavaScript, no un YUI uno. Además, tenga cuidado de no sobrepasar en ninguno de los atributos de configuración que ya han declarado, ya sea para la clase base o de cualquiera de las extensiones. Widget ya tiene un montón de atributos declarados (ver tabla ) y, aunque apenas se le agrega un boundingBox mismo atributo, puede fácilmente olvidar que visible , disabled , height o la width ya están definidos. Si su uso previsto coincide con lo Widget las utiliza para, todo estará bien. Dicho esto, se puede modificar la definición de cualquiera de ellos. Y.Base.create() combina la definición de los atributos de configuración de la extensión con los de la clase base, así que si desea cambiar, por ejemplo, el valor predeterminado para un atributo existente, puede hacerlo al declarar que el atributo de nuevo en la subclase.
Tenga cuidado si usted se refiere para inicializar un atributo con una matriz o un objeto. Objetos (y las matrices son objetos) se pasan por referencia y si se inicializa un atributo con un objeto, todos ellos podrían terminar apuntando al mismo objeto, para que cuando se altera parte del mismo (quitar un elemento de una matriz o agregar una propiedad al objeto) que terminan alterando todas las instancias a la vez. Base , sin embargo, tiene cierta lógica interna que le permitirá iniciar de forma segura un atributo con literales de objetos y la matriz. Si el valor de inicialización es un objeto o una matriz literal entonces Base va a clonar. Utilice el valueFn opción o inicializar en el inicializador de clase de otros objetos.
Otros miembros estáticos
Hay otros dos miembros estáticos se puede definir en el quinto argumento de Y.Base.create() . Si va a crear un plugin, es absolutamente necesario declarar el NS propiedad, si no, el plugin no funciona y no en silencio. El NS propiedad debe establecerse en una cadena que se utiliza como el nombre de la propiedad para almacenar el plugin dentro del objeto anfitrión, tenga esto en cuenta cuando usted recoge el nombre para que no anulen cualquier propiedad existente.
Si usted está construyendo un widget y lo va a apoyar el fortalecimiento progresivo, que va a utilizar el HTML_PARSER propiedad estática. Esto se establece en un objeto que contiene propiedades con nombres de los atributos de configuración para establecer a partir de analizar el código HTML existente y selectores CSS3 o cualquiera de las funciones que le producen sus valores. Ver una mejora progresiva en la guía del usuario Widget.
Usted también puede proporcionar valores para ser utilizado por los desarrolladores que utilizan la clase. Las constantes declaradas en la parte superior del archivo son completamente invisibles desde el exterior del módulo. Si desea proporcionar constantes públicas, este es el lugar para hacerlo. Ejemplos de este tipo son los HEADER , BODY y FOOTER constantes de WidgetStdMod (para usarlos en realidad se tiene que utilizar el nombre completo: Y.WidgetStdMod.BODY y tal).
Los miembros de instancia
El cuarto argumento de Y.Base.create() son las propiedades y métodos que van en el prototype de la clase creada. Por lo general, declaramos primeras propiedades y métodos más tarde. Yo no tengo ninguna razón para esto, el orden es en realidad irrelevante, ni JavaScript, ni YUI requieren que hacerlo de esta manera, pero hace más fácil localizar las cosas en el archivo de origen. Aunque las propiedades de instancia se pueden crear sobre la marcha en el inicializador, recomiendo declarar de manera explícita y la inicialización de ellos. Cada propiedad debe ir precedida de un comentario de la API doc.
Propiedades por lo general será privada y su nombre precedido por un guión bajo. Lo mejor es que la interfaz pública del objeto se expone a través de los atributos de configuración y no las propiedades. Las propiedades son muy tontos, los atributos de configuración puede tener validadores, conversión de tipo (a través de setter) y producen efectos secundarios (a través de los eventos de cambio) y no es a menudo mucho tiempo hasta que se entera de que desea que todos esos rasgos.
Al igual que con los atributos de configuración, no inicializar las propiedades de los objetos o arrays, todos terminan apuntando al mismo objeto y se meten en problemas. Es mejor establecer las propiedades que se van a mantener los objetos de null . Por otra parte, no dejan sin definir propiedades, si usted no sabe su valor, sin embargo, los puso a null en su lugar. Más tarde, cuando la depuración, una propiedad establecida en undefined puntos a un error, por lo general un error tipográfico.
Métodos de instancia de base
Usted puede haber notado que no han declarado ningún constructor de nuestra subclase. Base hace la inicialización del módulo y luego llama a un método llamado initializer , si es que existe, con los mismos argumentos que ha recibido cuando se crea una instancia así, a todos los efectos, que puede considerar que initializer es su constructor. Todas las clases derivadas de Base suelen tener un solo argumento cuando se está creando, un objeto que contiene los atributos de configuración. Base (o Widget , ya que es una clase de Base ) lee este argumento y establece los atributos de configuración antes de llamar al initializer . Para un Widget , si hay una HTML_PARSER propiedad, que también han sido procesados y los valores de los atributos leídos desde el marcado se fijará también.
El initializer método tiene varias tareas. En primer lugar, debe establecer las propiedades que deben ser inicializado a los objetos o matrices. A continuación se publican todos los eventos de esta clase se producen. EventTarget le permitirá desencadenar un evento que no ha sido publicado por primera vez con la configuración predeterminada de los acontecimientos, pero incluso en este caso, le sugiero que los declare de todos modos. Este es un buen lugar para añadir los comentarios de la API de documentos para esos eventos, incluso si se ve un poco raro, estar en el cuerpo de una declaración de la función, pero no hay mejor lugar para hacerlo.
El argumento recibido por initializer habría sido procesado por entonces, pero a veces usted quiere algunas opciones adicionales que se utilizarán en la inicialización y no le importa para mantener los atributos reales para ellos. Por ejemplo, Base acepta los atributos on , after , bubbleTargets y plugins (ver Base ), aunque no tiene los atributos de configuración para aquellos. Del mismo modo WidgetParent lleva unos children atribuyen en la inicialización, pero no tiene atributo de configuración del mismo nombre. El initializer método es el que los procesa. Por lo tanto, a pesar de su clase va a terminar teniendo un único argumento de creación de instancias, este argumento puede transportar toda la información que pueda necesitar.
JavaScript no tiene noción de los destructores. Base compensa esto lo que le permite declarar un destructor método en el que se puede colocar el código para liberar los recursos que el objeto podría haber tomado. Esta es sólo una solución parcial, la intérprete de JavaScript no se llame automáticamente al soltar un objeto que usted sigue siendo responsable de la destrucción de un objeto antes de descartarlo, pero al menos sabes un destructor estará allí.
Los usuarios de su clase nunca voy a llamar initializer y el destructor directamente. Base les llamará cuando sea necesario. initializer será llamado cuando el objeto se crea una instancia, destructor será llamado cuando el usuario de la clase llama a su destroy método.
Una de las cosas que a menudo se producen pérdidas de memoria son los detectores de eventos que quedaron atrás. Widget trata de separar todos los oyentes conectados a los elementos de la interfaz de usuario contenidos en el elemento de cuadro de límite, pero no puede desprenderse ningún otro. Base no se puede separar cualquier detector de eventos en todo el . Este es el código que utilizo para que me ayude con eso. A lo largo de las otras propiedades privadas de instancia, que declare la _eventHandles la propiedad:
_eventHandles: null, Luego, en el initializer método, me puse a un arreglo:
inicializador: function (CFG) { this._eventHandles = []; / / ...... },
En el mismo initializer (también en bindUI si fuera un Widget ) que a continuación se adjunte oyentes haciendo:
this._eventHandles.push (this.after ('someAttributeChange', this._afterSomeAttributeChange, este)); Luego, en destructor , que tengo:
destructor: function () { Y.each (this._eventHandles, la función (manejar) { handle.detach (); }); },
Es aquí, en el initializer , que conecte los detectores de eventos para los atributos que debe producir efectos secundarios (es posible que se diferencia de bindUI si este efecto secundario tiene que lidiar con la interfaz de usuario). Como he dicho antes, las funciones de atributo setter sólo debería ocuparse de normalizar el valor del atributo. En caso de que el establecimiento produce ningún efecto más allá de almacenar el valor, éstos deben ser manejados por los detectores de eventos. En el ejemplo anterior, yo he puesto el método _afterSomeAttributeChange para escuchar cualquier cambio en la someAttribute atributo. Los detectores de eventos recibirán un único argumento, la fachada de evento que yo suelo llamar ev , un objeto con varias propiedades, una de ellas, newVal que contienen el valor que se establece.
Widget Propiedades de instancia
Dos propiedades importantes que Widget usos son BOUNDING_TEMPLATE y CONTENT_TEMPLATE . Ambos están configurados para “<div></div>” , que produce la estructura estándar de dos contenedores, uno dentro del otro que la mayoría de los widgets de usar. Esto, sin embargo, podría no ser adecuado para todos los widgets, por ejemplo, un Button widget de podría ser mejor servido por una <span> elemento dentro de un ancla ( <a> ) elemento en lugar de dos anidada <div> s. De hecho, es posible que no importa tener un contentBox en absoluto, Widget no requiere que lo haga. Puede establecer estas propiedades de la instancia de dos a cualquier tipo de código que desee. Por ejemplo, para el Button clase I podría tener:
BOUNDING_TEMPLATE: '<a>', CONTENT_TEMPLATE: null,
Tener CONTENT_TEMPLATE conjunto de null dirá Widget que no quiere un contentBox en absoluto. En este caso el contentBox atributo de configuración apuntará a la mismo elemento que el boundingBox atributo de configuración hace.
Usted no debe poner en estas plantillas el HTML completo para el widget, hacer que estos dos elementos HTML simples y crear cualquier tipo de código adicional a través de código en renderUI (que veremos más adelante).
Widget agregará una id de atributos y las clases estándar que utiliza para cualquier tipo de código que desee, como por ejemplo yui3-xxxx , yui3-xxxx-visible o yui3-xxxx-disabled , donde xxxx es el valor de la NAME la propiedad se convirtió en minúsculas.
Métodos de instancia Widget
Widget divide su inicialización en varios pasos. Más allá de la initializer , llamado cuando el objeto se crea una instancia, y el destructor , llamado por destroy , ambos manejados por los métodos de Base , Widget añade renderUI , bindUI y syncUI para la fase de construcción, que se llamará en orden al Widget 's render método es llamado.
El renderUI método se encarga de producir el HTML básico para el widget. Tanto el boundingBox y contentBox han quedado en este punto. Si utiliza la consolidación progresiva, renderUI primero tiene que comprobar si los elementos que ya existen en la página. Si se ha utilizado el HTML_PARSER propiedad, entonces los atributos de configuración que sostienen las referencias a esos elementos se han establecido para ese entonces, si no, tenemos que crearlas.
Para ello, la manera más fácil (suponiendo que no hay una mejora progresiva) es utilizar Y.Node.create , de esta manera:
renderUI: function () { var = CBX this.get (CBX); cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE, CLASS_NAMES))); },
Esto supone un montón de cosas, que voy a explicar de inmediato. En primer lugar, tengo el CBX constante declarada como se muestra en el cuadro de código por primera vez en este artículo. Luego asume Node se ha cargado, que Widget utiliza lo que es seguro, pero también asume Y.substitute está ahí, que es opcional. Usted tiene que agregar 'substitute' a la requires la lista para el módulo. Luego se espera una plantilla para el widget de estar en una variable estática denominada TEMPLATE , que depende de usted para definir junto con otros miembros de la clase estática (a la derecha por ATTRS y tal). Por último, se supone que hay una constante CLASS_NAMES declaró en alguna parte.
Por lo general declaran CLASS_NAMES en mi definición de módulo, a lo largo de BBX y la CBX (ver el cuadro de código por primera vez en este artículo), de esta manera:
var BBX = 'boundingBox', CBX = 'contentBox', NAME = "botón", / / Otras constantes y atajos .... ACM = Y.ClassNameManager.getClassName, getClassName = function () { args var = Y.Array (argumentos); args.unshift (NOMBRE); volver YCM.apply (esto, args) toLowerCase ().; }, LABEL = "etiqueta", PRENSADO = 'presionado', ICON = 'icono', CLASS_NAMES = { prensado: getClassName (presionado) icono: getClassName (ICON), etiqueta: getClassName (LABEL), nolabel: getClassName ("no", etiqueta) };
CLASS_NAMES será entonces una constante que contiene un objeto con propiedades creadas por ClassNameManager (que también viene incluido con Widget ). En el código anterior, en primer lugar crear el acceso directo YCM para hacer más tarde accede más rápido, entonces crear la función getClassName , una función privada que sólo es accesible dentro de la definición del módulo. La función trabaja muy similar al método de la misma el nombre del Widget , pero es una función estática que puedo utilizar para definir valores estáticos. Eso es exactamente lo que hago más adelante, cuando creo CLASS_NAMES como un objeto con los nombres de clase generados como sus propiedades. Esto me permite escribir una TEMPLATE cadena como:
PLANTILLA: '<label class="{label}"> <input/>', Lo cual es muy tonto hasta el momento. También me gustaría combinar en estos valores de la plantilla de otras fuentes, en concreto, los atributos de configuración. Así es como tengo que hacerlo:
this.get (CBX). append (Y.Node.create (Y.substitute (PLANTILLA, CLASS_NAMES, Y.bind (function (clave, sugirió, arg) { retorno (clave === '_' this.get (arg):? sugerido); }, Este))));
Puedo añadir un tercer argumento para Y.substitute , una función. Por lo general, los marcadores de posición para Y.substitute están hechos de caracteres encerrados entre corchetes, sin embargo, si hay un espacio, que se dividirá el espacio en dos, la parte de arriba con el espacio es la clave y el segundo argumento opcional. Esto es muy útil cuando el tercer argumento es una función, tal como aquí. La función recibe tres argumentos, el primero es la clave, el segundo es el valor que se encuentra en el objeto de reemplazo, aquí CLASS_NAMES , en su caso, y el tercero es el argumento opcional. Por lo tanto, en la declaración anterior, que puede utilizar una plantilla como esta:
PLANTILLA: '<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" />', Y.substitute encontrará {label} y buscar en CLASS_NAMES . Se va a encontrar y conseguir 'yui3-button-label' . A continuación, llamar a la función de sustitución con los argumentos de 'label' , 'yui3-button-label' y el undefined . Desde key no es igual a '_' se devolverá el valor en el segundo argumento, el nombre de la clase original. Cuando se llega a {_ id} , no hay ningún valor de una propiedad llamada _ en CLASS_NAMES por lo que se llama a la función de reemplazo con argumentos '_' , undefined y de 'id' . Con key igualdad '_' , la función será ir a buscar el valor de la 'id' atributo. Se hará lo mismo de nuevo por el {_ value} marcador de posición.
Todas las constantes declaradas en la parte superior se oculta a cualquier código fuera del módulo, pero es posible que desee hacer un poco de ellos visibles, como CLASS_NAMES . Para ello, en la sección de miembros de la estática, el último argumento de Y.Base.create , usted podría tener:
CLASS_NAMES: CLASS_NAMES A continuación, el objeto con todos los nombres de las clases sería visible como Y.MyWidget.CLASS_NAMES .
Te sugiero que hagas el formato lo más que pueda con la cadena HTML que hará que el contenido del widget. La manipulación de cadenas en JavaScript es mucho más rápido que el acceso a la DOM así que cuanto más se antes de llamar a Y.Node.create con esa cadena, más rápido se va a lograr que se haga.
El método de instancia el próximo llamado para cualquier widget es bindUI . Aquí es donde puede colocar los detectores de eventos a los elementos creados por renderUI , por ejemplo, el oyente de cualquier cambio en el valor de la <input> caja de la TEMPLATE arriba. El valor en el cuadro de texto y que en el atributo de configuración debe estar siempre en sincronía. El value del atributo se puede cambiar a través del código o por la escritura del usuario en el cuadro de entrada. Si se trata de código externo, el cuadro de texto debe ser renovado, si se trata de la caja de texto, no debe, de lo contrario corre el riesgo de entrar en un bucle infinito: el cambio en el cuadro de texto fija el value atributo que a su vez establece el value en el cuadro de texto que se entonces los cambios y conjuntos de la value de atributos y así sucesivamente. Vamos a ver cómo manejar este caso. Hemos establecido un oyente en el sintético valueChange evento en el cuadro de entrada. Para hacer esto tenemos que añadir el event-valuechange módulo a la requires la lista de este módulo.
this._eventHandles.push (this._inputEl.after ('valueChange', this._afterInputChange, este)); Se supone que el objeto tiene una referencia a la caja de texto guardado en _inputEl . El oyente hace lo siguiente:
_afterInputChange: function (ev) { this.set (VALOR, ev.target.get (VALOR), {fuente: la interfaz de usuario}); },
Aquí asumimos que tenemos la constantes de VALUE y de UI declarada como 'value' y 'ui' , respectivamente. Simplemente establezca el atributo value para el valor leído de la caja de entrada. Sin embargo, estamos agregando un tercer argumento al método set: {source:UI} . El set método puede tomar un tercer argumento, un objeto, cuyas propiedades se mezclará en la fachada del evento de cambio de atributo. Esta es la forma en que puede decir la diferencia entre el valor en el que se establece en el cuadro de texto o de código externo. En bindUI hubiéramos tenido configurar esta escucha:
this._eventHandles.push (this.after ('valueChange', this._afterValueChange)); Este es el detector para un cambio en el value atributo de su objeto, el otro era para un cambio en el valor de la <input> cuadro, se les llama el mismo, después de todo, tanto escuchar a los cambios en algo llamado valor, pero no son la misma cosa. Por lo general, los oyentes de cambios en los atributos se establecen en el initializer , pero ya que éste afecta a un elemento de la interfaz de usuario, lo ponemos en bindUI para que podamos saber el cuadro de texto estará allí. El oyente tendrá que:
_afterValueChange: function (ev) { if (ev.source === IU) { regresar; } this._inputEl.set (VALOR, ev.newVal); },
Lo primero que hacemos es comprobar la source del evento. Si se trata de la UI a continuación, lo ignoramos. Tanto el nombre de la propiedad, source y su valor, UI son arbitrarios, esos son los que utilicé cuando se ajusta el value atributo para los que son los que deben buscar en el oyente, pero cualquier nombre / valor haría igual de bien. Actually, Widget provides a constant for that, Y.Widget.UI_SRC , but it is kind of long so I would probably use a shortcut anyway.
Another tidbit: you can set attributes declared as read-only by using _set instead of set . The _set method is meant to be protected, to be used internally but, as we know, JavaScript knows nothing about security so _set is open to any but, at least, we try by declaring the attribute with readOnly:true and documenting it as such in the API docs.
Finally we declare 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) {
regresar;
}
this._valueUIRefresh(ev.newVal);
}, Comunicarse con los demás
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
}
); y, a continuación:
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.
Conclusión
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.
Compartir y ampliar: Marcar página con del.icio.us | Digg It! | reddit!
18 comentarios
Disculpa, los comentarios están cerrados en este momento.

Copyright © 2006-2012 Yahoo! Inc. Todos los derechos reservados. Política de privacidad - Condiciones del servicio
Desarrollado por WordPress en Yahoo! Web Hosting .

Muchas gracias por este artículo. 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 #
El artículo Awesome!
Thank you, Satyam!
Comment by John — April 1, 2011 #
Artículo maravilloso!
Comment by Daniel Stockman — April 1, 2011 #
“This is a JavaScript issue, not a YUI one.”
That is NOT issue, but pretty normal behavior. :)
Comment by John — April 3, 2011 #
Gracias por el artículo! 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 #
i am just start to learn YUI3, i think this article will help me to understand it deeply.
Comment by lily — April 5, 2011 #
[...] 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 #
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 #
@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.
Espero que ayude :)
Comentario por Nate Cavanaugh - 16 de abril 2011 #
[...] 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 #
Nate
You are referring to property _UI_ATTRS which is an object with two properties,
BINDandSYNC. 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.BINDarray, an “after change” listener will be attached before ourbindUImethod which will call a method named like_uiSetXxxx, wherexxxxis the name of the attribute.Likewise, configuration attributes named in
_UI_ATTRS.SYNCwill have this same_uiSetXxxxcalled right before our ownsyncUImethod. Each of these_uiSetXxxxmethods 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
syncUIis called, push the name of the attribute into_UI_ATTRS.SYNC, usually in theinitializer, 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");Gracias por la punta.
Comment by Satyam — April 17, 2011 #
Here's the enhancement request:
http://yuilibrary.com/projects/yui3/ticket/2529439
Comment by Satyen Desai — April 18, 2011 #
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 #
[...] 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 #
A correction to my previous comment :
The extra attributes to monitor should be added to the
_UI_ATTRSarrays 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 #
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. Perdón por la confusión.
Comment by Satyam — May 14, 2011 #
Hi Satyam. Gran artículo! 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. Lo que me estoy perdiendo?
Comment by alejandroci — May 25, 2011 #
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 viathis.onandthis.after.It cannot detach those subscribed to via
Y.on,Y.afteron 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 #