AEM 6550 - Compose Emails in AEM Assets Console

Goal

Provide Send Mail feature in Assets Console - http://localhost:4502/assets.html, so users can send free form emails about selected assets without leaving the AEM context...

FakeSMTP configured as mail server, for more info check this


Demo | Package Install | Github


Email Modal



Fake SMTP Server

                              Fake SMTP Server intercepting emails sent to localhost on port 25


 

Configure FakeSMTP properties in AEM

                              http://localhost:4502/system/console/configMgr/com.day.cq.mailer.DefaultMailService



Solution

1) Create email template nt:file /apps/eaem-assets-send-mail/mail-templates/send-assets.html with the following code...

Subject: ${subject}

<table style="width:100%" width="100%" bgcolor="#ffffff" style="background-color:#ffffff;" border="0" cellpadding="0" cellspacing="0">
    <tr>
        <td style="width:100%">${body}</td>
    </tr>
</table>


2) Create a servlet apps.experienceaem.assets.SendMailServlet for sending emails...

package apps.experienceaem.assets;


import javax.jcr.Session;
import javax.servlet.Servlet;
import javax.servlet.ServletException;

import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.mailer.MessageGatewayService;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
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.resource.ValueMap;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.framework.Constants;

import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.commons.mail.MailTemplate;
import com.day.cq.mailer.MessageGateway;
import org.apache.commons.lang.text.StrLookup;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.HtmlEmail;

import javax.jcr.Session;
import javax.mail.internet.InternetAddress;
import java.util.*;

import java.io.IOException;

@Component(
        name = "Experience AEM Send Mail Servlet",
        immediate = true,
        service = Servlet.class,
        property = {
                "sling.servlet.methods=POST",
                "sling.servlet.paths=/bin/experience-aem/send-mail"
        }
)
public class SendMailServlet extends SlingAllMethodsServlet {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static String EMAIL_TEMPLATE_PATH = "/apps/eaem-assets-send-mail/mail-templates/send-assets.html";

    @Reference
    private MessageGatewayService messageGatewayService;

    @Override
    protected void doPost(final SlingHttpServletRequest req,
                         final SlingHttpServletResponse resp) throws ServletException, IOException {
        ResourceResolver resourceResolver = req.getResourceResolver();

        try{
            String to = req.getParameter("./to");
            String subject = req.getParameter("./subject");
            String body = req.getParameter("./body");

            Map<String, String> emailParams = new HashMap<String,String>();

            emailParams.put("subject", subject);
            emailParams.put("body", body.replaceAll("\r\n", ""));

            sendMail(resourceResolver, emailParams, to);
        }catch(Exception e){
            logger.error("Error sending email", e);
        }
    }

    private Email sendMail(ResourceResolver resolver, Map<String, String> emailParams, String recipientEmail) throws Exception{
        MailTemplate mailTemplate = MailTemplate.create(EMAIL_TEMPLATE_PATH, resolver.adaptTo(Session.class));

        if (mailTemplate == null) {
            throw new Exception("Template missing - " + EMAIL_TEMPLATE_PATH);
        }

        Email email = mailTemplate.getEmail(StrLookup.mapLookup(emailParams), HtmlEmail.class);

        email.setTo(Collections.singleton(new InternetAddress(recipientEmail)));

        MessageGateway<Email> messageGateway = messageGatewayService.getGateway(email.getClass());

        messageGateway.send(email);

        return email;
    }
}


3) Add the action bar button Send Mail at path /apps/eaem-assets-send-mail/content/send-mail-but with following configuration...

<?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:rel="cq-damadmin-admin-actions-send-mail-activator"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/collection/action"
    icon="email"
    target=".cq-damadmin-admin-childpages"
    text="Send Mail"
    variant="actionBar">
    <data
        jcr:primaryType="nt:unstructured"
        text="Email sent..."/>
</jcr:root>


4) Add the send mail modal /apps/eaem-assets-send-mail/send-mail-dialog, a form with To, Subject and Body. When user selects assets and clicks on Send Mail button the modal opens with the assets paths added to content field (email body). User can add additional content before clicking Send....

<?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="Experience AEM Send Mail"
        sling:resourceType="granite/ui/components/coral/foundation/page">
        <head jcr:primaryType="nt:unstructured">
            <favicon
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
            <viewport
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
            <clientlibs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
                categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral,eaem.assets.send.mail]"/>
        </head>
        <body
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/page/body">
            <items jcr:primaryType="nt:unstructured">
                <content
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/form"
                    action="/bin/experience-aem/send-mail"
                    foundationForm="{Boolean}true"
                    maximized="{Boolean}true"
                    method="post"
                    novalidate="{Boolean}true"
                    style="vertical">
                    <successresponse
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Success"
                        sling:resourceType="granite/ui/components/coral/foundation/form/responses/openprompt"
                        open="/assets.html"
                        redirect="/apps/eaem-assets-send-mail/send-mail-dialog.html"
                        text="Email sent"/>
                    <items jcr:primaryType="nt:unstructured">
                        <wizard
                            jcr:primaryType="nt:unstructured"
                            jcr:title="Compose"
                            sling:resourceType="granite/ui/components/coral/foundation/wizard">
                            <items jcr:primaryType="nt:unstructured">
                                <container
                                    granite:class="eaem-send-mail-form"
                                    jcr:primaryType="nt:unstructured"
                                    jcr:title="Compose"
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <actionbar
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/container">
                                            <items jcr:primaryType="nt:unstructured">
                                                <to
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                    fieldDescription="Comma separated 'To' list..."
                                                    fieldLabel="To"
                                                    name="./to"/>
                                                <subject
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                    fieldDescription="Enter Subject..."
                                                    fieldLabel="Subject"
                                                    name="./subject"/>
                                                <body
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
                                                    cols="15"
                                                    fieldDescription="Email body content"
                                                    fieldLabel="Content"
                                                    name="./body"
                                                    rows="22"/>
                                            </items>
                                        </actionbar>
                                    </items>
                                    <parentConfig jcr:primaryType="nt:unstructured">
                                        <prev
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
                                            href="/aem/start.html"
                                            text="Cancel">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="cancel"/>
                                        </prev>
                                        <next
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/button"
                                            text="Send"
                                            type="submit"
                                            variant="primary">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="next"/>
                                        </next>
                                    </parentConfig>
                                </container>
                            </items>
                        </wizard>
                    </items>
                </content>
            </items>
        </body>
    </jcr:content>
