Created
October 12, 2016 15:12
-
-
Save th3hunt/f92dda9c890a0e97a4c4260d4ef1fb55 to your computer and use it in GitHub Desktop.
Megatron
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'underscore.global'; | |
import Marionette from 'backbone.marionette-1.x'; | |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
const defaultModelEvents = 'change'; | |
const defaultCollectionEvents = 'add remove reset'; | |
function defaultGetProps({model, collection, state = {}} = {}) { | |
if (model) { | |
_.extend(state, model.toJSON()); | |
} | |
if (collection) { | |
_.extend(state, {items: collection.toJSON()}); | |
} | |
return state; | |
} | |
function extractNewState(options = {}) { | |
const {getProps, props} = options; | |
return _.extend(getProps ? getProps(options) : defaultGetProps(options), props); | |
} | |
function viewCallback(...args) { | |
this.options.dispatch(...args); | |
} | |
const Megatron = { | |
/** | |
* function to wrap a React Component in a Marionette View | |
* | |
* @param {React Component} Component, the react component which will be rendered inside the Marionette View | |
* @param {Object} options, configuration for the React Component | |
*/ | |
viewFromComponent(Component, options) { | |
const ReactMarionetteView = Marionette.View.extend({ | |
onShow() { | |
if (this._reactInternalInstance) { | |
return false; | |
} | |
// create and render a container component wrapping the given component | |
class Container extends React.Component { | |
constructor() { | |
super(); | |
this.state = extractNewState(options); | |
} | |
componentDidMount() { | |
_.defaults(options, {observe: {}}); | |
this.initListener(options.model, options.observe.model || defaultModelEvents); | |
this.initListener(options.collections, options.observe.collection || defaultCollectionEvents); | |
} | |
componentWillUnmount() { | |
this.destroyListener(options.model); | |
this.destroyListener(options.collection); | |
} | |
initListener(entity, events) { | |
if (!entity) { | |
return; | |
} | |
if (events instanceof Array) { | |
events = events.join(' '); | |
} | |
entity.on(events, this.updateComponentState.bind(this)); | |
} | |
updateComponentState() { | |
const newState = extractNewState(options); | |
this.setState(newState); | |
} | |
destroyListener(entity) { | |
if (!entity) { | |
return; | |
} | |
entity.off(); | |
} | |
render() { | |
if (React.isValidElement(Component)) { | |
return React.cloneElement(Component, this.state); | |
} | |
return <Component { ...this.state } />; | |
} | |
} | |
this._reactInternalInstance = ReactDOM.render(<Container/>, this.el); | |
}, | |
onBeforeDestroy() { | |
this._reactInternalInstance._isMounted = false; | |
ReactDOM.unmountComponentAtNode(this.el); | |
}, | |
onClose() { | |
this._reactInternalInstance._isMounted = false; | |
ReactDOM.unmountComponentAtNode(this.el); | |
} | |
}); | |
return new ReactMarionetteView(options); | |
}, | |
/** | |
* function to wrap a Marionette View in a React Component | |
* | |
* @param {Marionette View} MarionetteView, the view which will be wrapped inside the React Component | |
* @param {Object} options, configuration for the Marionette View | |
*/ | |
componentFromView(MarionetteView, options) { | |
return React.createClass({ | |
componentDidMount() { | |
const parentElem = ReactDOM.findDOMNode(this._reactInternalInstance._instance); | |
if (MarionetteView instanceof Marionette.View) { | |
MarionetteView.setElement(parentElem); | |
this._marionetteView = MarionetteView; | |
} else { | |
options.el = parentElem; | |
this._marionetteView = new MarionetteView(options); | |
} | |
this.mapEventsToActions(options); | |
this._marionetteView.render(); | |
}, | |
shouldComponentUpdate() { | |
return false; | |
}, | |
componentWillUnmount() { | |
// unregister listeners for the user defined marionette events | |
_.map(options.eventsToActions, (action, event) => { | |
this._marionetteView.stopListening( | |
this._marionetteView, | |
event); | |
}); | |
this._marionetteView.destroy && this._marionetteView.destroy(); | |
this._marionetteView.close && this._marionetteView.close(); | |
}, | |
createTemplate(view, opts) { | |
let {tagName = 'div'} = opts; | |
let className = view.prototype.className; | |
if (opts.className) { | |
className = opts.className; | |
} | |
if (!tagName) { | |
tagName = view.prototype.tagName; | |
} | |
return React.createElement(tagName, {className}, null); | |
}, | |
delegate(methodName, ...args) { | |
// check if method exists | |
if (!this._marionetteView[methodName]) { | |
return; | |
} | |
// pass the arguments to marionette method | |
return this._marionetteView[methodName](...args); | |
}, | |
mapEventsToActions(opts) { | |
if (!opts.dispatch || !opts.dispatch instanceof Function) { | |
console.error('The `dispatch` function is not defined.'); | |
return; | |
} | |
_.map(opts.eventsToActions, (action, event) => { | |
this._marionetteView.listenTo(this._marionetteView, event, (...args) => { | |
const myAction = typeof action === 'function' ? action(...args) : action; | |
viewCallback.apply(this._marionetteView, [myAction, ...args]); | |
}); | |
}); | |
return this._marionetteView; | |
}, | |
render() { | |
return this.createTemplate(MarionetteView, options); | |
} | |
}); | |
} | |
}; | |
export default Megatron; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment