AEM 62 - Touch UI Replace Asset Binary with AEM Asset or Local File Upload

Goal


Replace the binary (not metadata) of an AEM Asset with binary of another AEM Asset or Local File Upload

Thank you Rahul Singh Rawat, Drew Robinson for the code snippets

Demo | Package Install | Source Code | Git Hub


Replace Options


Replace with AEM File



Solution


1) Create a servlet apps.experienceaem.assets.ReplaceBinary with following code for performing the binary replace by adding original rendition

package apps.experienceaem.assets;

import com.day.cq.dam.api.Asset;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Session;
import javax.servlet.ServletException;
import java.io.InputStream;

@Component(metatype = true, label = "Experience AEM Replace Binary Servlet")
@Service
@Properties({
        @Property(name = "sling.servlet.methods", value = { "POST" }, propertyPrivate = true),
        @Property(name = "sling.servlet.paths", value = "/bin/eaem/replace-binary", propertyPrivate = true)
})
public class ReplaceBinary extends SlingAllMethodsServlet {
    private static final Logger log = LoggerFactory.getLogger(ReplaceBinary.class);

    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
                            throws ServletException {
        ResourceResolver resourceResolver = request.getResourceResolver();

        String toBeReplacedAssetPath = request.getParameter("toBeReplacedAssetPath");
        String replaceWithAssetPath = request.getParameter("replaceWithAssetPath");
        String deleteSource = request.getParameter("deleteSource");

        replaceBinary(resourceResolver, toBeReplacedAssetPath, replaceWithAssetPath, deleteSource);
    }

    private void replaceBinary(ResourceResolver resourceResolver, String toBeReplacedAssetPath,
                               String replaceWithAssetPath, String deleteSource) {
        Resource toBeReplacedResource = resourceResolver.getResource(toBeReplacedAssetPath);
        Resource replacingResource = resourceResolver.getResource(replaceWithAssetPath);

        Asset toBeReplacedAsset = toBeReplacedResource.adaptTo(Asset.class);
        Asset replacingAsset = replacingResource.adaptTo(Asset.class);

        String mimeType = toBeReplacedAsset.getMimeType();

        Resource original = replacingAsset.getOriginal();
        InputStream stream = original.adaptTo(InputStream.class);

        toBeReplacedAsset.addRendition("original", stream, mimeType);

        if(!"true".equals(deleteSource)){
            return;
        }

        try{
            Session session = resourceResolver.adaptTo(Session.class);
            session.removeItem(replacingResource.getPath());

            session.save();
        }catch(Exception e){
            log.warn("Error removing asset - " + replacingResource.getPath());
        }
    }
}

2) Create nt:unstructured node /apps/eaem-assets-replace-binary/button/replace-binary with the following xml for generating pull down html, added to Action Bar, showing the Replace options - Replace with Local File, Replace with AEM File


<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" 
                   xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    granite:id="eaem-binary-replace"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/pulldown"
    icon="globe"
    text="Replace"
    variant="actionBar">
    <items jcr:primaryType="nt:unstructured">
        <replace-with-local-file
            granite:class="replace-with-local-file"
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/collection/actionlink"
            target=".eaem-placeholder"
            text="Replace with Local File"/>
        <replace-with-aem-file
            granite:class="replace-with-aem-file"
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/collection/actionlink"
            target=".eaem-placeholder"
            text="Replace with AEM File"/>
    </items>
</jcr:root>

3) Create nt:unstructured node /apps/eaem-assets-replace-binary/button/replace-with-local-fileupload with following xml, for generating the file upload button html added to action bar (as hidden, to workaround the IE/Firefox issue)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" 
                   xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    granite:id="replace-with-local-fileupload"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/fileupload"
    accept="image/*"
    async="{Boolean}true"
    autoStart="{Boolean}false"
    chunkuploadsupported="{Boolean}true"
    multiple="{Boolean}false"
    name="file"/>