</jcr:root>


5) Create node /apps/eaem-assets-send-mail/send-mail-dialog/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [dam.gui.actions.coral, eaem.assets.send.mail], String[] property dependencies with value lodash.


6) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/js.txt, add

                        dialog-send-mail.css

7) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/dialog-send-mail.css, add the following code...

.eaem-send-mail-form{
    margin: 10px 40px 0px 40px;
    height: 87%;
}

.eaem-send-mail-col1{
    width: 30%;
    margin-top: 10px;
    overflow-y: auto;
}

.eaem-send-mail-col2{
    height: 65%;
    padding: 1rem;
    overflow: hidden;
    width: 65%;
    margin-top: 30px;
    margin-left: 50px;
    text-align: center;
    background: white;
}

.eaem-send-mail-apply{
    margin-top: 20px;
    margin-left: 88%;
}

.eaem-send-mail-apply-modal {
    width: 50%;
    margin-left: -50%;
    height: 63%;
    margin-top: -50%;
    box-sizing: content-box;
    z-index: 10100;
}

.eaem-send-mail-apply-modal > iframe {
    width: 100%;
    height: 100%;
    border: 1px solid #888;
}


8) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/js.txt, add

                        dialog-send-mail.js

9) Create file (nt:file) /apps/eaem-assets-send-mail/send-mail-dialog/dialog-send-mail.js, add the following code...

(function ($, $document) {
    var BUTTON_URL = "/apps/eaem-assets-send-mail/content/send-mail-but.html",
        SHARE_ACTIVATOR = "cq-damadmin-admin-actions-adhocassetshare-activator",
        SEND_MAIL_SERVLET = "/bin/experience-aem/send-mail",
        SEND_MAIL_URL = "/apps/eaem-assets-send-mail/send-mail-dialog.html",
        CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
        SENDER = "experience-aem", REQUESTER = "requester", $mailModal,
        url = document.location.pathname;

    if( url.indexOf("/assets.html") == 0 ){
        $document.on("foundation-selections-change", addSendMail);
    }else if(url.indexOf(SEND_MAIL_URL) == 0){
        handleSendMailDialog();
    }

    function handleSendMailDialog(){
        $document.on("foundation-contentloaded", fillDefaultValues);

        $document.on("click", CANCEL_CSS, sendCancelMessage);

        $document.submit(sendMailSentMessage);
    }

    function sendMailSentMessage(){
        var message = {
            sender: SENDER,
            action: "send"
        };

        getParent().postMessage(JSON.stringify(message), "*");
    }

    function sendCancelMessage(){
        var message = {
            sender: SENDER,
            action: "cancel"
        };

        getParent().postMessage(JSON.stringify(message), "*");
    }

    function getParent() {
        if (window.opener) {
            return window.opener;
        }

        return parent;
    }

    function closeModal(event){
        event = event.originalEvent || {};

        if (_.isEmpty(event.data) || _.isEmpty($mailModal)) {
            return;
        }

        var message, action;

        try{
            message = JSON.parse(event.data);
        }catch(err){
            return;
        }

        if (!message || message.sender !== SENDER) {
            return;
        }

        var modal = $mailModal.data('modal');
        modal.hide();
        modal.$element.remove();

        if(message.action == "send"){
            showAlert("Email sent...", $mailModal.mailSentMessage);
        }
    }

    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 fillDefaultValues(){
        var queryParams = queryParameters(),
            form = $("form")[0];

        setWidgetValue(form, "[name='./subject']", queryParams.subject);

        setWidgetValue(form, "[name='./body']", queryParams.body);
    }

    function setWidgetValue(form, selector, value){
        Coral.commons.ready(form.querySelector(selector), function (field) {
            field.value = _.isEmpty(value) ? "" : decodeURIComponent(value);
        });
    }

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

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

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

        return result;
    }

    function addSendMail(){
        $.ajax(BUTTON_URL).done(addButton);
    }

    function addButton(html) {
        var $eActivator = $("." + SHARE_ACTIVATOR);

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

        var $mail = $(html).css("margin-left", "20px").insertBefore($eActivator);

        $mail.click(openModal);

        $(window).off('message', closeModal).on('message', closeModal);
    }

    function openModal(){
        var actionConfig = ($(this)).data("foundationCollectionAction");

        var $items = $(".foundation-selections-item"),
            assetPaths = [];

        $items.each(function () {
            assetPaths.push($(this).data("foundationCollectionItemId"));
        });

        var body = "Please review the following assets... \n\n" + assetPaths.join("\n");

        showMailModal(getModalIFrameUrl("Experience AEM: Review Assets...", body), actionConfig.data.text);
    }

    function showMailModal(url, mailSentMessage){
        var $iframe = $('<iframe>'),
            $modal = $('<div>').addClass('eaem-send-mail-apply-modal coral-Modal');

        $iframe.attr('src', url).appendTo($modal);

        $modal.appendTo('body').modal({
            type: 'default',
            buttons: [],
            visible: true
        });

        $mailModal = $modal;

        $mailModal.mailSentMessage = mailSentMessage;
    }

    function getModalIFrameUrl(subject, body){
        var url = Granite.HTTP.externalize(SEND_MAIL_URL) + "?" + REQUESTER + "=" + SENDER;

        url = url + "&subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body);

        return url;
    }
}(jQuery, jQuery(document)));


AEM 6550 - AEM Dynamic Media SPA React Container for Video Backgrounds


The following steps explain creating a React SPA Container component (eaem-sites-spa-dm-video-container/components/container) extending Core Container component (core/wcm/components/container/v1/container) for Video Backgrounds and Component (eg. text) overlays...

