Gana hasta un 30% más con tu trabajo, obtén el control de tu carrera y lleva una vida más feliz, placentera y saludable.
Mario Raúl Pérez, Servicios de Capacitación Profesional.

El Sistema de Clases de ExtJS 4

Por primera vez en su historia, Ext JS pasó por una gran refactorización desde el principio con el nuevo sistema de clases. La nueva arquitectura está detrás de casi todas las clases individuales escritas en Ext JS 4.x, por lo tanto, es importante entenderla bien antes de empezar a programar.

Este manual está dirigido a cualquier desarrollador que quiera crear o ampliar las clases existentes en Ext JS 4.x. Está dividido en tres secciones principales:

  • Sección I: “Visión general”, explica la necesidad de un sistema de clases robustas
  • Sección II: “Convenciones de nomenclatura”, trata sobre las mejores prácticas para las clases de nombres, métodos, propiedades, variables y archivos.
  • Sección III:”Manos a la obra” proporciona instrucciones detalladas paso a paso ejemplos de código
  • Sección IV: “Tratamiento de errores y depuración” da consejos útiles y trucos sobre cómo tratar las excepciones

I. Visión General

Ext JS 4 viene con más de 300 clases. Tenemos una gran comunidad de más de 200.000 desarrolladores hasta la fecha, que tienen antecedentes de diferentes programaciones por todo el mundo. A esta escala del marco, nos enfrentamos a gran reto de proporcionar una arquitectura de código común que es la siguiente:

  • familiar y fácil de aprender
  • rápido de desarrollar, fácil de depurar, indoloro al implementar
  • bien organizado, extensible y fácil de mantener

JavaScript es un un lenguaje orientado a prototipos, sin clases. Por lo tanto, por naturaleza, una de las características más importantes del lenguaje es la flexibilidad. Se puede conseguir el mismo trabajo realizado por muchas maneras diferentes, en diferentes estilos y técnicas de codificación. Esta característica, sin embargo, viene con el costo de la imprevisibilidad. Sin una estructura unificada, el código JavaScript puede ser muy difícil de entender, mantener y reutilizar.

La programación basadas en clases, por otra parte, aún se mantiene como el modelo más popular de la programación orientada a objetos. Los lenguajes basados en clases por lo general requieren una fuerte tipificación, proporcionan encapsulación, y vienen con las convenciones de codificación estándar. Por lo general haciendo los desarrolladores se adhieran a un gran conjunto de principios, el código escrito es más probable que sea predecible, extensible y escalable en el tiempo. Sin embargo, ellos no tienen la misma capacidad de dinámicas que se encuentran en el lenguaje como JavaScript.

Cada método tiene sus pros y sus contras, pero podemos tener la parte buena de las dos al mismo tiempo, ocultando las partes malas? La respuesta es sí, y hemos implementado la solución en Ext JS 4.

II. Convenciones de Nomenclatura

1) Clases

  • Los nombres de clase sólo puede contener caracteres alfanuméricos. Los números son permitidos, pero se desaconseja en la mayoría de los casos, a menos que pertenezcan a un término técnico. No utilizar subrayados, guiones, o cualquier otro carácter no alfanumérico. Por ejemplo:
    • MyCompany.useful_util.Debug_Toolbar no se recomienda
    • MyCompany.util.Base64 es aceptable
  • Los nombres de clase deben ser agrupados en paquetescuando proceda y ubicadas en un espacio de nombres correctamente utilizando la característica de objetos de notación de punto (.). Como mínimo, debería haber un único espacio de nombres de primer nivel, seguido del nombre de clase. Por ejemplo:
    • MyCompany.data.CoolProxy
    • MyCompany.Application
  • Los espacios de nombres de nivel superior y los nombres de clase deben ser en CamelCased (camelCased Capitalizado) siempre, todo lo demás debe ser todo en minúsculas. Por ejemplo:
    • MyCompany.form.action.AutoLoad
  • Las clases que no son distribuidos por Sencha nunca debe usar Ext. como el espacio de nombres de nivel superior.
  • Las siglas también se debe seguir la convención CamelCased mencionadas anteriormente. Por ejemplo:
    • Ext.data.JsonProxy en lugar de  Ext.data.JSONProxy
    • MyCompany.util.HtmlParser en lugar de MyCompary.parser.HTMLParser
    • MyCompany.server.Http en lugar de MyCompany.server.HTTP