4) Create the wizard /apps/eaem-assets-replace-binary/wizard/select-assets of type cq:Page with following xml, to select an AEM Asset (source binary) for the Replace with AEM File option

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/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="cq:Page">
    <jcr:content
        jcr:mixinTypes="[sling:VanityPath]"
        jcr:primaryType="nt:unstructured"
        jcr:title="Select Replacement File"
        sling:resourceType="granite/ui/components/coral/foundation/page">
        <head jcr:primaryType="nt:unstructured">
            <viewport
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
            <favicon
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
            <clientlibs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
                categories="[coralui3,granite.ui.coral.foundation,cq.listview.coral.columns.personalization,dam.gui.admin.util]"/>
        </head>
        <body
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/page/body">
            <items jcr:primaryType="nt:unstructured">
                <form
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/form"
                    foundationForm="{Boolean}true"
                    maximized="{Boolean}true"
                    method="post"
                    novalidate="{Boolean}true"
                    style="vertical">
                    <items jcr:primaryType="nt:unstructured">
                        <charset
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/form/hidden"
                            name="_charset_"
                            value="utf-8"/>
                        <wizard
                            jcr:primaryType="nt:unstructured"
                            jcr:title="Select Replacement File"
                            sling:resourceType="granite/ui/components/coral/foundation/wizard">
                            <items jcr:primaryType="nt:unstructured">
                                <step1
                                    jcr:primaryType="nt:unstructured"
                                    jcr:title="Select Assets"
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <column
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/include"
                                            path="/libs/dam/gui/content/assets/jcr:content/views/column"/>
                                    </items>
                                    <parentConfig jcr:primaryType="nt:unstructured">
                                        <next
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/button"
                                            text="Next"
                                            variant="primary">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="next"/>
                                        </next>
                                    </parentConfig>
                                </step1>
                                <step2
                                    jcr:primaryType="nt:unstructured"
                                    jcr:title="Confirm"
                                    sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
                                    <parentConfig jcr:primaryType="nt:unstructured">
                                        <next
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/button"
                                            text="Replace"
                                            variant="primary">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="next"/>
                                        </next>
                                    </parentConfig>
                                    <items jcr:primaryType="nt:unstructured">
                                        <column1
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/container">
                                            <items jcr:primaryType="nt:unstructured">
                                                <text1
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/heading"
                                                    level="{Long}3"
                                                    text="you are going to replace 'PLACEHOLDER_SRC' with 'PLACEHOLDER_DEST'"/>
                                                <text2
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/heading"
                                                    level="{Long}3"
                                                    text="Select 'Replace' to confirm."/>
                                            </items>
                                        </column1>
                                    </items>
                                </step2>
                            </items>
                            <granite:data
                                jcr:primaryType="nt:unstructured"
                                suffix="${requestPathInfo.suffix}"/>
                        </wizard>
                    </items>
                </form>
            </items>
        </body>
    </jcr:content>
</jcr:root>

5) Create /apps/eaem-assets-replace-binary/clientlib of type cq:ClientLibraryFolder with categories dam.gui.admin.util and dependencies underscore

6) Create file /apps/eaem-assets-replace-binary/clientlib/js.txt with the following content

                      replace-binary.js

7) Create file /apps/eaem-assets-replace-binary/clientlib/replace-binary.js with the following code

(function ($, $document) {
    var FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
        FOUNDATION_MODE_CHANGE = "foundation-mode-change",
        FOUNDATION_SELECTIONS_CHANGE = "foundation-selections-change",
        FOUNDATION_WIZARD_STEPCHANGE = "foundation-wizard-stepchange",
        EDIT_ACTIVATOR = "aem-assets-admin-actions-edit-activator",
        REPLACE_WITH_LOCAL_FILE = ".replace-with-local-file",
        REPLACE_WITH_LOCAL_FILE_UPLOAD = "#replace-with-local-fileupload",
        REPLACE_WITH_AEM_FILE = ".replace-with-aem-file",
        EAEM_BINARY_REPLACE = "#eaem-binary-replace",
        DAM_ADMIN_CHILD_PAGES = ".cq-damadmin-admin-childpages",
        PLACEHOLDER_DEST = "PLACEHOLDER_DEST",
        PLACEHOLDER_SRC = "PLACEHOLDER_SRC",
        REPLACE_WITH_ASSET_PATH = "replaceWithAssetPath",
        TO_BE_REPLACED_ASSET_PATH = "toBeReplacedAssetPath",
        DELETE_SOURCE = "deleteSource",
        fui = $(window).adaptTo("foundation-ui"),
        SUBMIT_URL = "/bin/eaem/replace-binary?",
        SELECT_ASSET_URL = "/apps/eaem-assets-replace-binary/wizard/select-assets.html",
        REPLACE_PULL_DOWN_URL = "/apps/eaem-assets-replace-binary/button/replace-binary.html",
        FILE_UPLOAD_BUT_URL = "/apps/eaem-assets-replace-binary/button/replace-with-local-fileupload.html";

    var pathName = window.location.pathname;

    if(pathName.indexOf("/assets.html") === 0){
        handleAssetsConsole();
    }else if (pathName.indexOf(SELECT_ASSET_URL) === 0){
        handleSelectAssetsWizard();
    }

    function handleSelectAssetsWizard(){
        var replaceWithAssetPath, submitHandlerAdded = false,
            toBeReplacedAssetPath = queryParameters()[TO_BE_REPLACED_ASSET_PATH] ;

        $document.on(FOUNDATION_CONTENT_LOADED, handleFirstStep);

        function handleFirstStep(){
            var stepNumber = getWizardStepNumber();

            if(stepNumber !== 1){
                return;
            }

            var $cancelBut = getWizardCancelButton();

            $cancelBut.on("click", function(){
                window.parent.location.reload();
            });

            $document.on(FOUNDATION_SELECTIONS_CHANGE, ".foundation-collection", disableNextIfNoAssetsSelected);

            $document.on(FOUNDATION_WIZARD_STEPCHANGE, handleWizardSteps);
        }

        function handleWizardSteps(event, nextStep){
            var stepNumber = getWizardStepNumber();

            if(stepNumber !== 2){
                return;
            }

            var $nextStep = $(nextStep), html = $nextStep.html(),
                dest = getStringAfterLastSlash(replaceWithAssetPath),
                src = getStringAfterLastSlash(toBeReplacedAssetPath);

            $nextStep.html(html.replace(PLACEHOLDER_DEST, dest).replace(PLACEHOLDER_SRC, src));

            if(submitHandlerAdded){
                return;
            }

            submitHandlerAdded = true;

            var $nextButton = getCurrentStepNextButton();

            $nextButton.on("click", registerSubmitHandler);
        }

        function registerSubmitHandler(){
            var url = SUBMIT_URL + REPLACE_WITH_ASSET_PATH + "=" + replaceWithAssetPath + "&"
                            + TO_BE_REPLACED_ASSET_PATH + "=" + toBeReplacedAssetPath;

            fui.wait();

            $.ajax({url : url, type: "POST"}).done(function(){
                fui.clearWait();

                var dest = getStringAfterLastSlash(replaceWithAssetPath),
                    src = getStringAfterLastSlash(toBeReplacedAssetPath);

                showAlert("File '" + src + "' replaced with '" + dest + "' binary", 'Replaced', callback);
            });

            function callback(){
                window.parent.location.reload();
            }
        }

        function disableNextIfNoAssetsSelected(){
            var stepNumber = getWizardStepNumber();

            if(stepNumber !== 1){
                return;
            }

            disableWizardNext();

            var fSelections = $(".foundation-collection").adaptTo("foundation-selections");

            if(fSelections && (fSelections.count() > 1)){
                showAlert("Select one asset", 'Error');
                return;
            }

            replaceWithAssetPath = getToBeReplacedPaths();

            replaceWithAssetPath = _.isEmpty(replaceWithAssetPath) ? "" : replaceWithAssetPath[0];

            if(_.isEmpty(replaceWithAssetPath) || _.isEmpty(toBeReplacedAssetPath)){
                return;
            }

            var destExtn = getStringAfterLastDot(replaceWithAssetPath),
                srcExtn = getStringAfterLastDot(toBeReplacedAssetPath);

            if (destExtn !== srcExtn) {
                var dest = decodeURIComponent(getStringAfterLastSlash(replaceWithAssetPath)),
                    src = decodeURIComponent(getStringAfterLastSlash(toBeReplacedAssetPath));

                showAlert("'" + dest + "' and '" + src + "' donot have the same extension", 'Error');

                replaceWithAssetPath = "";

                return;
            }

            if(!_.isEmpty(replaceWithAssetPath)){
                enableWizardNext();
            }
        }

        function disableWizardNext(){
            toggleWizard(false);
        }

        function enableWizardNext(){
            toggleWizard(true);
        }

        function toggleWizard(isEnable){
            var $wizard = $(".foundation-wizard");

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

            var wizardApi = $wizard.adaptTo("foundation-wizard");
            wizardApi.toggleNext(isEnable);
        }

        function getWizardCancelButton(){
            var $wizard = $(".foundation-wizard");

            return $($wizard.find("[data-foundation-wizard-control-action=cancel]")[0]);
        }

        function getWizardStepNumber(){
            var $wizard = $(".foundation-wizard"),
                currentStep = $wizard.find(".foundation-wizard-step-active"),
                wizardApi = $wizard.adaptTo("foundation-wizard");

            return wizardApi.getPrevSteps(currentStep).length + 1;
        }

        function getCurrentStepNextButton(){
            var stepNumber = getWizardStepNumber(),
                $wizard = $(".foundation-wizard");

            return $($wizard.find("[data-foundation-wizard-control-action=next]")[stepNumber - 1]);
        }
    }

    function handleAssetsConsole(){
        var replaceDialog = null, folderPath;

        $document.on(FOUNDATION_MODE_CHANGE, function(e, mode){
            if(mode !== "selection" ){
                return;
            }

            var $replaceButton = $(EAEM_BINARY_REPLACE);

            if(!_.isEmpty($replaceButton)){
                return;
            }

            $.ajax(REPLACE_PULL_DOWN_URL).done(addPullDown);

            $.ajax(FILE_UPLOAD_BUT_URL).done(addFileUpload);
        });

        function addFileUpload(html){
            var $abContainer = $("coral-actionbar-container:first"),
                $childPage = $(DAM_ADMIN_CHILD_PAGES),
                folderPath = $childPage.data("foundation-collection-id");

            var $fileUpload = $(html).appendTo($abContainer).attr("hidden", "hidden");

            $fileUpload.off('coral-fileupload:fileadded')
                       .on('coral-fileupload:fileadded', uploadHandler)
                       .off('coral-fileupload:loadend')
                       .on('coral-fileupload:loadend', fileUploaded);

            function fileUploaded(){
                var replaceWithAssetPath = folderPath + "/" + getStringAfterLastSlash(this.value),
                    toBeReplacedAssetPath = getToBeReplacedPaths()[0];

                var url = SUBMIT_URL + REPLACE_WITH_ASSET_PATH + "="
                            + replaceWithAssetPath + "&"
                            + TO_BE_REPLACED_ASSET_PATH + "=" + toBeReplacedAssetPath + "&"
                            + DELETE_SOURCE + "=true";

                fui.wait();

                $.ajax({url : url, type: "POST"}).done(function(){
                    fui.clearWait();

                    var dest = getStringAfterLastSlash(replaceWithAssetPath),
                        src = getStringAfterLastSlash(toBeReplacedAssetPath);

                    showAlert("File '" + src + "' replaced with '" + dest + "' binary", 'Replaced', function(){
                        window.location.reload();
                    });
                });
            }

            function uploadHandler(){
                var toBeReplacedAssetPath = getToBeReplacedPaths()[0],
                    replaceWithAssetPath = this.value,
                    destExtn = getStringAfterLastDot(toBeReplacedAssetPath),
                    srcExtn = getStringAfterLastDot(replaceWithAssetPath);

                if (destExtn !== srcExtn) {
                    var dest = decodeURIComponent(getStringAfterLastSlash(toBeReplacedAssetPath)),
                        src = decodeURIComponent(getStringAfterLastSlash(replaceWithAssetPath));

                    showAlert("'" + dest + "' and '" + src + "' donot have the same extension", 'Error');

                    return;
                }

                this.action = folderPath + ".createasset.html";
                this.upload();
            }
        }

        function addPullDown(html){
            var $eActivator = $("." + EDIT_ACTIVATOR);

            if ($eActivator.length == 0) {
                return;
            }

            $(html).insertBefore( $eActivator );

            handleReplaceWithLocalFile();

            handleReplaceWithAEMFile();
        }

        function handleReplaceWithLocalFile(){
            var $replaceWithLocalFile = $(REPLACE_WITH_LOCAL_FILE);

            $replaceWithLocalFile.click(function(event){
                event.preventDefault();

                var toBeReplacedAssetPath = getToBeReplacedPaths();

                if(toBeReplacedAssetPath.length > 1){
                    showAlert("Select one asset...", "Error");
                    return;
                }

                //upload not added in pulldown to workaround IE11/firefox issue
                $(REPLACE_WITH_LOCAL_FILE_UPLOAD).find("button").click();
            });
        }

        function handleReplaceWithAEMFile(){
            var $childPage = $(DAM_ADMIN_CHILD_PAGES),
                foundationLayout = $childPage.data("foundation-layout"),
                $replaceAEMFile = $(REPLACE_WITH_AEM_FILE);

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

            folderPath = $childPage.data("foundation-collection-id");

            $replaceAEMFile.click(function(event){
                event.preventDefault();

                var toBeReplacedAssetPath = getToBeReplacedPaths();

                if(toBeReplacedAssetPath.length > 1){
                    showAlert("Select one asset...", "Error");
                    return;
                }

                replaceDialog = getReplaceAEMFileDialog(folderPath + "?toBeReplacedAssetPath=" + toBeReplacedAssetPath[0]);

                replaceDialog.show();
            });
        }

        function getReplaceAEMFileDialog(path){
            return new Coral.Dialog().set({
                closable: "on",
                header: {
                    innerHTML: 'File Replace'
                },
                content: {
                    innerHTML: getReplaceAEMFileDialogContent(path)
                }
            });
        }

        function getReplaceAEMFileDialogContent(path){
            var url = SELECT_ASSET_URL + path;

            return "<iframe width='1300px' height='700px' frameBorder='0' src='" + url + "'></iframe>";
        }
    }

    function getToBeReplacedPaths(){
        var toBeReplacedAssetPaths = [];

        $(".foundation-selections-item").each(function(index, asset){
            toBeReplacedAssetPaths.push($(asset).data("foundation-collection-item-id"));
        });

        return toBeReplacedAssetPaths;
    }

    function getStringAfterLastSlash(str){
        if(!str){
            return "";
        }

        var find = "";

        if(str.indexOf("/") !== -1){
            find = "/";
        }else if(str.indexOf("\\") !== -1){
            find = "\\";
        }

        return str.substr(str.lastIndexOf(find) + 1);
    }

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

        return str.substr(str.lastIndexOf(".") + 1);
    }

    function showAlert(message, title, callback){
        var fui = $(window).adaptTo("foundation-ui"),
            options = [{
                id: "ok",
                text: "OK",
                primary: true
            }];

        message = message || "Unknown Error";
        title = title || "Error";

        fui.prompt(title, message, "default", options, callback);
    }

    function queryParameters(searchStr) {
        var result = {}, param,
            params = (searchStr ? searchStr.split(/\?|\&/) : document.location.search.split(/\?|\&/));

        params.forEach( function(it) {
            if (_.isEmpty(it)) {
                return;
            }

            param = it.split("=");
            result[param[0]] = param[1];
        });

        return result;
    }
}($, $(document)));

1 comment:

  1. Hi Sreekanth, i need to disable/remove the "Replace" button, when the user tries to upload an asset that is already present in the folder, they are presented with a "Name conflict" pop-up that has the following buttons 1) "cancel" 2) "Keep Both" 3) "Replace" 4) "create version". i want to disable/remove the "replace" button. any pointers?

    ReplyDelete