For creating a simple SPA navigation app check this post. For product sample check adobe documentation

For configuring Dynamic Media check this post


Demo | Package Install | Github

         


Dynamic Media Configuration



Sync Configuration at Folder Level


Set Dynamic Media Video Profile


Video Renditions (Encodes)



Component Dialog


View as Published




Solution


1) Encodes for video are fetched and shown in the dropdown dynamically, when a user selects a dynamic media video...




2) To get the video dynamic renditions (encodes) of an asset,  create the following nt:file /apps/eaem-sites-spa-dm-video-container/video-dyn-renditions/video-dyn-renditions.jsp to return the encodes of a video as JSON (serves as a client side datasource for Encodes drop down, created in next steps...)


<%@include file="/libs/granite/ui/global.jsp"%>

<%@page session="false"
        import="org.apache.sling.commons.json.JSONObject"%>
<%@ page import="org.apache.sling.api.SlingHttpServletRequest" %>
<%@ page import="com.day.cq.dam.api.Asset" %>
<%@ page import="com.day.cq.dam.api.renditions.DynamicMediaRenditionProvider" %>
<%@ page import="com.day.cq.dam.api.Rendition" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.List" %>

<%
    response.setContentType("application/json");

    SlingHttpServletRequest eaemSlingRequest = slingRequest;
    String contentPath = eaemSlingRequest.getRequestPathInfo().getSuffix();

    Resource currentResource = eaemSlingRequest.getResourceResolver().getResource(contentPath);
    Asset asset = (currentResource != null ? currentResource.adaptTo(Asset.class) : null);

    JSONObject dynRenditions = new JSONObject();

    if( (asset == null) || !(asset.getMimeType().startsWith("video/")) || (asset.getMetadata("dam:scene7ID") == null)) {
        dynRenditions.write(response.getWriter());
        return;
    }

    DynamicMediaRenditionProvider dmRendProvider = sling.getService(DynamicMediaRenditionProvider.class);
    String s7Domain = String.valueOf(asset.getMetadata("dam:scene7Domain"));
    String scene7FileAvs = String.valueOf(asset.getMetadata("dam:scene7FileAvs"));

    HashMap<String, Object> rules = new HashMap<String, Object>();
    rules.put("remote", true);
    rules.put("video", true);

    List<Rendition> dmRenditions = dmRendProvider.getRenditions(asset, rules);
    JSONObject dynRendition = new JSONObject();
    String image = null, avsName = scene7FileAvs.substring(scene7FileAvs.lastIndexOf("/") + 1);

    dynRendition.put("url", getScene7Url(s7Domain, scene7FileAvs));
    dynRendition.put("image", getRendThumbnail(s7Domain, scene7FileAvs));
    dynRendition.put("name", avsName);

    dynRenditions.put(avsName, dynRendition);

    for (Rendition dmRendition : dmRenditions) {
        dynRendition = new JSONObject();

        image = dmRendition.getPath();

        if(image.endsWith(".mp4")){
            image = image.substring(0, image.lastIndexOf(".mp4"));
        }

        dynRendition.put("name", dmRendition.getName());
        dynRendition.put("image", getRendThumbnail(s7Domain, image));
        dynRendition.put("url", getScene7Url(s7Domain, dmRendition.getPath()));

        dynRenditions.put(dmRendition.getName(), dynRendition);
    }

    dynRenditions.write(response.getWriter());
%>

<%!
    private static String getScene7Url(String s7Domain, String rendPath){
        return s7Domain + "/s7viewers/html5/VideoViewer.html?asset=" + rendPath;
    }

    private static String getRendThumbnail(String s7Domain, String rendPath){
        return s7Domain + "/is/image/" + rendPath + "?fit=constrain,1&wid=200&hei=200";
    }
%>

3) Create /apps/eaem-sites-spa-dm-video-container/video-dyn-renditions/content and set the sling:resourceType to /apps/eaem-sites-spa-dm-video-container/video-dyn-renditions. This is the content node for fetching renditions...

http://localhost:4502/apps/eaem-sites-spa-dm-video-container/video-dyn-renditions/content.html/content/dam/experience-aem/Ayutthaya_360_VR.mp4



4) Create node /apps/eaem-sites-spa-dm-video-container/clientlibs/clientlib-dmvideo of type cq:ClientLibraryFolder, add String[] property categories with value [cq.authoring.dialog.all], String[] property dependencies with value lodash.


5) Create file (nt:file) /apps/eaem-sites-spa-dm-video-container/clientlibs/clientlib-dmvideo/js.txt, add

                        spa-dmvideo.js

6) Create file (nt:file) /apps/eaem-sites-spa-dm-video-container/clientlibs/clientlib-dmvideo/spa-dmvideo.js, add the following code...

(function ($, $document) {
    var DM_VIDEO_FIELD = "./eaemDMVideo",
        ENCODE_SELECT = "./eaemDMEncode",
        DM_VIDEO_RENDS_URL = "/apps/eaem-sites-spa-dm-video-container/video-dyn-renditions/content.html";

    $document.on('dialog-ready', handleVideoField);

    function handleVideoField(){
        var $videoField = $("foundation-autocomplete[name='" + DM_VIDEO_FIELD + "']");

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

        setSelectedEncode($videoField.val());

        $videoField.on("change", addEncodes);
    }

    function addEncodes(event){
        var $encodeField = $("coral-select[name='" + ENCODE_SELECT + "']"),
            selVideoPath = event.target.value;

        if(_.isEmpty(selVideoPath) || _.isEmpty($encodeField)){
            return;
        }

        $encodeField.find("coral-select-item").not("[value='NONE']").remove();

        loadEncodesInSelect(selVideoPath);
    }

    function getCoralSelectItem(text, value, selected){
        return '<coral-select-item value="' + value + '" ' + selected + '>' + text + '</coral-select-item>';
    }

    function loadEncodesInSelect(videoPath, selectedValue){
        var $encodeField = $("coral-select[name='" + ENCODE_SELECT + "']");

        $.ajax(DM_VIDEO_RENDS_URL + videoPath).done(function(renditions){
            _.each(renditions,function(rendition){
                $encodeField.append(getCoralSelectItem(rendition.name, rendition.name,
                                        ((selectedValue == rendition.name) ? "selected" : "")));
            });
        });
    }

    function setSelectedEncode(selVideoPath){
        if(!selVideoPath){
            return;
        }

        var dialogPath;

        try{
            dialogPath = Granite.author.DialogFrame.currentDialog.editable.slingPath;
        }catch(err){
            console.log("Error getting dialog path...", err);
        }

        if(!dialogPath){
            return;
        }

        $.ajax(dialogPath).done(function(data){
            var dmEncode = data[ENCODE_SELECT.substr(2)];

            if(!dmEncode){
                return;
            }

            loadEncodesInSelect(selVideoPath, dmEncode);
        })

    }
}(jQuery, jQuery(document)));

