Using the TinyMCE rich text editor in Bryntum Grid

TinyMCE is a popular WYSIWYG rich text editor that can be used to create a Microsoft Word-like editing experience in web apps. The basic open-source version of the editor is available for free. You can enhance its capabilities by paying for a TinyMCE subscription and optional add-on features, such as an AI assistant, support for math equations, and revision history.
In this tutorial, we’ll show you how to use the TinyMCE rich text editor in a Bryntum Grid, which is a high-performance, feature-rich, and customizable JavaScript table component.
You’ll learn how to:
- Create a Bryntum Grid
- Create a custom TinyMCE input widget
- Use the TinyMCE input as a custom editor for cells of a column
You can find the code for the completed tutorial on the completed-grid
branch of this GitHub repository.
Getting started
Clone the starter GitHub repository. This starter repository uses the development server and JavaScript bundler, Vite. You need Node.js version 18+ for Vite to work.
Install the dependencies by running the following command:
npm install
Then, run the local dev server as follows:
npm run dev
Finally, open http://localhost:5173
.
You should see a blank white screen.
The src/public
folder has two example data files that contain information about people who are interested in food:
- Simple Foodies: The data in
foodiesSimple.json
includes simple text data in thedescription
fields. - Foodies: The data in
foodies.json
includes rich text HTML data in thedescription
fields, and in one case, a Base64 image.
Creating a Bryntum Grid
Let’s start by creating a Bryntum Grid to display the Simple Foodies data. We’ll install the Bryntum Grid, configure it, and style it.
Installing Bryntum Grid
Before you can install the Bryntum Grid component, you first need to access and log in to the Bryntum npm registry.
Bryntum components are licensed commercial products, but you can use the free trial version of Bryntum Grid for this tutorial.
If you’re installing the trial version, run the following command:
npm install @bryntum/grid@npm:@bryntum/grid-trial@6.3.0-alpha-1
If you have a Bryntum Grid license, install the component as follows:
npm install @bryntum/grid@6.3.0-alpha-1
We’re using the 6.3.0-alpha-1 version of Bryntum Grid for this tutorial because it includes the
RichTextField
widget that we’ll use to create a custom TinyMCE input field.
Configuring and rendering the Bryntum Grid
Replace the console log in the src/main.js
file with the following lines of code:
import { Grid } from '@bryntum/grid';
const grid = new Grid({
appendTo : 'container',
columns : [
{
text : 'Name',
field : 'name',
flex : 1
}, {
text : 'Email',
field : 'email',
type : 'template',
template : ({ value }) => `<a href="mailto:${value}">${value}</a>`,
flex : 1
}, {
text : 'City',
field : 'city',
flex : 1
}, {
text : 'Description',
field : 'description',
flex : 2,
minWidth : 334
},
{
text : 'Date',
field : 'date',
type : 'date',
format : 'YYYY-MM-DD',
flex : 1
},
{
text : 'Color',
field : 'color',
type : 'color',
width : 80
}
],
store : {
readUrl : 'foodiesSimple.json',
autoLoad : true
}
});
Here, we create an instance of the Bryntum Grid and set the following configuration properties:
- The
appendTo
config appends the Grid component to thediv
with anid
of'container'
. - The
columns
config creates a column for each field in the Foodies data. - The
store
is an AjaxStore that uses the Fetch API to read data from a remote server specified by thereadUrl
. For simplicity, in this guide, we set thereadUrl
to thefoodiesSimple.json
file in thesrc/public
folder.
Styling the Bryntum Grid
Now that we have a component, we can style it.
By default, the Grid component is configured to occupy 100%
of the parent DOM element and has a min-height
of 10em
.
In the src/styles.css
file, make the grid take up the full height of the screen by adding the following styles to its parent div
element:
#container {
margin : 0;
display : flex;
flex-direction : column;
height : 100vh;
font-size : 14px;
}
Import the Bryntum Grid Stockholm theme by adding the following line of code to the top of the src/styles.css
file:
@import "../node_modules/@bryntum/grid/grid.stockholm.css";
The Stockholm theme is one of five available themes for Bryntum Grid. If you want to create a custom theme or use multiple themes, check out the Bryntum Grid Styling documentation.
Run the local dev server using npm run dev
. You’ll see a Bryntum Grid component displaying the Simple Foodies data:
Adding TinyMCE to your app
Now, let’s create a custom TinyMCE input field that we can use as a custom editor for cells in the Description column. This will allow us to format and add images to the description text.
To use TinyMCE, you first need to create an account. You can sign up for a free account, which includes a 14-day trial of advanced features. No credit card details are required.
Once you’ve logged in, the Integrate TinyMCE page will open in your account dashboard. You can install TinyMCE using Tiny Cloud, package managers, or self-hosted downloads. We’ll use Tiny Cloud for this guide.
Copy the API key by clicking the Get your API key button:
In the src/index.html
file in your Bryntum Grid app, add the following script below the div
with the id="container"
:
<script src="https://cdn.tiny.cloud/1/your-api-key/tinymce/7/tinymce.min.js" referrerpolicy="origin"></script>
- In the above
src
URL, replaceyour-api-key
with your TinyMCE API key.
To load the script, TinyMCE needs at least one approved root domain. By default, localhost is an approved domain. When you deploy your app, add the domain using the Approved Domains page in your Tiny Cloud dashboard.
Note that an API key is used when loading TinyMCE from the Tiny Cloud. Use a license key when self-hosting TinyMCE.
Creating a custom TinyMCE input field widget
Bryntum components have UI elements called widgets that you can add to your project interface. These widgets, such as Button
, DatePicker
, and RichTextField
, are instances of classes that extend the base Widget
class.
The RichTextField
widget is an interface class designed to be used with rich text field editors like TinyMCE. It uses a div
as the input field, which rich text editors use as their target element. However, RichTextField
isn’t used directly. Instead, we’ll use it as a base class to create a rich text editor-specific class.
Let’s create a TinyMCE input field widget.
Create a lib
folder in the src
folder, then create a TinyMceField.js
file within src/lib
and add the following lines of code to it:
import { DomHelper, GlobalEvents, RichTextField } from '@bryntum/grid';
export default class TinyMceField extends RichTextField {
static $name = 'TinyMceField';
static type = 'tinymcefield';
tinymce = null;
}
TinyMceField.initClass();
In this code, the TinyMceField
class extends the RichTextField
class. We give the widget its own $name
and type
, and create a property that will hold the TinyMCE instance. The initClass()
method registers the widget with the base Widget
class, which allows us to create the TinyMceField
widget by type.
Next, add the following static configurable
property below the tinymce
property, making sure to replace 'your-api-key'
with your TinyMCE API key:
static configurable = {
tinyMceConfig : {},
apiKey : 'your-api-key',
inline : false,
resize : false,
menubar : false,
autoFocus : true,
rootBlock : 'div',
inputAttributes : {
tag : 'textarea'
}
};
This configuration disables widget resizing and hides the drop-down menu bar.
When the widget is made visible, we’ll populate the tinyMceConfig
using the default iframe-based TinyMCE editor. You can experiment with this inline version of the editor in the Bryntum Grid TinyMCE Cell editor demo.
Add the following _onPaint
method below the configurable
property:
_onPaint() {
const me = this;
if (me.editor) {
me.destroyEditor();
}
if (!me.editor) {
const html = me.value ?? '';
me.input.value = html;
globalThis.tinymce.init({
...me.tinyMceConfig,
apiKey : me.apiKey,
auto_focus : me.autoFocus,
inline : me.inline,
forced_root_block : me.rootBlock,
menubar : me.menubar,
resize : me.resize,
height : me.height,
target : me.input,
skin : DomHelper.themeInfo?.name
?.toLowerCase().endsWith('-dark')
? 'oxide-dark'
: 'oxide',
ui_mode : 'split',
setup : editor => {
editor.on('init', () => editor.setContent(html, { format : 'html' }));
editor.on('NodeChange', () => {
if (me.isDestroying) return;
const newVal = editor.getContent();
if (newVal !== me.value) {
me.richText = newVal;
me.triggerFieldChange({
value : newVal,
oldValue : me.value,
userAction : true
});
}
});
editor.on('blur', () => {
const grid = me.up('grid');
grid.finishEditing();
});
}
}).then(editors => (me.editor = editors[0]));
}
}
The onPaint
method is called when the widget or an element in the widget becomes visible. The _onPaint
above is called when the TinyMCE cell editor is opened in our Bryntum Grid:
- In it, we first destroy the existing editor (if there is one), then create a new TinyMCE editor instance and pass in the configuration options.
- The TinyMCE
setup
option lets us provide a callback that is executed before the TinyMCE editor is rendered. - Inside the setup option, we add three event listeners. When the editor is initiated, we set its content to the input value of the Bryntum Grid cell value using the
setContent
method. - The
NodeChange
event is fired when the editor’s content changes. When this happens, we update the value of the Bryntum Grid cell with the value of the editor’s content. - The
blur
event occurs when the editor loses focus. When this happens, we call the Bryntum GridfinishEditing
method to update the cell value and hide the editor. We need to do this because we’ll disablemanagedCellEditing
to have control over when the TinyMCE editor closes.
Now add the following owns
method below the _onPaint
method:
owns(target) {
return super.owns(target) || Boolean(target?.closest('.tox-tinymce'));
}
The owns
method returns true
if the widget owns the passed-in element, event, or widget. We use it to indicate that the TinyMCE floating toolbar element is part of this widget.
Add the following destroyEditor
method below the owns
method:
destroyEditor() {
this.editor?.destroy(); // Destroy the existing instance
this.editor = null;
}
We use this method in _onPaint
to destroy
the editor object.
Lastly, add the following construct
method below the configurable property:
construct(config = {}) {
super.construct(config);
const me = this;
GlobalEvents.ion({
theme : 'destroyEditor',
thisObj : me
});
me.up(w => w.isPopup)?.ion({
hide : 'destroyEditor',
thisObj : me
});
me.ion({
paint : '_onPaint',
thisObj : me
});
}
The construct
method applies the configuration. When the global theme changes or a surrounding popup is hidden, we call destroyEditor
, and on every paint, we rerun _onPaint
.
Adding the TinyMCE editor to the Description column
In the src/main.js
file, import your custom TinyMCE input field widget:
import './lib/TinyMceField.js';
Add the following properties to the 'Description'
column object in the Grid columns
config array:
type : 'template',
template : ({ value }) => value,
autoHeight : true,
revertOnEscape : false,
managedCellEditing : false,
cellEditor : {
// The rich text editor is allowed to overflow the cell height.
matchSize : {
height : false
}
},
editor : {
type : 'tinymcefield',
inline : false,
height : 400
}
We change the column type to template
, allowing us to use a template for the cell content, so we can render rich HTML content, like image tags, in the cell. We use the editor
config object to create the input field for editing cells in the Description column and set its type
to the TinyMCE input field.
In the store
config, replace the Simple Foodies data with the rich text data in the Foodies JSON file by changing the readUrl
as follows:
- readUrl : 'foodiesSimple.json',
+ readUrl : 'foodies.json',
Add the following styles to the src/styles.css
file:
.b-grid-cell[data-column="description"] {
display : block;
padding-block : .75em;
h3 {
margin-block : 0 .5em;
}
}
.b-tinymcefield {
.tox.tox-tinymce {
width : 100%;
border-radius : 2px;
&:not(.tox-tinymce-inline) {
border : 1px solid #d0d1d2;
}
&.tox-edit-focus {
border-color : rgba(254, 172, 49, 0.6);
}
.tox-edit-area::before {
border : none;
}
}
}
When you run the dev server, you’ll now see rich text rendered in the Description column. You can format the text of the Description cells and add images to them.
Next steps
Take a look at the TinyMCE editing demo on the Tiny Cloud homepage to see the advanced features that you can add to your editor, such as the Microsoft Word import and export functionality.
Visit the Bryntum Grid demo page to see some of the other features you can add to your component, such as: