Expand my Community achievements bar.

Add Custom button in AEM sites console

Avatar

Level 2

Hi AEM Community, I'm working on migration project , migrating from classic UI to touch UI. we have one button on siteadmin navigation bar called "start workflow" when clicked content tree dialog will open first where user where dialog will open with selected page(s) will show along next and cancel button if user selects next button then workflow model screen will load where model will be prepopulated.

 

To achieve same in touch UI I have create a button at /apps/wcm/core/content/sites/jcr:content/actions/secondary/wokflow-button and below is xml

<?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:primaryType="nt:unstructured">
<actions jcr:primaryType="nt:unstructured">
<secondary jcr:primaryType="nt:unstructured">
<workflow-button
granite:rel="workflow-wizard"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
class="cq-admin-action-bar-activator"
icon="workflow"
text="start workflow"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
cq-admin-activator-src="/apps/project/tools/start-workflow.html"/>
</secondary>
<selection/>
</actions>
</jcr:content>
<jcr_content/>
</jcr:root>

which is visble on sites console and i created dialog at /apps/project/tools/start-workflow

<?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="cq:Page">
<jcr:content
jcr:primaryType="nt:unstructured"
jcr:title="Start Workflow"
sling:resourceType="granite/ui/components/coral/foundation/page">
<head jcr:primaryType="nt:unstructured">
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="coralui3,granite.ui.coral.foundation,granite.ui.shell,cq.wcm.admin"/>
</head>
<body jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<form
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
action="/bin/startworkflow"
class="foundation-form foundation-wizard-form"
method="post"
style="vertical">
<successresponse
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/responses/reload"/>
<items jcr:primaryType="nt:unstructured">
<wizard
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<selectPayload
jcr:primaryType="nt:unstructured"
jcr:title="Select Content"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<path
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
fieldLabel="Content to start workflow on"
name="path"
required="{Boolean}true"
rootPath="/content/project"/>
</items>
</selectPayload>
<selectModel
jcr:primaryType="nt:unstructured"
jcr:title="Select Workflow"
sling:resourceType="granite/ui/components/coral/foundation/form/fieldset">
<items jcr:primaryType="nt:unstructured">
<model
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Workflow Model"
name="model"
required="{Boolean}true"
value="/conf/global/settings/workflow/models/project/workflowmodel">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="cq/core/content/projects/workflow/datasource/models"
itemResourceType="granite/ui/components/coral/foundation/select/item"/>
</model>
</items>
</selectModel>
</items>
</wizard>
</items>
</form>
</items>
</body>
<actions jcr:primaryType="nt:unstructured">
<cancel
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
class="foundation-wizard-control"
data-foundation-wizard-control-action="cancel"
text="Cancel"/>
<next
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
class="foundation-wizard-control"
data-foundation-wizard-control-action="next"
text="Start Workflow"
variant="primary"/>
</actions>
</jcr:content>
</jcr:root>

 

am able to load dialog if I access directly with path but not loading upon clicking on button

please let me know if someone had worked on it or know the issue, I'm facing attaching classic UI screenshot for reference

sams16001423_0-1762955499442.png

 

 

Thanks in advance.

 
 
7 Replies

Avatar

Level 10

hi @sams16001423

With the touch UI, this is possible out of the box. See this page for a detailed explanation: https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/sites/authoring/... 

I want to highlight that you can select multiple nodes before clicking the Create > Workflow button, and all those pages will already be set as targets. Additionally, as a final step, the user can choose to add other pages directly from the wizard window before completing the process.

Avatar

Employee Advisor

Hello @sams16001423 ,

 

The dialog itself is fine (it loads directly), so the problem is the activator wiring on the Sites console. Quick checklist & fix:

  1. Check browser Console for JS errors or failing requests when you click the button.

  2. Ensure the activator node is under /apps/wcm/core/content/sites/jcr:content/actions/secondary and the author user can read /apps/project/tools/start-workflow.

  3. Make sure cq-admin-activator-src is present (use .html and try ?wcmmode=disabled).

  4. Quick sanity test (paste in console on Sites page):

