Skip to content

Instantly share code, notes, and snippets.

@barneycarroll
Last active August 4, 2022 11:08
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save barneycarroll/0e3c1b9811b47c012b13 to your computer and use it in GitHub Desktop.
Save barneycarroll/0e3c1b9811b47c012b13 to your computer and use it in GitHub Desktop.
Modulator: a light-touch API (with heavy internals) for auto-instantiating Mithril modules. Makes Mithril lifecycle management more user-friendly.
var mod = ( function initModulator(){
if( !Map ){
// A naive shim for maps functionality
var Map = shim;
var WeakMap = shim;
}
// 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( component, key ){
return arguments.length > 2
? mod( component, unique, key )
? mod( component, unique )
};
return mod;
function mod( component, context, key ){
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, keys ){
var keyed = typeof keys === 'array';
var path = [].slice.call( arguments, 1 );
return Object.keys( collection ).map( function getItemIdentifier( index ){
var key;
if( keyed ){
key = keys[ index ];
}
else 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 ], index, collection );
} );
};
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 ) ) )();
// Shorthand for instantiating 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 );
}
// 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(){}
}() );
@barneycarroll
Copy link
Author

Update

Mithril's config API for real DOM access has similar limitations to 0.2 component invocation in that it mandates a 1-to-1 relationship between virtual and real DOM in order for lifecycle hooks to behave as expected.

This interface solves that problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment