wink 1.4.3 released!

blog

Mixing Frameworks

Mixing Wink and Dojo Mobile

When we released Wink 1.4.1, you may have noticed that we also published the "Community Connections" webapp. This is the first webapp that mixes Wink and Dojo Mobile UI widgets; all this made possible by the adoption of the AMD syntax in both Wink (Since Wink 1.4) and Dojo (Since Dojo 1.6). Everything is not yet perfect and we still have some work to do... but to me it's an encouraging start.

In this article, I'll try to guide you through the different steps that led to the birth of the "Community Connections" webapp, and depict you the few other steps that are now ahead of us.

AMD (Asynchronous Module Definition)

For those of you who don't know AMD yet, AMD is an "API which specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded". In other words, AMD provides a standard way of defining modules (e.g. : a Wink component like the carousel is a module) and also specifies standard methods that should be used to load these modules.

Now if different resources, let's say different Frameworks (like Dojo, Wink, jQuery...) start to use this same standard syntax, it means it will be much easier to make them work together. It won't solve the possible interoperability problems (for instance if you have two frameworks redefining and accessing the same global variable) but it will definetly help you with integration.

Just to be clear, let me give you an example of the modules definition syntax. Remember I told you we integrated AMD in Wink 1.4, so prior to that we defined our modules like this:

wink.ui.xy.Carousel = function(properties)
{
	// ...
}

Now, to define the carousel, we use this syntax:

define(['../../../../_amd/core'], function(wink)
{
	wink.ui.xy.Carousel = function(properties)
	{
		// ...
	}
	
	return wink.ui.xy.Carousel;
});

In the above example, "define" is one of those standard methods AMD specifies. In this example, the first parameter corresponds to the list of dependencies of the carousel (in this case it just needs Wink core), the second is our contructor. If we try to go one level deeper, here is what the Wink core dependency (the "../../../../_amd/core" file) looks like

define(
	[
		'../_base/_base/js/base',
		'../_base/error/js/error',
		'../math/_basics/js/basics',
		'../_base/_feat/js/feat',
		'../_base/_feat/js/feat_json',
	 	'../_base/_feat/js/feat_css',
	 	'../_base/_feat/js/feat_event',
	 	'../_base/_feat/js/feat_dom',
		'../_base/json/js/json',
		'../_base/ua/js/ua',
		'../_base/_dom/js/dom',
		'../_base/topics/js/topics',
	 	'../fx/_xy/js/2dfx',
	 	'../net/xhr/js/xhr',
	 	'../ui/xy/layer/js/layer',
	 	'../ux/event/js/event',
	 	'../ux/touch/js/touch'
	 ], 
	 function(wink)
	 {
		return wink;
	 }
);

As you can see, it's just another AMD module which lists all the files that actually compose the core of Wink.

I won't get into any more details, if you'd like to know more, you should really be reading the AMD API spec.

Now you may ask, what is the interest of using AMD, because if you've been using Wink for some time now, you may have noticed that the introduction of the new syntax didn't change that much the way we are building our webapps... We still include the scripts manually in the page and instantiate our components in the exact same way as before. This is because, in all our examples, we are not using Wink with a javascript "loader". In the "Community Connections" webapp, we did...

AMD Loaders

If you remember the introduction about AMD, it said: "...specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded". That's it: "asynchronously loaded". What an AMD loader does, is basically implementing the AMD API (and hence implementing the "define" method). To put it in a nutshell, when you ask for a module, the "loader" will load the module's dependencies if they have not been previoulsy loaded and then call the constructor. A legitimate question at that point would be: "OK, but in Wink, if you don't use a loader, then where is the "define" method implementation ?? Well, we introduced in Wink 1.4 an empty shell for this, it's in "_amd/js/amd.js" and it looks like this:

define = function(mid, dependencies, factory)
{
	//--> DUMB AMD define IMPLEMENTATION
	if (typeof wink == 'undefined')
	{
		wink = {};
	}

	var args = arguments, 
		arity = args.length, 
		f, 
		d = null;
	
	if ( arity == 1 )
	{
		f = args[0];
	}
	else if (  arity == 2 )
	{
		f = args[1];
		if (typeof args[0] == "array" || args[0] instanceof Array)
		{
			d = args[0];
		}
	}
	else
	{
		f = args[2];
		d = args[1];
	}
	
	return f(wink);
	//<-- DUMB AMD define IMPLEMENTATION
};

As you can see, it doesn't do much :) But that's not the case for other loaders. The most famous one is certainly requireJS. Dojo also has it's own "loader" implementation and that's the one we used in the "Community Connections" webapp.

The Community Connections webapp

As you may already know, Wink is a project of the Dojo Foundation. Of course in the Dojo Foundation lives the Dojo Toolkit and Dojo Mobile. From the start, we thought it might be interresting to see how we could use both frameworks at the same time in a single webapp and in an intelligent manner. Of course, AMD was the first step. The first, not the last. Anyway, even if there is still work to do, we thought it would be nice if we could already show people what this convergence means and how it works. And that's how the "Community Connections" webapp was born. We would build a webapp, based on the Dojo AMD loader and which would use Dojo mobile components (the Dojo Mobile carousel, the Dojo Mobile Spinwheel...) and Wink components (the Wink scroller, the Wink coverflow...).

In this example, what matters is how modules get loaded and how we use them afterwards. As I said, we were going to use the dojo loader, which is in "dojo.js", so we have to include it first.

	<script type="text/javascript" src="./dojo/dojo.js" djConfig="async: true"></script>

Now, this is the most important step ; at the page startup, we are going to load all the things that we need from Wink and from Dojo:

require
(
	[
		"wink/ui/layout/scroller/js/scroller.js",
		"wink/ui/xyz/coverflow/js/coverflow.js",
		"dojox/mobile",
		"dojox/mobile/parser",
		"dojox/mobile/compat",
		"dojox/mobile/RadioButton",
		"dojox/mobile/Carousel",
		"dojox/mobile/Opener",
		"dojox/mobile/SpinWheel",
		"dojo/data/ItemFileReadStore"
		
	], function(Scroller, Coverflow, dojoMobile)
	{
		// ...
	}
);

And that's it :)

By the way, "require" is another of those standard methods specified in AMD.

It's easy but let me give you more details. As you have seen, we just included one script in our page and then directly called "require". the "require" method will go through each module that we are requiring and for each of those it will also load its list of dependencies if they have not yet been loaded. Let's take the coverflow component for instance (the 2nd in the require list). If we have a look at its implementation we see this:

define(['../../../../_amd/core', '../../../../math/_geometric/js/geometric', '../../../../fx/_xyz/js/3dfx', '../../../../ux/movementtracker/js/movementtracker', '../../../../ux/gesture/js/gesture', '../../../../ux/window/js/window'], function(wink)
{
	wink.ui.xyz.CoverFlow = function(properties) 
	{
		// ...
	}
	
	return wink.ui.xyz.CoverFlow;
});

Meaning that, in order to work, the coverflow needs Wink core, the geometric, 3d library and window libraries and the MovementTracker and Gesture components for handling the touch events. The Dojo AMD loader will hence load all these dependencies. And it does that for each module that we need.

Once everything has been loaded you are able to use the Wink scroller as well as the Dojo SpinWheel ; pretty cool, right?

Next steps

As I said, AMD was just the first step, because if we can already have a Dojo/Wink webapp, it's still far from being the most performant webapp. We are still confronted to an overlap problem (the exact same problem that Greg mentionned in his Wink/jQuery article).

Dojo widgets rely on Dojo core. Wink widgets rely on Wink core. And there are duplicated methods there: methods which don't have the same name, sometimes not the same signature but who do the same thing in the end. Take "wink.byId" and "dojo.byId" for instance. They have the same signature, "dojo.byId" can do more than "wink.byId", so you could easily replace "wink.byId" by "dojo.byId" but right now we need to download them both...

