Custom Block Components
Draft is designed to solve problems for straightforward rich text interfaces like comments and chat messages, but it also powers richer editor experiences like Facebook Notes.
Users can embed images within their Notes, either loading from their existing Facebook photos or by uploading new images from the desktop. To that end, the Draft framework supports custom rendering at the block level, to render content like rich media in place of plain text.
The TeX editor in the Draft repository provides a live example of custom block rendering, with TeX syntax translated on the fly into editable embedded formula rendering via the KaTeX library.
A media example is also available, which showcases custom block rendering of audio, image, and video.
By using a custom block renderer, it is possible to introduce complex rich interactions within the frame of your editor.
Custom Block Components
Within the Editor
component, one may specify the blockRendererFn
prop.
This prop function allows a higher-level component to define custom React
rendering for ContentBlock
objects, based on block type, text, or other
criteria.
For instance, we may wish to render ContentBlock
objects of type 'atomic'
using a custom MediaComponent
.
function myBlockRenderer(contentBlock) {
const type = contentBlock.getType();
if (type === 'atomic') {
return {
component: MediaComponent,
editable: false,
props: {
foo: 'bar',
},
};
}
}
// Then...
import {Editor} from 'draft-js';
class EditorWithMedia extends React.Component {
...
render() {
return <Editor ... blockRendererFn={myBlockRenderer} />;
}
}
If no custom renderer object is returned by the blockRendererFn
function,
Editor
will render the default EditorBlock
text block component.
The component
property defines the component to be used, while the optional
props
object includes props that will be passed through to the rendered
custom component via the props.blockProps
sub property object. In addition,
the optional editable
property determines whether the custom component is
contentEditable
.
It is strongly recommended that you use editable: false
if your custom
component will not contain text.
If your component contains text as provided by your ContentState
, your custom
component should compose an EditorBlock
component. This will allow the
Draft framework to properly maintain cursor behavior within your contents.
By defining this function within the context of a higher-level component, the props for this custom component may be bound to that component, allowing instance methods for custom component props.
Defining custom block components
Within MediaComponent
, the most likely use case is that you will want to
retrieve entity metadata to render your custom block. You may apply an entity
key to the text within a 'atomic'
block during EditorState
management,
then retrieve the metadata for that key in your custom component render()
code.
class MediaComponent extends React.Component {
render() {
const {block, contentState} = this.props;
const {foo} = this.props.blockProps;
const data = contentState.getEntity(block.getEntityAt(0)).getData();
// Return a <figure> or some other content using this data.
}
}
The ContentBlock
object and the ContentState
record are made available
within the custom component, along with the props defined at the top level. By
extracting entity information from the ContentBlock
and the Entity
map, you
can obtain the metadata required to render your custom component.
Retrieving the entity from the block is admittedly a bit of an awkward API, and is worth revisiting.
Recommendations and other notes
If your custom block renderer requires mouse interaction, it is often wise
to temporarily set your Editor
to readOnly={true}
during this
interaction. In this way, the user does not trigger any selection changes within
the editor while interacting with the custom block. This should not be a problem
with respect to editor behavior, since interacting with your custom block
component is most likely mutually exclusive from text changes within the editor.
The recommendation above is especially important for custom block renderers that involve text input, like the TeX editor example.
It is also worth noting that within the Facebook Notes editor, we have not tried to perform any specific SelectionState rendering or management on embedded media, such as rendering a highlight on an embedded photo when selecting it. This is in part because of the rich interaction provided on the media itself, with resize handles and other controls exposed to mouse behavior.
Since an engineer using Draft has full awareness of the selection state of the editor and full control over native Selection APIs, it would be possible to build selection behavior on static embedded media if desired. So far, though, we have not tried to solve this at Facebook, so we have not packaged solutions for this use case into the Draft project at this time.