Goal
In this post we are going to add the necessary logic to show scheduled activations of page or group of pages in the Activate Later dialog of siteadmin console (http://localhost:4502/siteadmin). Package is available for install,Source code and demo video are available for download
Activate Later
Customized Activate Later
Prerequisites
If you are new to CQ
1) Read this post on how to create a sample page component
2) Read this post on how to setup your IDE and create an OSGI component
1) There are two parts. First, code servlet to return the scheduled activations of page.
2) Add Scheduled Activations grid to the siteadmin console Activate Later dialog.
1) The first step is creating a servlet and registering it as an OSGI component in CQ. Read this post on how to create an OSGI component for deploying to CQ.
2) Create servlet ScheduledActivationInstances and add the following code
3) Deploy the servlet and check if its working by accessing url http://localhost:4502/bin/mycomponents/schactivation?path=/content/geometrixx/es. If the page /content/geometrixx/es has scheduled activations set, you should see the following response
{"data":[{"path":"/content/geometrixx/es","dt":"Fri, 15 Nov, 2013 12:24","id":"/etc/workflow/instances/2013-11-08/model_12548425989383","name":"Español","st":"Fri, 08 Nov, 2013 12:24","ini":"admin"},{"path":"/content/geometrixx/es","dt":"Sat, 07 Dec, 2013 16:17","id":"/etc/workflow/instances/2013-11-08/model_26521845578422","name":"Español","st":"Fri, 08 Nov, 2013 16:17","ini":"admin"}]}
1) The next step is extending siteadmin console UI and add Activations grid to Activate Later dialog
2) Use the overlay architecture of CQ to overlay /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js. Create file /apps/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js and add the following code
3) Done
2) Read this post on how to setup your IDE and create an OSGI component
Solution
1) There are two parts. First, code servlet to return the scheduled activations of page.
2) Add Scheduled Activations grid to the siteadmin console Activate Later dialog.
Create Servlet
1) The first step is creating a servlet and registering it as an OSGI component in CQ. Read this post on how to create an OSGI component for deploying to CQ.
2) Create servlet ScheduledActivationInstances and add the following code
package apps.mysample.scheduledactivations;
import com.day.cq.workflow.WorkflowService;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.Workflow;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.metadata.MetaDataMap;
import com.day.cq.workflow.ui.JcrPathBuilderManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.sling.SlingServlet;
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.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Session;
import javax.servlet.ServletException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
@SlingServlet(
paths="/bin/mycomponents/schactivation",
methods = "GET",
metatype = false,
label = "Scheduled Activation Instances"
)
public class ScheduledActivationInstances extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(ScheduledActivationInstances.class);
private static SimpleDateFormat FORMATTER = new SimpleDateFormat("EEE, dd MMM, yyyy HH:mm");
private static final String ACTIVATE_MODEL = "/etc/workflow/models/scheduled_activation/jcr:content/model";
private static final String DEACTIVATE_MODEL = "/etc/workflow/models/scheduled_deactivation/jcr:content/model";
@Reference(policy = ReferencePolicy.STATIC)
private WorkflowService wfService;
@Reference(policy = ReferencePolicy.STATIC)
JcrPathBuilderManager pathBuilder;
@Override
protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
JSONWriter jw = new JSONWriter(response.getWriter());
String pageStr = request.getParameter("path");
try{
jw.object();
if(StringUtils.isEmpty(pageStr)){
jw.key("error").value("page input required");
jw.endObject();
return;
}
String type = request.getParameter("type");
if(StringUtils.isEmpty(type)){
type = "ACTIVATE";
}
pageStr = pageStr.trim();
List<String> pages = Arrays.asList(pageStr.split(","));
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
WorkflowSession wfSession = wfService.getWorkflowSession(session);
String[] state = new String[] { "RUNNING" };
Workflow workflows[] = wfSession.getWorkflows(state);
WorkflowData data = null;
String payload, absTime = null;
MetaDataMap mdMap = null;
Map<String, List<Map<String, String>>> retMap = new HashMap<String, List<Map<String, String>>>();
Map<String, String> map = null;
List<Map<String, String>> list = null;
Resource resource = null;
for(Workflow w : workflows){
data = w.getWorkflowData();
if(type.equals("ACTIVATE") && !w.getWorkflowModel().getId().equals(ACTIVATE_MODEL)){
continue;
}else if(type.equals("DEACTIVATE") && !w.getWorkflowModel().getId().equals(DEACTIVATE_MODEL)){
continue;
}
if (!"JCR_PATH".equals(data.getPayloadType()) && !"URL".equals(data.getPayloadType())) {
continue;
}
if (data.getPayloadType().equals("JCR_PATH") && w.getWorkItems().size() > 0) {
payload = pathBuilder.getPath(w.getWorkItems().get(0));
} else {
payload = (String) data.getPayload();
}
if(StringUtils.isEmpty(payload)){
continue;
}
payload = payload.trim();
if(payload.startsWith("/cf#")){
payload = payload.substring(4);
}
if(payload.endsWith(".html")){
payload = payload.substring(0, payload.lastIndexOf(".html"));
}
if(!pages.contains(payload)){
continue;
}
mdMap = w.getMetaDataMap();
if(mdMap == null){
continue;
}
absTime = mdMap.get("absoluteTime", String.class);
if(StringUtils.isEmpty(absTime)){
continue;
}
list = retMap.get(payload);
if(list == null){
list = new ArrayList<Map<String, String>>();
retMap.put(payload, list);
}
map = new HashMap<String, String>();
list.add(map);
resource = resolver.getResource(payload);
if(resource != null){
map.put("id", w.getId());
map.put("name", resource.getChild("jcr:content").adaptTo(ValueMap.class).get("jcr:title", String.class));
map.put("st",FORMATTER.format(w.getTimeStarted().getTime()));
map.put("ini", w.getInitiator());
map.put("dt", FORMATTER.format(Long.parseLong(absTime)));
}
}
String path = null;
Iterator<Map<String, String>> itr = null;
jw.key("data").array();
for(Map.Entry<String, List<Map<String, String>>> entry : retMap.entrySet()){
list = entry.getValue();
path = entry.getKey();
itr = list.iterator();
while(itr.hasNext()){
jw.object();
jw.key("path").value(path);
for(Map.Entry<String, String> mEntry : itr.next().entrySet()){
jw.key(mEntry.getKey()).value(mEntry.getValue());
}
jw.endObject();
}
}
jw.endArray();
jw.endObject();
}catch(Exception e){
log.error("Error getting schedule activation instances",e);
throw new ServletException(e);
}
}
}
3) Deploy the servlet and check if its working by accessing url http://localhost:4502/bin/mycomponents/schactivation?path=/content/geometrixx/es. If the page /content/geometrixx/es has scheduled activations set, you should see the following response
{"data":[{"path":"/content/geometrixx/es","dt":"Fri, 15 Nov, 2013 12:24","id":"/etc/workflow/instances/2013-11-08/model_12548425989383","name":"Español","st":"Fri, 08 Nov, 2013 12:24","ini":"admin"},{"path":"/content/geometrixx/es","dt":"Sat, 07 Dec, 2013 16:17","id":"/etc/workflow/instances/2013-11-08/model_26521845578422","name":"Español","st":"Fri, 08 Nov, 2013 16:17","ini":"admin"}]}
Extend SiteAdmin UI
1) The next step is extending siteadmin console UI and add Activations grid to Activate Later dialog
2) Use the overlay architecture of CQ to overlay /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js. Create file /apps/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js and add the following code
CQ.Ext.ns("MyClientLib");
MyClientLib.SiteAdmin = {
SA_GRID: "cq-siteadmin-grid",
ACTIVATE_BUT: "Activate",
ACTIVATE_LATER_BUT: "Activate Later...",
ACTIVATE_LATER_DID: "cq-activate-later-dialog",
ACTIVATIONS_GRID_ID_PREFIX: "myclientlib-scheduled-activations-grid",
getGrid: function(config){
var store = new CQ.Ext.data.Store({
baseParams: config.storeBaseParams,
proxy: new CQ.Ext.data.HttpProxy({
"autoLoad":false,
url: "/bin/mycomponents/schactivation",
method: 'GET'
}),
reader: new CQ.Ext.data.JsonReader({
root: 'data',
fields: [
{name: 'id', mapping: 'id'},
{name: 'name', mapping: 'name'},
{name: 'path', mapping: 'path'},
{name: 'actDate', mapping: 'dt'},
{name: 'startDate', mapping: 'st'},
{name: 'initiator', mapping: 'ini'}
]
})
});
store.load();
return new CQ.Ext.grid.GridPanel({
store: store,
id: config.gridId,
colModel: new CQ.Ext.grid.ColumnModel({
defaults: {
width: 120,
sortable: true
},
columns: [
{id: 'name', header: 'Name', width: 80, dataIndex: 'name'},
{id: 'path', header: 'Path', width: 160, dataIndex: 'path'},
{id: 'actDate', width: 160, header: config.actColHeader, dataIndex: 'actDate'},
{id: 'startDate', header: 'Start Date', dataIndex: 'startDate'},
{id: 'initiator', header: 'Initiator', width: 80, dataIndex: 'initiator'}
]
}),
sm: new CQ.Ext.grid.RowSelectionModel(),
tbar: [{
xtype: "tbbutton",
text: 'Terminate',
disabled: true,
tooltip: 'Terminate the selected workflows',
handler: function(){
var commentBox = new CQ.Ext.form.TextArea({
xtype: 'textarea',
name:'terminateComment',
fieldLabel:CQ.I18n.getMessage('Comment')
});
var tConfig = {
xtype: 'dialog',
title:CQ.I18n.getMessage('Terminate Workflow'),
params: {"_charset_":"utf-8"},
items: {
xtype:'panel',
items:[commentBox,{
xtype: 'hidden',
name:'state',
value:'ABORTED'
}]
},
buttons:[{
"text": CQ.I18n.getMessage("OK"),
"handler": function() {
var sGrid = CQ.Ext.getCmp(config.gridId);
var sFunc = function(options, success, response) {
if (!success) {
CQ.Ext.Msg.alert(CQ.I18n.getMessage("Error"),
CQ.I18n.getMessage("Termination of workflow failed"));
}else{
sGrid.getStore().reload();
}
};
CQ.Ext.each(sGrid.getSelectionModel().getSelections(), function(selection){
CQ.HTTP.post(selection.id,sFunc,{
"state":"ABORTED",
"_charset_":"utf-8",
"terminateComment": commentBox.getValue()
}
);
});
this.close();
}
},CQ.Dialog.CANCEL ]
};
var tDialog = CQ.WCM.getDialog(tConfig);
tDialog.show();
}
}],
width: 600,
height: 350,
frame: true,
title: config.title,
style: "margin:25px 0 0 0",
listeners: {
'click': function(){
var button = this.getTopToolbar().find('xtype','tbbutton')[0];
button.setDisabled(false);
}
}
});
},
addGrid: function(grid, config){
var toolBar = grid.getTopToolbar();
var actBut = toolBar.find("text", config.topButtonName)[0];
var actLaterBut = actBut.menu.find("text", config.laterButtonName);
if(!actLaterBut || actLaterBut.length == 0){
return;
}
actLaterBut[0].on('click', function(){
var nextId = CQ.Util.createId(config.dialogIdPrefix);
nextId = parseInt(nextId.substring(nextId.lastIndexOf("-") + 1), 10);
var prevId = config.dialogIdPrefix + "-" + (nextId - 1);
var dialog = CQ.Ext.getCmp(prevId);
if(!dialog){
return;
}
dialog.setWidth(700);
dialog.setHeight(500);
var panel = dialog.findBy(function(comp){
return comp["jcr:primaryType"] == "cq:Panel";
}, dialog);
if(!panel || panel.length == 0){
return;
}
panel = panel[0];
var paths = "";
CQ.Ext.each(grid.getSelectionModel().getSelections(), function(row){
paths = paths+ row.id + ",";
});
var gConfig = {};
gConfig.gridId = CQ.Util.createId(config.gridIdPrefix);
gConfig.title = config.gridTitle;
gConfig.actColHeader = config.actColHeader;
gConfig.storeBaseParams = { path : paths.substr(0, paths.lastIndexOf(",")), type: config.gridType };
panel.add(this.getGrid(gConfig));
panel.doLayout();
},this);
},
addScheduledActivationGrid: function(grid){
var config = {};
config.topButtonName = this.ACTIVATE_BUT;
config.laterButtonName = this.ACTIVATE_LATER_BUT;
config.dialogIdPrefix = this.ACTIVATE_LATER_DID;
config.gridIdPrefix = this.ACTIVATIONS_GRID_ID_PREFIX;
config.gridTitle = 'Pending Scheduled Activations';
config.actColHeader = 'Activation Date';
config.gridType = "ACTIVATE";
this.addGrid(grid, config);
}
};
$.getScript("/libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js", function(){
if(window.location.pathname == "/siteadmin"){
var INTERVAL = setInterval(function(){
var s = MyClientLib.SiteAdmin;
var grid = CQ.Ext.getCmp(s.SA_GRID);
if(grid){
clearInterval(INTERVAL);
s.addScheduledActivationGrid(grid);
}
}, 250);
}
}).fail(function(jqxhr, settings, exception){
console.log("Error parsing /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js");
console.log(exception);
});
3) Done