application/hocs/draftable.js
import React from 'react';
import moment from 'moment';
import {DraftService, DraftsStore} from './../services/draft-service';
/**
* Draftable HOC
*
* Wrap a component in withDraftable, then call
* <code>
* this.props.draftable.shouldCreateDraft({'srn': ..., 'raw': ..., 'html': ...});
* </code>
*
* To create drafts. Draftable is single threaded, timed and stateful so you won't
* create more than one draft every 5 seconds, or create more than one draft at a time,
* or create a draft when nothing has changed.
*
* To check on the status of a draft, you set a hook:
* <code>
* componentWillMount() {
* this.props.draftable.doesHaveDraft({'srn': ...}, (draft) => {
* if (!draft) {
* ... no draft
* } else {
* this.setState({foo: draft.content_raw});
* }
* });
* }
* </code>
*
* @param {Object} WrappedComponent React component to wrap
* @return {Object} Draftable component
*/
export const withDraftable = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
this.draftCreateHook = false;
this.interval = false;
this.isDirty = false;
this.isRunning = false;
this.stopAll = false;
this.hasDraftHook = () => {};
this.draftCreatedHook = () => {};
this.state = {};
this.onChange = this.onChange.bind(this);
this.onStoreChange = this.onStoreChange.bind(this);
this.onDraftCallback = this.onDraftCallback.bind(this);
this.draftable = {
setDraftCreatedHook: (cb) => {
this.draftCreatedHook = cb;
},
shouldCreateDraft: (state) => {
this.onChange(state);
},
doesHaveDraft: (state, cb) => {
this.hasDraftHook = cb;
this.setState(state, this.checkDraft);
},
haltDraft: (stopAll) => {
this.stopAll = stopAll;
}
}
}
componentWillMount() {
this.interval = setInterval(this.createDraft.bind(this), 5000);
DraftsStore.listen(this.onStoreChange);
}
componentWillUnmount() {
clearInterval(this.interval);
DraftsStore.unlisten(this.onStoreChange);
}
/**
* Creates the draft, this is called via the 'shouldCreateDraft' method
* exposed to the wrapped component
*/
createDraft() {
if (!this.isDirty || this.stopAll) {
return;
}
// avoid concurrent requests
if (this.isRunning) {
return;
}
this.isRunning = true;
DraftService.create(this.state.srn, {
'content': this.state.html,
'content_raw': this.state.raw
}).then(() => {
this.isDirty = false;
this.isRunning = false;
this.draftCreatedHook();
});
}
checkDraft() {
DraftService.fetchOne(this.state.srn).then(() => {
this.onDraftCallback(this.state.draft);
});
}
onDraftCallback(draft) {
if (draft && draft.size) {
this.hasDraftHook(draft);
return;
}
this.hasDraftHook(false);
return;
}
onStoreChange(state) {
this.setState(state);
}
onChange(state) {
if (JSON.stringify(state.raw) != JSON.stringify(this.state.raw)) {
this.isDirty = true;
this.setState(state);
}
}
render() {
return <WrappedComponent {...this.props} draftable={this.draftable}/>
}
}