Using the WordPress WYSIWYG editor within custom Widgets

by Jeffrey van Rossum

Sometimes you want to create a custom widget for your WordPress theme or plugin. And sometimes, you might want to include the tiny WYSIWYG editor like the one that is within the Text widget WordPress offers out of the box.

Now, you might think this is easy to do. But it turns out, it's not. The interwebs is full of threads and articles of using the editor within widgets, with all kinds of trickery and workarounds.

One might think, why not just use the wp_editor function. And this will indeed work fine for things like settings pages. But for widgets, you'd be running into issues mostly because widgets are handled through AJAX-requests. And all workarounds you'll find online don't seem to be working anymore.

Going the JavaScript route

Now instead of using the wp_editor function, I tried getting it to work with the wp.editor JavaScript object. And I seem to have a working solution now. Maybe not perfect, but at least it works.

First, to be able to use this function, we need to include the necessary editor scripts. Also, to be able to add our own JavaScript, will add a file wp-editor-widgets.js (but you can call it differently if you want of course).

function custom_widgets_widget_editor_script() {
    global $pagenow;

    if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) {
        wp_enqueue_editor();

        wp_enqueue_script( 'wp-editor-widgets', get_stylesheet_directory_uri() . '/js/wp-editor-widgets.js', array( 'jquery' ), false, false );
    }
}

add_action( 'admin_enqueue_scripts', 'custom_widgets_widget_editor_script' );

Initializing multiple wp.editor instances

In our custom widget, we'll just add a textarea. To identify the editors in our script, we'll add a CSS-class to the textarea called custom-widget-wp-editor.

So within your widget a textarea that needs to become an editor, can look something like this:

<textarea id="<?php echo $this->get_field_id( 'example' ); ?>" name="<?php echo $this->get_field_name( 'example' ); ?>" class="custom-widget-wp-editor"><?php echo $instance['example'] ?? null; ?></textarea>

Let's get started with the script. Be sure to wrap your script within document ready function.

jQuery(document).ready(function ($) {
    // Our script.
});

In our script, we'll make a function to collect all textarea's that we need to convert to editors.

function custom_widgets_get_editors() {
    let editors = [];

    $(document).find("#customize-theme-controls .custom-widget-wp-editor, #widgets-right .custom-widget-wp-editor").each(function () {
        editors.push($(this).attr('id'));
    });

    return editors;
}

We target the widget areas both in the Customizer view and the regular widgets view.

Next, we'll initialize our editors.

function custom_widgets_init_editors() {
    custom_widgets_get_editors().forEach(function (id) {
        wp.editor.initialize($('#' + id).attr('id'), {
            tinymce: {
                wpautop: true
            },
            quicktags: true,
            mediaButtons: true
        });
    });
}

// Init the function.
custom_widgets_init_editors();

Now this will already display our editors, but it will result in the values not being submitted. We can solve this by triggering a click on the HTML-tab. Let's add it to our custom_widgets_init_editors function.

function custom_widgets_init_editors() {
    custom_widgets_get_editors().forEach(function (id) {
        wp.editor.initialize($('#' + id).attr('id'), {
            tinymce: {
                wpautop: true
            },
            quicktags: true,
            mediaButtons: true
        });
    });

    // To prevent the editor from not submitting the value, we click the switch html tab.
    $(document).contents().find('.widget-control-save').off().on('click', function (e) {
        custom_widgets_get_editors().forEach(function (editor) {
            let form = $('#' + editor).closest('form');
            form.find('.switch-html').click();
    });
});

Now if we add or update our widget, we'll have to re-initialize our editors. Otherwise, since widgets are handled through AJAX-requests, our editors will break. But before we can re-initialize them, we'll need to remove the current instances.

function custom_widgets_remove_editors() {
    custom_widgets_get_editors().forEach(function (id) {
        wp.editor.remove(id);
    });
}

Finally, we listen to the widget events and trigger the necessary functions.

$(document).on('widget-updated widget-added', function () {
    custom_widgets_remove_editors();
    custom_widgets_init_editors();
});

Just want a gist? Here it is.

Comments

Talk about this article on X.
Replied by Martin Smaxwil
Thanks for sharing your code. TinyMCE appears in custom widgets as expected 👍 But when making changes in the visual tab, the widget save button remains disabled. When changing text on the code tab the button gets active. Any Ideas? Do I have to add a trigger for a change event?
Replied by Jeffrey van Rossum
I see what you mean. What you could do is add an hidden input field. Then trigger a change on that field, by hooking in to the change event of the editor. Just did a quick try and that seems to do the trick.
Did you like this post?

If you sign up for my newsletter, I can keep you up to date on more posts like this when they are published.