AEM 6440 - Touch UI Composite Image Multifield

Goal


Before trying out this extension, check if the AEM Core components Carousel serves your purpose - http://opensource.adobe.com/aem-core-wcm-components/library/carousel.html or using a Pathbrowser is good enough - /libs/granite/ui/components/coral/foundation/form/pathbrowser

Create  a Touch UI Composite Multifield configuration supporting Images, widgets of type /libs/cq/gui/components/authoring/dialog/fileupload

For AEM 62 check this post

Demo | Package Install | Github


Component Rendering




Image Multifield Structure




Nodes in CRX




Dialog



Dialog XML

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" 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="nt:unstructured"
    jcr:title="64 Image Multifield"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
        <items jcr:primaryType="nt:unstructured">
            <column
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/container">
                <items jcr:primaryType="nt:unstructured">
                    <products
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                        composite="{Boolean}true"
                        fieldLabel="Products">
                        <field
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/container"
                            name="./products">
                            <items jcr:primaryType="nt:unstructured">
                                <column
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <product
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                            fieldDescription="Name of Product"
                                            fieldLabel="Product Name"
                                            name="./product"/>
                                        <path
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
                                            fieldDescription="Select Path"
                                            fieldLabel="Path"
                                            name="./path"
                                            rootPath="/content"/>
                                        <file
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="cq/gui/components/authoring/dialog/fileupload"
                                            allowUpload="false"
                                            autoStart="{Boolean}false"
                                            class="cq-droptarget"
                                            fileNameParameter="./fileName"
                                            fileReferenceParameter="./fileReference"
                                            mimeTypes="[image/gif,image/jpeg,image/png,image/webp,image/tiff]"
                                            multiple="{Boolean}false"
                                            name="./file"
                                            title="Upload Image Asset"
                                            uploadUrl="${suffix.path}"
                                            useHTML5="{Boolean}true"/>
                                    </items>
                                </column>
                            </items>
                        </field>
                    </products>
                </items>
            </column>
        </items>
    </content>
</jcr:root>


Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-image-multifield

2) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-image-multifield/clientlib and set a property categories of String type to cq.authoring.dialog.all, dependencies of type String[] with value underscore

3) Create file ( type nt:file ) /apps/eaem-touchui-image-multifield/clientlib/js.txt, add the following

                         image-multifield.js

4) Create file ( type nt:file ) /apps/eaem-touchui-image-multifield/clientlib/image-multifield.js, add the following code

(function ($, $document) {
    var COMPOSITE_MULTIFIELD_SELECTOR = "coral-multifield[data-granite-coral-multifield-composite]",
        FILE_REFERENCE_PARAM = "fileReference",
        registry = $(window).adaptTo("foundation-registry"),
        ALLOWED_MIME_TYPE = "image/jpeg",
        adapters = registry.get("foundation.adapters");

    var fuAdapter = _.reject(adapters, function(adapter){
        return ((adapter.type !== "foundation-field") || (adapter.selector !== "coral-fileupload.cq-FileUpload"));
    });

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

    fuAdapter = fuAdapter[0];

    var orignFn = fuAdapter.adapter;

    fuAdapter.adapter = function(el) {
        return Object.assign(orignFn.call(el), {
            getName: function () {
                return el.name;
            },
            setName: function(name) {
                var prefix = name.substr(0, name.lastIndexOf(el.name));

                el.name = name;

                $("input[type='hidden'][data-cq-fileupload-parameter]", el).each(function(i, el) {
                    if ($(el).data("data-cq-fileupload-parameter") !== "filemovefrom") {
                        this.setAttribute("name", prefix + this.getAttribute("name"));
                    }
                });
            }
        });
    };

    $document.on("foundation-contentloaded", function(e) {
        var composites = $(COMPOSITE_MULTIFIELD_SELECTOR, e.target);

        composites.each(function() {
            Coral.commons.ready(this, function(el) {
                addThumbnails(el);
            });
        });
    });

    function addThumbnails(multifield){
        var $multifield = $(multifield),
            dataPath = $multifield.closest(".cq-dialog").attr("action"),
            mfName = $multifield.attr("data-granite-coral-multifield-name");

        dataPath = dataPath + "/" + getStringAfterLastSlash(mfName);

        $.ajax({
            url: dataPath + ".2.json",
            cache: false
        }).done(handler);

        function handler(mfData){
            multifield.items.getAll().forEach(function(item, i) {
                var $mfItem = $(item),
                    $fileUpload = $mfItem.find("coral-fileupload");

                if(_.isEmpty($fileUpload)){
                    return;
                }

                var itemName = getJustItemName($fileUpload.attr("name"));

                if(_.isEmpty(mfData[itemName]) || _.isEmpty((mfData[itemName][FILE_REFERENCE_PARAM]))){
                    return;
                }

                var imagePath = mfData[itemName][FILE_REFERENCE_PARAM];

                $fileUpload.trigger($.Event("assetselected", {
                    path: imagePath,
                    group: "",
                    mimetype: ALLOWED_MIME_TYPE, // workaround to add thumbnail
                    param: "",
                    thumbnail: getThumbnailHtml(imagePath)
                }));
            });
        }

        function getThumbnailHtml(path){
            return "<img class='cq-dd-image' src='" + path + "/_jcr_content/renditions/cq5dam.thumbnail.319.319.png'>";
        }

        function getJustItemName(itemName){
            itemName = itemName.substr(itemName.indexOf(mfName) + mfName.length + 1);

            itemName = itemName.substring(0, itemName.indexOf("/"));

            return itemName;
        }
    }

    function getStringAfterLastSlash(str){
        if(!str || (str.indexOf("/") == -1)){
            return "";
        }

        return str.substr(str.lastIndexOf("/") + 1);
    }
}(jQuery, jQuery(document)));


