Initialize Redactor Plugins

r1pe

Member
Hello,

some minutes ago, I updated XenForo to Version 1.2 Beta. Now I want to adjust the Redactor Editor. I read the official Redactor documentation to create some additional BBCodes but I have a question regarding the implementation.

The development of plugins is well explained in the documentation but the integration to XenForo is a little bit tricky.

The documentation recommends to initialize plugins this way:

Code:
$('#redactor').redactor({
    plugins: ['advanced']
});

Now to the question:

The example above is a possibility to implement a plugin while the initialization process. But XenForo initialize the Redactor editor itself so I can't re-initialize the editor. Is there a way to manipulate the XenForo editor initialization?

With tinyMCE it was possible with something like this:

Code:
$contents = str_replace("var plugins = '", "var plugins = 'spoiler,", $contents);
$contents .= ",theme_xenforo_buttons2 : 'bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,|,bullist,numlist,outdent,indent,|,link,unlink,image,xenforo_media,xenforo_custom_bbcode,|,xenforo_code,|,spoiler'";
 
If I don't make a mistake the plugins are automatically loaded (I was mistaken) (check the fullscreen plugin on this page, I remember there is an useful example inside). Then for the button configuration, if you just want to add a button without changing the default layout, there are a few function in the API (add before this button, after this button, etc.).
 
Last edited:
Yes, I can configure the buttons with buttonAddAfter, buttonAddBefore ... I can do this in the 'init'-Function ... for example:

Code:
RedactorPlugins.spoiler = {
   
    init: function()
    {
        this.buttonAddAfter('link', 'advanced', 'Advanced', function()
        {
            this.insertHtml('<b>It\'s awesome!</b> ');
        });

        this.buttonAddSeparatorBefore('advanced');
    }
}

But after this I have to load the spoiler plugin. The examples on the website do this with:

Code:
$('#redactor').redactor({
    plugins: ['advanced']
});

But XenForo initialize the redactor itself, so I can't call the function $('#redactor').redactor again to initialize my own plugins. So I have to change the initialization process of XenForo. Where calls XenForo $('#redactor').redactor to initialize the redactor editor?
 
I don't think it should be a major problem. Try this (untested):
In the editor_js_setup template:
Code:
$textarea = $("#{xen:jsescape $editorId}_html");
var redactorObj = $textarea.data('redactor');
//use the jquery $.extend function to extend the above object  then do this:
$textarea.redactor(redactorObj);
 
@cclaerhout,

My JS customizations will have to be executed before the XenForo framework is launched. How I can use functions of the XenForo Framework. I need to open some Modals, need the wrapSelectionInHtml function and something more. I could copy all the functions to my customization but then I have all the code twice and I think that is not good practice. Can you give me some hint?

Tank You!
 
@cclaerhout,

My JS customizations will have to be executed before the XenForo framework is launched. How I can use functions of the XenForo Framework. I need to open some Modals, need the wrapSelectionInHtml function and something more. I could copy all the functions to my customization but then I have all the code twice and I think that is not good practice. Can you give me some hint?

Tank You!

That was what I wanted to do at the beginning but after spending quite some time on it, I think there is no solution. The framework has not any static functions. It's all dynamic and once loaded with "new XenForo.BbmCustomEditor($ed)" it will systematically load redactor at the the end.

But one question, do you succeed to use any of the functions of Redactor? I don't know why but with this integration they don't work.
 
Last edited:
You mean the functions of the readactor api? I don't tried it yet. I want to do something like this:

Code:
createButtonsCustom: function()
{
    return {
        spoiler: {
            title: this.getText('spoiler'),
            callback: function(ed)
            {
                self.wrapSelectionInHtml(ed, '[SPOILER]', '[/SPOILER]', true);
            }
        }
    },

until I noticed that a click on the button don't fire the callback function. Even in this simple function I need two functions (getText, wrapSelectionInHtml) of the XenForo framework, so I hoped there is a way to call the XenForo framework functions. Otherwise I have to copy the XenForo functions but then I have all the functions twice or I extend the core file itself but this is not really the best way to develop some addons. Now I could try to work with the redactor api:

Code:
if (typeof RedactorPlugins === 'undefined') var RedactorPlugins = {};

RedactorPlugins.advanced = {
    init: function()
    {
        this.buttonAddAfter('link', 'advanced', 'Advanced', function()
        {
            this.insertHtml('<b>It\'s awesome!</b> ');
        });
        this.buttonAddSeparatorBefore('advanced');
    }
}

and initialize the plugins with your solution but I think I will get the same problem and can't use the XenForo framework functions in my plugins. So what I can do, change the core file "bb_code_edit.js" but this is a dirty solution. What do you think about all of this?
 
For your first question, if you only want to use the BbCode insertion function, there's a gate. Check this ressource. That what I use in this addon.
For the api, I've tried inside a plugin. I've tried the direct method:
Code:
$('#myEditorID').redactor('cmd');
It doesn't work.

I've tried to get the editor like this
Code:
var redactor = $('#myEditorID').data('redactor');
redactor->cmd();
It doesn't work.
 
@cclaerhout

I have found a solution for our problem. It's a bit more complicated so I will explain step by step.

First of all I will show you the first five lines of the "editor_js_setup" template:

Code:
<xen:require js="js/redactor/redactor.js" />
<xen:require js="js/addons/overwrite_redactor_options.js" />
<xen:require js="js/xenforo/bb_code_edit.js" />
<xen:require js="js/addons/xenforo_framework_extension.js" />
<xen:require css="editor_ui.css" />

You can see, I have added two new JS files. The first one change the button configuration based on your demo. Here the complete content of "overwrite_redactor_options.js:

Code:
!function($, window, document, _undefined)
{
    XenForo.AdditionalBbCodes= function($textarea) { this.__construct($textarea); };
    XenForo.AdditionalBbCodes.prototype =
    {
        __construct: function($textarea)
        {
            var redactorOptions = $textarea.data('options'),
                customButtons = this.createCustomButtons(),
                customOptions = {
                    editorOptions: {
                        buttons:[
                            ['switchmode'],
                            ['removeformat'],
                            ['bold', 'italic', 'underline', 'deleted'],
                            ['fontcolor', 'fontsize', 'fontfamily'],
                            ['createlink', 'unlink'],
                            ['alignment'],
                            ['unorderedlist', 'orderedlist', 'outdent', 'indent'],
                            ['smilies', 'image', 'media'],
                            ['code', 'quote'],
                            ['rating'], ['draft'],
                            ['undo', 'redo']
                        ]
                    },
                    buttons: customButtons
                };
         
            $textarea.data('options', $.extend(redactorOptions, customOptions));
        },
     
        createCustomButtons: function()
        {
            return {
                rating: {
                    title: 'rating',
                    callback: 'getRatingModal'
                }
            }
        }
    }
 
    XenForo.register('textarea.BbCodeWysiwygEditor', 'XenForo.AdditionalBbCodes');
}
(jQuery, this, document);

This adds an additional button for a rating tag. Goal was to open a modal window if someone click on the button. I dont't post the CSS for positioning the button and the HTML of the modal window because that should be easy stuff for you. Now to the second JS file. I post the complete content first and then I will explain. So, let us begin with the content of "xenforo_framework_extension.js":

Code:
!function($, window, document, _undefined)
{
    XenForo.BbCodeWysiwygEditor.prototype.getRatingModal = function(ed)
    {
        var self = this;
         
        ed.saveSelection();
        ed.modalInit(this.getText('rating'), { url: this.dialogUrl + '&dialog=rating' }, 600, $.proxy(function()
        {
            $('#redactor_insert_rating_btn').click(function(e) {
                e.preventDefault();
                self.insertRating(e, ed);
            });

        }, ed));
    }
 
    XenForo.BbCodeWysiwygEditor.prototype.insertRating = function(e, ed)
    {
        var grade, spelling, output;
     
        grade= $('#redactor_grade_rating').val();
        spelling= $('#redactor_spelling_rating').val();
     
        output = '[RATING]' + grade + '/' + spelling + '[/RATING]';
     
        ed.restoreSelection();
        ed.execCommand('inserthtml', output);
        ed.modalClose();
    }
    XenForo.BbCodeWysiwygEditor.prototype._adjustButtonConfig = function(config, extraButtons)
    {
        var self = this,
            extra = [];
        for (var i in extraButtons)
        {
            if (!extraButtons.hasOwnProperty(i))
            {
                continue;
            }
            (function(i) {
                var button = extraButtons[i];
                config.buttonsCustom[i] = {
                    title: self.getText(i, button.title),
                    callback: function(ed)
                    {
                        if (button.exec)
                        {
                            ed.execCommand(button.exec);
                        }
                        else if (button.tag)
                        {
                            var tag = button.tag;
                            self.wrapSelectionInHtml(ed, '[' + tag + ']', '[/' + tag + ']', true);
                        }
                        else if (button.callback)
                        {
                            eval("self."+button.callback+"(ed)");
                        }
                    }
                };
                extra.push(i);
            })(i);
        }
        if (extra.length)
        {
            config.buttons.push(extra);
        }
        return config;
    }
}(jQuery, this, document);

The first two functions are more or less copied from "js/xenforo/full/bb_code_edit.js". The first one opens a modal window and the second one puts the content from the modal window into a valid bbcode. The third function does the real magic. The function overwrite the _adjustButtonConfig from "js/xenforo/full/bb_code_edit.js" and adds a small thing. The

Code:
else if (button.callback)
{
    eval("self."+button.callback+"(ed)");
}

is the only part of the function that have changed. This small modification gives you the possibilty to set a "callback" at your button configuration. And all that stuff is working without a change of the core file :)

