Tutorials

ReactJS Example

In this section, we will create a minimalistic react app that uses the ORY Editor. Before we skip ahead, make sure that node.js is installed on your system.

At the moment, the ORY Editor is available only through npm and works best in a ReactJS environment.

ReactJS

Our goal is to create a ReactJS app that uses the editor. To scaffold the react app for the purpose of this tutorial, we use create-react-app

$ npm i -g create-react-app
$ create-react-app .

and install the editor using npm:

$ npm i --save ory-editor

Next, open the file src/components/App.js and include the ORY Editor:

import React, {Component} from 'react'

// The editor core
import Editor, { Editable, createEmptyState } from 'ory-editor-core'
import 'ory-editor-core/lib/index.css' // we also want to load the stylesheets

// Require our ui components (optional). You can implement and use your own ui too!
import { Trash, DisplayModeToggle, Toolbar } from 'ory-editor-ui'
import 'ory-editor-ui/lib/index.css'

// Load some exemplary plugins:
import slate from 'ory-editor-plugins-slate' // The rich text area plugin
import 'ory-editor-plugins-slate/lib/index.css' // Stylesheets for the rich text area plugin
import parallax from 'ory-editor-plugins-parallax-background' // A plugin for parallax background images
import 'ory-editor-plugins-parallax-background/lib/index.css' // Stylesheets for parallax background images
require('react-tap-event-plugin')() // react-tap-event-plugin is required by material-ui which is used by ory-editor-ui so we need to call it here

// Define which plugins we want to use. We only have slate and parallax available, so load those.
const plugins = {
  content: [slate()], // Define plugins for content cells. To import multiple plugins, use [slate(), image, spacer, divider]
  layout: [parallax({ defaultPlugin: slate() })] // Define plugins for layout cells
}

// Creates an empty editable
const content = createEmptyState()

// Instantiate the editor
const editor = new Editor({
  plugins,
  // pass the content state - you can add multiple editables here
  editables: [content],
})

class App extends Component {
  render() {
    return (
      <div>
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo"/>
          <h2>Welcome to React</h2>
        </div>

        {/* Content area */}
        <Editable editor={editor} id={content.id}/>

        {/*  Default user interface  */}
        <Trash editor={editor}/>
        <DisplayModeToggle editor={editor}/>
        <Toolbar editor={editor}/>
      </div>
    );
  }
}

export default App;

That's it, congratulations! You should see something like this now:

Example app

Writing Plugins

Writing a content plugin

Of course, you are not limited to this functionality and can easily write your own plugins. Plugins have two parts, one plugin definition and a ReactJS component. A minimal plugin definition looks as followed

import React, {Component} from 'react'

// You are obviously not limited to material-ui, but we really enjoy
// the material-ui svg icons!
import StarIcon from 'material-ui/svg-icons/toggle/star'

// This is the ReactJS component which you can find below this snippet
import InputTextField from './Component'

export default {
  Component: InputTextField,
  IconComponent: <StarIcon />,
  name: 'example/content/input-text-field',
  version: '0.0.1',
  text: 'Input Text Field'
}

and a minimalistic plugin example could look like:

import React from 'react'

// A callback function for the input field
const onInput = (onChange) => {
  return (e) => {
    // Dispatch the onChange action with the new value
    onChange({
      value: e.target.value
    })
  }
}

const InputTextField = (props) => {
  const {
    state: { value },
    readOnly,
    onChange
  } = props

  // If readOnly is false, it means that we are in edit mode!
  if (!readOnly) {
    return (
      <div className="my-plugin">
        <input
          type="text"
          onChange={onInput(onChange)} value={value} />
      </div>
    )
  }

  // If we are not in edit mode, remove the input field
  return (
    <div className="my-plugin">
      {value}
    </div>
  )
}

export default InputTextField

Of course, there are more settings and callbacks available. We encourage checking out the API docs on this topic!

Make sure the onChange prop is never passed through to an HTML element (eg via {...props}), as this will overwrite the state of your plugin with any change event emitted by that element. This applies to both content and layout plugins.

Writing a layout plugin

Of course, you are not limited to this functionality and can easily write your own plugins. Plugins have two parts, one plugin definition and a ReactJS component. A layout plugin will require an initial children, otherwise, it will automatically destroyed. A minimal layout plugin definition looks as follows

import React from 'react'
import slate from 'ory-editor-plugins-slate'

// You are obviously not limited to material-ui, but we really enjoy
// the material-ui svg icons!
import CropSquare from 'material-ui/svg-icons/image/crop-square'