7) Create the component /apps/eaem-sites-spa-dm-video-container/components/container extending Core Container component core/wcm/components/container/v1/container  (core container component does not provide render script for React) for the SPA Editor. In the next step we'd be creating a react render script (coded in ES6)...




8) Add the component render script in eaem-sites-spa-dm-video-container\ui.frontend\src\components\DMVideoContainer\DMVideoContainer.js with the following code...
import React from 'react';
import {MapTo, withComponentMappingContext, Container, ResponsiveGrid, ComponentMapping} from '@adobe/cq-react-editable-components';
import {Helmet} from "react-helmet";

class EAEMContainer extends Container  {
    get containerProps() {
        let containerProps = super.containerProps;

        containerProps.style = {
            "width": '100%',
            "height": '500px'
        };

        return containerProps;
    }

    get videoDivProps() {
        return {
            "id": "eaem-dm-video-viewer",
            "style": {
                "zIndex": "0",
                "position": "relative"
            }
        };
    }

    get overlayDivProps() {
        return {
            "style" : {
                ...{
                "position": "absolute",
                "zIndex": "1"
                } ,
                ...this.props.overlayDivStyle
            }
        };
    }

    componentDidMount() {
        const timer = setInterval((() => {
            if(window.s7viewers){
                clearInterval(timer);
                this.loadVideo()
            }
        }).bind(this), 500);
    }

    loadVideo(){
        new window.s7viewers.VideoViewer({
            "containerId": "eaem-dm-video-viewer",
            "params": {
                "asset": this.props.dmVideoEncode,
                "serverurl": this.props.dmServerUrl,
                "videoserverurl": this.props.dmVideoServerUrl
            }
        }).init();
    }

    render() {
        return (
            <div {...this.containerProps}>
                {   this.props.dmVideoPath &&
                    <Helmet>
                        <script src={ this.props.dmVideoViewerPath }></script>
                    </Helmet>
                }

                {   this.props.dmVideoPath  &&
                    <div {...this.videoDivProps}>
                        <div {...this.overlayDivProps}>
                            { this.childComponents }
                            { this.placeholderComponent }
                        </div>
                    </div>
                }

                {   !this.props.dmVideoPath &&
                    <div>
                        { this.childComponents }
                        { this.placeholderComponent }
                    </div>
                }
            </div>
        );
    }
}

export default MapTo('eaem-sites-spa-dm-video-container/components/container')(EAEMContainer);

9) DMVideoContainer was imported in ui.frontend\src\components\import-components.js

                             import './DMVideoContainer/DMVideoContainer';
                             import './Page/Page';
                             import './Text/Text';

10) #88 specifies the script was mapped to component eaem-sites-spa-dm-video-container/components/container for rendering..

                             export default MapTo('eaem-sites-spa-dm-video-container/components/container')(EAEMContainer);


11) #63 adds the Video Viewer JS (eg. https://s7d1.scene7.com/s7viewers/html5/js/VideoViewer.js) to the <head> tag...

                             <Helmet>
                                    <script src={ this.props.dmVideoViewerPath }></script>
                             </Helmet>

12) #48 loadVideo() loads the video into container div eaem-dm-video-viewer

13) Create a Sling Model Exporter com.eaem.core.models.impl.EAEMDMVideoContainerModelImpl for exporting the component properties





14) Add the following code in com.eaem.core.models.impl.EAEMDMVideoContainerModelImpl.

package com.eaem.core.models.impl;

import com.adobe.cq.export.json.ContainerExporter;
import com.day.cq.dam.api.Asset;
import com.day.cq.wcm.foundation.model.responsivegrid.ResponsiveGrid;
import com.eaem.core.models.EAEMDMVideoContainerModelExporter;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import com.adobe.cq.export.json.ComponentExporter;

import javax.annotation.PostConstruct;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

import java.util.HashMap;
import java.util.Map;

@Model(
        adaptables = {SlingHttpServletRequest.class},
        adapters = {ContainerExporter.class, ComponentExporter.class},
        resourceType = {"eaem-sites-spa-dm-video-container/components/container"}
)
@Exporter(
        name = "jackson",
        extensions = {"json"}
)
@JsonSerialize(as = EAEMDMVideoContainerModelExporter.class)
public class EAEMDMVideoContainerModelImpl extends ResponsiveGrid implements EAEMDMVideoContainerModelExporter{
    @ScriptVariable
    private Resource resource;

    @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
    private String eaemDMVideo;

    @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
    private String eaemDMEncode;

    private Map<String, Object> metadata;

    @PostConstruct
    protected void initModel() {
        super.initModel();

        if( (this.resource == null) || StringUtils.isEmpty(eaemDMVideo)) {
            return;
        }

        ResourceResolver resolver = this.resource.getResourceResolver();
        Resource videoRes = resolver.getResource(eaemDMVideo);

        if(videoRes == null){
            return;
        }

        metadata = videoRes.adaptTo(Asset.class).getMetadata();
    }