window.open('/apps/project/tools/start-workflow.html?wcmmode=disabled','startWorkflowTest','width=900,height=700');


-> Minimal activator example (compare property names/placement):


<workflow-button class="cq-admin-action-bar-activator" sling:resourceType="granite/ui/components/coral/foundation/anchorbutton">
<granite:data>
<cq-admin-activator-src>/apps/project/tools/start-workflow.html?wcmmode=disabled</cq-admin-activator-src>
</granite:data>
</workflow-button>

If it still fails, paste the Console error / Network request.



Avatar

Level 2

dialog loading now @ManviSharma  but it is not loading workflow model drop down I have attached the test xml's above. please see if you can help thanks in advance!. 

Avatar

Level 5

Hi @sams16001423 ,

Try this below approch:

 

Use AEM’s activator pattern instead of granite:rel.

<workflow-button
    jcr:primaryType="nt:unstructured"
    sling:resourceType="cq/gui/components/common/admin/ext/activator"
    text="Start Workflow"
    icon="workflow"
    variant="primary">
    <granite:data
        jcr:primaryType="nt:unstructured"
        href="/apps/project/tools/start-workflow.html"
        target="_blank"
        rel="noopener noreferrer"/>
</workflow-button>

This will open your dialog in a new tab.
Optional (for popup behavior):
Add JS in a clientlib:

$(document).on("click", ".cq-admin-action-bar-activator[icon='workflow']", function(e){
  e.preventDefault();
  Granite.UI.Foundation.Utils.openWindow("/apps/project/tools/start-workflow.html",
    "Start Workflow", { modal: true, width: 800, height: 600 });
});

 

Avatar

Level 2

no its not working @VishalKa5 

Avatar

Level 2

I have created on button as below 

 


<?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:primaryType="nt:unstructured">
<actions jcr:primaryType="nt:unstructured">
<secondary jcr:primaryType="nt:unstructured">
<test-imp/>
<test-workflow
granite:rel="test-workflow-wizard cq-admin-action-bar-activator"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
icon="workflow"
text="start test workflow"
variant="primary">
</test-workflow>
</secondary>
</actions>
</jcr:content>
<jcr_content/>
</jcr:root>

 

my dialog


<?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="cq:Page">
<jcr:content
jcr:primaryType="nt:unstructured"
jcr:title="Start test Workflow"
sling:resourceType="granite/ui/components/coral/foundation/page">
<head jcr:primaryType="nt:unstructured">
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="coralui3,granite.ui.coral.foundation,granite.ui.shell,cq.wcm.admin"/>
</head>
<body
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<form
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
action="/bin/startworkflow"
class="foundation-form foundation-wizard-form"
method="post"
style="vertical">
<successresponse
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/responses/reload"/>
<items jcr:primaryType="nt:unstructured">
<wizard
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<selectPayload
jcr:primaryType="nt:unstructured"
jcr:title="Select Content"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<path
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
fieldLabel="Content to start workflow on"
name="path"
required="{Boolean}true"
rootPath="/content/gtest"/>
</items>
</selectPayload>
<selectModel
jcr:primaryType="nt:unstructured"
jcr:title="Select Workflow"
sling:resourceType="granite/ui/components/coral/foundation/form/fieldset">
<items jcr:primaryType="nt:unstructured">
<model
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Workflow Model"
name="model"
required="{Boolean}true"
value="/conf/global/settings/workflow/models/project/test_Simple_Deploy">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="cq/core/content/projects/workflow/datasource/models"
itemResourceType="granite/ui/components/coral/foundation/select/item"/>
</model>
</items>
</selectModel>
</items>
</wizard>
</items>
</form>
</items>
</body>
<actions jcr:primaryType="nt:unstructured">
<cancel
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
class="foundation-wizard-control"
data-foundation-wizard-control-action="cancel"
text="Cancel"/>
<next
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
class="foundation-wizard-control"
data-foundation-wizard-control-action="next"
text="Start Workflow"
variant="primary"/>
</actions>
</jcr:content>
</jcr:root>