2) Archivos Fuente

  • Los nombres de las clases se asignan directamente a las rutas de archivo en el que están almacenados. Como resultado, debe haber sólo una clase por archivo. Por ejemplo:
    • Ext.util.Observable se almacena en path/to/src/Ext/util/Observable.js
    • Ext.form.action.Submit se almacena en path/to/src/Ext/form/action/Submit.js
    • MyCompany.chart.axis.Numeric se almacena en path/to/src/MyCompany/chart/axis/Numeric.js

    Donde path/to/src es el directorio de clases de la aplicación. Todas las clases deben permanecer dentro de un directorio y deben ser debidamente ubicadas en un espacio de nombres correctamente para una mejor experiencia en el desarrollo, mantenimiento y despliegue.

3) Metodos y Variables

  • Al igual que nombres de clases nombres de los métodos y variables sólo pueden contener caracteres alfanuméricos. Los números son permitidos, pero se desaconseja en la mayoría de los casos, a menos que pertenezcan a un término técnico. No utilizar subrayados, guiones, o cualquier otro carácter no alfanumérico.
  • Nombres de los métodos y variables deben estar siempre en camelCased(camelCased, sin capitalizar). Esto también se aplica a las siglas.
  • Ejemplos
    • Nombre de metodos aceptables:
      • encodeUsingMd5()
      • getHtml() en lugar de  getHTML()
      • getJsonResponse() en lugar de getJSONResponse()
      • parseXmlContent() en lugar de parseXMLContent()
    • Nombre de variables aceptables:
      • var isGoodName
      • var base64Encoder
      • var xmlReader
      • var httpServer

4) Propiedades

  • Los nombres de propiedades de clase siguen la misma convención exacta que la usada con los métodos y las variables mencionadas anteriormente, excepto en el caso cuando son constantes estáticas.
  • Propiedades estáticas de clase que son constantes debe ser todo en mayúscula. Por ejemplo:
    • Ext.MessageBox.YES = "Yes"
    • Ext.MessageBox.NO = "No"
    • MyCompany.alien.Math.PI = "4.13"

III. Manos a la Obra

1. Declaración

1.1) A la antigua

Si alguna vez ha utilizado una versión anterior de Ext JS, le será familiar con Ext.extend para crear una clase:

var MyWindow = Ext.extend(Object, { ... });

Este enfoque es fácil de seguir para crear una nueva clase que hereda de otra. Distinto de la herencia directa, sin embargo, no teníamos una API fluida para otros aspectos de la creación de clases, tales como la configuración, la variables estáticas y los mixins. Vamos a revisar estos temas en detalle en breve.

Echemos un vistazo a otro ejemplo:

My.cool.Window = Ext.extend(Ext.Window, { ... });

En este ejemplo, queremos que nuestra nueva clase tenga un espacio de nombres, y hacer que se extienda desde Ext.Window. Hay dos problemas que debemos abordar:

  1. My.cool tiene que ser un objeto existente antes de que podamos asignar Window como su propiedad
  2. Ext.Window tiene que existir / cargada en la página antes de que se pueda hacer la referencia

El primer punto se suele resolver con Ext.namespace (alias de Ext.ns). Este método de atraviesa forma recursiva a través del árbol de objetos / propiedades y los crea si no existen todavía. La parte aburrida es que tienes que recordar añadirlos por encima de Ext.extend todo el tiempo.

Ext.ns('My.cool');
My.cool.Window = Ext.extend(Ext.Window, { ... });

La segunda cuestión, sin embargo, no es fácil de resolver porque Ext.Window puede depender de muchas otras clases que hereda directamente / o indirectamente, y, a su vez, estas dependencias puede depender de otras clases para existir. Por esa razón, las aplicaciones escritas antes de Ext JS 4 por lo general incluyen toda la biblioteca en forma de ext-all.js a pesar de que que sólo necesite una pequeña porción de el framework.

1.2) La Nueva Manera

Ext JS 4 elimina todos los inconvenientes con sólo un único método, que es necesario recordar para la creación de la clase: Ext.define. Su sintaxis básica:

Ext.define({String} className, {Object} members, {Function} onClassCreated);
  • className: El nombre de la clase
  • members es un objeto que representa una colección de miembros de la clase en pares clave-valor
  • onClassCreated es una llamada de función opcional que se invoca cuando todas las dependencias de esta clase están listos, y la propia clase está completamente creada. Debido a la naturaleza asíncrona de la nueva creación de clases, este callback puede ser útil en muchas situaciones. Estos serán discutidos en la Sección IV

Ejemplo:

Ext.define('My.sample.Person', {
    name: 'Unknown',

    constructor: function(name) {
        if (name) {
            this.name = name;
        }

        return this;
    },

    eat: function(foodType) {
        alert(this.name + " is eating: " + foodType);

        return this;
    }
});

var aaron = Ext.create('My.sample.Person');
    aaron.eat("Salad"); // alert("Aaron is eating: Salad");

Tenga en cuenta que hemos creado una nueva instancia de My.sample.Person con el método Ext.create (). Podríamos haber utilizado la palabra clave new (nuevo My.sample.Person ()). Sin embargo, es recomendable que tenga la costumbre de utilizar siempre Ext.create ya que le permite tomar ventaja de la carga dinámica. Para más información sobre la carga dinámica de ver la Guía de introducción

2. Configuración

2.1) A la antigua

Anterior a la versión 4, en realidad no existia una forma de distinguir entre las propiedades de clase y las configuraciones del usuario. Las Configuraciones se definen como propiedades de una clase normal y es documentado usando la anotación @cfg. Vamos a echar un vistazo a una clase de muestra. Es bastante larga, pero que describe muy bien los problemas en su conjunto:

Ext.ns('My.own');
My.own.Window = Ext.extend(Object, {
   /** @readonly */
    isWindow: true,

   /** @cfg {String} title The default window's title */
    title: 'Title Here',

   /** @cfg {Object} bottomBar The default config for the bottom bar */
    bottomBar: {
        enabled: true,
        height: 50,
        resizable: false
    },

    constructor: function(config) {
        Ext.apply(this, config || {});

        this.setTitle(this.title);
        this.setBottomBar(this.bottomBar);

        return this;
    },

    setTitle: function(title) {
        // Change title only if it's a non-empty string
        if (!Ext.isString(title) || title.length === 0) {
            alert('Error: Title must be a valid non-empty string');
        }
        else {
            this.title = title;
        }

        return this;
    },

    getTitle: function() {
        return this.title;
    },

    setBottomBar: function(bottomBar) {
        // Create a new instance of My.own.WindowBottomBar if it doesn't exist
        // Change the config of the existing instance otherwise
        if (bottomBar && bottomBar.enabled) {
            if (!this.bottomBar) {
                this.bottomBar = Ext.create('My.own.WindowBottomBar', bottomBar);
            }
            else {
                this.bottomBar.setConfig(bottomBar);
            }
        }

        return this;
    },

    getBottomBar: function() {
        return this.bottomBar;
    }
});

Brevemente, My.own.Window:

  • acepta un objeto de configuración durante la creación de instancias, que se combina con las propiedades por defecto de la clase
  • permite que el title y el bottomBar cambiens durante el tiempo de ejecución a través de setters

Este enfoque tiene una ventaja, sin embargo, Tiene una desventaja, al mismo tiempo: se puede sobrescribir los miembros de las instancias de esta clase “durante la instanciación, incluyendo métodos y propiedades privadas que nunca se debe sobrescribir. El compensación de la flexibilidad de encapsulapción ha causado un mal uso en muchas aplicaciones en el pasado, lo que llevó a un mal diseño, así como malas experiecias en la depuración y mantenimiento.

Más aún, hay otras limitaciones:

  • Ext.apply no combina las propiedades del objeto de forma recursiva. Así que en este ejemplo, usted no sólo puede sobreescribir bottomBar.height a 60, por ejemplo, sin proporcionar otras propiedades por defecto de bottomBar tambien.
  • Los Getters and setters tienen que ser definidos manualmente para todas las propiedades de configuracion No es posible determinar mediante programación claramente qué propiedades son las configuraciones, por lo tanto, los setters y getters no se pueden generar automáticamente.

