jueves, 3 de octubre de 2013

Esqueleto de un plugin de jQuery

Si quieres escribir un plug-in de jQuery, la mejor documentación está disponible en la propia página de jQuery How to Create a Basic Plugin. Sin embargo, y después de haber escrito unos cuantos, me doy cuenta de que siempre sigo un mismo patrón que bien podría incorporar como un snippet en Visual Studio y así me ahorraría el estar copiando código o revisitando en enlace anterior.

El esqueleto básico de cualquier plugin (según mi opinión, claro) sería el siguiente:

(function ($) {

     var pluginName = "yourPluginName";

 

     function run($el, settings) {

          //do something important

     }

 

     var methods = {

          init: function (options) {

                return this.each(function () {

                     var $this = $(this);

                     if (!$this.data(pluginName)) {

                          var settings = $.extend(true, {}, $.fn[pluginName].defaults, $this.data(), options);

                          $this.data(pluginName, settings);

                          run($this, settings);

                     }

                });

          },

          destroy: function () {

                return this.each(function () {

                     var $this = $(this);

                     var data = $this.data(pluginName);

                     if (data) {

                          $this.removeData(pluginName);

                          $this.off("." + pluginName);

                     }

                });

          }

     };

     $.fn[pluginName] = function (method) {

          if (methods[method]) {

                return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));

          } else if (typeof method === "object" || !method) {

                return methods.init.apply(this, arguments);

          } else {

                $.error("Method " + method + " does not exist on jQuery." + pluginName);

          }

     };

     $.fn[pluginName].defaults = {

          myProperty: "myValue"

     };

 

    var staticMethods = {};

    extend[pluginName] = (function () {

        return {

            oneMethod: function() {               

            },

            secondMethod: function() {               

            }

        }

    })();

    $.extend(staticMethods);

    //$.extend({

    //    yourPluginNameGoesHere: (function () {

    //        return {

    //            oneMethod: function() { },

    //            secondMethod: function() { }

    //        }

    //    })()

    //});

})(jQuery);

Los puntos claves son:

  • Declara una variable pluginName con el nombre del plugin.
    • Esto es porque sino hay que acordarse de cambiar la “magic string” en un montón de sitios y puede conllevar errores más tarde.
  • Soporta chaining
    • Esto es un “must-have” porque sino no sería un plugin como Dios manda y al usarlo te acordarías de su creador… que curiosamente eres tú!
  • Controla si el plugin está inicializado sobre el elemento para no volver a inicializarlo una segunda vez.
  • Para el tema de las opciones tenemos disponibles las siguientes:
    • Las opciones por defecto para cualquier nueva instancia del plugin son las que están en $.fn[pluginName].defaults.
      • Estas opciones se pueden cambiar antes de llamar al plugin para que siguientes instancias tomen como predeterminadas las que nosotros queramos.
    • Opciones que tomará el plugin automáticamente a partir de atributos data-
      • Tomarán precedencia sobre los valores predeterminados.
      • Esta idea la vi el otro el día en un webcast de buenas prácticas de jQuery de desarrolloweb.com y me pareció acertadísima, le da mucha versatilidad al código.
      • La parte negativa es que si son varios los plugins que se anexan al elemento, $this.data() irá devolviendo la acumulación de todos los valores de todos los plugins vigentes en el elemento, siendo así hay que tener mucho cuidado con su utilización e incluso a veces prescindir de esta opción o realizar un tratamiento más concreto para sólo leer los atributos data que queremos tratar.
    • Las opciones que podemos incluir en la inicialización del plugin, el escenario más común.
      • Tomarán precedencia sobre los valores predeterminados y sobre los atributos data-.
  • A no ser que sea muy costoso de implementar, me gusta dar un método destroy que de serie borra los datos guardados y los eventos anexados (por espacio de nombres) e incluso deshace si procede todo lo que hizo el plugin al inicializarse.
  • Por último, si queremos incluir algún método estático, es decir, poder invocarlo de la forma $.yourPluginName.method() pongo ahí el código necesario, que cierto es se complica un poco por el tema de no querer hardcodear el nombre del plugin.

Por poner un ejemplo sencillo, imaginemos que queremos un input que al tomar el foco cambe su color de fondo al perder el foco se quede como estaba inicialmente.

(function ($) {

       var pluginName = "bgColorFocus";

 

       function run($el, settings) {

             settings.originalBgColor = $el.css("background-color");

             $el.on("focus."+ pluginName, function () {

                    $el.css("background-color", settings.bgColor);

             }).on("blur."+ pluginName, function () {

                    $el.css("background-color", settings.originalBgColor);

             });

       }

 

       var methods = {

             init: function (options) {

                    return this.each(function () {

                           var $this = $(this);

                           if (!$this.data(pluginName)) {

                                  var settings = $.extend(true, {}, $.fn[pluginName].defaults, $this.data(), options);

                                  $this.data(pluginName, settings);

                                  run($this, settings);

                           }

                    });

             },

             destroy: function () {

                    return this.each(function () {

                           var $this = $(this);

                           var data = $this.data(pluginName);

                           if (data) {

                                  $this.removeData(pluginName);

                                  $this.off("." + pluginName);

                           }

                    });

             }

       };

       $.fn[pluginName] = function (method) {

             if (methods[method]) {

                    return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));

             } else if (typeof method === "object" || !method) {

                    return methods.init.apply(this, arguments);

             } else {

                    $.error("Method " + method + " does not exist on jQuery." + pluginName);

             }

       };

       $.fn[pluginName].defaults = {

             bgColor: "yellow"

       };

})(jQuery);

Si nos fijamos, los únicos cambios necesarios han sido el nombre del plugin, los defaults y el cuerpo del método run.

También es interesante ver como los eventos asociados al elemento usan un espacio de nombres, es decir, en vez de focus el evento es focus.bgColorFocus, así después en destroy podremos sólo eliminar nuestros eventos sin interferir con el resto.

Un saludo.

No hay comentarios:

Publicar un comentario