0
votes

I need to create a workflow in AEM that for a page (specified as payload) finds all the assets used on the page and uploads a list of them to an external service. So far I have most of the code ready, but business process requires me to use a special code for each of the pages (different for each run of the workflow), so that the list is uploaded to correct place.

That is when I have a question - Can you somehow add more input values for an AEM workflow? Maybe by extending the starting dialog, or adding some special step that takes user input? I need to be able to somehow specify the code when launching the workflow or during its runtime.

I have read a lot of documentation but as this is my first time using workflows, I might be missing something really obvious. I will be grateful for any piece of advice, including a link to a relevant piece of docs.

2

2 Answers

0
votes

You could:

  1. Create a custom menu entry somewhere in AEM (e.g. Page Editor, /apps/wcm/core/content/editor/_jcr_content/content/items/content/header/items/headerbar/items/pageinfopopover/items/list/items/<my-action>, see under libs for examples)

  2. Create a client-library with the categories="[cq.authoring.editor]". So it is loaded as part of the page editor (and not inside the iframe with your page)

  3. Create a JS-Listener, that opens a dialog if the menu-entry was clicked (see code). You can either use plain Coral UI dialogs, or my example misused a Granite page dialog (Granite reads the data-structure in cq:dialog, and creates a Coral UI component edit-dialog out of it - while Coral is the plain JS UI-framework)

  4. Create a Java-Servlet, that catches your request, and creates the workflow. You could theoretically use the AEM servlet. But I often have to write my own, because it lacks some features.


Here is the JS Listener:

/*global Granite,jQuery,document,window */
(function ($, ns, channel, window) {
    "use strict";

    var START_WORKFLOW_ACTIVATOR_SELECTOR = ".js-editor-myexample-activator";

    function onSuccess() {
        ns.ui.helpers.notify({
            heading: "Example Workflow",
            content: "successfully started",
            type: ns.ui.helpers.NOTIFICATION_TYPES.SUCCESS
        });
    }

    function onSubmitFail(event, jqXHR) {
        var errorMsg = Granite.I18n.getVar($(jqXHR.responseText).find("#Message").html());
        ns.ui.helpers.notify({
            heading: "Example Workflow",
            content: errorMsg,
            type: ns.ui.helpers.NOTIFICATION_TYPES.ERROR
        });
    }

    function onReady() {
        // add selector for special servlet to form action-url
        var $form = ns.DialogFrame.currentFloatingDialog.find("form");
        var action = $form.attr("action");
        if (action) {
            $form.attr("action", action + ".myexample-selector.html");
        }

        // register dialog-fail event, to show a relevant error message
        $(document).on("dialog-fail", onSubmitFail);

        // init your dialog here ...
    }


    function onClose() {
        $(document).off("dialog-fail", onSubmitFail);
    }

    // Listen for the tap on the 'myexample' activator
    channel.on("click", START_WORKFLOW_ACTIVATOR_SELECTOR, function () {
        var activator = $(this);
        // this is a dirty trick, to use a Granite dialog directly (point to data-structure like in cq:dialog)
        var dialogUrl = Granite.HTTP.externalize("/apps/...." + Granite.author.ContentFrame.getContentPath());

        var dlg = new ns.ui.Dialog({
            getConfig: function () {
                return {
                    src: dialogUrl,
                    loadingMode: "auto",
                    layout: "auto"
                }
            },
            getRequestData: function () {
                return {};
            },
            "onSuccess": onSuccess,
            "onReady": onReady,
            "onClose": onClose
        });

        ns.DialogFrame.openDialog(dlg);
    });

}(jQuery, Granite.author, jQuery(document), window));

And here is the servlet

@Component(service = Servlet.class,
        property = {
                SLING_SERVLET_RESOURCE_TYPES + "=cq:Page",
                SLING_SERVLET_SELECTORS + "=myexample-selector",
                SLING_SERVLET_METHODS + "=POST",
                SLING_SERVLET_EXTENSIONS + "=html"
        })
public class RequestExampleWorkflowServlet extends SlingAllMethodsServlet {

    @Override
    protected void doPost(@Nonnull SlingHttpServletRequest request, @Nonnull SlingHttpServletResponse response) throws IOException {

        final Page page = request.getResource().adaptTo(Page.class);

        if (page != null) {
            Map<String, Object> wfMetaData = new HashMap<>();
            wfMetaData.put("workflowTitle", "Request Translation for " + page.getTitle());
            wfMetaData.put("something", "Hello World");

            try {
                WorkflowSession wfSession = request.getResourceResolver().adaptTo(WorkflowSession.class);
                if (wfSession != null) {
                    WorkflowModel wfModel = wfSession.getModel("/var/workflow/models/example-workflow");
                    WorkflowData wfData = wfSession.newWorkflowData(PayloadInfo.PAYLOAD_TYPE.JCR_PATH.name(), page.getPath());
                    wfSession.startWorkflow(wfModel, wfData, wfMetaData);
                    MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_OK, "Triggered Example Workflow");
                } else {
                    throw new WorkflowException("Cannot retrieve WorkflowSession");
                }
            } catch (WorkflowException e) {
                MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
            }
        } else {
            MyServletUtil.respondSlingStyleHtml(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal error - cannot get page");
        }
    }
}