how to use and customize TinyMCE5

TinyMCE 5.0 is a JavaScript based WYSIWYG editor, used for content creation. Today we explore how to implement the library and customize it for our own needs.

include and initialize the editor

The first part of our journey contains a quick introduction to the editor and shows how to include and load the basic editor.

include the script

First of all we set up a basic folder structure for our project and create an index file:

tinymce-example
|___css
|___js
|   index.php

Next we will download the library from the official website. Pick the TinyMCE Community 5.0.9 version. Once you extracted the zip file, navigate into tinymce/js and copy the tinymce folder you find there into your JavaScript directory. This will result in following structure:

tinymce-example
|___css
|___js
    |___tinymce
        |___langs
        |___plugins
        |___skins
        |___themes
        |   jquery.tinymce.min.js
        |   license.txt
        |   tinymce.min.js
|   index.php

Let's setup a HTML boilerplate and include the script inside the head tag:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>TinyMCE example</title>
        <script src='./js/tinymce/tinymce.min.js'></script>
    </head>
    <body>

    </body>
</html>

initialize the default editor

TinyMCE has three main integration modes (classic, inline, distraction free). The editor uses the silver theme by default which will automatically render the editor in classic mode. The classic mode turns a textarea element into an iframe to display the editor. To initialize the editor we have to create a form and tell the script which textarea to use:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>TinyMCE example</title>
        <script src='js/tinymce/tinymce.min.js'></script>
        <script type="text/javascript">
            tinymce.init({
                selector: '#tinymce'
            });
        </script>
    </head>
    <body>
        <form method="post" action="request.php">
            <textarea id="tinymce"></textarea>
        </form>
    </body>
</html>

This is how our basic setup should look like:

create an API key

Our editor works fine on a local environment but if we upload the project to our host and access the site, we will see an annoying overlay which tells us our provided API key is incorrect. To avoid this, you have to register an API key on the official website. To do so, you have to create an account and then you can manage your keys.

After we registered a key for our domain, everything should work. Keep in mind that you have to adapt the include statement of the library if you load it from the cloud. But since we are "self hosting" the library, we should be fine already.

dig into the integration modes provided by TinyMCE

As mentioned above, TinyMCE supports three integration modes. The classic one is quite common and should be familiar to everyone. The inline integration can be used on single elements, and the distraction free integration can be applied to a complete container.

classic integration mode

Our project uses the classic integration mode, because the default theme of TinyMCE uses classic integration by default. There are several options for TinyMCE to customize the editor which we will discover later in this article.

inline integration mode

The inline integration mode allows us to make specific elements inside our DOM editable. For instance we could make a headline element editable - click into the text of the following headline to check it out.

editable headline

The following Markup and JavaScript is used to achieve the result above:

<h3 id="inline-integration-example"></h2>
tinymce.init({
    selector: '#inline-integration-example',
    inline: true
});

Pretty neat.

distraction free integration mode

The distraction free integration mode is similar to the inline mode, but instead of making only one element and its text node editable, you can make a container with all its child elements editable. It can be invoked the same way as the inline integration mode is invoked, let's give it a try:

distraction free editor

Lorem ipsum dolor sit amet, consectetur adipisicing elit. At consectetur cupiditate dolorem impedit magni molestias perspiciatis rem sapiente voluptatibus? Assumenda laborum nobis obcaecati! A autem beatae dolorem expedita explicabo facilis harum illum impedit incidunt nam neque non obcaecati odio odit omnis provident quasi quis rerum sed, voluptates? Dolore non, sunt.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. At consectetur cupiditate dolorem impedit magni molestias perspiciatis rem sapiente voluptatibus? Assumenda laborum nobis obcaecati! A autem beatae dolorem expedita explicabo facilis harum illum impedit incidunt nam neque non obcaecati odio odit omnis provident quasi quis rerum sed, voluptates? Dolore non, sunt.

The following Markup and JavaScript is used to achieve the result above:

