WYSIWYG in 2019 Getting Started with the Ory Editor

WYSIWYG editors have been around forever. If you ever researched the history of web development you probably came across Adobe's Dreamweaver, one of the first of its kind.

Since then there have been many versions of WYSIWYG editors. Some are used standalone while others integrate into existing systems, for example content management systems like Wordpress or TYPO3.

Recently there has been competition that tries to deliver rich WYSIWYG functionality using latest technology. It's one of those I want to talk about in this post.

Introducing the Ory Editor

Ory is a modern tool for editing web pages in a WYSIWYG fashion. It's built with React which makes integrating it in a React-based Single-Page application fairly easy.

Ory also manages the state of the pages you edit using a JSON-object rather than shortcodes like many other WYSIWYG editors do. This enables developers to serialize the current state of a page and save it, for instance to a database, without any need to compile to HTML first.

In this post, we will see how to integrate Ory in a React application, extend the editor by adding our own plugins to it and save the configuration of a page to a database.

Setting up the Project

Since Ory is based on React, we will build a small React application demonstrating how to write your own plugins. Start by creating a new React application using create-react-app:

npx create-react-app ory-demo-project

Once the installation is finished, change into the directory and add the Ory Editor:

cd ory-demo-project && npm install --save ory-editor

Ory requires a couple of peer dependencies to run which will not be installed along with it. If you try to use Ory in your code right now, the application will error because of the missing dependencies. To fix that, we need to install them ourselves:

npm install --save @material-ui/core @material-ui/icons react-jss

Now we are all set to start coding, so let's fire up the development server:

npm run start

Begin by removing the default template that create-react-app comes with so we have a clean page. With that out of the way, add the Ory Editor to the page. I'll show you the code first and then break it down line by line:

import React, { Component } from 'react'
// The editor core
import Editor, { Editable } from 'ory-editor-core'
import { createEmptyState } from 'ory-editor-core'
import 'ory-editor-core/lib/index.css'
// The default ui components
import { Trash, DisplayModeToggle, Toolbar } from 'ory-editor-ui'
import 'ory-editor-ui/lib/index.css'
// The rich text area plugin
import slate from 'ory-editor-plugins-slate'
import 'ory-editor-plugins-slate/lib/index.css'

// Define which plugins we want to use.
const plugins = {
  content: [slate()],
}

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

const editor = new Editor({
  // pass the plugins
  plugins: plugins,
  // pass the content
  editables: [content],
})

class App extends Component {
  render() {
    return (
      <div className="App">
        <Editable
          editor={editor}
          id={content.id}
        />

        <Trash editor={editor} />
        <DisplayModeToggle editor={editor} />
        <Toolbar editor={editor} />
      </div>
    )
  }
}

export default App

First, we import the core dependencies of the Ory Editor along with a couple of UI components that enable basic interaction with the editor's content.

Additionally, the so-called slate plugin is imported. This plugin is Ory's version of a rich text editor. It comes with a set of possibilities to format text that you are probably familiar with if you ever used a text editor, email client or something similar before. For the sake of simplicity, let's leave it at one plugin right now. Don't forget to import the corresponding style sheets as well if you want things to look nice.

Next, specify what plugins should be available to the editor. Ory offers two kinds of plugins - I'll get to that later in more detail. For now, only specify the aforementioned slate plugin under the key content.

Ory manages the modifications you apply to the page in its internal state object. You generally provide this state during initialization of the editor. Since we are starting with a fresh page here, use the helper function createEmptyState that the Editor provides to generate an initial state and save it to the content variable.

Once the plugins and content are specified, use them to initialize the editor. After that, add the Ory Editor to the page using the Editable component that receives the editor as its property.

Finish by also adding the UI components I mentioned earlier to the application. These provide you with the possibility to interact with the editor, like removing plugins from the page, moving them around or switching between presentation or editing mode.

Visit the application served by the development server at http://localhost:3000/ to see the Ory Editor. On the right, you can find the toolbar which you can use to add one or more rich text areas, edit them or move them around as you please.

Of course, you won't get too far with building a nice website when only a rich text editor is available. Fortunately, Ory offers a couple more default plugins for common use cases like adding images or videos. It also offers the possibility to build and use your own plugins if you want even more options when designing your pages. Let's see how we can extend Ory's functionality with our very own plugin.

Types of Ory Plugins

Ory offers two kinds of plugins:

  • Layout plugins act as a wrapper for content that you put into them. They do not exist on their own and require at least one child element.
  • Content plugins represent elements on your page that can be configured on their own but do not contain any child elements.

We have already used a content plugin earlier when we added the rich text editor to the page. But when do you use layout plugins?

One example would be if you wanted the possibility to specify a section on your page, give that section a background image or color, and put arbitrary content inside it. In this case you could implement this kind of functionality with a layout plugin. Shall we?

Building our own Plugin

Extending Ory with your own plugin is a two-step process. First, create a new React component that implements the behavior we want. Second, you need to specify the plugin definition to supply Ory with some information about the plugin and actually have it added to the editor.

Let's start by building a new component. Create a new directory SectionPlugin, inside it create the file component.js and paste in the following code:

// src/SectionPlugin/component.js

import React, { Component, Fragment } from 'react'
import { BottomToolbar } from 'ory-editor-ui'
import {
  darkTheme,
  default as ThemeProvider,
} from 'ory-editor-ui/lib/ThemeProvider'
import TextField from '@material-ui/core/TextField'