const BlackBorderPlugin = ({ children }) => (
  <div style={{ border: '1px solid black', padding: '16px' }}>
    {children}
  </div>
)

export default {
  Component: BlackBorderPlugin,
  IconComponent: <CropSquare />,
  name: 'example/layout/black-border',
  version: '0.0.1',
  text: 'Black border',
  createInitialChildren: () => ({
    id: v4(),
    rows: [
      {
        id: v4(),
        cells: [
          {
            content: {
              plugin: slate(),
              state: slate().createInitialState()
            },
            id: v4()
          }
        ]
      }
    ]
  })
}

On that example, the initial children is a slate plugin.

Rendering HTML

The ory-editor-renderer package ships a lightweight HTML renderer module. You can use it for server-side rendering and rendering the content client side.

import { HTMLRenderer } from 'ory-editor-renderer'

const state = { /* ... */ }
const plugins = {
  layout: [/* ... */],
  content: [/* ... */]
}

const element = document.getElementById('editable')
ReactDOM.render((
  <HTMLRenderer state={content[0]} plugins={plugins}/>
), element)

Saving and restoring editor contents

Use the onChange callback to obtain a copy of the editor's state for saving to persistent storage. The state can then be later loaded into the editor, or used by the ory-editor-renderer package for rendering to HTML.

import React, {Component} from 'react'
import Editor, { Editable, createEmptyState } from 'ory-editor-core'
import slate from 'ory-editor-plugins-slate' // The rich text area plugin
import { Trash, DisplayModeToggle, Toolbar } from 'ory-editor-ui'
const EditorPlugins = {
  content: [slate()],
  layout: [/* ... */],
};

function saveToDatabase(state) {
    return fetch('/my/save/url', { method: 'POST', body: state });
}

class MyEditor extends Component {
  componentWillMount() {
    this.editorState = this.props.content || createEmptyState();
    this.editor = new Editor({ EditorPlugins, editables: [content] });
  }
  render() {
    return (
      <div className="my-editor">
        <toolbar>
          <button onClick={() => saveToDatabase(this.editorState)}>Save</button>
        </toolbar>
        <Editable editor={editor} id={content.id} onChange={state => (this.editorState = state)} />
        <Trash editor={editor}/>
        <DisplayModeToggle editor={editor}/>
        <Toolbar editor={editor}/>
      </div>
    )
  }
}

The state could then be fetched and rendered by doing something like:

import React, {Component} from 'react'

import { HTMLRenderer } from 'ory-editor-renderer'
import { createEmptyState } from 'ory-editor-core'

class MyEditorRenderer extends Component {

  componentWillMount() {
    this.plugins = {
    this.setState({ contents: createEmptyState() });
    fetch('/my/save/url').then((savedState) => {
      this.setState({ contents: savedState });
    })
  }

  render() {
    return (
      <div className="my-editor">
        <HTMLRenderer state={this.state.contents} plugins={EditorPlugins} />
      </div>
    )
  }
}

const element = document.getElementById('editable')
ReactDOM.render((
  <MyEditorRenderer />
), element)

Handling native drag events

The ORY Editor is capable of handling native drag and drop events. Native events include dragging of links, text, and images. Native drag support can be enabled by writing a NativePlugin and passing it during instantiation. In this example, we will use the default plugin, and take a look at how you can create your own later.

import native from 'ory-editor-plugins-default-native'

const editor = new Editor({
  plugins: {
   layout: [],
   content: [],
   native
 }
})

If native is undefined or null, native dragging wil be disabled. This is the default setting.

Writing a native plugin is like writing a content or layout plugin. The only difference is that our native plugin must be wrapped in a factory that receives three arguments - hover, monitor, and component. Hover is the cell or row that is currently being hovered. Monitor is the DropTargetMonitor coming from react-dnd and component is the React component of the cell or row that is currently being hovered.

In sum, an exemplary plugin looks like this:

import React from 'react'

const Native = () => <div>native</div>

export default (hover, monitor, component) => ({
  Component: Native,
  name: 'my-native-plugin',
  version: '0.0.1'
})

Because this plugin is wrapped in a factory, we are able to modify its behaviour based on the properties we receive. One such example would be to add the item's data to the initial state for later use.

export default (hover, monitor, component) => ({
  // ...
  createInitialState: () => ({
    item: monitor.getItem()
  })
})

Per default, the editor assumes that dropping the native element creates a content cell. To change this behaviour, use the key type:

export default (hover, monitor, component) => ({
  // ...
  type: 'layout'
})

results matching ""

    No results matching ""