<div id="distraction-free-integration-example" style="border:1px solid #ff0000">
    <h2>distraction free editor</h2>
    <p>
        Lorem ipsum dolor sit <strong>amet</strong>, consectetur adipisicing elit. At consectetur cupiditate dolorem
        impedit magni molestias perspiciatis rem sapiente voluptatibus? Assumenda laborum nobis obcaecati! A autem
        beatae dolorem expedita explicabo facilis harum illum impedit incidunt nam neque non obcaecati odio odit
        omnis provident quasi quis rerum sed, voluptates? Dolore non, sunt.
    </p>
    <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. At consectetur cupiditate dolorem impedit magni
        molestias perspiciatis rem sapiente voluptatibus? Assumenda laborum nobis obcaecati! A autem beatae
        dolorem expedita explicabo facilis harum illum impedit incidunt nam neque non obcaecati odio omnis
        provident quasi quis rerum sed, voluptates? Dolore non, sunt.
    </p>
</div>
tinymce.init({
    selector: '#distraction-free-integration-example',
    inline: true
});

customize the editor

Silver is TinyMCE's default theme and can be customized very easily. We have access to the size of the editor, status bar, menu and toolbar. You can create your own themes, but this requires a more in depth-knowledge of the API. Before we customize the editor we add some HTML elements and CSS to our boilerplate, to check out how the editor reacts to it. Change our boilerplate to:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>TinyMCE example</title>
        <link rel="stylesheet" href="css/main.css">
        <script src='js/tinymce/tinymce.min.js'></script>
        <script type="text/javascript">
            tinymce.init({
                selector: '#tinymce'
            });
        </script>
    </head>
    <body>
        <h1>h1 headline</h1>
        <h2>h2 headline</h2>
        <h3>h3 headline</h3>
        <h4>h4 headline</h4>
        <h5>h5 headline</h5>
        <h6>h6 headline</h6>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto assumenda cum eaque, eius est
            inventore maiores modi nostrum optio qui quia quos tempora. Adipisci consequatur eum exercitationem
            illum minus praesentium repellat. Alias culpa laudantium provident quae quod reprehenderit tempore
            ut voluptatum. Dolores numquam quibusdam rem sed! Accusamus eius illo magnam?
        </p>
        <form method="post" action="request.php">
            <textarea id="tinymce"></textarea>
        </form>
    </body>
</html>

And create a CSS file inside the css folder to give the added elements some colors:

body {
    font-family:        verdana, sans-serif;
    color:              rgb(128, 139, 150);
}
h1 {
    color:              rgb(24, 106, 59);
}
h2 {
    color:              rgb(29, 131, 72);
}
h3 {
    color:              rgb(35, 155, 86);
}
h4 {
    color:              rgb(40, 180, 99);
}
h5 {
    color:              rgb(46, 204, 113);
}
h6 {
    color:              rgb(88, 214, 141);
}

As we can see now, the TinyMCE editor still uses another font family, and the headlines (Format -> Formats -> Headings or Format -> Blocks) didn't update to our color definitions, because by default the editor will fallback to the CSS definition from the silver theme.

load custom css

TinyMCE provides a property to load custom css. By adding the property content_css we can include our styles into the editor. Let's change the init method of the tinyMCE library and take a look at the result:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css', // resolved into http://domain.ltd/css/main.css
});

You can chain more than one CSS file to the editor with the , as seperator:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css,/css/someotherfile.css',
});

Now the editor uses our styles inside the editor. Keep in mind how the path to the file is resolved. Anyhow, if you run into some troubles, TinyMCE will show an error in the console which should be pretty self-explanatory. Now we are finally ready to customize our editor - let's have some fun:

disable the menubar

To disable the menubar, we simply set the menubar property to false and we are left with the toolbar only.

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false
});

customize the toolbar

The library provides us the property toolbar to change the appearance of the toolbar. The default value for the toolbar property is: undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent. A full list for the available values can be found here.

Our example editor should only contain the select form for the styles, bold, italic and underline. To achieve this, we set the value for the toolbar property to styleselect | bold italic underline:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'styleselect | bold italic underline'
});

Replace the styleselect with formatselect in the next step. The editor now only shows the formats in the dropdown. We keep it like that for now. In the next step we check out how to customize those formats.

customize formats

TinyMCE differentiates between block formats and inline formats. We are going to change the block formats first and look into inline formats later when we are going to create our own buttons.

The property to customize the block formats in TinyMCE is - you wouldn't believe - block_formats. The property behaves the same way as the toolbar property does. If we set a value, we override the default value. The property accepts a String with the following structure: label=format;. A list of built-in formats can be found here. To keep it simple, our goal is to only have one headline (h1), a classic paragraph (p) and an alert box (div.alert.alert-danger).

For the headline and the classic paragraph we can use the built-in formats, and for the colored paragraph we have to create a custom format. Let's add the built-in formats first and change our JavaScript to:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline',
    block_formats: 'Headline=h1;Paragraph=p;'
});

To create our own format we use the property formats. The value for the property is an object with the format label as key and another object with the definition of the format as value. div.alert.alert-danger is provided by bootstrap, and since we dont use bootstrap in our project we have to add the CSS for the alert box first:

.alert {
    position: relative;
    padding: .75rem 1.25rem;
    margin-bottom: 1rem;
    border: 1px solid transparent;
    border-radius: 0;
}
.alert-danger {
    color: #721c24;
    background-color: #f8d7da;
    border-color: #f5c6cb;
}

And finally we change our JavaScript to:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' }
    }
});

create your own button

Another thing you probably want to do in your projects is to create your own buttons for some custom actions. To accomplish this we have to utilize the setup property of the library. This property holds a function where we can register our own UI elements and define their behavior. Let's start with a simple text button that will insert some text content into the editor:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline | customTextButton',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' }
    },
    setup: function(editor) {
        editor.ui.registry.addButton('customTextButton', {
            text: 'fancyText',
            onAction: function(_) {
                editor.insertContent(' Jimi Hendrix for president ');
            }
        });
    }
});

Inside the setup function we use the editor.ui.registry.addButton() method to register our custom button. The first argument of the method contains the name of the button and the second argument contains the definition. After we registered the button, we have to change the toolbar property and add the name of our button to it. In our case we changed it to: toolbar: 'formatselect | bold italic underline | customTextButton',.

So if you click on the created button and then put the cursor inside the inserted text node, the button still works and inserts the text again. We can prevent this behavior with the onSetup() function inside editor.ui.registry.addButton(). For this we take an example of the TinyMCE documentation and check out where the magic happens. The button inserts the current time into the editor, and if you put the cursor into the inserted node, the button will be disabled. Let's adapt our JavaScript first and then we take a look into the code:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline | customTextButton customDateButton',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' }
    },
    setup: function(editor) {
        editor.ui.registry.addButton('customTextButton', {
            text: 'fancyText',
            onAction: function(_) {
                editor.insertContent(' Jimi Hendrix for president ');
            }
        });

        let DateToHtml = function(date) {
            return '';
        };
        editor.ui.registry.addButton('customDateButton', {
            text: 'currentDate',
            tooltip: 'Insert Current Date',
            disabled: true,
            onAction: function(_) {
                editor.insertContent(DateToHtml(new Date()));
            },
            onSetup: function(buttonApi) {
                let editorEventCallback = function(eventApi) {
                    buttonApi.setDisabled(eventApi.element.nodeName.toLowerCase() === 'time');
                };
                editor.on('NodeChange', editorEventCallback);

                /* onSetup should always return the unbind handlers */
                return function(buttonApi) {
                    editor.off('NodeChange', editorEventCallback);
                };
            }
        });
    }
});

Let's look at the new code. Before we register the new button, we create a simple function which expects a Date() object as argument and returns a HTML node. We use this function inside the onAction function. Then we register the new button - the property tooltip should be self-explanatory, and the property disabled sets the state of the button when the editor is loaded. This property isn't needed for this example, because it will be enabled anyway inside the onSetup function. But to keep the example the same as inside the documentation, we leave it here for now. Nothing new inside the onAction function except that we pass the DateToHTML function instead of a String as argument.

The "magic" happens inside the onSetup function. There we create a callback which checks if the selected node (where the cursor is currently placed) is a <time> node. And if so, it will disable the button. Then we simply register the eventHandler on the editor and return the unbind handler.

use an icon for your own button

Instead of using text, you can use icons for your buttons. There are several predefined icons available and the option to register your own icons. In this step we will change our currentDate button to use an icon instead of a text node. Simply remove the text property and add the icon property:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline | customTextButton customDateButton',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' }
    },
    setup: function(editor) {
        editor.ui.registry.addButton('customTextButton', {
            text: 'fancyText',
            onAction: function(_) {
                editor.insertContent(' Jimi Hendrix for president ');
            }
        });

        let DateToHtml = function(date) {
            return '';
        };
        editor.ui.registry.addButton('customDateButton', {
            icon: 'insert-time',
            tooltip: 'Insert Current Date',
            disabled: true,
            onAction: function(_) {
                editor.insertContent(DateToHtml(new Date()));
            },
            onSetup: function(buttonApi) {
                let editorEventCallback = function(eventApi) {
                    buttonApi.setDisabled(eventApi.element.nodeName.toLowerCase() === 'time');
                };
                editor.on('NodeChange', editorEventCallback);

                /* onSetup should always return the unbind handlers */
                return function(buttonApi) {
                    editor.off('NodeChange', editorEventCallback);
                };
            }
        });
    }
});