I hope you understand my short tutorial, if not, let me know.

If you have some improvements for the given code, let me also know :)
 
@r1pe the problem is that you override the function and not extend it or use it, which means it only works once. And for the last part, I've only learnt Javascript with tutorials and reading codes. And during this time I've read many times "never use eval()", so I won't dare to use it ^^.
 
I think to overwrite the function is an acceptable way to solve this problem, I think it's better than edit the core code and I didn't find another solution. I never used "eval()" before but you are right, searching for "eval()" gives no postitive responses. The usage of "eval()" was the first I have tried and it worked directly but I will find another solution for this and let you know what I'll use instead. Thank you for the hint with "eval()".

EDIT: Okay, I will check the AddOn tomorrow, thanks for that.
 
Last edited:
Personally I'd go for something more like:

Code:
else if (typeof button.callback == 'function')
{
    button.callback(ed);
}
 
Hello! I want to add a button to the editor. I need that return text media=hide post=3 /media by pressing the button. How to do? Here's a code does not work ...
Code:
!function($, window, document, _undefined)
{
    XenForo.customEditorForMyAddon = function($textarea) { this.__construct($textarea); };

    XenForo.customEditorForMyAddon.prototype =
    {
        __construct: function($textarea)
        {
            var redactorOptions = $textarea.data('options'),
            myButtons = this.createCustomButtons(),
            myOptions = {
                editorOptions:{
                    plugins: ['post'],
                    buttons:[
                             ['switchmode'],
                             ['removeformat'],
                        ['bold', 'italic', 'underline', 'deleted'],
                        ['fontcolor', 'fontsize', 'fontfamily'],
                        ['createlink', 'unlink'],
                        ['alignment'],
                        ['unorderedlist', 'orderedlist', 'outdent', 'indent'],
                        ['smilies', 'image', 'media'],
                        ['code', 'quote'],
                        ['draft'],
                        ['post'],
                        ['undo', 'redo']
                        ]
                },
                buttons: myButtons
            };

            if(typeof RedactorPlugins == 'undefined')
                RedactorPlugins = {};

            $textarea.data('options', $.extend(redactorOptions, myOptions));
        },
        createCustomButtons: function()
        {
            //Some functions should be static, ie: wrapSelectionInHtml
            //wrapSelectionInHtml should have an option to allow to set BbCode Options or Content
            
            return {
                post: {
                    title: 'description tag.',
                    callback: function(ed)
                           {
                           self.wrapSelectionInHtml(ed, '[media=hide post=]', '[/media]', true);
                           }
                
                }
            }
        }
    }

    XenForo.register('textarea.BbCodeWysiwygEditor', 'XenForo.customEditorForMyAddon');

}(jQuery, this, document);
 
I did something similar today. Just a heads up to someone who wants to do the same, don't use eval, it's unnecessary here.

Use instead
Code:
self[button.callback](ed);
 
Top Bottom