    public String getDmAccountName(){
        if(metadata == null){
            return "";
        }

        String fileName = String.valueOf(metadata.get("dam:scene7File"));

        if(StringUtils.isEmpty(fileName)){
            return "";
        }

        return fileName.substring(0, fileName.indexOf("/"));
    }

    public String getDmServerUrl() {
        if(metadata == null){
            return "";
        }

        return metadata.get("dam:scene7Domain") + "is/image/";
    }

    public String getDmVideoViewerPath() {
        if(metadata == null){
            return "";
        }

        return metadata.get("dam:scene7Domain") + "s7viewers/html5/js/VideoViewer.js";
    }

    public String getDmVideoServerUrl() {
        if(metadata == null){
            return "";
        }

        return metadata.get("dam:scene7Domain") + "is/content/";
    }

    public Map<String, String> getOverlayDivStyle() {
        Map<String, String> divStyles = new HashMap<String, String>();
        ValueMap vm = this.resource.getValueMap();

        divStyles.put("top" , vm.get("overlayTop", ""));
        divStyles.put("left" , vm.get("overlayLeft", ""));
        divStyles.put("backgroundColor" , vm.get("overlayBGColor", "#FFFFFF"));
        divStyles.put("padding" , vm.get("overlayPadding", "10px 20px 10px 20px"));

        return divStyles;
    }

    public String getDmVideoPath() {
        return eaemDMVideo;
    }

    public String getDmVideoEncode() {
        return getDmAccountName() + "/" + eaemDMEncode;
    }
}

AEM 6550 - Move Render Condition for Assets with References

 

A sample render condition to show Move action bar button in Asset console only if one (or some) of the assets in a folder have references (say they are part of a Dynamic Media Image Set)


Package Install | Github


Assets have references (Move shown)



None of Assets have references (Move not shown)


Solution

1) Overlay /libs/dam/gui/content/assets/jcr:content/actions/selection/moveasset/granite:rendercondition into /apps/dam/gui/content/assets/jcr:content/actions/selection/moveasset/granite:rendercondition 


2) Set /apps/dam/gui/content/assets/jcr:content/actions/selection/moveasset/granite:rendercondition@sling:resourceType to /apps/eaem-move-references-rendercondition



3) Create the Render condition /apps/eaem-move-references-rendercondition/eaem-move-references-rendercondition.jsp with following code 


<%@page session="false"
        import="com.adobe.granite.ui.components.ComponentHelper,
                com.adobe.granite.ui.components.Config,
                org.apache.sling.api.resource.Resource,
                org.apache.sling.api.resource.ValueMap,
                com.adobe.granite.ui.components.rendercondition.RenderCondition,
                com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition,
                javax.jcr.Node,
                com.day.cq.dam.commons.util.UIHelper,
                com.day.cq.dam.api.DamConstants" %>
<%@ page import="javax.jcr.Session" %>
<%@ page import="javax.jcr.query.QueryManager" %>
<%@ page import="javax.jcr.query.Query" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="javax.jcr.NodeIterator" %>
<%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.2" %>
<%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %>

<sling:defineObjects/>
<cq:defineObjects/>

<%

    ComponentHelper cmp = new ComponentHelper(pageContext);
    Config cfg = cmp.getConfig();
    String path = cmp.getExpressionHelper().getString(cfg.get("path", String.class));
    Resource contentRes = null;

    if (path != null) {
        contentRes = slingRequest.getResourceResolver().getResource(path);
    } else {
        contentRes = UIHelper.getCurrentSuffixResource(slingRequest);
    }

    if (contentRes == null) {
        return;
    }

    Iterator<Resource> itr = contentRes.listChildren();
    StringBuilder strBuilder = new StringBuilder();

    strBuilder.append("//element(*, nt:unstructured)[");

    while(itr.hasNext()){
        strBuilder.append("@sling:resource = '").append(itr.next().getPath()).append("' or");
    }

    String queryStmt = strBuilder.toString();

    queryStmt = queryStmt.substring(0, queryStmt.lastIndexOf("or")) + "]";

    Session session = resourceResolver.adaptTo(Session.class);
    QueryManager qm = session.getWorkspace().getQueryManager();

    Query query = qm.createQuery(queryStmt, Query.XPATH);

    NodeIterator results = query.execute().getNodes();

    boolean showMove = results.hasNext();

    if(showMove){
%>
        <sling:include path="/libs/dam/gui/coral/components/commons/renderconditions/mainasset"/>
<%
    }else{
        request.setAttribute(RenderCondition.class.getName(), new SimpleRenderCondition(false));
    }
%>

AEM 6550 - Modify the Filename on Upload Before Executing DAM Update Asset Workflow

 

Adjust the filename on upload (organization standards?), before executing DAM Update Asset workflow. The sample in this post adds a filter executing prior to com.day.cq.dam.core.impl.servlet.CreateAssetServlet, intercepting the file upload stream 

Code in this post was not production harnessed, make sure you test (and improve it) for large files, small files, binaries, text files etc..

For validating file names on client side check this post


For handling files uploaded using Web Interface & Asset Link - Demo | Package Install | Github

For handling files uploaded using Web Interface  - Demo | Package Install | Github


Uploaded using Web Interface



Uploaded using Asset Link




Solution


Add a filter eg. apps.experienceaem.assets.EAEMChangeFileNameFilter intercepting the file name stream and adjust the name (here it replaces spaces with hyphens)


package apps.experienceaem.assets;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
import org.osgi.framework.Constants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Part;

import javax.servlet.*;
import java.io.*;
import java.util.*;

@Component(
        immediate = true,
        service = Filter.class,
        name = "Experience AEM Request Name Change Filter for CreateAssetServlet Streaming Requests",
        property = {
                Constants.SERVICE_RANKING + ":Integer=-99",
                "sling.filter.scope=REQUEST"
        })
public class EAEMChangeFileNameFilter implements Filter {
    private static Logger log = LoggerFactory.getLogger(EAEMChangeFileNameFilter.class);