Basically that is our next challenge : converging on a common core footprint. How can we do this ? One lead is to have another Wink core modules definition, that would load Dojo modules instead of Wink modules and make the link between the different functions. Something a bit like this:

var dojoBaseUrl = '../..';

define(
	[
	 	dojoBaseUrl + '/dojo/dom',
	 	dojoBaseUrl + '/dojo/dom-geometry',
	 	
		'../_base/_base/js/base',
		
		'../_base/error/js/error',
		'../math/_basics/js/basics',
		'../_base/_feat/js/feat',
		'../_base/_feat/js/feat_json',
	 	'../_base/_feat/js/feat_css',
	 	'../_base/_feat/js/feat_event',
	 	'../_base/_feat/js/feat_dom',
		'../_base/json/js/json',
		'../_base/ua/js/ua',
		'../_base/_dom/js/dom',
		'../_base/topics/js/topics',
	 	'../fx/_xy/js/2dfx',
	 	'../net/xhr/js/xhr',
	 	'../ui/xy/layer/js/layer',
	 	'../ux/event/js/event',
	 	'../ux/touch/js/touch',
	 	
	 	dojoBaseUrl + '/dojo/_base/lang',
	 	dojoBaseUrl + '/dojo/_base/connect',
	 	dojoBaseUrl + '/dojo/dom-class',
	 	
	 	dojoBaseUrl + '/dojo/dom-style',
	 	dojoBaseUrl + '/dojo/json',
	 	dojoBaseUrl + '/dojo/query',
	 	
	 	dojoBaseUrl + '/dijit/_base/manager',
	 	dojoBaseUrl + '/dijit/dialogUnderlay',
	 	
	 	dojoBaseUrl + '/dojox/math/round'
	 ], 
	 function(dom, geom, wink)
	 {
		// dojo/dom
		$ = wink.byId = dom.byId;
		
		// dojo/_base/kernel
		wink.mixin = dojo.mixin;
		
		// dojo/_base/lang
		wink.isString = dojo.isString;
		wink.isFunction = dojo.isFunction;
		wink.isArray = dojo.isArray;
		
		wink.trim = dojo.trim;
		
		wink.bind = function(method, context)
		{
			return dojo.hitch(context, method);
		};
		
		wink.publish = function(topic, value)
		{
			dojo.publish(topic, [value]);
		};
		
		// dojo/dom-class
		wink.addClass = dojo.addClass;
		wink.removeClass = dojo.removeClass;
		
		// dojo/dom-geometry
		HTMLElement.prototype.getTopPosition = function()
		{
			return(geom.position(this).y);
		};
		
		HTMLElement.prototype.getLeftPosition = function()
		{
			return(geom.position(this).x);
		};
		
		// dojo/json
		wink.json.parse = function(str)
		{
			JSON.parse(str, true);
		};
		
		// dojo/query
		wink.query = dojo.query;
		
		// dijit/_base/manager
		wink.getUId = function()
		{
			return dijit.getUniqueId("wink");
		};
		
		// dijit/dialogUnderlay
		wink.layer.show = function()
		{
			if ( !this.dojoLayer )
			{
				this.dojoLayer = new dijit.DialogUnderlay();
			}
			
			this.dojoLayer.show();
		};
		
		wink.layer.hide = function()
		{
			if ( this.dojoLayer )
			{
				this.dojoLayer.hide();
			}
		};
		
		wink.layer.refresh = function()
		{
			if ( this.dojoLayer )
			{
				this.dojoLayer.layout();
			}
		};
		
		// dojox/math/round
		wink.math.round = dojox.math.round;
		
		return wink;
	 }
);

And in the end being able to remove as much Wink core references as possible. This will maybe mean that we will have to explode Wink core into smaller files, but this will be the price to pay (not that it is very harmful) for a better convergence.

Once we will have that, I guess it will also be time to think about a build tool that will work with both frameworks in order to develop, and this time for real, high performances Dojo/Wink webapps.