Modular jQuery Plugins

Slides: http://gregfranko.com/modular-jquery-plugins-talk/

Greg Franko

  • Day Job: JavaScript Engineer at AddThis
  • Open Source: BoilerplateMVC, jQuery plugins, etc
  • Author: Dependency Managment w/ RequireJS
  • Speaker: jQuery Portland!
  • What does Modular Mean to You?

  • Smaller Pieces
  • Limited Scope (does one thing and does it well)
  • Maintainable
  • Inspiration For This Talk

  • Example Require.js Module

  •               
    // Options Module
    // --------------
    // The define method is passed a JavaScript function
    define(function () {
      // Returns an object literal with our options
      return {
        showEffect: 'fadeIn',
        hideEffect: 'slideUp'
      }
    });
                  
              
  • How Does This Apply to jQuery Plugins?

  • Typical jQuery Plugin:
                  
    // Adding the tooltip method to the jQuery prototype
    $.fn.tooltip = function ( options ) {
      return this.each(function () {
        // Boilerplate jQuery plugin code goes here
      });
      // Plugin Constructor
      function Tooltip() {};
      // All my plugin logic goes here
      Tooltip.prototype = {};
      //Default Options
      $.fn.tooltip.options = {};
    }
                  
              
  • Is this bad?

  • For small plugins this will work fine
  • For medium to large plugins this is not maintainable
  • How can we make it maintainable?
  • Abstract the boilerplate
  • Assemble our plugin into independent pieces
  • Let the Refactor Begin!

  • We have tools to help us...
  • jQueryUI Widget Factory

  • Provides a consistent foundation and API
  • Removes jQuery plugin boilerplate code
  • Let's us create plugins using object literals
  • Can be used independently of jQueryUI
  • Example Widget Factory Plugin

  • 
    // Calling the jQueryUI Widget Factory Method
    $.widget("an.example", {
      // These options will be used as defaults
      options: {
        someOption: true
      },
      // Constructor
      _create: function(internal) {}
      // Custom method we added to our plugin instance
      customMethod: function() {}
      // The rest of your plugin goes here
    });
                  
              
  • Calling Our Plugin

  • The same as any other jQuery plugin
                
                    $('.test').example();
                
              
  • Let's Review The State Of Our Plugin

  • What We Have Done:
  • Removed the jQuery plugin boilerplate
  • What We Still Need To Do:
  • Separate our plugin into independent pieces
  • Create a command-line build process
  • Create a web build process
  • Let's Create Our First File!

  • This file will hold our default options
    
    // options.js
    // ----------
    var example = {};
    example.options = {
      someOption: true
    };
                  
              
  • On To The Next File!

  • This file will hold our plugin constructor function
    
    // _create.js
    // ----------
    example._create = function() {
      // Our constructor logic goes here
    };
                  
              
  • Can you see the pattern?

  • It is a bit overkill to create a new file for each method
  • Make logical modular design decisions
  • Once We Are Done Decoupling...

  • We create our plugin!
    
    // _plugin.js
    // ----------
    // Calling the jQueryUI Widget Factory Method
    $.widget("an.example", example);
                  
              
  • Let's Review The State Of Our Plugin

  • What We Have Done:
  • Removed the jQuery plugin boilerplate
  • Separated our plugin into independent pieces
  • What We Still Need To Do:
  • Create a command-line build process
  • Create a web build process
  • Before We Review the Build Process...

  • Let's Answer a Common Question
  • Are There Alternatives To The Widget Factory?
  • Now that you mention it...
  • I would like to officially release jqfactory
  • jqfactory

  • Inspired by the jQueryUI Widget Factory
  • Supports jQuery prototype namespacing
  • Includes an elegant, promises-based, solution for asynchronous initializations
  • Automatic event binding
  • Example jqfactory Plugin

  • 
        // Calling the jqfactory Method
       $.jqfactory('person.greg', {
            // Default plugin options
            options: {
                occupation: 'JavaScript Engineer'
            },
            // Plugin Constructor (called first)
            _create: function() {},
            // Dom manipulation goes here (called second)
            _render: function() {},
            // Plugin event bindings (called third)
            _events: {
                'click': function() {}
            },
            // All event listeners are now bound (called last)
            _postevents: function() {}
        });
                  
              
  • jQueryUI Widget Factory or jqfactory?

  • It depends
  • Both are great solutions with fundamental differences
  • Important Note:
  • jQueryUI Widget Factory is battle tested
  • jqfactory is new
  • We Won't Be Reviewing jqfactory In Detail...

  • But if you are interested in it:
  • https://github.com/gfranko/jqfactory
  • Now back to our plugin!
  • Plugin Build Process

  • With Grunt.js!
  • Grunt

  • JavaScript (Node.js) Task Runner
  • Perfect for automating repetitive tasks
  • A huge plugin ecosystem
  • Creating Our Gruntfile Part 1

  • 
    // Gruntfile.js
    module.exports = function(grunt) {
      var baseFilePath = 'src/js/',
        fileToBuild = baseFilePath + 'jquery.customBuild.js';
      // Our Grunt configuration
      grunt.initConfig({
        concat: {
          dist: {
            src: '<%= customBuild.files %>',
            dest: fileToBuild
          }
        },
        wrap: {
          modules: {
            src: [fileToBuild],
            wrapper: [';(function($, undefined) {\n', '\n}(jQuery));']
          }
        }
      });
      grunt.loadNpmTasks('grunt-contrib-concat');
      grunt.loadNpmTasks('grunt-wrap');
      // Our custom task will go here next
    }
                  
              
  • Creating Our Gruntfile Part 2

  • 
    // Registers a default Grunt Task
    grunt.registerTask('customBuild', 'customBuild task', function() {
      var defaultFiles = ['options', '_create', '_plugin', 'customMethod'],
        args = this.args, customFiles = [], index, i = -1;
      // Loops through and excludes any file that was passed
      if(args.length) {
        while(++i < args.length) {
          index = defaultFiles.indexOf(args[i]);
          if(index !== -1) defaultFiles.splice(index, 1);
        }
      }
      // Makes sure that each passed file has the correct file path
      customFiles = defaultFiles.map(function(currentFile) {
        return baseFilePath + currentFile + '.js';
      });
      // Sets a Grunt configuration variable
      grunt.config.set('customBuild.files', customFiles);
      // Run's the Grunt concat plugin
      grunt.task.run('concat', 'wrap');
    });
                  
              
  • Running Our Custom Build

  • On The Command Line:
                
                grunt customBuild
                
              
  • This will include all of our files
  • Excluding a File In Your Build

  • On The Command Line:
                
                grunt customBuild:customMethod
                
              
  • Success!

  • Plugin Source Code

  • 
    ;(function($, undefined) {
    // options.js
    // ----------
    var example = {};
    example.options = {
      someOption: true
    };
    // _create.js
    // ----------
    example._create = function() {
      // Our constructor logic goes here
    };
    // _plugin.js
    // ----------
    // Calling the jQueryUI Widget Factory Method
    $.widget("an.example", example);
    }(jQuery));
                  
              
  • Let's Review The State Of Our Plugin

  • What We Have Done:
  • Removed the jQuery plugin boilerplate
  • Separated our plugin into independent pieces
  • Created a command-line build process
  • What We Still Need to Do:
  • Create a web build process
  • Web Custom Build

  • With DownloadBuilder.js!
  • DownloadBuilder.js

  • Fetches and Concatenates Files From Github
  • Saves files using the HTML5 FileSystem API
  • Supports caching via HTML5 Session Storage
  • First Step

  • Create your HTML
  • 
    
    
    
    
                  
              
  • Second Step

  • Create a builder instance
  • 
                      var builder = new DownloadBuilder({
                        location: 'github',
                        author: 'gfranko',
                        repo: 'jquery.examplePlugin.js',
                        branch: 'master',
                        cache: true,
                        client_id: '',
                        client_secret: ''
                      });
                  
              
  • Sorry, you have to use the new keyword
  • Final Step

  • Build Your File
  • 
    $('#generate-button').on('click', function() {
      var checkedBoxes = $('#js-files:checked'),
        fileName = 'jquery.examplePlugin.custom.js',
        language = 'javascript';
    
      builder.buildURL(checkedBoxes, fileName, language, function(data) {
        /*
        file - data.content
        file url - data.url
        file name - data.fileName
        file language - data.lang
        */
      });
    
    });
                  
              
  • Live Example

    Closing Thoughts

  • JavaScript Is Finally Being Taken Seriously
  • jQuery Plugins Should Be Too
  • Thank You!

  • Github: @gfranko
  • Twitter: @gregfranko