Skip to content

Instantly share code, notes, and snippets.

@barneycarroll
Forked from barneycarroll/modulator.js
Last active August 29, 2015 14:17
Show Gist options
  • Save barneycarroll/89535aca20bb825f74e2 to your computer and use it in GitHub Desktop.
Save barneycarroll/89535aca20bb825f74e2 to your computer and use it in GitHub Desktop.
var mod = ( function initModulator(){
if( !Map ){
// A naive shim for maps functionality
var Map = shim;
var WeakMap = shim;
}
// Garbage collection flag
mod.cleanup = true;
// Registry of instantiation contexts
var contexts = new WeakMap();
// All automated counts
var counts = new Map();
// Prevent infinite recursion if a modulated controller calls redraw
var pauseRedraw = ( function(){
var snapRedraw = m.redraw;
var redraw;
var forced;
for( var key in m.redraw ){
queueRedraw[ key ] = snapRedraw[ key ] = m.redraw[ key ];
}
return function pause(){
m.redraw = queueRedraw;
setTimeout( function unpause(){
m.redraw = snapRedraw;
if( redraw ) m.redraw( forced );
redraw = forced = false;
} );
}
function queueRedraw( force ){
redraw = true;
if( force ) forced = true;
}
}() );
var unique = {};
// Clear counts at the begninning of every redraw
m.module( document.createElement( 'x' ), {
view : counts.clear.bind( counts )
} );
// Shorthand for a component which will always return the same instance
mod.unique = function( component ){
return mod( component, unique, unique );
};
// Shorthand for a keyed component with a global context
mod.global = function( x ){
return mod( x, unique );
};
// Extend controllers with extra utility functions
mod.extend = true;
return mod;
function mod( component, context, key ){
// Stand in for m.module, eg mod( document.body, component, context );
if( component instanceof HTMLElement ){
// Stand in for m.route
if( !component.controller && !component.view ){
var routes = {};
for( var route in context ){
routes[ route ].controller = mod.unique( {
controller : context[ route ].controller || noop
} ).bind();
}
return m.route( component, routes );
}
return m.module( component, mod.apply( undefined, [].slice.call( arguments, 1 ) ) )();
}
var components = register( contexts, context || unique, WeakMap );
var keys = register( components, component, WeakMap );
return function identify( key ){
var count = key === undefined && register( counts, keys, m.prop.bind( undefined, 0 ) );
// eg. ctrl.mod( profile ).mapWith( users(), 'username' );
apply.mapWith = function( collection ){
var path = [].slice.call( arguments, 1 );
return Object.keys( collection ).map( function getItemIdentifier( index ){
var key;
if( path.length ){
key = path.reduce( function getKeyValue( source, segment ){
var node = source[ segment ];
if( node instanceof Function ) node = node.call( source );
return node;
}, collection[ index ] );
}
else {
key = index;
}
return identify( key )( collection[ index ] );
} );
};
return apply;
function apply(){
var args = [].slice.call( arguments );
var view;
if( count ){
key = count( count() + 1 );
}
var ctrl = register( keys, key, function newController(){
pauseRedraw();
var controller = component.controller || noop;
var instance = new ( controller.bind.apply( controller, [ controller ].concat( args ) ) )();
if( mod.cleanup ){
garbageCollect( instance );
}
if( mod.extend ){
// Shorthand for instantiatin sub-modules
instance.mod = function( component, key ){
return mod( component, instance, key );
};
// Force a re-instantiation of this controller on next redraw.
// Returns m.redraw to allow instant re-instantiation.
// So, to re-initialise with the same arguments and run a forced
// redraw immediately:
// ctrl.refresh( [].slice.call( arguments, 1 ) )()
instance.refresh = function(){
args = [].slice.call( arguments );
ctrl = register( keys, key, newController, true );
return m.redraw;
};
}
return instance;
} );
// Return the controller instance if the component is view-less.
if( component.view ){
if( args.length ){
view = component.view.apply( undefined, [ ctrl ].concat( args ) );
}
else {
view = component.view( ctrl );
}
if( view instanceof Object ){
view.ctrl = ctrl;
}
return view;
}
return ctrl;
}
}( key );
// Performance: when controllers succesfully unload, destroy their associated maps
function garbageCollect( ctrl ){
onunload = ctrl.onunload;
if( onunload === teardown ){
return;
}
ctrl.onunload = teardown;
function teardown( e ){
var go = true;
if( onunload ){
onunload( {
preventDefault : function(){
go = false;
}
} );
}
if( go ){
contexts.delete( context );
}
}
}
}
// Convenience map method: retrieve key from map. If it's not registered, set it first with Constructor.
function register( map, key, Constructor, force ){
return !force && map.has( key ) ? map.get( key ) : map.set( key, new Constructor() ).get( key );
}
function shim(){
var keys = [];
var values = [];
var map = {
get : function( key ){
var index = keys.indexOf( key );
return values[ index ];
},
has : function( key ){
var index = keys.indexOf( key );
return index > -1;
},
set : function( key, value ){
var index = map.has( key ) ? keys.indexOf( key ) : keys.length;
keys[ index ] = key;
values[ index ] = value;
return map;
},
clear : function(){
keys = [];
values = [];
},
delete : function( key ){
var index = keys.indexOf( key );
if( index > -1 ){
keys.splice( index, 1 );
values.splice( index, 1 );
return true;
}
return false;
}
};
return map;
}
function noop(){}
}() );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment