XF 2.0 Editor init error

Lukas W.

Well-known member
I've noticed an error with my editor extension not being able to initalize the editor under a certain circumstance as described below.

This is the relevant part of the script:
JavaScript:
    XF.Editor.prototype.getEditorConfigEMOriginal = XF.Editor.prototype.getEditorConfig;

    XF.Editor.prototype.getEditorConfig = function () {
        var config = this.getEditorConfigEMOriginal(),
            newConfig,
            fonts,
            fontFamily = {},
            bbc = [],
            pos,
            removedButtons;

        config.xfInsertOptions = [
            'xfMedia',
            'xfQuote',
            'xfSpoiler',
            'xfKLEMiSpoiler',
            'xfCode',
            'xfInlineCode'
        ];

        removedButtons = this.options.buttonsRemove.split(',');

        /* Load config overwrites */
        try {
            newConfig = $.parseJSON($('.js-klEditorConfig').first().html()) || {};
        } catch (e) {
            console.error(e);
            newConfig = {
                font_sizes: false,
                enabled_bbcodes: false,
                toolbar_layout: false
            };
        }
        config.pluginsEnabled = ['draggable', 'file', 'bbCode'];

        /* Override Button List */
        if(newConfig.toolbar_layout) {
            config.toolbarButtons = newConfig.toolbar_layout.toolbarButtons;
            config.toolbarButtonsMD = newConfig.toolbar_layout.toolbarButtonsMD;
            config.toolbarButtonsSM = newConfig.toolbar_layout.toolbarButtonsSM;
            config.toolbarButtonsXS = newConfig.toolbar_layout.toolbarButtonsXS;
        }

        /* Remove Smilie Button */
        if(newConfig.disable_smilies) {
            /* Remove from toolbar */
            pos = config.toolbarButtons.indexOf('xfSmilie');
            if (pos > -1) {config.toolbarButtons.splice(pos, 1);}
        }

        var buttonClass = {
            _basic: ['bold', 'italic', 'underline', 'strikeThrough'],
            _extended: ['color', 'fontFamily', 'fontSize'],
            _link: ['insertLink', 'xfLink'],
            _align: ['align', 'xfAlign'],
            _list: ['formatOL', 'formatUL', 'outdent', 'indent', 'xfList'],
            _indent: ['outdent', 'indent'],
            _smilies: ['xfSmilie'],
            _image: ['insertImage', 'xfImage'],
            _media: ['xfMedia'],
            _block: ['xfQuote', 'xfCode', 'xfSpoiler']
        };

        for(var index in buttonClass) {
            if($.inArray(index, removedButtons) >= 0) {
                for(var index2 in buttonClass[index]) {
                    removedButtons.push(buttonClass[index][index2]);
                }
                removedButtons = removedButtons.filter(function(item) {
                    return item !== index;
                });
            }
        }

        console.log(removedButtons);

        $.each(newConfig.enabled_bbcodes, function(index, enabled) {
            if(enabled && $.inArray(index, removedButtons) >= 0) {
                newConfig.enabled_bbcodes[index] = 0;
            }
        });

        /* Remove disabled bbcodes */
        if(newConfig.enabled_bbcodes) {
            /* CLEAR EDITOR TOOLBAR */
            /* B, I, U, S*/
            bbc.push(['bold', newConfig.enabled_bbcodes.bold]);
            bbc.push(['italic', newConfig.enabled_bbcodes.italic]);
            bbc.push(['underline', newConfig.enabled_bbcodes.underline]);
            bbc.push(['strikeThrough', newConfig.enabled_bbcodes.strike]);

            /* COLOR, FONT, SIZE */
            bbc.push(['color', newConfig.enabled_bbcodes.color || newConfig.enabled_bbcodes.bgcolor]);
            if(newConfig.enabled_bbcodes.color || newConfig.enabled_bbcodes.bgcolor) {
                config.pluginsEnabled.push('colors');
            }
            bbc.push(['fontFamily', newConfig.enabled_bbcodes.font]);
            if(newConfig.enabled_bbcodes.font) {
                /* Apply Font Families */
                try {
                    fonts = $.parseJSON($('.js-klEditorFonts').first().html()) || {};
                } catch (e) {
                    console.error(e);
                    fonts = {};
                }
                fonts.forEach(function (font) {
                    fontFamily[font.family.replace(/"/g, "'")] = font.title;
                });
                config.fontFamily = fontFamily;
                config.pluginsEnabled.push('fontFamily');
            }
            bbc.push(['fontSize', newConfig.enabled_bbcodes.size]);
            if(newConfig.enabled_bbcodes.size) {
                config.fontSize = newConfig.font_sizes || config.fontSize;
                config.pluginsEnabled.push('fontSize');
            }

            /* IMG, URL */
            bbc.push(['insertLink', newConfig.enabled_bbcodes.url]);
            if(newConfig.enabled_bbcodes.url) {
                config.pluginsEnabled.push('link');
            }
            bbc.push(['insertImage', newConfig.enabled_bbcodes.img]);
            if(newConfig.enabled_bbcodes.img) {
                config.pluginsEnabled.push('image');
            }

            /* LEFT, CENTER, RIGHT, UL, OL, INDENT */
            bbc.push(['align', newConfig.enabled_bbcodes.align]);
            if(newConfig.enabled_bbcodes.align) {
                config.pluginsEnabled.push('align');
            }
            bbc.push(['xfList', newConfig.enabled_bbcodes.list]);
            if(newConfig.enabled_bbcodes.list) {
                config.pluginsEnabled.push('lists');
            }

            /* TABLE, TR, TH, TD */
            if(newConfig.enabled_bbcodes.table) {
                config.pluginsEnabled.push('table');
                config.tableEditButtons = ['tableHeader', 'tableRemove', '|', 'tableRows', 'tableColumns', 'tableCellVerticalAlign', 'tableCellHorizontalAlign'];
            }
            bbc.forEach(function (bbcode) {
                if(!bbcode[1]) {
                    /* Remove from toolbar */
                    pos = config.toolbarButtons.indexOf(bbcode[0]);
                    if (pos > -1) {config.toolbarButtons.splice(pos, 1);}
                }
            });

            /* CLEAR [...] DROPDOWN */
            bbc = [];
            /* MEDIA, QUOTE, SPOILER, CODE, ICODE */
            bbc.push(['xfMedia', newConfig.enabled_bbcodes.media]);
            bbc.push(['xfQuote', newConfig.enabled_bbcodes.quote]);
            bbc.push(['xfSpoiler', newConfig.enabled_bbcodes.spoiler]);
            bbc.push(['xfKLEMiSpoiler', newConfig.enabled_bbcodes.ispoiler]);
            bbc.push(['xfCode', newConfig.enabled_bbcodes.code]);
            bbc.push(['xfInlineCode', newConfig.enabled_bbcodes.icode]);

            bbc.forEach(function(bbcode) {
                if(!bbcode[1]) {
                    pos = config.xfInsertOptions.indexOf(bbcode[0]);
                    if (pos > -1) {config.xfInsertOptions.splice(pos, 1);}
                }
            });
           
           
            if(config.xfInsertOptions.length === 0) {
                /* Remove from toolbar */
                pos = config.toolbarButtons.indexOf('xfInsert');
                if (pos > -1) {config.toolbarButtons.splice(pos, 1);}
            }
        }
        return config;
    };

This is the initalization of the editor:
HTML:
<xf:editor name="message"
                        value="{$message}"
                        data-min-height="100"
                        maxlength="{$xf.options.messageMaxLength}"
                        placeholder="{{ phrase('reply_placeholder') }}"
                        removebuttons="{{ ['_list'] }}"
                        data-xf-key="{{ phrase('shortcut.quick_reply') }}"/>

The thing now is, as soon as I add '_list' to the removebuttons, the editor toolbar goes missing and the editor design breaks.
List seems the only thing that triggers this behaviour, I can use _basic, _align, etc. without any problems.


Ftxrsvd.png

If I now replace the very first line of the script
JavaScript:
var config = this.getEditorConfigEMOriginal()
with simply
JavaScript:
var config = [],
It starts working again. If I just remove the extension all together, it also works. I can't really understand what's happening there, and the browser console doesn't log any error as well. Any ideas what could be wrong?
 
This is a really weird one. I've noticed that xfListOptions inside the config element that I get returned from the default config script, there is a single empty string. If I replace it with an original value like formatUL, the editor starts working again. If I replace it with something random like a, it will still not work. If I just empty it, it will also make the editor work again. I can't really figure out why this faulty behaviour only pops up in conjunction with my code though... I'm not touching that part of the config anywhere.
 
So a few things:
  1. It's a bug that an empty list options are returned as [""]. It should be []. I've fixed that for the next release.
  2. However if you aren't putting anything in the list menu, you shouldn't output the xfList button.
  3. Rather than monkey patching like this, you can use XF.Element.extend(). This is a wrapper around XF.extend that can handle doing extensions for things that might not be loaded yet, so you pass an identifier (like "XF.Editor") in first and then the extended parts in as the second argument. You can see some uses of XF.extend in tooltips and editor dialogs. (Though bear in mind that the XF.Element.extend version is a little different in that it's not returning an extended object as the extension may not happen that instant.)
  4. In alternative to extension (which might be a little bit last resort), look at using either the editor:config or editor:buttons events. The former allows more customization overall while the latter will piggyback off the default functionality.
 
Last edited:
Thanks for the insight and tips. I believe I didn't output the xfList button, at least it doesn't appear in the editor now that I've emptied the list manually.
I always wanted and intended to use the xf.extend methods but failed at teaching myself how to do it just by the given code base. I think/hope your tips will bring the last few pieces I need to puzzle this eternal mystery out now. :D(y)
 
@Mike, seems like I still can't figure it out. Would you be so kind to provide a basic example on how to react to the editor:config event?

My latest try looks like this, but as kinda expected, it does simply nothing:
JavaScript:
(function ($, window, document, _undefined) {
    "use strict";
    
    $.on('editor:config', function(data) {
        console.log(data);
    });
}(jQuery, window, document));
 
JavaScript:
    $(document).on('editor:config', function(e, config, xfEditor)
    {
        console.log('Textarea being converted:', xfEditor.$target);
        config.editorClass = 'testTest';
    });

This is using event bubbling to listen to any event that we triggered. The config is the second argument, the instance of the XF.Editor object is the third. Since the config is an object, you can change its properties as if it's passed by reference. You'll see that the "testTest" class gets applied to the fr-box element with this code.
 
Top Bottom