In this example we used a predefined icon. There is currently no list for all predefined icons available in the documentation (what the heck?!), but you can look up all the icon definitions inside the code on github.

If you wanna use your own icons for the buttons, you have to register the icon first by using the editor.ui.registry.addIcon() method. We will use a svg icon from fontawesome:

<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="calendar-alt" class="svg-inline--fa fa-calendar-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">

Let's adapt our existing currentDate button again:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline | customTextButton customDateButton',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' }
    },
    setup: function(editor) {
        editor.ui.registry.addButton('customTextButton', {
            text: 'fancyText',
            onAction: function(_) {
                editor.insertContent(' Jimi Hendrix for president ');
            }
        });

        let DateToHtml = function(date) {
            return '';
        };
        editor.ui.registry.addIcon('calendar', '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="calendar-alt" class="svg-inline--fa fa-calendar-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">');
        editor.ui.registry.addButton('customDateButton', {
            icon: 'calendar',
            tooltip: 'Insert Current Date',
            disabled: true,
            onAction: function(_) {
                editor.insertContent(DateToHtml(new Date()));
            },
            onSetup: function(buttonApi) {
                let editorEventCallback = function(eventApi) {
                    buttonApi.setDisabled(eventApi.element.nodeName.toLowerCase() === 'time');
                };
                editor.on('NodeChange', editorEventCallback);

                /* onSetup should always return the unbind handlers */
                return function(buttonApi) {
                    editor.off('NodeChange', editorEventCallback);
                };
            }
        });
    }
});

We registered the new icon on line 21, and we changed the icon property to use the registered custom icon on line 23.

create your own toggle button

Another type of button you may want to use in your projects is the toggleButton. The bold, italic and underline buttons are toggleButtons and make it clear how they are supposed to work. Let's create an example toggleButton that will change the color of the selected text to red. To achieve this, we have to create a custom format inside the formats property and utilize the editor.execCommand() method inside editor.ui.registry.addToggleButton(). First of all we add some CSS to our stylesheet:

span.red {
    color: rgb(255, 0, 0);
}

Now we adapt our JavaScript:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    toolbar: 'formatselect | bold italic underline | customTextButton customDateButton',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' },
        'red-text': { inline: 'span', classes: 'red' }
    },
    setup: function(editor) {
        editor.ui.registry.addButton('customTextButton', {
            text: 'fancyText',
            onAction: function(_) {
                editor.insertContent(' Jimi Hendrix for president ');
            }
        });

        let DateToHtml = function(date) {
            return '';
        };
        editor.ui.registry.addIcon('calendar', '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="calendar-alt" class="svg-inline--fa fa-calendar-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">');
        editor.ui.registry.addButton('customDateButton', {
            icon: 'calendar',
            tooltip: 'Insert Current Date',
            disabled: true,
            onAction: function(_) {
                editor.insertContent(DateToHtml(new Date()));
            },
            onSetup: function(buttonApi) {
                let editorEventCallback = function(eventApi) {
                    buttonApi.setDisabled(eventApi.element.nodeName.toLowerCase() === 'time');
                };
                editor.on('NodeChange', editorEventCallback);

                /* onSetup should always return the unbind handlers */
                return function(buttonApi) {
                    editor.off('NodeChange', editorEventCallback);
                };
            }
        });

        editor.ui.registry.addToggleButton('customToggleButton', {
            text: 'toggleButtonExample',
            onAction: function(_) {
                editor.execCommand('mceToggleFormat', false, 'red-text');
            },
            onSetup: function(buttonApi) {
                editor.formatter.formatChanged('red-text', function(state) {
                    buttonApi.setActive(state);
                });
            }
        });
    }
});