5) To render the composite multifield items eg. to create a Image Gallery component, create a HTL render script /apps/eaem-touchui-image-multifield/sample-image-multifield/sample-image-multifield.html

<div>
    <b>6420 Composite Image Multifield</b>

    <div data-sly-use.company="helper.js" data-sly-unwrap>
        <div data-sly-test="${!company.products && wcmmode.edit}">
            Add products using component dialog
        </div>

        <div data-sly-test="${company.products}">
            <div data-sly-list.product="${company.products}">
                <div>
                    <div>${product.name}</div>
                    <div>${product.path}</div>
                    <div><img src="${product.fileReference}" width="150" height="150"/></div>
                </div>
            </div>
        </div>
    </div>
</div>


6) Finally a HTL use-script to read the multifield data /apps/eaem-touchui-image-multifield/sample-image-multifield/helper.js

"use strict";
 
use( ["/libs/wcm/foundation/components/utils/ResourceUtils.js","/libs/sightly/js/3rd-party/q.js" ], function(ResourceUtils, Q){
    var prodPromise = Q.defer(), company = {},
        productsPath = granite.resource.path + "/products";
 
    company.products = undefined;
 
    ResourceUtils.getResource(productsPath)
            .then(function (prodParent) {
                return prodParent.getChildren();
            })
            .then(function(products) {
                addProduct(products, 0);
            });
 
    function addProduct(products, currIndex){
        if(!company.products){
            company.products = [];
        }
 
        if (currIndex >= products.length) {
            prodPromise.resolve(company);
            return;
        }
 
        var productRes = products[currIndex],
            properties = productRes.properties;
 
        var product = {
            path: properties.path,
            name: properties.product,
            fileReference: properties.fileReference
        };

        company.products.push(product);
 
        addProduct(products, (currIndex + 1));
    }

    return prodPromise.promise;
} );



8 comments:

  1. Hi Sreekanth Choudry,
    Do you have a similar fix for 6.4.2 version? I tried the above provided fix on 6.4.2 but as you mentioned it is working only on 6.4.4.

    ReplyDelete
  2. Thanks for this customization.

    How to know there are events like "assetselected"? How can I find more events of other granite ui?

    ReplyDelete
  3. Reordering is not working, fields other than images are re ordered but images are not getting re ordered

    ReplyDelete
  4. If you experience order problem like me, you can user below code part:

    setName: function(name) {
    var prefix = name.substr(0, name.lastIndexOf(el.name));
    var oldRootElementName = "";
    var newRootElementName = "";
    if(prefix == ""){
    oldRootElementName = el.name.substr(0, el.name.lastIndexOf("./icon"));
    newRootElementName = name.substr(0, name.lastIndexOf("./icon"));;
    }
    el.name = name;
    $("input[type='hidden'][data-cq-fileupload-parameter]", el).each(function(i, el) {
    if ($(el).data("data-cq-fileupload-parameter") !== "filemovefrom") {
    if(prefix == ""){
    var currentChildName = this.getAttribute("name");
    currentChildName = currentChildName.replace(oldRootElementName,newRootElementName);
    this.setAttribute("name", currentChildName);
    }else{
    this.setAttribute("name", prefix + this.getAttribute("name"));
    }
    }
    });
    }

    ReplyDelete
    Replies
    1. Hi Emrah,
      I replaced the setName function to fix the reorder issue but its not working. Can you please suggest?

      Delete
  5. For AEM 6.5, the code has to be modified a bit. Please use the following code if working on AEM 6.5
    setName function to be replaced by the following code. Rest all remains as given in the article above.

    setName: function(name) {
    var prefix = name.substr(0, name.lastIndexOf(el.name));
    var oldRootElementName = "";
    var newRootElementName = "";
    if(prefix == ""){
    oldRootElementName = el.name.substr(0, el.name.lastIndexOf("./"));
    newRootElementName = name.substr(0, name.lastIndexOf("./"));
    }
    el.name = name;
    $("input[type='hidden'][data-cq-fileupload-parameter]", el).each(function(i, el) {
    if ($(el).data("data-cq-fileupload-parameter") !== "filemovefrom") {
    if(prefix == ""){
    var currentChildName = this.getAttribute("name");
    currentChildName = currentChildName.replace(oldRootElementName,newRootElementName);
    this.setAttribute("name", currentChildName);
    } else{
    this.setAttribute("name", prefix + this.getAttribute("name"));
    }
    }
    });
    }

    ReplyDelete
  6. Hi Sreekanth Choudry Nalabotu

    Can you provide solution for Image Upload feature in multifield in AEM 6.5

    ReplyDelete
  7. I feel a lot more people need to read this, very good info!
    https://victoriaparlett.blogspot.com/

    ReplyDelete