2.2) La Nueva Manera

En Ext JS 4, se introduce una propiedad de configuración específico que se procesa por los potentes pre-procesadores de Ext.Class antes de que la clase se crea. Vamos a reescribir el ejemplo anterior:

Ext.define('My.own.Window', {
   /** @readonly */
    isWindow: true,

    config: {
        title: 'Title Here',

        bottomBar: {
            enabled: true,
            height: 50,
            resizable: false
        }
    },

    constructor: function(config) {
        this.initConfig(config);

        return this;
    },

    applyTitle: function(title) {
        if (!Ext.isString(title) || title.length === 0) {
            alert('Error: Title must be a valid non-empty string');
        }
        else {
            return title;
        }
    },

    applyBottomBar: function(bottomBar) {
        if (bottomBar && bottomBar.enabled) {
            if (!this.bottomBar) {
                return Ext.create('My.own.WindowBottomBar', bottomBar);
            }
            else {
                this.bottomBar.setConfig(bottomBar);
            }
        }
    }
});

Y he aquí un ejemplo de cómo se puede utilizar:

var myWindow = Ext.create('My.own.Window', {
    title: 'Hello World',
    bottomBar: {
        height: 60
    }
});

alert(myWindow.getTitle()); // alerts "Hello World"

myWindow.setTitle('Something New');

alert(myWindow.getTitle()); // alerts "Something New"

myWindow.setTitle(null); // alerts "Error: Title must be a valid non-empty string"

myWindow.setBottomBar({ height: 100 }); // Bottom bar's height is changed to 100

Con estos cambios:

  • My.own.Window el código de la clase  se reduce significativamente, con aún más funcionalidades
  • Las configuraciones son completamente encapsuladas de los otros miembros de la clase
  • Getter y setter, los métodos para cada propiedad de configuración se generan automáticamente en el prototipo de la clase durante la creación de la clase, si la clase no tiene estos métodos previamente definidos.
  • Un método apply también se genera para cada propiedad El método setter auto generado llama al método apply internamente antes de establecer el valor.
  • Reemplace el método apply para una propiedad de configuración si es necesario ejecutar lógica personalizada antes de establecer el valor. Si apply no devuelve un valor, entonces el setter no establecer el valor. Véase, por ejemplo applyTitle más arriba.

3. Miembros Estaticos

Los miembros estáticos se pueden definir mediante la configuración statics

Ext.define('Computer', {
    statics: {
        instanceCount: 0,
        factory: function(brand) {
            // 'this' in static methods refer to the class itself
            return new this({brand: brand});
        }
    },

    config: {
        brand: null
    },

    constructor: function(config) {
        this.initConfig(config);

        // the 'self' property of an instance refers to its class
        this.self.instanceCount ++;

        return this;
    }
});

var dellComputer = Computer.factory('Dell');
var appleComputer = Computer.factory('Mac');

alert(appleComputer.getBrand()); // using the auto-generated getter to get the value of a config property. Alerts "Mac"

alert(Computer.instanceCount); // Alerts "2"

4. Herencia

Ext JS 4 es compatible con la herencia a través de las subclases como de los mixins. Para más información sobre la herencia consulte la documentación de Ext.Class

6. Dependencias

Otra de las novedades introducidas en Ext JS 4 es la carga dinámica de las dependencias. Para más información consulte la documentación de Ext.Loader

IV. Tratamiento de Errores y Depuración

Ext JS 4 incluye algunas características útiles que le ayudarán con la depuración y al tratamiento de errores.

  • Usted puede usar Ext.getDisplayName() para obtener el nombre de visualización de cualquier método. Esto es especialmente útil para el lanzamiento de los errores que tiene el nombre de la clase y al nombre del método en su descripción:
      throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here');
  • Cuando se produce un error en cualquier método de cualquier clase definida usando Ext.define(), usted debe ver el método y los nombres de clase en la pila de llamadas si está utilizando un navegador basado en WebKit (Safari o Chrome). Por ejemplo, aquí es lo que se vería en Chrome:

7. Continué leyendo:

Arquitectura MVC con ExtJS 4

ExtJS 4.0 Documentation from Sencha , versión en español por Mario Pérez.