Skip to content

Helpers and Extensions

Isiah Meadows edited this page Oct 11, 2017 · 24 revisions

A collection of extensions to vanilla Mithril that you might find useful.


View Helpers

m.withValue = function (callback) {
  return m.withAttr('value', callback)
}

Component Helpers

m.initComponent

This helper allows you to create an instance of a component and manage it yourself. Useful for when you want explicit control over your component's lifecycle, e.g. you want to control when your controller gets initialized.

m.initComponent = function (component, defaultOptions, defaultContent) {
  var ctrl = new component.controller(options)

  ctrl.render = function (options, content) {
    return component.view(
      controller,
      options || defaultOptions,
      content || defaultContent)
  }

  return ctrl
}

Example use:

var UserList = {
  controller: function () { /* ... */ },
  view: function () { /* ... */ }
}

var App = {
  controller: function () {
    this.userList = m.initComponent(UserList, {users: [ /* ... */ ]})
  },

  view: function (ctrl) {
    return m('.app', [
      m('h1', "My App"),
      ctrl.userList.render()
    ])
  }
}

Network Extensions

Helpers that deal with AJAX requests and the like.

m.deferred.resolve

This simple helper allows you to mock a promise with a successful value. Useful for AJAX cache layers and mocking server responses.

m.deferred.resolve = function (value) {
  var deferred = m.deferred()
  deferred.resolve(value)
  return deferred.promise
}

m.deferred.reject

This simple helper allows you to mock a rejected promise. Useful for mocking server responses.

m.deferred.reject = function (value) {
  var deferred = m.deferred()
  deferred.reject(value)
  return deferred.promise
}

Router Helpers

Route Wrapper

function Route(module, name) {
  return {
    controller: function() {
      // Do something generic like calling Google Analytics from here
      console.log("Router", name)
      return new module.controller()
    },

    view: module.view
  }
}

m.route(document.getElementById("page"), "/", {
    "/": Route(app, "app"),
    "/project/:id": Route(project, "project")
})

Events

Pub/Sub mixin

See this gist.

Utilizes the browser's event system. Useful in cases where you need communication between independent components. Registered events are automatically removed with onunload while preserving any pre-existing onunload handler.

function eventsMixin(target) {	
  var subscriptions = []
  target.broadcast = function(type, payload) {
    document.dispatchEvent(new CustomEvent(type, {
      detail: payload,
      bubbles: true,
      cancelable: true
    }))
  }

  target.subscribe = function (type, callback, capture) {
    capture = !!capture
    subscriptions.push([type, callback, capture])
    document.addEventListener(type, callback, capture)
  }

  target.ignore = function(type, callback, capture) {
    capture = !!capture
    for (var i = 0; i < subscriptions; i++) {
      var sub = subscriptions[i]
      if (sub[0] === type && sub[1] === callback && sub[2] === capture) {
        subscriptions.splice(i, 1)
      }
    }

    document.removeEventListener(type, callback, capture)
  }

  // save a reference to a possible present unload method
  var savedUnload = target.onunload || null

  target.onunload = function () {
    for (var i = 0; i < subscriptions; i++) {
      document.removeEventListener.apply(document, subscriptions[i])
    }
    subscriptions = []
    if (savedUnload) savedUnload.call(target)
  }

  return target
}

Example Use:

var obervableObject = eventsMixin({})

var component1 = {
  controller: function () {
    observable.subscribe('myFancyEvent', function (e) { /* ... */ })
    observable.broadcast('myOtherEvent', 'data')
  }
}

var component2 = {
  controller: function () {
    observable.subscribe('myOtherEvent', function (e) { /* ... */ })
    observable.broadcast('myFancyEvent', 'data')
  }
}

Utilities for m.prop

m.prop with change callback

Do something when an m.prop changes

m.prop = (function (old) {
  return function (store, callback) {
    var wrapped = old(store)
    if (!callback) return wrapped
    return function (value) {
      if (arguments.length) callback(value)
      return wrapped.apply(null, arguments)
    }
  }
})(m.prop);

// Example use
var myDomDestroyer = m.prop("", function () {
  // Mwahahaha!
  var head = document.getElementsByTagName("body")[0]
  head.parentElement.removeChild(head)

  var body = document.getElementsByTagName("body")[0]
  body.parentElement.removeChild(body)
})

Redraw on m.prop update

m.prop = (function (old) {
  return function (store) {
    var wrapped = old(store)
    return function (value) {
      var res = wrapped.apply(null, arguments)
      if (arguments.length) m.redraw()
      return res
    }
  }
})(m.prop);

Replace m.prop with a POJO

m.prop is sometimes difficult to debug. See this gist for a replacement.