AEM CQ 56 - Adding RichTextEditor in MultiField with Drag&Drop Support

Goal


Create a Multifield (CQ.form.MultiField) with RichText Editor widgets (CQ.form.RichText). User should be able to drag and drop images from content finder onto the richtext editors of multifield. Source code, Package Install and Demo Video are available for download

If an image is dragged into multifield item that already has image(s) added earlier, the new image dropped is appended (not replaced)


Bug Fixes

Tested on AEM 61 - Images couldnot be dropped on dialog close and reopen, unless page is refreshed. Removing a multifield item disables drop on new multifield items  Demo | Package Install
       



Prerequisites


If you are new to CQ visit this blog post; it explains page component basics and setting up your IDE

Create Component


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/richtext-multifield

2) Copy /libs/foundation/components/text to /apps/richtext-multifield/text and change the componentGroup of text component to My Components

3) Add the following to dialog xml (/apps/richtext-multifield/text/dialog)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:Dialog"
    helpPath="en/cq/current/wcm/default_components.html#Text"
    title="Text"
    xtype="tabpanel">
    <items jcr:primaryType="cq:WidgetCollection">
        <tab1
            jcr:primaryType="cq:Widget"
            anchor="100%"
            title="Text"
            xtype="panel">
            <items jcr:primaryType="cq:WidgetCollection">
                <text
                    jcr:primaryType="cq:Widget"
                    border="false"
                    hideLabel="{Boolean}true"
                    name="./text"
                    xtype="multifield">
                    <fieldConfig
                        jcr:primaryType="nt:unstructured"
                        xtype="mrichtext"/>
                </text>
            </items>
        </tab1>
    </items>
</jcr:root>

4) Create /apps/richtext-multifield/text/clientlib of type cq:ClientLibraryFolder and add property categories with value cq.widgets and dependencies with value underscore

5) Create /apps/richtext-multifield/text/clientlib/js.txt of type nt:file and add

                          richtextmultifield.js

6) Create /apps/richtext-multifield/text/clientlib/richtextmultifield.js of type nt:file and add the following JS logic.

CQ.Ext.ns("RichTextMultiField");

RichTextMultiField.RichText = CQ.Ext.extend(CQ.form.RichText, {
    afterRender: function() {
        RichTextMultiField.RichText.superclass.afterRender.call(this);

        var dialog = this.findParentByType('dialog');
        var target = this.dropTargets[0];

        if (dialog && dialog.el && target.highlight) {
            var dialogZIndex = parseInt(dialog.el.getStyle("z-index"), 10);

            if (!isNaN(dialogZIndex)) {
                target.highlight.zIndex = dialogZIndex + 1;
            }
        }

        target.parentMultiFieldItem = this.findParentByType("multifielditem");

        var multifield = this.findParentByType('multifield');

        if(!multifield.dropTargets){
            multifield.dropTargets = [];

            dialog.on('hide', function (){
                multifield.dropTargets = [];
            });

            multifield.on("removeditem", function(panel){
                resetDropTargets.call(this, panel);
            });
        }

        multifield.dropTargets.push(target);

        this.dropTargets = undefined;

        function resetDropTargets(panel){
            var dropTargets = [];

            var itemFields = panel.findByType("multifielditem");

            if(_.isEmpty(itemFields)){
                return;
            }

            var itemFieldsIds = _.pluck(itemFields, "id");

            CQ.Ext.each(this.dropTargets, function(dTarget){
                if(_.contains(itemFieldsIds, dTarget.parentMultiFieldItem.id)){
                    dropTargets.push(dTarget);
                }
            }, this);

            this.dropTargets = dropTargets;
        }
    },

    syncValue: function() {
        if(!this.el || !this.el.dom){
            return;
        }
        RichTextMultiField.RichText.superclass.syncValue.call(this);
    }
});

CQ.Ext.reg('mrichtext', RichTextMultiField.RichText);

7) Line 36, we are extending the ootb richtext with necessary logic and registering as new xtype mrichtext

8) Add the following logic in /apps/richtext-multifield/text/text.jsp

<%@ page import="org.apache.commons.lang3.ArrayUtils" %>
<%@include file="/libs/foundation/global.jsp" %>

<%
    String[] texts = properties.get("text", String[].class);

    if(ArrayUtils.isEmpty(texts)){
%>
        Configure Texts
<%
    }else{
        for(String text : texts){
%>
            <%= text %>
<%
        }
    }

%>





5 comments:

  1. Good post.
    Everething works correctly, but after dialog was closed and opened again I can't drag image from content finder.
    Error: Uncaught TypeError: Cannot read property 'id' of undefined.

    stopFx : function(finish){
    var me = this,
    id = me.dom.id; ----------------> in this place
    if(me.hasActiveFx()){

    Do you have any idea how to fix that?

    ReplyDelete
  2. Yes, facing the same issue, tried using cq:editConfig -> cq: listener -> afteredit REFRESH_PAGE property as a work around for a dialog "Ok" button click. However the problem persists when user may click a dialog "cancel" button.

    A fix to this issue will be of great help !

    ReplyDelete
  3. hi, check the bug fixes section above

    ReplyDelete
  4. Hi Sreekanth, I have been using this component and realized that up and down arrow keys have stopped working for other components as well. It does not REORDER the content but COPIES it Can you please provide he solution.

    ReplyDelete
    Replies
    1. hi Jatin, can you upload the component with dialog to dropbox and share the link here, i'll check when i get some free time..

      Delete