AEM CQ 56 - Extend RichText Editor, add new Plugin PullQuote

Goal


Add a new plugin to RichText Editor. Here we add the Pull Quote plugin for inserting text that can be formatted/styled during component rendering. Source code, Package Install and Demo Video are available for download. Please leave a comment if you find bugs.




Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/rte-pull-quote

2) Create clientlib (type cq:ClientLibraryFolder/apps/rte-pull-quote/clientlib and set a property categories of String type to cq.widgets

3) Create file ( type nt:file ) /apps/rte-pull-quote/clientlib/js.txt, add the following

                         js/rtepullquote.js.js

4) Create folder (nt:folder) /apps/rte-pull-quote/clientlib/js

5) Create file (type nt:file) /apps/rte-pull-quote/clientlib/js/rtepullquote.js, add the following code

CQ.Ext.ns("MyClientLib");

MyClientLib.PullQuote = {
    ADD_QUOTE_CMD : "addquote",
    REMOVE_QUOTE_CMD : "removequote",
    DEFAULT_PATTERN: "[pullquote:(align=<align>,text=<text>)]"
};

MyClientLib.PullQuote.Plugin = new Class({
    toString: "PullQuotePlugin",
    extend: CUI.rte.plugins.Plugin,
    P: MyClientLib.PullQuote,

    addQuoteUI: null,
    removeQuoteUI: null,

    getFeatures: function() {
        return [ this.P.ADD_QUOTE_CMD, this.P.REMOVE_QUOTE_CMD ];
    },

    initializeUI: function(tbGenerator) {
        var plg = CUI.rte.plugins;

        if (this.isFeatureEnabled(this.P.ADD_QUOTE_CMD)) {
            this.addQuoteUI = tbGenerator.createElement(this.P.ADD_QUOTE_CMD, this, true, "Add/Modify Pull Quote");
            tbGenerator.addElement("format", plg.Plugin.SORT_FORMAT,this.addQuoteUI,1000);
        }

        if (this.isFeatureEnabled(this.P.REMOVE_QUOTE_CMD)) {
            this.removeQuoteUI = tbGenerator.createElement(this.P.REMOVE_QUOTE_CMD, this, true,"Remove Pull Quote");
            tbGenerator.addElement("format", plg.Plugin.SORT_FORMAT,this.removeQuoteUI,1001);
        }
    },

    execute: function(cmd, value, env) {
        if (cmd == this.P.ADD_QUOTE_CMD) {
            this.showDialog(env.editContext);
        }else {
            this.editorKernel.relayCmd(MyClientLib.PullQuote.REMOVE_QUOTE_CMD, value);
        }
    },

    showDialog: function(context) {
        var editorKernel = this.editorKernel, dm = editorKernel.getDialogManager(), pattern = this.config.pattern;

        if(!pattern){
            pattern = this.P.DEFAULT_PATTERN;
        }

        var selection = CUI.rte.Selection.createProcessingSelection(context);
        var rteText = selection.startNode.data, initValue = { align : "LEFT", text : "" };

        if(rteText){
            //this parsing logic depends on pattern, so when you add new pattern for pullquote make
            //sure you modify the following code to suit your pattern
            try{
                var start = rteText.lastIndexOf("[pullquote:", selection.startOffset);

                if( start !== -1 ){
                    var dIndex = rteText.indexOf(",");
                    initValue.align = rteText.substring(rteText.indexOf("=",start) + 1, dIndex);
                    initValue.text = rteText.substring(rteText.indexOf("=", dIndex) + 1, rteText.indexOf(")]"));
                }
            }catch(err){
                CQ.Ext.Msg.alert("Error","Error parsing text with pattern : " + pattern);
            }
        }

        var dialogConfig = {
            "jcr:primaryType": "cq:Dialog",
            title: "Pull Quote",
            modal: true,
            width: 600,
            height: 400,
            items: [ {
                xtype: "panel",
                layout: "fit",
                padding: "20px 0 0 10px",
                items: [ {
                    xtype: "panel",
                    layout: "form",
                    border: false,
                    items: [ {
                        xtype: 'radiogroup',
                        columns: 4,
                        fieldLabel: "Align",
                        items: [{
                            boxLabel: ' Left',
                            name: 'align',
                            value: 'LEFT',
                            checked: (initValue.align === "LEFT")
                        }, {
                            name: 'align',
                            boxLabel: ' Right',
                            value: 'RIGHT',
                            checked: (initValue.align === "RIGHT")
                        }, {
                            name: 'align',
                            boxLabel: ' Center',
                            value: 'CENTER',
                            checked: (initValue.align === "CENTER")
                        }, {
                            name: 'align',
                            boxLabel: ' Justify',
                            value: 'JUSTIFY',
                            checked: (initValue.align === "JUSTIFY")
                        }]
                    },{
                        xtype: "textarea",
                        height: 250,
                        name: "text",
                        fieldLabel: "Text",
                        fieldDescription: "Enter quote text",
                        anchor: "90%",
                        value: initValue.text
                    } ]
                } ]
            } ],
            ok: function() {
                var tBox = this.findByType("textarea")[0];
                var rBox = this.findByType("radiogroup")[0];

                if(!tBox.getValue()){
                    CQ.Ext.Msg.alert("Error","Enter text for quote");
                    return;
                }

                var value = {
                    text: tBox.getValue(),
                    align: rBox.getValue().value,
                    pattern: pattern
                };

                this.close();
                editorKernel.relayCmd(MyClientLib.PullQuote.ADD_QUOTE_CMD, value);
            },
            listeners: {
                show: function() {
                    editorKernel.fireUIEvent("dialogshow");
                },
                hide: function() {
                    editorKernel.fireUIEvent("dialoghide");
                }
            }
        };

        dm.show(CQ.WCM.getDialog(dialogConfig));
    },

    notifyPluginConfig: function(pluginConfig) {
        pluginConfig = pluginConfig || { };

        CUI.rte.Utils.applyDefaults(pluginConfig, {
            "tooltips": {
                "addquote": {
                    "title": "Add/Modify Pull Quote",
                    "text": "Add/Modify Pull Quote"
                },
                "removequote": {
                    "title": "Remove Pull Quote",
                    "text": "Remove Pull Quote"
                }
            }
        });

        this.config = pluginConfig;
    },

    updateState: function(selDef) {
        var rteText = selDef.selection.startNode.data;

        //this parsing logic depends on pattern, so when you add new pattern for pullquote make
        //sure you modify the following code to suit your pattern
        if(rteText && (rteText.lastIndexOf("[pullquote:", selDef.startOffset) !== -1)){
            this.removeQuoteUI.setDisabled(false);
        }else{
            this.removeQuoteUI.setDisabled(true);
        }

        this.addQuoteUI.setSelected(false);
        this.removeQuoteUI.setSelected(false);
    }
});

