3

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?

chifer
  • 683
  • 5
  • 19

1 Answers1

4

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.

Barney
  • 16,181
  • 5
  • 62
  • 76
  • 1
    Thanks @barny, based on MultiView approach I have created 2 fiddlers https://jsfiddle.net/chifer/f8y1ndwg/1/ - This is an implementation of the pattern you described, however it assumes the widget is a view component without an controller. where as https://jsfiddle.net/chifer/rLv1pLg8/ - supports widget to have a controller & references the internal widgets controller as a property, love to hear your thought specially on the 2nd fiddler – chifer Sep 14 '16 at 03:08
  • Ive accepted the answer, hope you get a chance a to have a look at jsfiddle.net/chifer/f8y1ndwg/1 & jsfiddle.net/chifer/rLv1pLg8 comment. It would be nice for mithril recipes include simmilar – chifer Sep 14 '16 at 03:50
  • Ha cool. I don't understand the point in these examples though. Why not just have a normal component? https://jsfiddle.net/barney/95t587fb/ – Barney Sep 15 '16 at 14:29
  • My goal is to have the out component agnostic of the inner, hands down the 2 example are bad!. https://jsfiddle.net/chifer/f8y1ndwg/2/ - I have created a outer component & object wrapper containing inner sub components(header & content), I don't want to have duplicate loading of ajax calls, but only the out component knows how to render the inner subcontinents , Hope can get some feedback – chifer Sep 16 '16 at 03:23