jQuery UI Cómo usar la biblioteca de widgets (Widget Factory)

Vamos a crear una barra de progreso. Como se muestra en el siguiente ejemplo, esto se puede lograr llamando ajQuery.widget()para completarlo, que toma dos parámetros: el nombre del plugin a crear y un objeto literal que contiene las funciones que soportan el plugin. Cuando se invoca el plugin, se crea una nueva instancia del plugin, y todas las funciones se ejecutan en el contexto de esa instancia. Esto difiere de los plugins estándar de jQuery en dos aspectos importantes. Primero, el contexto es un objeto, no un elemento DOM. Segundo, el contexto es siempre un objeto único, no una colección.

$.widget( "custom.progressbar", {
    _create: function() {
        var progress = this.options.value + "%";
        this.element
            .addClass( "progressbar" )
            .text( progress );
    }
});

El nombre del plugin debe incluir un espacio de nombres; en este ejemplo, usamoscustomespacio de nombres. Solo puede crear espacios de nombres de un nivel de profundidad; por lo tanto,custom.progressbares un nombre de plugin válido, mientras quevery.custom.progressbarno es un nombre de plugin válido.

Vemos que la biblioteca de widgets (Widget Factory) nos proporciona dos propiedades.this.elementes un objeto jQuery que contiene un elemento. Si nuestro plugin se invoca sobre un objeto jQuery que contiene varios elementos, se crea una instancia de plugin separada para cada elemento, y cada instancia tendrá su propiothis.element. La segunda propiedad,this.optionses un hash de pares clave/valor que contiene todas las opciones del plugin. Estas opciones pueden pasarse al plugin, como se muestra a continuación:

$( "<div></div>" )
    .appendTo( "body" )
    .progressbar({ value: 20 });

Cuando llamamos ajQuery.widget()se extiende jQuery añadiendo funciones ajQuery.fn(el sistema utilizado para crear plugins estándar). El nombre de la función añadida se basa en el nombre que usted pasa ajQuery.widget()sin el espacio de nombres: "progressbar". Las opciones pasadas al plugin son los valores que se obtienen establecidos en la instancia del plugin. Como se muestra en el siguiente ejemplo, podemos especificar valores predeterminados para cualquier opción. Al diseñar su API, debe tener claros los casos de uso más comunes de su plugin, de modo que pueda establecer valores predeterminados adecuados y asegurarse de que todas las opciones sean realmente opcionales.

$.widget( "custom.progressbar", {
 
    // Default options.
    options: {
        value: 0
    },
    _create: function() {
        var progress = this.options.value + "%";
        this.element
            .addClass( "progressbar" )
            .text( progress );
    }
});

Llamar a los métodos del plugin

Ahora que podemos inicializar nuestra barra de progreso, ejecutaremos acciones llamando a métodos en la instancia del plugin. Para definir un método de plugin, solo tenemos que hacer referencia a la función en el objeto que pasamos ajQuery.widget()También podemos definir métodos "privados" anteponiendo un guion bajo al nombre de la función.

$.widget( "custom.progressbar", {
 
    options: {
        value: 0
    },
 
    _create: function() {
        var progress = this.options.value + "%";
        this.element
            .addClass( "progressbar" )
            .text( progress );
    },
 
    // Create a public method.
    value: function( value ) {
 
        // No value passed, act as a getter.
        if ( value === undefined ) {
            return this.options.value;
        }
 
        // Value passed, act as a setter.
        this.options.value = this._constrain( value );
        var progress = this.options.value + "%";
        this.element.text( progress );
    },
 
    // Create a private method.
    _constrain: function( value ) {
        if ( value > 100 ) {
            value = 100;
        }
        if ( value < 0 ) {
            value = 0;
        }
        return value;
    }
});

Para llamar a un método en la instancia del plugin, puede pasar el nombre del método al plugin de jQuery. Si el método que llama acepta parámetros, simplemente páselos después del nombre del método.

Nota:Los métodos se ejecutan pasando el nombre del método a la misma función jQuery que se utilizó para inicializar el plugin. Esto se hace para evitar la contaminación del espacio de nombres de jQuery mientras se mantiene el encadenamiento de métodos. Más adelante en este capítulo, veremos otros usos que parecen más naturales.

var bar = $( "<div></div>" )
    .appendTo( "body" )
    .progressbar({ value: 20 });
 
// Get the current value.
alert( bar.progressbar( "value" ) );
 
// Update the value.
bar.progressbar( "value", 50 );
 
// Get the current value again.
alert( bar.progressbar( "value" ) );

Usar opciones

option()El método se proporciona automáticamente al plugin.option()El método le permite obtener y establecer opciones después de la inicialización. Este método es similar al método.css()y.attr()de jQuery: puede pasar solo un nombre para usarlo como getter, o pasar un nombre y un valor para usarlo como setter, o pasar un hash de pares clave/valor para establecer varios valores. Cuando se usa como getter, el plugin devuelve el valor actual de la opción correspondiente al nombre pasado. Cuando se usa como setter, el método_setOptionse llamará para cada opción establecida. Podemos especificar un método_setOptionen nuestro plugin para reaccionar a los cambios de opciones. Para acciones que deben ejecutarse de forma independiente al cambiar las opciones, podemos sobrescribir_setOptions。

$.widget( "custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        this.options.value = this._constrain(this.options.value);
        this.element.addClass( "progressbar" );
        this.refresh();
    },
    _setOption: function( key, value ) {
        if ( key === "value" ) {
            value = this._constrain( value );
        }
        this._super( key, value );
    },
    _setOptions: function( options ) {
        this._super( options );
        this.refresh();
    },
    refresh: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
    },
    _constrain: function( value ) {
        if ( value > 100 ) {
            value = 100;
        }
        if ( value < 0 ) {
            value = 0;
        }
        return value;
    }
});

Agregar devoluciones de llamada

La forma más sencilla de extender un plugin es agregar devoluciones de llamada, de modo que los usuarios puedan reaccionar cuando cambia el estado del plugin. Podemos ver en el siguiente ejemplo cómo agregar una devolución de llamada a la barra de progreso cuando el progreso alcanza el 100%._trigger()El método tiene tres parámetros: el nombre de la devolución de llamada, un objeto de evento jQuery que activa la devolución de llamada y un hash de datos relacionado con el evento. El nombre de la devolución de llamada es el único parámetro obligatorio, pero los otros parámetros son muy útiles para los usuarios que desean implementar funcionalidades personalizadas en el plugin. Por ejemplo, si creamos un plugin arrastrable, podemos pasar el evento mousemove al activar la devolución de llamada de arrastre, lo que permitirá a los usuarios reaccionar al arrastre basándose en las coordenadas x/y proporcionadas por el objeto de evento. Tenga en cuenta que el evento original pasado a_trigger()debe ser un evento jQuery, no un evento nativo del navegador.

$.widget( "custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        this.options.value = this._constrain(this.options.value);
        this.element.addClass( "progressbar" );
        this.refresh();
    },
    _setOption: function( key, value ) {
        if ( key === "value" ) {
            value = this._constrain( value );
        }
        this._super( key, value );
    },
    _setOptions: function( options ) {
        this._super( options );
        this.refresh();
    },
    refresh: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
        if ( this.options.value == 100 ) {
            this._trigger( "complete", null, { value: 100 } );
        }
    },
    _constrain: function( value ) {
        if ( value > 100 ) {
            value = 100;
        }
        if ( value < 0 ) {
            value = 0;
        }
        return value;
    }
});

Las funciones de devolución de llamada son esencialmente solo opciones adicionales, por lo que puede obtenerlas y establecerlas como cualquier otra opción. Cada vez que se ejecuta una devolución de llamada, se activa un evento correspondiente. El tipo de evento se determina concatenando el nombre del plugin y el nombre de la función de devolución de llamada. Tanto la devolución de llamada como el evento aceptan los mismos dos parámetros: un objeto de evento y un hash de datos relacionado con el evento, como se muestra en el siguiente ejemplo.

Su plugin puede necesitar incluir funcionalidad para impedir ciertas acciones del usuario; para lograrlo, la mejor manera es crear una devolución de llamada cancelable. Los usuarios pueden cancelar la devolución de llamada o el evento relacionado, igual que cancelan cualquier evento nativo, llamando aevent.preventDefault()o devolviendofalsepara lograrlo. Si el usuario cancela la devolución de llamada,_trigger()el método devolveráfalse, de modo que pueda implementar la funcionalidad adecuada dentro del plugin.

var bar = $( "<div></div>" )
    .appendTo( "body" )
    .progressbar({
        complete: function( event, data ) {
            alert( "Callbacks are great!" );
        }
    })
    .bind( "progressbarcomplete", function( event, data ) {
        alert( "Events bubble and support many handlers for extreme flexibility." );
        alert( "The progress bar value is " + data.value );
    });
 
bar.progressbar( "option", "value", 100 );

Funcionamiento interno

Ahora que hemos visto cómo crear un plugin con la biblioteca de widgets (Widget Factory), veamos cómo funciona realmente. Cuando llama ajQuery.widget(), se crea un constructor para el plugin y se establece el objeto que pasó como prototipo para las instancias del plugin. Toda la funcionalidad agregada automáticamente al plugin proviene de un prototipo de widget base, que se define comojQuery.Widget.prototype. Cuando se crea una instancia del plugin, se utilizajQuery.datapara almacenarla en el elemento DOM original, con el nombre del plugin como clave.

Dado que la instancia del plugin está directamente vinculada al elemento DOM, puede acceder directamente a la instancia del plugin sin pasar por los métodos del plugin. Esto le permitirá llamar a métodos directamente en la instancia del plugin sin necesidad de pasar el nombre del método como cadena, y también le permitirá acceder directamente a las propiedades del plugin.

var bar = $( "<div></div>" )
    .appendTo( "body" )
    .progressbar()
    .data( "progressbar" );
 
// Call a method directly on the plugin instance.
bar.option( "value", 50 );
 
// Access properties on the plugin instance.
alert( bar.options.value );

También puede crear una instancia sin pasar por los métodos del plugin, llamando directamente al constructor con las opciones y el elemento:

var bar = $.custom.progressbar( {}, $( "<div></div>" ).appendTo( "body") );
 
// Same result as before.
alert( bar.options.value );

Extender el prototipo del plugin

La mayor ventaja de que los plugins tengan constructores y prototipos es que son fáciles de extender. Al agregar o modificar métodos en el prototipo del plugin, podemos modificar el comportamiento de todas las instancias del plugin. Por ejemplo, si queremos agregar un método a la barra de progreso para restablecer el progreso al 0%, podemos agregar este método al prototipo y estará disponible en todas las instancias del plugin.

$.custom.progressbar.prototype.reset = function() {
    this._setOption( "value", 0 );
};

Para obtener más detalles sobre la extensión de widgets y sobre cómo crear un widget completamente nuevo sobre un widget existente, consulteExtender widgets mediante la biblioteca de widgets (Widget Factory)。

Limpieza

En algunos casos, permita a los usuarios aplicar el plugin y luego desaplicarlo. Puede hacerlo mediante el_destroy()método para lograrlo. Dentro del_destroy()método, debe deshacer todo lo que el plugin hizo durante la inicialización y el uso posterior._destroy()Se llama a través del.destroy()método, y.destroy()El método se invoca automáticamente cuando el elemento al que está vinculada la instancia del plugin se elimina del DOM, por lo que puede utilizarse para la recolección de basura. El.destroy()método base también maneja algunas operaciones de limpieza comunes, como eliminar la referencia de la instancia del elemento DOM del widget, desvincular todos los eventos en el espacio de nombres del widget del elemento y desvincular todos los eventos_bind()agregados.

$.widget( "custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        this.options.value = this._constrain(this.options.value);
        this.element.addClass( "progressbar" );
        this.refresh();
    },
    _setOption: function( key, value ) {
        if ( key === "value" ) {
            value = this._constrain( value );
        }
        this._super( key, value );
    },
    _setOptions: function( options ) {
        this._super( options );
        this.refresh();
    },
    refresh: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
        if ( this.options.value == 100 ) {
            this._trigger( "complete", null, { value: 100 } );
        }
    },
    _constrain: function( value ) {
        if ( value > 100 ) {
            value = 100;
        }
        if ( value < 0 ) {
            value = 0;
        }
        return value;
    },
    _destroy: function() {
        this.element
            .removeClass( "progressbar" )
            .text( "" );
    }
});

Comentarios de cierre

La biblioteca de widgets (Widget Factory) es solo una forma de crear plugins con estado. Hay otros modelos diferentes que se pueden usar, y cada uno tiene sus propias ventajas y desventajas. La biblioteca de widgets (Widget Factory) resuelve muchos problemas comunes, aumenta en gran medida la eficiencia y mejora enormemente la reutilización del código, lo que la hace adecuada para jQuery UI y otros plugins con estado.

Tenga en cuenta que en este capítulo hemos utilizadocustomel espacio de nombres.uiEl espacio de nombres está reservado para los plugins oficiales de jQuery UI. Al crear su propio plugin, debe crear su propio espacio de nombres. De esta manera, quedará más claro de dónde proviene el plugin y a qué ámbito pertenece.

Otras extensiones