Skip to content
Permalink
Browse files

NEW HtmlEditorField component for react rich text

This allows one to use a Rich Text field from within a react form, where
as previously it was only possible from server side PHP rendering
templates, and Entwine.

The downside to this is that although `@tinymce/tinymce-react` is the
official package for this, it is in turn only a very loose wrapper
around the window.TinyMCE global. It also posed problems with the
configuration generated by the PHP config class, and altering that would
destroy backwards compatiblity - which for now is critical for adoption.

So while there is now a React component for rendering TinyMCE, it is
only a bare bone component that still relies on Entwine to boot the
actual editor via the `data-config` attribute on the React output
`textarea` element.
  • Loading branch information...
NightJar committed Sep 3, 2018
1 parent 75ce5c4 commit 239419424ee001200830bc8b77dfef3ce7a05d7e

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -40,6 +40,7 @@ import TagList from 'components/Tag/TagList';
import CompactTagList from 'components/Tag/CompactTagList';
import Search from 'components/Search/Search';
import SearchToggle from 'components/Search/SearchToggle';
import HtmlEditorField from 'components/HtmlEditorField/HtmlEditorField';

export default () => {
Injector.component.registerMany({
@@ -84,5 +85,6 @@ export default () => {
CompactTagList,
Search,
SearchToggle,
HtmlEditorField,
});
};
@@ -42,6 +42,7 @@ class Form extends Component {
const fields = this.props.mapFieldsToComponents(this.props.fields);
const actions = this.props.mapActionsToComponents(this.props.actions);
const messages = this.renderMessages();
const FormTag = this.props.formTag;

const className = ['form'];
if (valid === false) {
@@ -57,9 +58,10 @@ class Form extends Component {
};

return (
<form
<FormTag
{...formProps}
ref={(form) => { this.form = form; this.props.setDOM(form); }}
role="form"
>
{fields &&
<fieldset>
@@ -74,7 +76,7 @@ class Form extends Component {
? <div className="btn-toolbar" role="group">{actions}</div>
: null
}
</form>
</FormTag>
);
}
}
@@ -102,10 +104,12 @@ Form.propTypes = {
value: PropTypes.any,
type: PropTypes.string,
})),
formTag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
};

Form.defaultProps = {
setDOM: () => null,
formTag: 'form',
};

export default Form;
@@ -321,6 +321,7 @@ class FormBuilder extends Component {
form,
afterMessages,
autoFocus,
formTag,
} = this.props;

const props = {
@@ -345,7 +346,8 @@ class FormBuilder extends Component {
persistentSubmitErrors,
validate: this.validateForm,
autoFocus,
setDOM: (formDOM) => { this.formDOM = formDOM; }
setDOM: (formDOM) => { this.formDOM = formDOM; },
formTag,
};

return (
@@ -0,0 +1,81 @@
/* global window */
import React from 'react';
import Script from 'react-load-script';
import { Component as TextField } from 'components/TextField/TextField';
import fieldHolder from 'components/FieldHolder/FieldHolder';


class HtmlEditorField extends TextField {
/**
* sets initial state:
* if editorjs IS defined, we are NOT ready (must check dependency first).
* if editorjs is NOT defined, we ARE ready (no dependency).
*/
constructor(props) {
super(props);
this.state = {
isReady: !props.data.editorjs
};
this.handleReady = this.handleReady.bind(this);
}

getInputProps() {
return {
...super.getInputProps(),
...this.props.data.attributes,
};
}

/**
* Once the dependency script is loaded, updating the internal state
* will trigger a reload and present the editor to the user
*/
handleReady() {
if (!window.TinyMCE && window.tinymce) {
window.TinyMCE = window.tinymce;
}
this.setState({ isReady: true });
}

/**
* TinyMCE operates from a global script being loaded in first.
* We must ensure this dependency is loaded before proceeding to
* render the editor proper
*/
renderDependencyScript() {
return <Script url={this.props.data.editorjs} onLoad={this.handleReady} />;
}

/**
* Renders the rich text editor (TinyMCE)
* Happens only after the dependency script has been loaded
*/
renderRichTextEditor() {
const config = JSON.parse(this.props.data.attributes['data-config']);
return super.render(config);
}

render() {
return (this.state.isReady) ? this.renderRichTextEditor() : this.renderDependencyScript();
}

/**
* When the handleReady callback is run, the state is changed.
* This state change triggers the render of the .htmleditor element
* however since this is not added by entwine, the entwine hook for
* onadd is not run - we must trigger this manually.
*/
componentDidUpdate() {
if (this.state.isReady) {
const { document, jQuery: $ } = window;
const mountEvent = $.Event('EntwineElementsAdded');
const editorElement = document.getElementById(this.getInputProps().id);
mountEvent.targets = [editorElement];
$(document).triggerHandler(mountEvent);
}
}
}

export { HtmlEditorField as Component };

export default fieldHolder(HtmlEditorField);
@@ -298,6 +298,11 @@ jQuery.entwine('ss', function($) {
this._super();
},

onmatch: function() {
this.getEditor() || this.onadd();
this._super();
},

/**
* Destructor: onunmatch
*/
@@ -74,6 +74,7 @@
"react-dnd": "^2.2.3",
"react-dnd-html5-backend": "^2.2.3",
"react-dom": "15.3.1",
"react-load-script": "^0.0.6",
"react-redux": "^4.4.1",
"react-router": "^2.4.1",
"react-router-redux": "^4.0.5",
@@ -8089,6 +8089,10 @@ react-is@^16.4.1:
version "16.4.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.2.tgz#84891b56c2b6d9efdee577cc83501dfc5ecead88"

react-load-script@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/react-load-script/-/react-load-script-0.0.6.tgz#db6851236aaa25bb622677a2eb51dad4f8d2c258"

react-modal@^3.1.10:
version "3.2.1"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.2.1.tgz#fa8f76aed55b67c22dcf1a1c15b05c8d11f18afe"

0 comments on commit 2394194

Please sign in to comment.
You can’t perform that action at this time.