CUI.rte.plugins.PluginRegistry.register("pullquote", MyClientLib.PullQuote.Plugin);

MyClientLib.PullQuote.Cmd = new Class({
    toString: "PullQuote",
    extend: CUI.rte.commands.Command,

    P: MyClientLib.PullQuote,

    isCommand: function(cmdStr) {
        return (cmdStr == this.P.ADD_QUOTE_CMD) || (cmdStr == this.P.REMOVE_QUOTE_CMD);
    },

    getProcessingOptions: function() {
        var cmd = CUI.rte.commands.Command;
        return cmd.PO_SELECTION | cmd.PO_NODELIST;
    },

    addPullQuote: function(execDef){
        var value = execDef.value, selection = execDef.selection;
        var node = CUI.rte.DomProcessor.createNode(execDef.editContext, "span");

        var rteText = selection.startNode.data;
        var start = rteText ? rteText.lastIndexOf("[pullquote:", selection.startOffset) : -1;

        if( start !== -1 ){
            CUI.rte.Common.insertNode(node, selection.startNode, start);
            selection.startNode.parentNode.removeChild(selection.startNode);
        }else{
            CUI.rte.Common.insertNode(node, selection.startNode, selection.startOffset);
        }

        if(value.pattern){
            node.innerHTML = value.pattern.replace("<align>", value.align).replace("<text>", value.text);
        }else{
            node.innerHTML = "[pullquote:(align=\"" + value.align + "\",text=\"" + value.text + "\")]";
        }
    },

    removePullQuote: function(execDef){
        var selection = execDef.selection;

        var rteText = selection.startNode.data;
        var start = rteText.lastIndexOf("[pullquote:", selection.startOffset);

        if( start !== -1 ){
            selection.startNode.parentNode.removeChild(selection.startNode);
        }
    },

    execute: function(execDef) {
        if(execDef.command == this.P.ADD_QUOTE_CMD){
            this.addPullQuote(execDef);
        }else{
            this.removePullQuote(execDef);
        }
    }
});

CUI.rte.commands.CommandRegistry.register("pullquote", MyClientLib.PullQuote.Cmd);

6) #185, #243 we are registering the plugin and necessary action command. #25, #30 creates the toolbar buttons for add, remove quotes

7) Let us add the not-so-great icons and necessary css. Create folder (nt:folder) /apps/rte-pull-quote/clientlib/themes

8) Create clientlib (type cq:ClientLibraryFolder/apps/rte-pull-quote/clientlib/themes/default and set a property categories of String type to cq.widgets

9) Create file (nt:file) /apps/rte-pull-quote/clientlib/themes/default/css.txt, add the following

                    css/rtepullquote.css

10) Create folder (nt:folder) /apps/rte-pull-quote/clientlib/themes/default/css and make sure you add the icons addquote.png and removequote.png

11) Create file (nt:file) /apps/rte-pull-quote/clientlib/themes/default/css/rtepullquote.css, add the following code. RTE looks for css classes x-edit-addquote and x-edit-removequote (for the plugin toolbar buttons added namely addquote and removequote )

#CQ .x-html-editor-tb .x-edit-addquote {
    background: url(addquote.png) center no-repeat;
}

#CQ .x-html-editor-tb .x-edit-removequote {
    background: url(removequote.png) center no-repeat;
}

12) Add any text component with RichText editor and in the rtePlugins path of dialog (eg. /apps/rte-pull-quote/text/dialog/items/tab1/items/text/rtePlugins) add the pullquote node to enable pull quote plugins




7 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. How do you suggest styling the pull-quote afterwards?

    ReplyDelete
  3. where can i download the video from?

    ReplyDelete
  4. Does this apply to the TouchUI as well?

    ReplyDelete
  5. Hi Sreekanth Choudry Nalabotu,

    Nice tutorial
    When you click ok button its showing text [pullquote:(align=LEFT,text=asdfasdf)] instead of showing quotes. Can you please help

    ReplyDelete
  6. Hi Sreekanth,
    Can you get the code link for Touch UI compatible RTE pullquote plugin ?
    Thanks,
    Kiran Parab

    ReplyDelete
    Replies
    1. Kiran, you can achieve this with ACS Commons dialog plugin - https://github.com/Adobe-Consulting-Services/acs-aem-commons/pull/546

      Delete