    private static final String STRING_MATCH = " ";

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (!(request instanceof SlingHttpServletRequest) || !(response instanceof SlingHttpServletResponse)) {
            chain.doFilter(request, response);
            return;
        }

        final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

        if (!StringUtils.equals("POST", slingRequest.getMethod()) || !isCreateAssetRequest(slingRequest) ) {
            chain.doFilter(request, response);
            return;
        }

        Iterator parts = (Iterator)request.getAttribute("request-parts-iterator");

        if( (parts == null) || !parts.hasNext()){
            chain.doFilter(request, response);
            return;
        }

        List<Part> otherParts = new ArrayList<Part>();
        Part part = null;

        while(parts.hasNext()) {
            part = (Part) parts.next();

            otherParts.add(new EAEMFileNameRequestPart(part));
        }

        request.setAttribute("request-parts-iterator", otherParts.iterator());

        chain.doFilter(request, response);
    }

    private boolean isCreateAssetRequest(SlingHttpServletRequest slingRequest){
        String[] selectors = slingRequest.getRequestPathInfo().getSelectors();

        if(ArrayUtils.isEmpty(selectors) || (selectors.length > 1)){
            return false;
        }

        return selectors[0].equals("createasset");
    }

    public void destroy() {
    }

    //code copied form https://svn.apache.org/repos/asf/sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/parameters/RequestPartsIterator.java
    private static class EAEMFileNameRequestPart implements Part {
        private final Part part;
        private final InputStream inputStream;

        public EAEMFileNameRequestPart(Part part) throws IOException {
            this.part = part;

            if(!isFileNamePart(part)){
                this.inputStream = new ByteArrayInputStream(IOUtils.toByteArray(part.getInputStream()));
            }else{
                this.inputStream = this.getFileNameAdjustedStream(part);
            }
        }

        private InputStream getFileNameAdjustedStream(Part part) throws IOException{
            String fileName = null;

            try{
                fileName = IOUtils.toString(part.getInputStream(), "UTF-8");
            }catch(Exception e){
                log.error("Error reading filename from stream...");
            }

            if(fileName == null){
                fileName = "";
            }

            if(!fileName.contains(STRING_MATCH)){
                log.debug("Return unprocessed file name : " + fileName);
                return new ByteArrayInputStream(fileName.getBytes());
            }

            fileName = fileName.trim().replaceAll(STRING_MATCH, "-");

            log.debug("Uploaded file name changed to : " + fileName);

            return new ByteArrayInputStream(fileName.getBytes());
        }

        private boolean isFileNamePart(Part part){
            return ("fileName".equals(part.getName()));
        }

        public InputStream getInputStream() throws IOException {
            return inputStream;
        }

        public  String getContentType() {
            return part.getContentType();
        }

        public String getName() {
            return part.getName();
        }

        public long getSize() {
            return 0;
        }

        public  void write(String s) throws IOException {
            throw new UnsupportedOperationException("Writing parts directly to disk is not supported by this implementation, use getInputStream instead");
        }

        public  void delete() throws IOException {
        }

        public  String getHeader(String headerName) {
            return part.getHeader(headerName);
        }

        public  Collection<String> getHeaders(String headerName) {
            return part.getHeaders(headerName);
        }

        public  Collection<String> getHeaderNames() {
            return part.getHeaderNames();
        }

        public String getSubmittedFileName() {
            return part.getSubmittedFileName();
        }

        private <T> Collection<T> toCollection(Iterator<T> i) {
            if ( i == null ) {
                return Collections.emptyList();
            } else {
                List<T> c = new ArrayList<T>();
                while(i.hasNext()) {
                    c.add(i.next());
                }
                return c;
            }
        }
    }
}

AEM 6550 - Dynamic Media (Scene7) Allowed IP addresses Test Publish Context


Published Dynamic Media assets are publicly available. If the usecase is to restrict asset access to specific IP addresses, the scene7 account can be configured to use a test server so the unpublished assets can be viewed from specific IP addresses without authentication check product documentation


Allowed IPs Configuration

                                                     Setup > Application Setup > Publish Setup > Image Server


Test Server settings


Forbidden (View unpublished not allowed)


                                                     https://s7test1.scene7.com/is/image/EAEM/texas?$EAEM$


Allowed (View unpublished allowed from configured IPs)

                                                     https://s7test1.scene7.com/is/image/EAEM/texas?$EAEM$


Image Not Published (Not available on delivery servers yet)

                                                     https://s7d1.scene7.com/is/image/EAEM/texas?$EAEM$



Image Published (Available on delivery servers, open to everyone)

                                                     https://s7d1.scene7.com/is/image/EAEM/texas?$EAEM$


AEM 6550 - Extend AEM Asset Picker to select Image Smart Crops and Video Dynamic Renditions


AEM Asset Picker (http://localhost:4502/aem/assetpicker) out of the box does not show Dynamic Renditions for selecting them in third party applications (using iframe). This post is on extending the Asset Picker column view to show (and select) Video Renditions (encodes) and Image Smart Crops...

For showing dynamic renditions in card view check this post


Demo | Package Install | Github


Product - Asset Details Video Renditions



Extension - Asset Picker Video Renditions (Published)



Extension - Asset Picker Message to Parent Window on Video Rendition select



Product - Asset Details Image Smart Crop Renditions



Extension - Asset Picker Smart Crops Renditions



Extension - Asset Picker Select Smart Crops Rendition




Solution

1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-asset-selector-show-dyn-renditions

2) Create the following nt:file /apps/eaem-asset-selector-show-dyn-renditions/smart-crop-renditions/smart-crop-renditions.jsp to return the smart crop renditions of an image as JSON


<%@include file="/libs/granite/ui/global.jsp"%>

<%@page session="false"
        import="java.util.Iterator,
                  org.apache.sling.commons.json.JSONObject,
                  com.adobe.granite.ui.components.Config,
                  com.adobe.granite.ui.components.Tag"%>
<%@ page import="com.adobe.granite.ui.components.ds.ValueMapResource" %>

