I am building a dashboard using mithril components. The dashboard consist of generic widget components which nest more specific components such as twitter widgets, sales widgets etc. The generic widget is agnostic of the nested component.
Now I have a requirement to compliment the generic widget from the nested component. Eg, the generic widget has a toolbar with common operations with delete & refresh, I want to add inner component specific operations such as change date range in case of sale widget, change account in case of twitter widget.
Straight forward approach to this is decompose the inner component is to 2 subcompoents a toolbar and the content, eg sales_toolbar_component & sales_content_component. Both these subcomponnets needs the same data loaded via ajax, hence it will lead to duplication. An approach is to follow Hybrid architecture (http://mithril.js.org/components.html#hybrid-architecture) where a top level component will do the ajax calls and pass data at constructor of each sub component. However in this case the parent component is generic is unaware of child data requirements
Another approach is for one sub-component to do the ajax call and notify the sibling via observer pattern, this seems quite a bit of internal coupling/dependency
Another consideration was to create a component that can have multiple views & for the parent to render each view as required using the same controller instance.Something like:
//create a component whose controller and view functions receive some arguments
var component = m.component(MyComponent, {name: "world"}, "this is a test")
var ctrl = new component.controller() // logs "world", "this is a test"
m.component(MyComponent.toolbar(ctrl));
m.component(MyComponent.content(ctrl));
None of these seems complete, is there a reference pattern I can consider?
The memoization pattern might suit you. Memoization involves wrapping a function - in this case the AJAX request - such that the first call with any given input triggers the underlying function, but its return value is stored in an internal cache; subsequent calls with the same input retrieve the cached value without touching the underlying function. Here's a simple implementation1 and a memoized AJAX requesting function that wraps Mithril's m.request:
function memoize( fn ){
var cache = {}
return function memoized( input ){
if( !( input in cache ) )
cache[ input ] = fn( input )
return cache[ input ]
}
}
var get = memoize( function( url ){
return m.request( { method : 'GET', url : url } )
} )
This way whichever components executes first will make the request; the next component (which will execute immediately afterwards) will retrieve the same value.
Regarding the idea of having a multi-view component, this isn't really any different in practical terms from your original proposal. Consider the following code:
var Wrapper = {
controller : function(){
// AJAX or whatnot
},
view : function( ctrl ){
return m( '.Wrapper',
m( Toolbar, ctrl )
m( Content, ctrl )
)
}
}
m( Wrapper, { name : 'world' }, 'This is a test' )
Here, I used the reference Wrapper instead of MyComponent, and the Toolbar and Content are just components as originally proposed. The wrapper isn't generic, but neither was MyComponent. I find that trying to reduce individual units of Mithril code to components where possible - even if you'd rather have less components in an ideal world - generally makes code much easier to maintain, because whereas you may end up with many context-specific modules instead of a few highly configurable generic modules, all of these context-specific modules are generic in the way they're called, which is much more useful in terms of code predictability.
Having said that, it would be possible to refine your idea of a pattern for passing one controller to multiple views. Again, I would reduce this pattern to component form, so that we can deal with the complications internally but expose an interface that's consistent across Mithril:
var MultiView = {
controller : function( controller ){
return new controller()
},
view : function( ctrl ){
var views = [].slice.call( arguments, 2 )
return m( 'div',
view.map( function( view ){
return view( ctrl )
} )
)
}
}
m( MultiView, function controller(){ /* AJAX or whatnot */ }, Toolbar.view, Content.view )
1 This memoization function will work for any function which accepts a single string argument, which is perfect for AJAX requests. If you ever want a more comprehensive memoization solution, check out funes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With