wp-admin-modal-example icon indicating copy to clipboard operation
wp-admin-modal-example copied to clipboard

Add TinyMCE Plugin to Backbone Example

Open aut0poietic opened this issue 9 years ago • 9 comments

Extend the backbone plugin to include a sample TinyMCE plugin.

See Issue #5

aut0poietic avatar May 20 '15 17:05 aut0poietic

First, thanks for sharing this on github! It was an amazing help to my latest project.

I've got a tinymce editor working in your backbone modal.

A couple of steps ( note: modal-editor is the ID of the textarea):

  1. ensure that editor is one of the dependencies for enqueing the modal script
  2. duplicate the HTML markup in your template, some of this is handled via PHP so I had to hard-code some div IDs/classes
<div class="wp-editor-wrap">

    <div class="postarea wp-editor-expand">

        <div id="wp-modal-editor-wrap" class="wp-core-ui wp-editor-wrap has-dfw">

            <div id="wp-modal-editor-editor-tools" class="wp-editor-tools hide-if-no-js">

                <div class="wp-media-buttons">
                    <button type="button" class="button insert-media add_media" data-editor="modal-editor"><span class="wp-media-buttons-icon"></span><?php _e( 'Add Media', 'backbone_modal' );?></button>             
                </div>

                <div class="wp-editor-tabs">
                    <button type="button" id="content-tmce" class="wp-switch-editor switch-tmce" data-wp-editor-id="modal-editor"><?php _e( 'Visual', 'backbone_modal' ); ?></button>
                    <button type="button" id="content-html" class="wp-switch-editor switch-html" data-wp-editor-id="modal-editor"><?php _ex( 'Text', 'Name for the Text editor tab (formerly HTML)', 'backbone_modal' ); ?></button>
                </div>

            </div><!-- .wp-editor-tools -->

            <div class="wp-editor-container">
                <textarea id="modal-editor" class="wp-editor-area" autocomplete="off" cols="40" name="content">{{{ data.content }}}</textarea>
            </div>

        </div>

    </div>

</div>
  1. "borrow" the settings from the tinyMCEPreInit object. I run this function on the view's initialize()
    /**
     * Merge the default TinyMCE settings
     */
    tinyMCEsettings: function() {
        // get the #content"s tinyMCE settings or use default
        var init_settings = typeof tinyMCEPreInit == "object" && "mceInit" in tinyMCEPreInit && "content" in tinyMCEPreInit.mceInit ? tinyMCEPreInit.mceInit.content : this.tmc_defaults;

        // get the #content"s quicktags settings or use default
        var qt_settings = typeof tinyMCEPreInit == "object" && "qtInit" in tinyMCEPreInit && "content" in tinyMCEPreInit.qtInit ? tinyMCEPreInit.qtInit.content : this.qt_defaults;

        var _this = this;
        var custom_settings = {
            selector: "#modal-editor"
        }

        // merge our settings with WordPress" and store for later use
        this.tmc_settings = $.extend({}, init_settings, custom_settings);
        this.qt_settings = $.extend({}, qt_settings, {
            id: "modal-editor"
        });
    },
  1. then after the modal and textarea are rendered, run
    /**
     * Start TinyMCE on the textarea
     */
    startTinyMCE: function() {

        // add our copy to the collection in the tinyMCEPreInit object because switch editors
        if (typeof tinyMCEPreInit == 'object') {
            tinyMCEPreInit.mceInit["modal-editor"] = this.tmc_settings;
            tinyMCEPreInit.qtInit["modal-editor"] = this.qt_settings;
        }

        try {

            var rich = (typeof tinyMCE != "undefined");

            // turn on the quicktags editor
            quicktags(this.qt_settings);

            // attempt to fix problem of quicktags toolbar with no buttons
            QTags._buttonsInit();

            if (rich !== false) {
                // turn on tinyMCE
                tinyMCE.init(this.tmc_settings);
            }

        } catch (e) {}

    },

I think that's pretty much everything I have to get media buttons plus text/visual editor switching working. I have one remaining problem with the "link" plugin not appearing, but otherwise it's all working.

Thanks again!

helgatheviking avatar Jul 30 '16 23:07 helgatheviking

Thanks @helgatheviking ! This is good stuff and I'm glad my code helped you out -- though I have to apologize for how outdated it is.

I have a working example of a custom modal launching from a tinyMCE plugin/button containing a fully-functioning tinyMCE editor I did for a client I'll share if I can find time to circle back to this repo. I'll need to re-write it to pull out all their domain-specific stuff and I've been overloaded with projects (excuses, excuses, right? ;-)

As for the link-modal, there are a ton of things that could be causing you grief. One easy fix is that that the link-modal will not appear if the body has the modal-open class, so it might benefit you to create your own version of modal-open . Doubt that helps in this situation (but it shouldn't hurt either).

Thanks for the info and the follow on twitter.

aut0poietic avatar Aug 01 '16 14:08 aut0poietic

the link-modal will not appear if the body has the modal-open class

WHAAAAAAAAAAAAAT? Stop the presses. I've been struggling with that for days! I just switched the class name in my addClass and the link plugin appears. You can't type in it yet, but damn, that's a nice improvement. Would love to see your example if you get the time.

Thanks again!

helgatheviking avatar Aug 02 '16 16:08 helgatheviking

I've discovered that your preserveFocus function is what is preventing the link plugin from appearing.

Borrowing from this answer I've added an extra if condition:

preserveFocus: function(e) {
    "use strict";

    // this allows for the tinymce plugins to still work
    if ($(e.target).closest(".mce-container").length) {
        e.stopImmediatePropagation();
    } else if (this.$el[0] !== e.target && !this.$el.has(e.target).length) {
        this.$el.focus();
    }
},

The link plugin is working! it's generating this error "Uncaught TypeError: Cannot read property 'fire' of undefined" even though everything seems to be working.

Anyway, I've been trying to tweak the modal to copy the media modal's markup so that it inherits the media modal's styles. It seems like I ought to be able to use the media modals's backbone views somehow, but I'm not at that level of backbone badassery yet. Thanks again!!

helgatheviking avatar Aug 03 '16 04:08 helgatheviking

Just to keep adding on for posterity, the above preserveFocus() still isn't doing everything I need because it's still blocking the media modal when you click on "Add Media". How much do you think this is really needed?

helgatheviking avatar Aug 05 '16 00:08 helgatheviking

That method keeps the user's focus on the modal. Without it, user of assistive technology or keyboard-only users can get lost/trapped in the content being covered by the modal.

If your project doesn't need to be accessible (comply with sec.508 or WCAG 2.0) and you don't envision disabled users using the product, then it can be removed.

Another solution is to listen for other modals being opened and suspend the method until the other modals are closed. This is what's recommended by the W3C in their ARIA best practices, and I suspect is being used in WP itself in places.

Hope that helped and sorry I have so little time to update this project.

aut0poietic avatar Aug 05 '16 14:08 aut0poietic

There's no need to apologize. I'm so grateful that this is even here and that you're willing to bounce this idea around with me. I learned something new about accessibility. I wonder if I let that .closest() bubble up a little higher to look for the modal frame if that might do the trick.

Another solution is to listen for other modals being opened and suspend the method until the other modals are closed.

If you did that could you not still get trapped in the content behind the modal?

Back to the drawing board!

helgatheviking avatar Aug 05 '16 19:08 helgatheviking

If you did that could you not still get trapped in the content behind the modal?

Theoretically, no, because the modal opened by your custom modal would also trap keyboard interaction within itself and relinquish it when closed. Obviously, it depends on if the link modal traps focus within itself -- I know the WP media modal traps input focus once opened with a very similar method ( might be where this came from, can't remember).

Another solution would be to add the media modal class to your list of allowed focus targets.

// untested, blind code:
preserveFocus: function(e) {
    "use strict";
var $target = $(e.target);
   if (e.target !== document &&
         $target.closest(".mce-container").length === 0 &&
         $target.closest(".media-modal").length === 0 &&
          this.$el[0] !== e.target &&
          !this.$el.has(e.target).length ) {
        this.$el.focus();
    }
},

Sorta doubt this is a good direction, but it might be good enough. If you had the time to invest in it, It would probably be better to listen for any of the modals your modal could open and add them to an array of "allowed" parents. if the parent .has() the active element, allow focus.

aut0poietic avatar Aug 05 '16 20:08 aut0poietic

Doing more research, I've found that the default media modal doesn't have a preserveFocus style function that is listening on the focusin event. It does use something called the focusManager which restricts the ability to "tab" around only to the modal.

Here's the focusManager and here it is being used in the media modal.

I don't really know enough about accessibility, but hopefully it's "good enough" if I copy core. :) It should at least get the project done, though I'd love to go back and see if I can't create a modal from exiting WP media views/states.

Thanks again!

helgatheviking avatar Aug 06 '16 15:08 helgatheviking