class SectionPlugin extends Component {
  handleColorChange = event => {
    this.props.onChange({ color: event.target.value })
  }

  render() {
    const {
      readOnly,
      focused,
      children,
      state: { color = 'lavenderblush' },
    } = this.props

    return (
      <Fragment>
        <section style={{ backgroundColor: color }}>{children}</section>

        {!readOnly && (
          <ThemeProvider theme={darkTheme}>
            <BottomToolbar open={focused} theme={darkTheme}>
              <TextField
                label="Background Color"
                style={{ width: '256px' }}
                value={color}
                onChange={this.handleColorChange}
              />
            </BottomToolbar>
          </ThemeProvider>
        )}
      </Fragment>
    )
  }
}

export default SectionPlugin

When writing plugins for Ory, it's important to understand that the plugins themselves never manage any state on their own. Instead, the Ory Editor itself manages the state of the page you are editing in a global JSON-object where all plugins and their configurations are represented. Therefore, the most important part to understand is how to let Ory know that the background color of our plugin was changed by the user.

Every plugin gets passed a couple of properties:

  • readOnly is true or false depending on whether you activated the edit-mode in the Ory toolbar we imported earlier
  • once you are in edit-mode, focused will become true if you selected the current instance of the plugin by clicking on it
  • children are the child elements of the layout plugin; content plugins do not need to use this property
  • with our plugin not managing its own state, the state gets passed in as a property to the component
  • onChange is a function that allows for updating the state of the plugin in the global JSON-object

The component itself is fairly simple. It's a section element with a background color — pulled out of the state property — set using inline-styles. Because the value would initially be undefined, you can set a default value while destructuring the properties.

We also import the BottomToolbar component from Ory and display it if the editor is in edit-mode and the current instance of the plugin is focused.

Inside the toolbar is a TextField component for changing the color value. In its event handler, we call the onChange function that gets passed in as a property to update the global state.

Now, to use the plugin inside Ory, we also need the plugin definition. Inside our new directory, create a new file index.js with the following content:

import React from 'react'
import slate from 'ory-editor-plugins-slate'
import { v4 } from 'uuid'
import CropSquare from '@material-ui/icons/CropSquare'
import Section from './component'

export default {
  Component: Section,
  IconComponent: <CropSquare />,
  text: 'Section',
  description: 'Specify a section with a background color',
  name: 'test-plugin/layout/section',
  version: '0.0.1',
  createInitialChildren: () => ({
    id: v4(),
    rows: [
      {
        id: v4(),
        cells: [
          {
            content: {
              plugin: slate(),
              state: slate().createInitialState(),
            },
            id: v4(),
          },
        ],
      },
    ],
  }),
}

Ory expects a name, version and the related component for a minimal plugin definition. If you remember, earlier I mentioned that layout components need at least one child element. In this case, we configure the plugin to have a rich text editor as its initial child. To make the plugin a little more usable, you can also supply a text, description, and icon to be provide further information about it.

Now, to make the plugin available in the editor, add it to the editor's configuration in src/App.js:

import slate from 'ory-editor-plugins-slate'
import 'ory-editor-plugins-slate/lib/index.css'
+import section from './SectionPlugin'

 // Define which plugins we want to use.
 const plugins = {
   content: [slate()],
+  layout: [section],
 }

Visit http://localhost:3000/ and you will find that two plugins are now available for use. Add the new section plugin to the page. If you enter edit mode and focus the element, the toolbar will show up at the bottom of the page and let you change the background color. You can also edit the rich text plugin inside the section plugin or add more content plugins to it.

Saving the Editor state

So far, we've seen how to integrate the Ory Editor and extends its functionality by implementing additional plugins. Users can now use your plugins to build pages on their own without the need of a developer. Unfortunately, the page they build only lives until they reload the page. We still need a way to save the configuration of the editor for later use, be it to deliver the page to the end user or to modify it again later on.

Fortunately, the Editable component offers an event handler that receives the new state every time you interact with a plugin on the page. You can use this handler to store the editors state in your applications state and - for example by clicking a button - save the state to a database.

In src/App.js, apply the following changes:

 class App extends Component {
+  state = {
+    editorContent: null,
+  }
+
+  handleEditorChange = content => {
+    this.setState({ editorContent: content })
+  }
+
   render() {
     return (
       <div className="App">
-        <Editable editor={editor} id={content.id} />
+        <Editable
+          editor={editor}
+          id={content.id}
+          onChange={this.handleEditorChange}
+        />

         <Trash editor={editor} />

Every time you interact with the editor, the application state will get updated and therefore hold Ory's current state. You could now add a button that - when clicked - saves this state to a database. Since Ory conveniently manages its state in a JSON-object, this can be as simple as calling:

handleSave = () => {
  fetch('www.api.com/save/', {
    method: 'POST',
    body: JSON.stringify(this.state.editorContent),
  })
}

Feel free to adjust the code to fit your own logic for saving content to a database of your choice.

And that's it! You can keep extending the functionality of Ory by writing more plugins and use them to build your pages.

Conclusion

In my opinion, the Ory Editor is a real and modern alternative if you want to integrate WYSIWYG workflows for your users into an application.

Since it's based on React, implementing new plugins can be achieved using a technology that is wildly popular right now and offers a nice developer experience. The fact that the editor's state is a JSON-object also allows for easy serialization without any compilation beforehand.

So far, I have had quite a satisfying time working with it. Let me know how you like it. :)

Thank you for reading! If you have feedback of any kind or just want to have a chat, feel free to reach out to me. You can find my contact information below.