<%
    Config cfg = cmp.getConfig();
    ValueMap dynVM = null;
    JSONObject dynRenditions = new JSONObject();

    response.setContentType("application/json");

    for (Iterator<Resource> items = cmp.getItemDataSource().iterator(); items.hasNext();) {
        JSONObject dynRendition = new JSONObject();

        dynVM = ((ValueMapResource)items.next()).getValueMap();

        String name = String.valueOf(dynVM.get("breakpoint-name"));

        dynRendition.put("name", name);
        dynRendition.put("image", dynVM.get("copyurl"));
        dynRendition.put("url", dynVM.get("copyurl"));

        dynRenditions.put(name, dynRendition);
    }

    dynRenditions.write(response.getWriter());
%>


3) Set the datasource for Image Smart Crops /apps/eaem-asset-selector-show-dyn-renditions/smart-crop-renditions/renditions/datasource@sling:resourceTypedam/gui/components/s7dam/smartcrop/datasource


4) Create the following nt:file /apps/eaem-asset-selector-show-dyn-renditions/video-dyn-renditions/video-dyn-renditions.jsp to return the encodes of a video as JSON


<%@include file="/libs/granite/ui/global.jsp"%>

<%@page session="false"
        import="org.apache.sling.commons.json.JSONObject"%>
<%@ page import="org.apache.sling.api.SlingHttpServletRequest" %>
<%@ page import="com.day.cq.dam.api.Asset" %>
<%@ page import="com.day.cq.dam.api.renditions.DynamicMediaRenditionProvider" %>
<%@ page import="com.day.cq.dam.api.Rendition" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.List" %>

<%
    response.setContentType("application/json");

    SlingHttpServletRequest eaemSlingRequest = slingRequest;
    String contentPath = eaemSlingRequest.getRequestPathInfo().getSuffix();

    Resource currentResource = eaemSlingRequest.getResourceResolver().getResource(contentPath);
    Asset asset = (currentResource != null ? currentResource.adaptTo(Asset.class) : null);

    JSONObject dynRenditions = new JSONObject();

    if( (asset == null) || !(asset.getMimeType().startsWith("video/")) || (asset.getMetadata("dam:scene7ID") == null)) {
        dynRenditions.write(response.getWriter());
        return;
    }

    DynamicMediaRenditionProvider dmRendProvider = sling.getService(DynamicMediaRenditionProvider.class);
    String s7Domain = String.valueOf(asset.getMetadata("dam:scene7Domain"));
    String scene7FileAvs = String.valueOf(asset.getMetadata("dam:scene7FileAvs"));

    HashMap<String, Object> rules = new HashMap<String, Object>();
    rules.put("remote", true);
    rules.put("video", true);

    List<Rendition> dmRenditions = dmRendProvider.getRenditions(asset, rules);
    JSONObject dynRendition = new JSONObject();
    String image = null, avsName = scene7FileAvs.substring(scene7FileAvs.lastIndexOf("/") + 1);

    dynRendition.put("url", getScene7Url(s7Domain, scene7FileAvs));
    dynRendition.put("image", getRendThumbnail(s7Domain, scene7FileAvs));
    dynRendition.put("name", avsName);

    dynRenditions.put(avsName, dynRendition);

    for (Rendition dmRendition : dmRenditions) {
        dynRendition = new JSONObject();

        image = dmRendition.getPath();

        if(image.endsWith(".mp4")){
            image = image.substring(0, image.lastIndexOf(".mp4"));
        }

        dynRendition.put("name", dmRendition.getName());
        dynRendition.put("image", getRendThumbnail(s7Domain, image));
        dynRendition.put("url", getScene7Url(s7Domain, dmRendition.getPath()));

        dynRenditions.put(dmRendition.getName(), dynRendition);
    }

    dynRenditions.write(response.getWriter());
%>

<%!
    private static String getScene7Url(String s7Domain, String rendPath){
        return s7Domain + "/s7viewers/html5/VideoViewer.html?asset=" + rendPath;
    }

    private static String getRendThumbnail(String s7Domain, String rendPath){
        return s7Domain + "/is/image/" + rendPath + "?fit=constrain,1&wid=200&hei=200";
    }
%>


5) Create node /apps/eaem-asset-selector-show-dyn-renditions/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [cq.gui.damadmin.assetselector], String[] property dependencies with value lodash.


6) Create file (nt:file) /apps/eaem-asset-selector-show-dyn-renditions/clientlib/js.txt, add

                        col-view-show-dyn-renditions.js

7) Create file (nt:file) /apps/eaem-asset-selector-show-dyn-renditions/clientlib/col-view-show-dyn-renditions.js, add the following code