We added a new format at code line 9. We already know how this property works, nothing special there. At line 43 we used a new method from the registry, the addToggleButton() method, which works like the addButton() method. Inside onAction we used another new method, the execCommand method, to call the mceToggleFormat. You can look up how the execCommand works at the documentation. Inside onSetup we simply enable or disable the button, depending on the state of the format.

You should be able to customize your editor for a lot of use-cases now, however there is a ton of other stuff you can do. The documentation will help you out! I suggest you take a tour there ;).

submit data

The last part of this article will take care of how to submit the data from the editor. Since we use a form, we can submit the editor the classic way or utilize a plugin called "save".

classic submit

The classic way to submit a form works here as well. Just add a submit button to your form and give your textarea element a name attribute. The Markup in our project could look like this:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>TinyMCE example</title>
        <link rel="stylesheet" href="css/main.css">
        <script src='js/tinymce/tinymce.min.js'></script>
        <script type="text/javascript">
            tinymce.init({
                // ...
            });
        </script>
    </head>
    <body>
        <h1>h1 headline</h1>
        <h2>h2 headline</h2>
        <h3>h3 headline</h3>
        <h4>h4 headline</h4>
        <h5>h5 headline</h5>
        <h6>h6 headline</h6>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto assumenda cum eaque, eius est
            inventore maiores modi nostrum optio qui quia quos tempora. Adipisci consequatur eum exercitationem
            illum minus praesentium repellat. Alias culpa laudantium provident quae quod reprehenderit tempore
            ut voluptatum. Dolores numquam quibusdam rem sed! Accusamus eius illo magnam?
        </p>
        <form method="post" action="request.php">
            <textarea id="tinymce" name=tinymce"></textarea>
            <input type="submit" value="submit" />
        </form>
    </body>
</html>

If you are going to use more than one instance of the TinyMCE in your form, just make sure you give them unique name attributes.

use the "save" plugin to submit

Another way to submit your editor content is the "save" plugin from TinyMCE. The plugin adds a button to your editor which will perform the submit. To enable this plugin, we have to tell TinyMCE to use the plugin and adapt the toolbar property, so it will display the button for it. It's fairly simple, let's change our JavaScript:

tinyMCE.init({
    selector: '#tinymce',
    content_css: '/css/main.css',
    menubar: false,
    plugins: 'save',
    toolbar: 'formatselect | bold italic underline | customTextButton customDateButton | save',
    block_formats: 'Headline=h1;Paragraph=p;alert box=alert-box;',
    formats: {
        'alert-box' : { block: 'div', classes: 'alert alert-danger' },
        'red-text': { inline: 'span', classes: 'red' }
    },
    setup: function(editor) {
        editor.ui.registry.addButton('customTextButton', {
            text: 'fancyText',
            onAction: function(_) {
                editor.insertContent(' Jimi Hendrix for president ');
            }
        });

        let DateToHtml = function(date) {
            return '';
        };
        editor.ui.registry.addIcon('calendar', '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="calendar-alt" class="svg-inline--fa fa-calendar-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">');
        editor.ui.registry.addButton('customDateButton', {
            icon: 'calendar',
            tooltip: 'Insert Current Date',
            disabled: true,
            onAction: function(_) {
                editor.insertContent(DateToHtml(new Date()));
            },
            onSetup: function(buttonApi) {
                let editorEventCallback = function(eventApi) {
                    buttonApi.setDisabled(eventApi.element.nodeName.toLowerCase() === 'time');
                };
                editor.on('NodeChange', editorEventCallback);

                /* onSetup should always return the unbind handlers */
                return function(buttonApi) {
                    editor.off('NodeChange', editorEventCallback);
                };
            }
        });

        editor.ui.registry.addToggleButton('customToggleButton', {
            text: 'toggleButtonExample',
            onAction: function(_) {
                editor.execCommand('mceToggleFormat', false, 'red-text');
            },
            onSetup: function(buttonApi) {
                editor.formatter.formatChanged('red-text', function(state) {
                    buttonApi.setActive(state);
                });
            }
        });
    }
});

Enjoy using TinyMCE in your projects! If you have any questions or would like to see another topic of TinyMCE highlighted, use the comment form and I will response asap.

With best regards
Sam

comment successfully committed

leave a comment:

please enter your first name above
please enter a valid email above
please enter your message before you send the form

connect

please enter your name
please enter a valid email above
please enter the subject for your request
please enter your message before you send the form
mail successfully committed