13 Feb 2018
How to use React in a Forge Viewer DockingPanel
The question came up last week from a colleague: How to use a React component inside a Viewer DockingPanel element?
DockingPanels are typically created dynamically by a Viewer extension loaded at runtime, a panel is creating a DOM node to host its content using the DOM API whereas React is using Virtual DOM. In order to use a React component you need to instantiate it somewhere within your React application tree, hopefully there is a workaround to bind a component to a DOM node dynamically: ReactDOM.render.
This is the trick that I use here: creating a DOM node inside an Autodesk.Viewing.UI.DockingPanel and using ReactDOM.render to inject our custom React component inside that panel.
Let's first write a basic React component that we will display in our Panel, this could be any existing React component that you have, as long as it doesn't depend on the state of your app:
import React from 'react'
export default class ReactPanelContent extends React.Component {
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
constructor (props) {
super (props)
this.update = this.update.bind(this)
this.state = {
date: ''
}
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
componentDidMount () {
this.intervalId = window.setInterval(this.update, 1000)
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
componentWillUnmount () {
window.clearInterval(this.intervalId)
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
update () {
this.setState({
date: new Date().toLocaleString()
})
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
render () {
const {date} = this.state
return (
<div className="react-content">
Rendered by React!
<br/>
{ date }
</div>
)
}
}
Then we write a simple wrapper class that extends Autodesk.Viewing.UI.DockingPanel and binds our React component to the content of the panel:
import ReactPanelContent from './ReactPanelContent'
import ReactDOM from 'react-dom'
import './ReactPanel.scss'
import React from 'react'
export default class ReactPanel extends Autodesk.Viewing.UI.DockingPanel {
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
constructor (viewer, options) {
super (viewer.container, options.id, options.title, {
addFooter: false,
viewer
})
this.container.classList.add('react-docking-panel')
this.DOMContent = document.createElement('div')
this.DOMContent.className = 'content'
this.container.appendChild(
this.DOMContent)
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
initialize () {
super.initialize()
this.viewer = this.options.viewer
this.footer = this.createFooter()
this.container.appendChild(
this.footer)
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
setVisible (show) {
super.setVisible(show)
if (show) {
this.reactNode = ReactDOM.render(
<ReactPanelContent/>,
this.DOMContent)
} else if (this.reactNode) {
ReactDOM.unmountComponentAtNode(
this.DOMContent)
this.reactNode = null
}
}
}
You can conveniently style the panel using css (or sass, scss, less whatever your build system is configured to support) rather than implementing the styling through JavaScript. Here is how my styles look like for this panel:
.react-docking-panel {
height: 208px;
width: 238px;
resize: auto;
left: 10px;
top: 10px;
.content {
height: calc(100% - 35px);
.react-content {
margin: 10px;
}
}
}
Finally I create a simple extension that instantiate our panel and display it upon loading:
/////////////////////////////////////////////////////////////////
// ReactPanel Viewer Extension
// By Philippe Leefsma, Autodesk Inc, Feb 2018
//
/////////////////////////////////////////////////////////////////
import ReactPanel from './ReactPanel'
class ReactPanelExtension extends Autodesk.Viewing.Extension {
/////////////////////////////////////////////////////////
// Class constructor
//
/////////////////////////////////////////////////////////
constructor (viewer, options) {
super (viewer, options)
options.loader.show(false)
this.panel = new ReactPanel(viewer, {
id: 'react-panel-id',
title: 'React Panel'
})
}
/////////////////////////////////////////////////////////
// Load callback
//
/////////////////////////////////////////////////////////
load () {
console.log('Viewing.Extension.ReactPanel loaded')
this.panel.setVisible(true)
return true
}
/////////////////////////////////////////////////////////
// Extension Id
//
/////////////////////////////////////////////////////////
static get ExtensionId () {
return 'Viewing.Extension.ReactPanel'
}
/////////////////////////////////////////////////////////
// Unload callback
//
/////////////////////////////////////////////////////////
unload () {
console.log('Viewing.Extension.ReactPanel unloaded')
return true
}
}
Autodesk.Viewing.theExtensionManager.registerExtension (
ReactPanelExtension.ExtensionId,
ReactPanelExtension)
export default 'Viewing.Extension.ReactPanel'
That's it! You can find the live demo here and the full source code at Viewing.Extension.ReactPanel
I am assuming here that you already have a web application that supports ES6 + JSX transpilling... if it's not the case the best way to get started with it is to take a look at create-react-app.