Magento – Magento 2 error in [unknown object].fireEvent(): event name: tinymceSetContent error message: Cannot read property ‘serialize’ of undefined

editormagento2tinymce

We are using a magento 2.2.4 version and recently installed an extension pavelleonidov/magento2-tinymce4 to our site to upgrade the editor to tinymce4. Now we are facing an error message at the category page from admin panel.

error: error in [unknown object].fireEvent(): event name: tinymceSetContent error message: Cannot read property 'serialize' of undefined

I have however found some related issue with the different error message in github but I was not able to fix this error with their reference.

Error Picture
enter image description here

The error message are coming 6 times. We do have 4 more content editors from the theme. So whenever we click the menu tab this error message pops up. You can see the menu tab from the above picture.

File Browser button missing

I am getting this issue in category edit page only, for product edit page it is working fine.

enter image description here

Files used for debugging

  • /vendor/pavelleonidov/module-tinymce4/view/base/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js
  • /lib/web/mage/adminhtml/events.js

After some time of debugging i saw that the issue was in the fireEvent() whenever it was calling with the event name tinymceSetContent.
For all the tinymceSetContent events the argument lengths are greater than 1 and the async is false so it is going in the else condition and setting the value for the result variable
result = this.arrEvents[evtName][i].method(arguments[1]);
but if i try to print the variable or if check the typeof() i am getting undefined.

Events Js File

/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/* global varienEvents */
/* eslint-disable strict */
define([
    'Magento_Ui/js/modal/alert',
    'prototype'
], function (alert) {
// from http://www.someelement.com/2007/03/eventpublisher-custom-events-la-pubsub.html
window.varienEvents = Class.create();

varienEvents.prototype = {
    /**
     * Initialize.
     */
    initialize: function () {
        this.arrEvents = {};
        this.eventPrefix = '';
    },

    /**
    * Attaches a {handler} function to the publisher's {eventName} event for execution upon the event firing
    * @param {String} eventName
    * @param {Function} handler
    * @param {Boolean} [asynchFlag] - Defaults to false if omitted.
    * Indicates whether to execute {handler} asynchronously (true) or not (false).
    */
    attachEventHandler: function (eventName, handler) {
        var asynchVar, handlerObj;

        if (typeof handler == 'undefined' || handler == null) {
            return;
        }
        eventName += this.eventPrefix;
        // using an event cache array to track all handlers for proper cleanup
        if (this.arrEvents[eventName] == null) {
            this.arrEvents[eventName] = [];
        }
        //create a custom object containing the handler method and the asynch flag
        asynchVar = arguments.length > 2 ? arguments[2] : false;
        handlerObj = {
            method: handler,
            asynch: asynchVar
        };
        this.arrEvents[eventName].push(handlerObj);
    },

    /**
    * Removes a single handler from a specific event
    * @param {String} eventName - The event name to clear the handler from
    * @param {Function} handler - A reference to the handler function to un-register from the event
    */
    removeEventHandler: function (eventName, handler) {
        eventName += this.eventPrefix;

        if (this.arrEvents[eventName] != null) {
            this.arrEvents[eventName] = this.arrEvents[eventName].reject(function (obj) {
                return obj.method == handler; //eslint-disable-line eqeqeq
            });
        }
    },

    /**
    * Removes all handlers from a single event
    * @param {String} eventName - The event name to clear handlers from
    */
    clearEventHandlers: function (eventName) {
        eventName += this.eventPrefix;
        this.arrEvents[eventName] = null;
    },

    /**
    * Removes all handlers from ALL events
    */
    clearAllEventHandlers: function () {
        this.arrEvents = {};
    },

    /**
    * Fires the event {eventName}, resulting in all registered handlers to be executed.
    * It also collects and returns results of all non-asynchronous handlers
    * @param {String} eventName - The name of the event to fire
    * @param {Object} [args] - Any object, will be passed into the handler function as the only argument
    * @return {Array}
    */
    fireEvent: function (eventName) {
        var evtName = eventName + this.eventPrefix,
            results = [],
            result, len, i, eventArgs, method, eventHandler;

        if (this.arrEvents[evtName] != null) {
            len = this.arrEvents[evtName].length; //optimization

            for (i = 0; i < len; i++) {
                /* eslint-disable max-depth */
                try {
                    if (arguments.length > 1) {
                        if (this.arrEvents[evtName][i].asynch) {
                            eventArgs = arguments[1];
                            method = this.arrEvents[evtName][i].method.bind(this);
                            setTimeout(function () { //eslint-disable-line no-loop-func
                                method(eventArgs);
                            }, 10);
                        } else {
                            result = this.arrEvents[evtName][i].method(arguments[1]);
                        }
                    } else {
                        if (this.arrEvents[evtName][i].asynch) { //eslint-disable-line no-lonely-if
                            eventHandler = this.arrEvents[evtName][i].method;
                            setTimeout(eventHandler, 1);
                        } else if (
                            this.arrEvents &&
                            this.arrEvents[evtName] &&
                            this.arrEvents[evtName][i] &&
                            this.arrEvents[evtName][i].method
                        ) {
                            result = this.arrEvents[evtName][i].method();
                        }
                    }
                    results.push(result);
                }
                catch (e) {
                    if (this.id) {
                        alert({
                            content: 'error: error in ' + this.id + '.fireEvent():\n\nevent name: ' +
                            eventName + '\n\nerror message: ' + e.message
                        });
                    } else {
                        alert({
                            content: 'error: error in [unknown object].fireEvent():\n\nevent name: ' +
                            eventName + '\n\nerror message: ' + e.message
                        });
                    }
                }

                /* eslint-enable max-depth */
            }
        }

        return results;
    }
};

window.varienGlobalEvents = new varienEvents(); //jscs:ignore requireCapitalizedConstructors
});

Setup Js File

define([
  'jquery',
  'underscore',
  "tinymce",
  'mage/adminhtml/wysiwyg/tiny_mce/html5-schema',
  'mage/translate',
  'prototype',
  'mage/adminhtml/events',
  'mage/adminhtml/browser'
], function (jQuery, _, tinyMCE, html5Schema) {

tinyMceWysiwygSetup = Class.create();

tinyMceWysiwygSetup.prototype = {
    mediaBrowserOpener: null,
    mediaBrowserTargetElementId: null,

    initialize: function(htmlId, config) {
        if (config.baseStaticUrl && config.baseStaticDefaultUrl) {
            window.tinymce.baseURL = window.tinymce.baseURL.replace(config.baseStaticUrl, config.baseStaticDefaultUrl);
        } else {
            window.tinymce.baseURL = require.toUrl('PavelLeonidov_TinyMce4/lib/tinymce4');
        }


        this.id = htmlId;
        this.config = config;
        this.schema = config.schema || html5Schema;

        _.bindAll(this, 'beforeSetContent', 'saveContent', 'onChangeContent', 'openFileBrowser', 'updateTextArea');

        varienGlobalEvents.attachEventHandler('tinymceChange', this.onChangeContent);
        varienGlobalEvents.attachEventHandler('tinymceBeforeSetContent', this.beforeSetContent);
        varienGlobalEvents.attachEventHandler('tinymceSetContent', this.updateTextArea);
        varienGlobalEvents.attachEventHandler('tinymceSaveContent', this.saveContent);

        if (typeof tinyMceEditors == 'undefined') {
            tinyMceEditors = $H({});
        }

        tinyMceEditors.set(this.id, this);
    },

    setup: function(mode) {
        if (this.config['widget_plugin_src']) {
            window.tinymce.PluginManager.load('magentowidget', this.config['widget_plugin_src']);
        }

        if (this.config.plugins) {
            this.config.plugins.each(function(plugin) {
                window.tinymce.PluginManager.load(plugin.name, plugin.src);
            });
        }

        if (jQuery.isReady) {
            window.tinymce.dom.Event.domLoaded = true;
        }


        window.tinymce.init(this.getSettings(mode));
    },

    getSettings: function(mode) {
        //var plugins = 'inlinepopups safari pagebreak style layer table advhr advimage emotions iespell media searchreplace contextmenu paste directionality fullscreen noneditable visualchars nonbreaking xhtmlxtras textcolor image';


        var plugins = [
            'advlist autolink lists link image charmap print preview hr anchor pagebreak',
            'searchreplace wordcount visualblocks visualchars code fullscreen',
            'insertdatetime media nonbreaking save table contextmenu directionality',
            'emoticons template paste textcolor colorpicker textpattern imagetools autoresize'
        ];

        self = this;


        if (this.config['widget_plugin_src']) {
            plugins.push('magentowidget');
        }


        var magentoPluginsOptions = $H({});
        var magentoPlugins = '';

        if (this.config.plugins) {
            this.config.plugins.each(function(plugin) {
                magentoPlugins = plugin.name + ' ' + magentoPlugins;
                magentoPluginsOptions.set(plugin.name, plugin.options);
            });
            if (magentoPlugins) {
                plugins.push(magentoPlugins);
            }
        }


        var settings = {
            mode: (mode != undefined ? mode : 'none'),
            elements: this.id,
        //  theme_advanced_buttons1: magentoPlugins + 'magentowidget,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect',
        //  theme_advanced_buttons2: 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor',
        //  theme_advanced_buttons3: 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,iespell,media,advhr,|,ltr,rtl,|,fullscreen',
        //  theme_advanced_buttons4: 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,pagebreak',
            theme_advanced_toolbar_location: 'top',
            theme_advanced_toolbar_align: 'left',
            theme_advanced_statusbar_location: 'bottom',
            //extended_valid_elements : "div[*],meta[*],span[*],link[*],a[name|href|target|title|onclick],img[id|class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]",

            //selector: '#' + this.htmlId,
            schema: "html5",
            theme: 'modern',
            plugins: plugins,
            toolbar1: 'styleselect | bold italic | forecolor backcolor | undo redo | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media | fullscreen | magentowidget' + (magentoPlugins.length ? ' ' + magentoPlugins : ""),
            image_advtab: true,
            //valid_elements: "*[*]",
            codemirror: { indentOnInit: true },
            //valid_children: "+body[a|meta|link],+div[*],+a[div|h1|h2|h3|h4|h5|h6|p|#text]",
            valid_elements: this.schema.validElements.join(','),
            extended_valid_elements: this.schema.validElements.join(','),
            valid_children: this.schema.validChildren.join(','),
            skin_url: this.config.skin_url,

            content_css: [
                this.config.content_css
            ],

            theme_advanced_resizing: true,
            theme_advanced_resize_horizontal: false,
            convert_urls: false,
            relative_urls: false,
            custom_popup_css: this.config['popup_css'],
            magentowidget_url: this.config['widget_window_url'],
            magentoPluginsOptions: magentoPluginsOptions,
            doctype: '<!DOCTYPE html>',
            setup: function(ed){
                ed.on('init', function(e) {
                    self.onEditorInit.bind(self)
                });

                ed.on('submit', function(e) {
                    varienGlobalEvents.fireEvent('tinymceSubmit', e);
                });

                ed.on('paste', function(o) {
                    varienGlobalEvents.fireEvent('tinymcePaste', o);

                });

                ed.on('BeforeSetContent', function(o) {
                    varienGlobalEvents.fireEvent('tinymceBeforeSetContent', o);

                });

                ed.on('SetContent', function(o) {
                    varienGlobalEvents.fireEvent('tinymceSetContent', o);

                });

                ed.on('SaveContent', function(o) {
                    varienGlobalEvents.fireEvent('tinymceSaveContent', o);

                });


                var onChange = function(ed, l) {
                    varienGlobalEvents.fireEvent('tinymceChange', l);
                };

                ed.on('change', onChange);

                ed.on('KeyUp', onChange);

                ed.on('ExecCommand', function(cmd, ui, val) {
                    varienGlobalEvents.fireEvent('tinymceExecCommand', cmd);

                });

            }

        };



        // Set the document base URL
        if (this.config.document_base_url) {
            settings.document_base_url = this.config.document_base_url;
        }

        if (this.config.files_browser_window_url) {
            settings.file_browser_callback = function(fieldName, url, objectType, w) {
                varienGlobalEvents.fireEvent("open_browser_callback", {
                    win: w,
                    type: objectType,
                    field: fieldName
                });
            };
        }

        if (this.config.width) {
            settings.width = this.config.width;
        }

        if (this.config.height) {
            settings.height = this.config.height;
        }

        if (this.config.settings) {
            Object.extend(settings, this.config.settings)
        }

        return settings;
    },

    applySchema: function (editor) {
        var schema      = editor.schema,
            schemaData  = this.schema,
            makeMap     = window.tinymce.makeMap;

        jQuery.extend(true, {
            nonEmpty: schema.getNonEmptyElements(),
            boolAttrs: schema.getBoolAttrs(),
            whiteSpace: schema.getWhiteSpaceElements(),
            shortEnded: schema.getShortEndedElements(),
            selfClosing: schema.getSelfClosingElements(),
            blockElements: schema.getBlockElements()
        }, {
            nonEmpty: makeMap(schemaData.nonEmpty),
            boolAttrs: makeMap(schemaData.boolAttrs),
            whiteSpace: makeMap(schemaData.whiteSpace),
            shortEnded: makeMap(schemaData.shortEnded),
            selfClosing: makeMap(schemaData.selfClosing),
            blockElements: makeMap(schemaData.blockElements)
        });
    },

    openFileBrowser: function(o) {
        var typeTitle,
            storeId = this.config.store_id !== null ? this.config.store_id : 0,
            frameDialog = jQuery('.mce-container[role="dialog"]'),

            wUrl = this.config.files_browser_window_url +
                'target_element_id/' + this.id + '/' +
                'store/' + storeId + '/';


        this.mediaBrowserOpener = o.win;
        this.mediaBrowserTargetElementId = o.field;

        if (typeof(o.type) != 'undefined' && o.type != "") {
            typeTitle = 'image' == o.type ? this.translate('Insert Image...') : this.translate('Insert Media...');
            wUrl = wUrl + "type/" + o.type + "/";
        } else {
            typeTitle = this.translate('Insert File...');
        }

        frameDialog.hide();
        jQuery('#mce-modal-block').hide();

        MediabrowserUtility.openDialog(wUrl, false, false, typeTitle, {
            closed: function() {
                frameDialog.show();
                jQuery('#mce-modal-block').show();
            }
        });
    },

    translate: function(string) {
        return jQuery.mage.__ ? jQuery.mage.__(string) : string;
    },

    getMediaBrowserOpener: function() {
        return this.mediaBrowserOpener;
    },

    getMediaBrowserTargetElementId: function() {
        return this.mediaBrowserTargetElementId;
    },

    getToggleButton: function() {
        return $('toggle' + this.id);
    },

    getPluginButtons: function() {
        return $$('#buttons' + this.id + ' > button.plugin');
    },

    turnOn: function(mode) {
        this.closePopups();

        this.setup(mode);

        window.tinymce.execCommand('mceAddEditor', false, this.id);

        this.getPluginButtons().each(function(e) {
            e.hide();
        });

        return this;
    },

    turnOff: function() {
        this.closePopups();

        window.tinymce.execCommand('mceRemoveEditor', false, this.id);

        this.getPluginButtons().each(function(e) {
            e.show();
        });

        return this;
    },

    closePopups: function() {
        if (typeof closeEditorPopup == 'function') {
            // close all popups to avoid problems with updating parent content area
            closeEditorPopup('widget_window' + this.id);
            closeEditorPopup('browser_window' + this.id);
        }
    },

    toggle: function() {
        if (!window.tinymce.get(this.id)) {
            this.turnOn();
            return true;
        } else {
            this.turnOff();
            return false;
        }
    },

    onEditorInit: function (editor) {
        this.applySchema(editor);
    },

    onFormValidation: function() {
        if (window.tinymce.get(this.id)) {
            $(this.id).value = window.tinymce.get(this.id).getContent();
        }
    },

    onChangeContent: function() {
        // Add "changed" to tab class if it exists
        this.updateTextArea();

        if (this.config.tab_id) {
            var tab = $$('a[id$=' + this.config.tab_id + ']')[0];
            if ($(tab) != undefined && $(tab).hasClassName('tab-item-link')) {
                $(tab).addClassName('changed');
            }
        }
    },

    // retrieve directives URL with substituted directive value
    makeDirectiveUrl: function(directive) {
        return this.config.directives_url.replace('directive', 'directive/___directive/' + directive);
    },

    encodeDirectives: function(content) {
        // collect all HTML tags with attributes that contain directives
        return content.gsub(/<([a-z0-9\-\_]+.+?)([a-z0-9\-\_]+=".*?\{\{.+?\}\}.*?".+?)>/i, function(match) {
            var attributesString = match[2];
            // process tag attributes string
            attributesString = attributesString.gsub(/([a-z0-9\-\_]+)="(.*?)(\{\{.+?\}\})(.*?)"/i, function(m) {
                return m[1] + '="' + m[2] + this.makeDirectiveUrl(Base64.mageEncode(m[3])) + m[4] + '"';
            }.bind(this));

            return '<' + match[1] + attributesString + '>';

        }.bind(this));
    },

    encodeWidgets: function (content) {
        return content.gsub(/\{\{widget(.*?)\}\}/i, function (match) {
            var attributes = this.parseAttributesString(match[1]),
                imageSrc, imageHtml;

            if (attributes.type) {
                attributes.type = attributes.type.replace(/\\\\/g, '\\');
                imageSrc = this.config['widget_placeholders'][attributes.type];
                imageHtml = '<img';
                imageHtml += ' id="' + Base64.idEncode(match[0]) + '"';
                imageHtml += ' src="' + imageSrc + '"';
                imageHtml += ' title="' +
                    match[0].replace(/\{\{/g, '{').replace(/\}\}/g, '}').replace(/\"/g, '&quot;') + '"';
                imageHtml += '>';
                return imageHtml;
            }
        }.bind(this));
    },

    decodeDirectives: function(content) {
        // escape special chars in directives url to use it in regular expression
        var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1');
        var reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)'));

        return content.gsub(reg, function(match) {
            return Base64.mageDecode(match[1]);
        }.bind(this));
    },

    /**
     * @param {Object} content
     * @return {*}
     */
    decodeWidgets: function (content) {
        return content.gsub(/<img([^>]+id=\"[^>]+)>/i, function (match) {
            var attributes = this.parseAttributesString(match[1]),
                widgetCode;

            if (attributes.id) {
                widgetCode = Base64.idDecode(attributes.id);

                if (widgetCode.indexOf('{{widget') !== -1) {
                    return widgetCode;
                }

                return match[0];
            }

            return match[0];
        }.bind(this));
    },

    parseAttributesString: function(attributes) {
        var result = {};

        attributes.gsub(
            /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/,
            function (match) {
                result[match[1]] = match[2];
            }
        );

        return result;
    },

    updateTextArea: function () {
        var editor = window.tinymce.get(this.id),
            content;

        if (!editor) {
            return;
        }

        content = editor.getContent();
        content = this.decodeContent(content);

        jQuery('#' + this.id).val(content).trigger('change');
    },

    decodeContent: function (content) {
        var result = content;
       // console.log("Decode: " + result);
        if (this.config.add_widgets) {
            result = this.decodeWidgets(result);
            result = this.decodeDirectives(result);
        } else if (this.config["add_directives"]) {
            result = this.decodeDirectives(result);
        }

        return result;
    },

    encodeContent: function (content) {
        var result = content;

        if (this.config["add_widgets"]) {
            result = this.encodeWidgets(result);
            result = this.encodeDirectives(result);
        } else if (this.config["add_directives"]) {
            result = this.encodeDirectives(result);
        }

        return result;
    },

    beforeSetContent: function(o){
        o.content = this.encodeContent(o.content);
    },

    saveContent: function(o) {
        o.content = this.decodeContent(o.content);
    }
};
});

Please help !

Best Answer

One of the possible cause of this issue, Sometimes when you create a piece of content and then save and reload it from the database you will see some random characters like  or ? or % are added to your content. When you see unusual characters like these in your content this is likely a character encoding issue.

One of the Possible solution which I know

Go to path: lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js

Comment out this line: content = decodeURIComponent(content);

I hope this will help, not sure whether it works for you or not but you should try this

Related Topic