NickGreen
NickGreen

Reputation: 1752

CKEditor: multiple widget templates

I'm currently creating a 'smartobject' widget. In the widgets dialog, the user can choose a 'smartobject', which simply put, generates some html, which should be added to the editor. Here comes the tricky part: the html sometimes div elements and sometimes simply span elements. In the case of the div variant, the widget should be wrapped in a div 'template'. In the case of a span variant, the widget should be wrapped in a span and the html should be added 'inline'.

In the widgets API I see the following way to define a template:

editor.widgets.add('smartobject', {
                dialog: 'smartobject',
                pathName: lang.pathName,
                template: '<div class="cke_smartobject"></div>', // <------

                upcast: function(element) {
                    return element.hasClass('smartObject');
                },

                init: function() {
                    this.setData('editorHtml', this.element.getOuterHtml());
                },

                data: function() {
                    var editorHtml = this.data.editorHtml;

                    var newElement = new CKEDITOR.dom.element.createFromHtml(editorHtml);

                    newElement.copyAttributes(this.element);

                    this.element.setText(newElement.getText());
                }
            });

But in my case, the template is more dynamic: sometimes a div and sometimes the span will do the correct thing..

How can I fix this without needing to create two widgets which will do the exact same thing, with only the wrapping element as difference?

I've already tried to replace the entire element in the 'data' method, like:

newElement.replace(this.element);
this.element = newElement;

But this seemed not supported: resulted in undefined errors after calling editor.getData().

I'm using ckeditor v4.5.9

Thanks for your help!

Upvotes: 3

Views: 1525

Answers (2)

NickGreen
NickGreen

Reputation: 1752

It seems I got it working (with a workaround). The code:

CKEDITOR.dialog.add('smartobject', this.path + 'dialogs/smartobject.js');

        editor.widgets.add('smartobject', {
            pathName: lang.pathName,

            // This template is needed, to activate the widget logic, but does nothing.
            // The entire widgets html is defined and created in the dialog.
            template: '<div class="cke_smartobject"></div>',

            init: function() {
                var widget = this;
                widget.on('doubleclick', function(evt) {
                    editor.execCommand('smartobject');
                }, null, null, 5);
            },

            upcast: function(element) {
                return element.hasClass('smartObject');
            }
        });

        // Add a custom command, instead of using the default widget command,
        // otherwise multiple smartobject variants (div / span / img) are not supported. 
        editor.addCommand('smartobject', new CKEDITOR.dialogCommand('smartobject'));

        editor.ui.addButton && editor.ui.addButton('CreateSmartobject', {
            label: lang.toolbar,
            command: 'smartobject',
            toolbar: 'insert,5',
            icon: 'smartobject'
        });

And in the dialog, to insert code looks like:

return {
    title: lang.title,
    minWidth: 300,
    minHeight: 80,

    onOk: function() {
        var element = CKEDITOR.dom.element.createFromHtml(smartobjectEditorHtml);

        editor.insertElement(element);

        // Trigge the setData method, so the widget html is transformed,
        // to an actual widget!
        editor.setData(editor.getData());
    },
...etc.

UPDATE I made the 'onOk' method a little bit better: the smartobject element is now selected after the insertion.

onOk: function() {
        var element = CKEDITOR.dom.element.createFromHtml(smartobjectEditorHtml);
        var elementId = "ckeditor-element-" + element.getUniqueId();

        element.setAttribute("id", elementId);

        editor.insertElement(element);

        // Trigger the setData method, so the widget html is transformed,
        // to an actual widget!
        editor.setData(editor.getData());

        // Get the element 'fresh' by it's ID, because the setData method,
        // makes the element change into a widget, and thats the element which should be selected,
        // after adding.
        var refreshedElement = CKEDITOR.document.getById(elementId);
        var widgetWrapperElement = CKEDITOR.document.getById(elementId).getParent();

        // Better safe then sorry: if the fresh element doesn't have a parent, simply select the element itself.
        var elementToSelect = widgetWrapperElement != null ? widgetWrapperElement : refreshedElement;

        // Normally the 'insertElement' makes sure the inserted element is selected,
        // but because we call the setData method (to ensure the element is transformed to a widget)
        // the selection is cleared and the cursor points to the start of the editor.
        editor.getSelection().selectElement(elementToSelect);
    },

So in short, I partially used the widget API for the parts I wanted: - Make the html of the widget not editable - Make it moveable

But I created a custom dialog command, which simply bypasses the default widget insertion, so I can entirely decide my own html structure for the widget.

All seems to work like this.

Any suggestions, to make it better are appreciated:)!

Upvotes: 4

Atzmon
Atzmon

Reputation: 1318

As suggested in this ckeditor forum thread, the best approach would be to set the template to include all possible content elements. Then, in the data function, remove the unnecessary parts according to your specific logic.

Upvotes: 0

Related Questions