Patching Modern Dojo

By on January 28, 2015 2:41 pm

DojoFAQ

While it will not happen often, there may be times when you need to patch your Dojo source. Perhaps you discovered a bug and are waiting for the fix to be committed or released, or your application uses an older version of Dojo but you want to use features found in newer releases. Dojo’s AMD plugin loader makes it possible to apply patches without resorting to modifying the source files themselves, making it easier to upgrade your version of Dojo.

As an example, Dojo 1.10 introduced dojo/throttle into the core—along with the extension event dojo/on/throttle—for ensuring that a function is fired only once during the specified interval. However, if you are developing with an earlier version of Dojo (for example, 1.9.6), you will either need to upgrade to 1.10.x or provide a patch to use it with 1.9.6. In this post, we will create patches for these modules.

First, let’s make sure we have our dojoConfig in order, as this will be the foundation for getting our patch system to work properly:

var dojoConfig = {
    baseUrl: '.',
    async: true,
    packages: [
        'dojo',
        'app'
    ],
    deps: [ 'app' ]
};

Following Dojo best practices, our custom application code will be housed in the “app” directory, and the deps configuration option indicates that “app/main.js” should be called immediately after the Dojo core has loaded. As noted, we will create a loader plugin located at “app/patch.js” to ensure that the rest of the application is not started until the patches have been applied. We have two patches we want to apply (dojo/throttle and dojo/on/throttle), so we will add these to the “app/patch” directory as “dojo-throttle.js” and “dojo-on-throttle.js”, respectively:

/src
    /dojo
    /app
        main.js
        startup.js (this is explained below)
        patch.js
        /patch
            dojo-throttle.js
            dojo-on-throttle.js

Note: While it is ultimately up to you and your team how you organize your patch directory, we recommend that you name the files after the bug ticket number when possible (for example, “app/patch/12345.js”), or with a descriptive name otherwise.

Since we know we want to access these modules as dojo/throttle and dojo/on/throttle, let’s update our dojoConfig to map these module names to the correct paths:

var dojoConfig = {
    // ... extra settings omitted
    map: {
        '*': {
            'dojo/throttle': 'app/patch/dojo-throttle.js',
            'dojo/on/throttle': 'app/patch/dojo-on-throttle.js'
        }
    }
};

Now that our configuration is complete, we can implement the loading sequence. Normally, “app/main.js” would be the entry point for our application. However, we need to guarantee that any patches have been applied before the application resources are loaded. To accomplish this, we load “app/patch.js” as a plugin, refactor “app/main.js” to load the patch plugin, and then require “app/startup.js” as our new entry point:

// app/main.js
define([
	'require',
	'./patch!'
], function (require) {
	require([ './startup' ], function (app) {
		app.startup();
	});
});
// app/startup.js
define([
    'app/widgets/Main',
    'dojo/domReady!'
], function (Main) {
	return {
		startup: function () {
		    var main = new Main();
		    main.placeAt(document.body);
		    main.startup();
		}
	};
});

Finally, we load the patches in the “app/patch” plugin. As with other AMD loader plugins, our patch module returns an object with a load method that receives three arguments, the most important of which for our purposes being loaderCallback. The AMD loader will not be resolved until loaderCallback is fired, allowing us to be certain that the patches are correctly loaded before the rest of the application:

// patch.js
define([
	'require'
], function (require) {
	return {
		load: function (id, parentRequire, loaderCallback) {
			require([
				'./patch/dojo-throttle',
				'./patch/dojo-on-throttle'
			], loaderCallback);
		}
	};
});

With the patches and map config setting in place, we can now use dojo/throttle and dojo/on/throttle as if they were already part of the Dojo source. Because the patches expose the same API as the 1.10 implementation, upgrading Dojo only means changing dojoConfig and “app/patch.js”, leaving our main application code untouched.

Fixing bugs

In order to fix bugs with existing modules, the same principle is applied. For example, suppose you are using a version of Dojo 1.8.x and are using dojox/mobile/TextBox and you want to track intermediateChanges. You discover that this was not fixed until 1.9 with Dojo ticket #16311. You see that the solution was to move the check for handling intermediateChanges from dijit/form/TextBox to dijit/form/_TextBoxMixin, code shared by both the Dijit and DojoX mobile version of this widget.

For the purposes of fixing this in your code, you simply want to make sure that the fix is applied to dojox/mobile/TextBox before using it within your application. So you need to update your file structure:

/src
    /dojo
    /app
        main.js
        startup.js
        patch.js
        /patch
            16311.js
            dojo-throttle.js
            dojo-on-throttle.js

And app/patch.js:

// patch.js
define([
	'require'
], function (require) {
	return {
		load: function (id, parentRequire, loaderCallback) {
			require([
				'./patch/16311',
				'./patch/dojo-throttle',
				'./patch/dojo-on-throttle'
			], loaderCallback);
		}
	};
});

And then you need to create a patch file. Rather than recreating the entire module, we determine a mechanism to apply the patch dynamically:

// app/patch/16311.js
define([
	// load the module that needs patching
	'dojox/mobile/TextBox'
], function (TextBox) {
	// The 'extend' method replaces the original '_onInput'
	// method of TextBox.
	TextBox.extend({
		_onInput: function () {
			// Since we are extending with `dojo/_base/declare`
			// we can still use the inheritance chain
			this.inherited(arguments);
			if (this.intermediateChanges) {
				this.defer(function () {
					this._handleOnChange(this.get('value'), false);
				});
			}
		}
	});
});

And now we have a patch that is applied. Now, when it is time to upgrade our Dojo version, instead of having to run a diff of the entire Dojo branch, we can simply look at each patch in our patch loader plugin, and determine if each patch is still needed or not, making Dojo version upgrades a more seamless experience.

Learning more

We cover advanced Dojo topics including how to patch your code and more in depth in our Dojo workshops offered throughout the US, Canada, and Europe, or at your location. We also provide expert JavaScript and Dojo support and development services, to help you get the most from JavaScript, Dojo, and AMD. Contact us to discuss how we can help with your application.