My JS
(function(document, $) {
'use strict';


function attachClickHandler() {
const buttonSelector = 'button[trackingelement="start test workflow"]';
const button = document.querySelector(buttonSelector);

if (button && !button.dataset.v2FixAttached) {

button.dataset.v2FixAttached = 'true';
console.log("test WF FIX: Button found. Attaching click listener.");

button.addEventListener('click', async function(event) {
event.preventDefault();
event.stopPropagation();

try {
const selectedItem = document.querySelector('coral-columnview-item[selected]');
if (!selectedItem) {
alert("Please select a page before starting the workflow.");
return;
}
const pagePath = selectedItem.dataset.foundationCollectionItemId;
const dialogContentUrl = `/apps/project/tools/start-workflow.content.html?item=${encodeURIComponent(pagePath)}`;

const response = await fetch(dialogContentUrl);
if (!response.ok) {
alert(`Error loading dialog: Server responded with status ${response.status}.`);
return;
}
const dialogHtml = await response.text();

const dialog = new Coral.Dialog();
dialog.id = 'test-workflow-dialog';
dialog.header.textContent = 'Start test Workflow';
dialog.content.innerHTML = dialogHtml;

dialog.on('coral-overlay:open', function() {
console.log("test WF FIX: Dialog opened. Triggering foundation-contentloaded to activate components.");
$(dialog.content).trigger('foundation-contentloaded');
});

document.body.appendChild(dialog);
dialog.show();

} catch (error) {
console.error("test WF FIX: A critical error occurred.", error);
alert("error occurred. See the console for details.");
}
});
}
}
$(document).on('foundation-contentloaded', attachClickHandler);

})(document, jQuery);

 

with this im able to click on button and dialog opens where i can select the page but workflow model is not clickable

 

 

Avatar

Level 5

The reason your workflow model dropdown is not clickable is because the Granite UI components inside the dynamically injected HTML are not being initialized properly. When you fetch the dialog HTML and inject it into a Coral.Dialog, the Coral and Granite behaviors (like foundation-select) need to be explicitly activated after the content is added.
Here’s what’s happening:

  • The <select> component uses granite/ui/components/coral/foundation/form/select which relies on Coral UI initialization and Granite foundation JS.
  • When you load the HTML via fetch and insert it into the dialog, those scripts don’t automatically run.
  • Triggering foundation-contentloaded on the dialog content is correct, but you also need to ensure Coral upgrades the DOM.

Fix: Initialize Coral + Granite Components After Injection
Update your JS after dialog.content.innerHTML = dialogHtml;:

    console.log("Dialog opened. Initializing Granite and Coral components...");
    // Trigger Granite foundation initialization
    $(dialog.content).trigger('foundation-contentloaded');
    // Upgrade Coral components
    Coral.commons.ready(dialog.content);
});

Coral.commons.ready() ensures Coral UI widgets (like dropdowns) are upgraded and interactive.

1. Include Clientlibs
Make sure your dialog page includes:

<clientlibs
    categories="granite.ui.coral.foundation,cq.authoring.dialog"/>

This loads the required JS for Coral and Granite.

2. Datasource Rendering

The datasource for workflow models (cq/core/content/projects/workflow/datasource/models) must return valid items. Test by opening the dialog directly in the browser.

3. Foundation Wizard Behavior
Since you’re using a custom JS approach, the wizard’s built-in navigation might not work fully. If you only need a single-step form, that’s fine. Otherwise, you’ll need to handle data-foundation-wizard-control-action="next" manually

 

FYI: 

Granite UI components are initialized during page load via foundation-contentloaded. When you inject HTML dynamically, you must manually trigger this event and upgrade Coral components.

 

Thanks