(function($, $document){
    var CORAL_COLUMNVIEW_PREVIEW = "coral-columnview-preview",
        THUMB_PATH = "/_jcr_content/renditions/cq5dam.thumbnail.48.48.png",
        EAEM_DATA_ASSET_PATH = "data-eaem-asset-path",
        EAEM_RENDITION_DATA = "data-eaem-rendition",
        EAEM_RENDITION_FIELD = "eaem-rendition-name",
        EAEM_DONE_ACTION = "EAEM_DONE",
        GET_SMART_CROPS_URL = "/apps/eaem-asset-selector-show-dyn-renditions/smart-crop-renditions/renditions.html",
        GET_VIDEO_RENDS_URL = "/apps/eaem-asset-selector-show-dyn-renditions/video-dyn-renditions/renditions.html",
        added = false, dynRendsCol;

    $document.on("foundation-contentloaded", registerSelectListener);

    $document.on("foundation-selections-change", function(){
        var isSelected = handleSelections();

        if(isSelected){
            return;
        }

        getUIWidget(CORAL_COLUMNVIEW_PREVIEW).then(showDynamicRenditions);
    });

    function registerSelectListener(){
        var saveHandler = getSaveHandler();

        $document.off('click', '.asset-picker-done');

        $(document).on("click", ".asset-picker-done", function(e) {
            e.stopImmediatePropagation();
            exportAssetInfo(e);
        });
    }

    function exportAssetInfo(e){
        var message = {
            config: {
                action: EAEM_DONE_ACTION
            },
            data: []
        };

        var $selItem = $("coral-columnview-item.is-selected"),
            selected = JSON.parse($selItem.attr(EAEM_RENDITION_DATA));

        message.data.push(selected);

        console.log(message);

        getParent().postMessage(JSON.stringify(message), $(".assetselector-content-container").data("targetorigin"));
    }

    function handleSelections(){
        var $selItem = $("coral-columnview-item.is-selected");

        if(_.isEmpty($selItem) || !$selItem[0].hasAttribute(EAEM_DATA_ASSET_PATH)){
            return false;
        }

        var metaHtml = getMetaHtml($selItem.attr(EAEM_DATA_ASSET_PATH));

        $selItem.prepend(metaHtml);

        $(".asset-picker-done")[0].disabled = false;

        return true;
    }

    function resetDynamicRenditionsColumnView(){
        $("coral-columnview-column").on("coral-columnview-column:_activeitemchanged", function(){
            added = false;

            if(dynRendsCol){
                $(dynRendsCol).remove();
            }
        });
    }

    function createDynamicRenditionsColumn($colPreview){
        dynRendsCol = new Coral.ColumnView.Column().set({});

        dynRendsCol._loadItems = function(count, item){};

        var $titleValue = $colPreview.find("coral-columnview-preview-label:contains('Title')").next(),
            $rendition = $("<coral-columnview-preview-label>Rendition</coral-columnview-preview-label>")
                .insertAfter( $titleValue );

        $("<coral-columnview-preview-value id='" + EAEM_RENDITION_FIELD + "'>Original</coral-columnview-preview-value>").insertAfter($rendition);
    }

    function isImage(typeValue){
        if(!typeValue){
            return false;
        }

        return (typeValue.trim() == "IMAGE");
    }

    function isVideo(typeValue){
        if(!typeValue){
            return false;
        }
        return (typeValue.trim() == "MULTIMEDIA");
    }

    function showDynamicRenditions($colPreview){
        if(_.isEmpty($colPreview)){
            return;
        }

        if(added){
            return;
        }

        added = true;

        resetDynamicRenditionsColumnView();

        var assetPath = $colPreview.attr("data-foundation-layout-columnview-columnid"),
            $type = $colPreview.find("coral-columnview-preview-label:contains('Type')"),
            typeValue = $type.next("coral-columnview-preview-value").html(),
            thumbPath = assetPath + THUMB_PATH;

        if(!isImage(typeValue) && !isVideo(typeValue)){
            return;
        }

        createDynamicRenditionsColumn($colPreview);

        var $dynRendsCol = $(dynRendsCol).insertBefore($colPreview),
            $dynRendsColContent = $dynRendsCol.find("coral-columnview-column-content");

        addOriginalImage();

        var rendsUrl = isImage(typeValue) ? GET_SMART_CROPS_URL : GET_VIDEO_RENDS_URL;

        rendsUrl = rendsUrl + assetPath;

        _.defer(function(){
            $.ajax( { url: rendsUrl, async: false } ).done(addDynRenditions);
        });

        function addDynRenditions(data){
            var $dynRendColItem;

            _.each(data, function(dynRendition, dynName){
                $dynRendColItem = $(getDynamicRenditionsHtml(thumbPath, dynRendition, assetPath))
                    .appendTo($dynRendsColContent);

                $dynRendColItem.click(showDynRendImage);
            });

            $dynRendsColContent.find("coral-columnview-item:first").click();
        }

        function addOriginalImage(){
            var origImgSrc = $colPreview.find("img").attr("src"),
                data = { image : origImgSrc, name : "Original" };

            var $orig = $(getDynamicRenditionsHtml(thumbPath, data, assetPath)).appendTo($dynRendsColContent);

            $orig.click(showDynRendImage);
        }

        function showDynRendImage(){
            $colPreview.find("img").attr("src", $(this).attr("data-foundation-collection-item-id"));
            $colPreview.find("#" + EAEM_RENDITION_FIELD).html($(this).find(".foundation-collection-item-title").html());
        }
    }

    function getMetaHtml(assetPath){
        var $meta = $('[data-foundation-collection-item-id="' + assetPath + '"]'),
            metaHtml = "";

        if(_.isEmpty($meta)){
            return metaHtml;
        }

        $meta = $meta.find(".foundation-collection-assets-meta");

        if(_.isEmpty($meta)){
            return metaHtml;
        }

        return $meta[0].outerHTML;
    }

    function getDynamicRenditionsHtml(thumbPath, dynRendition, assetPath) {
        return  '<coral-columnview-item data-foundation-collection-item-id="' + dynRendition.image + '" ' + EAEM_DATA_ASSET_PATH + '="' + assetPath
            +'" ' + EAEM_RENDITION_DATA + '="' + JSON.stringify(dynRendition).replace(/\"/g, """) + '">' +
            '<coral-columnview-item-thumbnail>' +
            '<img src="' + thumbPath + '" style="vertical-align: middle; width: auto; height: auto; max-width: 3rem; max-height: 3rem;">' +
            '</coral-columnview-item-thumbnail>' +
            '<div class="foundation-collection-item-title">' + dynRendition.name + '</div>' +
            '</coral-columnview-item>';
    }

    function getParent() {
        if (window.opener) {
            return window.opener;
        }
        return parent;
    }

    function getSaveHandler(){
        var handlers = $._data(document, "events")["click"];

        return _.reject(handlers, function(handler){
            return (handler.selector != ".asset-picker-done" );
        })[0].handler;
    }

    function getUIWidget(selector){
        if(_.isEmpty(selector)){
            return;
        }

        var deferred = $.Deferred();

        var INTERVAL = setInterval(function(){
            var $widget = $(selector);

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

            clearInterval(INTERVAL);

            deferred.resolve($widget);
        }, 100);

        return deferred.promise();
    }
}(jQuery, jQuery(document)));