mxn v2 Architecture

Introduction

This page outlines the architecture of the new mapstraction library as it is developed. It is aimed to be a central place for decisions made and discussion around these decisions.

Purpose

The library is being rewritten for a few main reasons:

  • To abstract core functionality away from provider-specific implementations. This allows each provider to be easier to manage and promotes the ability to only include functionality for specific providers, reducing overall script size in use.
  • To separate functional modules so only required functionality is included. Again, this reduces total script size and overall complexity.
  • To generally tidy the code base.

Additionally, the architecture needs to be designed to allow functionality to be easily added. Either as a new provider, new functional module or as an extension to an existing module.

Considerations

When undertaking the redesign, the following points need to be considered and balanced with functionality:

  • Ease of use. The end user should be able to use the library easily and not have to undertake a lot of rigging before use. The interface needs to be intuitive.
  • Backwards compatability. Where possible, little change should be made to the existing API allowing an easy upgrade path to version 2 from version 1. Any changes need to be documented.
  • Speed. Further scripts being loaded can cause initial rendering to slow down, so this requires careful balancing.

Decisions required

At this stage, the following decisions need to be made:

Namespace

Namespacing JavaScript libraries is a good practice to keep them from interfering with each other. The current library uses the ‘mxn’ namespace and it is recommended to keep this name. Moving forward, it is also recommended to drop the older compatability mode to keep the namespace declaration and use clear.

Module boundaries (what goes in each script)

There are two lines of thought for defining what functionality belongs to a module. A module in this system is pretty much the same as a single JavaScript file.

The first is to split functionality based on current design. This will produce the following modules:

  • Mapstraction (containing mapstraction, utilities, boundingbox, latlonpoint, marker and polyline)
  • Route
  • Geocode
  • Radius

This approach doesn’t cleanly separate concerns, but will mean fewer files are ultimately included in the page render.

The second approach is to split modules on ‘class’ or ‘object’ boundaries producing the following modules:

  • Mapstraction
  • Utilities
  • Boundingbox
  • Latlonpoint
  • Marker
  • Polyline
  • Route
  • Geocode
  • Radius

This is a much cleaner approach although may be seen to be too verbose as most implementations will always require the first 5 modules. This approach will also mean more scripts are included which does slow the initial render.

At this stage, the recommendation is to go for a ‘middle’ approach having a core library with most common functionality and further specialized modules like so:

  • Mapstraction (containing mapstraction, utilities, boundingbox, latlonpoint and marker)
  • Polyline
  • Route
  • Geocode
  • Radius

The only drawback of this approach is that provider-specific functions with the same name in the mapstraction library will clash (for example toGoogle() may be required to extend Marker and LatLonPoint).

Main library functionality

Does the main included library (which includes the loading and invoking functions) also include the main mapstraction functionality?

It is recommended that it does not so that a user can request to only load a specific module (geocode for example) if that is all that is required.

File naming convention

Two naming conventions have been suggested. The first is based around the project name ‘mapstraction’:

mapstraction.js - main library

Modules
mapstraction.core.js - mapstraction module
mapstraction.route.js - route module

Providers
mapstraction.google.core.js
mapstraction.google.route.js
mapstraction.yahoo.corejs

The second is based around the ‘mxn’ namespace:

mxn.js - main library

Modules
mxn.mapstraction.js - mapstraction module
mxn.route.js - route module

Providers
mxn.mapstraction.google.js
mxn.route.google.js
mxn.mapstraction.yahoo.js

This last naming convention is recommended as there is no clash between the ‘mapstraction’ project name and ‘mapstraction’ module name meaning a ‘core’ library is unneccessary.

Additionally, directories can be brought into play to separate provider implementations:

mxn.js - main library

Modules
modules/mxn.mapstraction.js - mapstraction module
modules/mxn.route.js - route module

Providers
google/mxn.mapstraction.google.js
google/mxn.route.google.js
yahoo/mxn.mapstraction.yahoo.js

Main script architecture

The main loading library is lightweight and loads scripts as required. Functions in scripts can also be invoked using this library.

It is aware of it’s physical location to aid loading of further scripts with correct src attributes.

The main mxn object follows the Yahoo YUI template pattern as follows:

var mxn = (function() {    // Global object
var scripts;           // Internal variable
...                    // Implementation
return {               // Expose specific functionality
   aFunction : aFunction
};
})();

The library exposes the following functionallity:

  • requireModules(’module1,module2′)
    This includes further scripts if they haven’t been included yet. used for dependencies between modules.
  • requireProviders(’currentmodule’)
    This includes provider-specific modules for the current module.
  • registerModule(’moduleName’, <module>)
    This registers a module as loaded and optionally stores the module functionality for later invocation.
  • invoke(’provider’, ‘module’, ‘function’, …)
    This invokes a function previously loaded for a specific provider by registerModule.

The initial script loading can specify which providers and modules to load. The pattern used and examples are as follows:

  • script/mxn.js - loads all modules and all providers
  • script/mxn.js?(google) - loads all modules, google provider only
  • script/mxn.js?(google, yahoo) - loads all modules, google and yahoo providers only
  • script/mxn.js?(google, [mapstraction,utilities]) - loads mapstraction and utilities modules, google provider only
  • script/mxn.js?([google,yahoo], [mapstraction,utilities]) - loads mapstraction and utilities modules, google and yahoo providers only

Module script architecture (for review)

Module scripts extend the base ‘mxn’ namespace object following the jQuery extension pattern.
This uses a reference to the namespace object declared as ‘$’ which is only in scope of the current code block and shouldn’t interfere with other libraries (check?).

This allows modules to be written and the namespace to change very easily if required by modifying the object at the bottom of the module script.

Template as follows:

// $ is a reference to the namspace object, module is the current module name
(function($, module){

//-------
// MODULE
//-------

    // Module and provider requirements
    // Optional, can include further modules
    $.requireModules('mod1, mod2');
    // Optional, can include provider-specific versions of this module
    $.requireProviders(module);      

    // Main module object
    $.Module = new function() {
    };

    // Extend main module with functionality
    $.Module.prototype.aFunction = function() {
        // Invoke provider-speciic functionality
        return $.invoke(this.api, module, 'aFunction ', this, arguments);
    };

    // Further implementation
    ...                               

    // Register module
    $.registerModule(module);

// Initializes module with mxn namespace object and module name
})(mxn, 'module1');

The mapstraction module implements Derek Fowler’s checkLoaded functionality replacing the need for the initial isloaded check in each function.

Provider script architecture (for review)

The provider-specific scripts also follow the jQuery pattern and can be used to extend existing functionality or implement a main function stub:

(function($, module) {

//------------------
// MODULE - PROVIDER
//------------------

    // Extends existing object for this provider, doesn't require registration below
    $.Module.prototype.toGoogle = function() {
        return new GLatLng(this.lat,this.lon);
    };

    // Implements existing function stub, requires registration below
    function setCenterAndZoom(point, zoom) {
        var map = this.maps[this.api];
        map.setCenter(point.toGoogle(), zoom);
    }

    // Registers module passing optional array of functions which can be invoked for this provider
    $.registerModule(module, {
        setCenterAndZoom : setCenterAndZoom
    });

// Module name includes provider name
})(mxn, 'module.google');

Please comment below…