Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all 513 articles
Browse latest View live

AEM 61 - TouchUI Rich Text Editor Remove Unused Plugins

$
0
0

Goal


A sample extension for removing unused plugins in RTE (Rich Text Editor) of Touch UI. With the extension Inline Editor is stripped to have buttons for just full screen, close & save; Fullscreen Editor with only switch to inline...

Demo | Package Install


Inline Editor

 


Fullscreen Editor



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touchui-hide-default-rte-plugins

2) Create node /apps/touchui-hide-default-rte-plugins/clientlib of type cq:ClientLibraryFolder and add a String property categories with value rte.coralui2

3) Create file (nt:file) /apps/touchui-hide-default-rte-plugins/clientlib/js.txt and add

                       remove-plugins.js

4) Create file (nt:file) /apps/touchui-hide-default-rte-plugins/clientlib/remove-plugins.js and add the following code.

(function(){
var INLINE_TOOLBAR = [ "fullscreen#start", "control#close", "control#save"],
FULLSCREEN_TOOLBAR = [ "fullscreen#finish"];

var EAMCuiToolbarBuilder = new Class({
toString: "EAEMCuiToolbarBuilder",

extend: CUI.rte.ui.cui.CuiToolbarBuilder,

_getUISettings: function(options) {
var uiSettings = this.superClass._getUISettings(options);

//inline toolbar - "#format", "#justify", "#lists", "links#modifylink",
// "links#unlink", "fullscreen#start", "control#close", "control#save"
uiSettings["inline"]["toolbar"] = INLINE_TOOLBAR.slice(0);

//fullscreen toolbar - "format#bold", "format#italic", "format#underline",
// "fullscreen#finish"....
uiSettings["fullscreen"]["toolbar"] = FULLSCREEN_TOOLBAR.slice(0);

return uiSettings;
}
});

var EAEMToolkitImpl = new Class({
toString: "EAEMToolkitImpl",

extend: CUI.rte.ui.cui.ToolkitImpl,

createToolbarBuilder: function() {
return new EAMCuiToolbarBuilder();
}
});

CUI.rte.ui.ToolkitRegistry.register("cui", EAEMToolkitImpl);
}());





AEM 61 - TouchUI Assets Console show node name instead of dc:title or jcr:title

$
0
0

Goal


In all 3 views (card, list, column) of Touch UI Assets Console, show the node name (filename) and NOT dc:title for assets or jcr:title for folders

Demo | Package Install


dc:title for Asset



Product



Extension



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/touchui-assets-show-filenames

2) Create clientlib (type cq:ClientLibraryFolder/apps/touchui-assets-show-filenames/clientlib, set a property categories of String type to granite.ui.foundation.admin and dependencies of type String[] to underscore

3) Create file ( type nt:file ) /apps/touchui-assets-show-filenames/clientlib/js.txt, add the following

                         show-file-name.js

4) Create file ( type nt:file ) /apps/touchui-assets-show-filenames/clientlib/show-file-name.js, add the following code

(function ($, $document) {
"use strict";

var DAM_ADMIN_CHILD_PAGES = "cq-damadmin-admin-childpages",
LOAD_EVENT = "coral-columnview-load",
FOUNDATION_LAYOUT_CARD = ".foundation-layout-card",
NS_COLUMNS = ".foundation-layout-columns";

$document.on("foundation-mode-change", function(e, mode, group){
//not assets console, return
if(group != DAM_ADMIN_CHILD_PAGES){
return;
}

showFileName();

var $collection = $(".foundation-collection[data-foundation-mode-group=" + group + "]");

//for column view
$collection.on(LOAD_EVENT, function(){
setTimeout( showFileName ,200);
});

//for column view select
$collection.on("coral-columnview-item-select" + NS_COLUMNS, showFileName);

if (!$collection.is(FOUNDATION_LAYOUT_CARD)) {
return;
}

var $scrollContainer = $collection.parent();

//for card view scroll
$scrollContainer.on("scroll" + FOUNDATION_LAYOUT_CARD, _.throttle(function(){
var paging = $collection.data("foundation-layout-card.internal.paging");

if(!paging.isLoading){
return;
}

var INTERVAL = setInterval(function(){
if(paging.isLoading){
return;
}

clearInterval(INTERVAL);

showFileName();
}, 250);
}, 100));
});

function showFileName(){
var $articles = $("article"), $article, name;

$articles.each(function(index, article){
$article = $(article);

name = getStringAfterLastSlash($article.data("path"));

$article.find("h4").html(name);
$article.find(".coral-ColumnView-label").html(name);
});
}

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

return str.substr(str.lastIndexOf("/") + 1);
}
})(jQuery, jQuery(document));

AEM 61 - TouchUI Slide Show Component with Image Multifield

$
0
0

Goal


Create a TouchUI slideshow component rendered using Sightly, images added using Image Multifield

This post focuses on creating a slideshow using sightly templating language with the images added in multifield. For more information on image multifield widget check this post

For ClassicUI slide show component check this post

To learn more about sightly controls check adobe documentation

Hotfix 6670 must be installed for this widget extension to work

Demo | Package Install




Solultion


1) Assuming user added the images for slideshow component using image multifield widget; the following structure gets created in CRX



2) To render images using html, create the component /apps/touchui-sightly-image-multifield/sample-image-multifield with following structure


3)  The file /apps/touchui-sightly-image-multifield/sample-image-multifield/sample-image-multifield.html uses sightly to create html. Add the following code to prepare html for slideshow..

<div    data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}"
style="display: block; padding: 10px">

<sly data-sly-call="${clientLib.all @ categories='eaem.slideshow'}" />

<b>Image Multi Field Sample</b>

<div data-sly-use.images="imgHelper.js" data-sly-unwrap>
<div data-sly-test="${images.gallery}" style="margin-bottom: 15px">
Gallery - ${images.gallery}
</div>

<div data-sly-test="${!images.artists && wcmmode.edit}">
Add images using component dialog
</div>

<div data-sly-test="${images.artists}" class="eaem-slideshow">
<div data-sly-list.artist="${images.artists}">
<div class="show-pic ${artistList.first ? 'active' : ''}">
<img src="${artist.painting}" height="530px" width="700px"/>
<div class="overlayText">
<span class="overlayTitle">${artist.name}</span>
<div class="overlayDesc">${artist.desc}</div>
</div>
</div>
</div>
</div>
</div>
</div>

4) #4 adds the clientlib eaem.slideshow with necessary css and js logic (available in package install) to run a simple slideshow with images. #8 initializes helper object (defined in java script) and exposes it through the variable images using sightly use-api

5) Create file /apps/touchui-sightly-image-multifield/sample-image-multifield/imgHelper.js with necessary logic to read the CRX node structure and create a js object for sightly script added in step3

"use strict";

use( ["/libs/wcm/foundation/components/utils/ResourceUtils.js" ], function(ResourceUtils){
var images = {}, properties = granite.resource.properties,
artistsPath = granite.resource.path + "/artists", counter = 1, artist;

images.gallery = properties["gallery"];
images.artists = undefined;

function recursiveImageRead(path){
ResourceUtils.getResource(path)
.then(addImage);
}

function addImage(artistRes){
if(!images.artists){
images.artists = [];
}

properties = artistRes.properties;

artist = {
painting: properties["paintingRef"],
desc: properties["desc"],
name: properties["artist"]
};

images.artists.push(artist);

recursiveImageRead(artistsPath + "/" + counter++);
}

recursiveImageRead(artistsPath + "/" + counter++);

return images;
} );


AEM 61 - TouchUI Date Picker Validator Comparing Two Date Fields in Dialog

$
0
0

Goal


Add a Validator on Granite (Coral) Date Picker in TouchUI Dialogs

Page properties dialog has two fields OnTime and OffTime for Activation / Deactivation; for demo purposes, validator here checks if date of OffTime is lesser than OnTime

Demo shows dialog of foundation page component modified to add the eaemCheckDateAfter config (/libs/foundation/components/page/cq:dialog/content/items/tabs/items/basic/items/column/items/onofftime/items/offdate). This is just for demonstration only (on Geometrixx pages), ideally the foundation components should never be altered

Demo | Package Install


Configuration

Set the property eaemCheckDateAfter value to the name of dialog property to check against. Here its set to ./onTime




Inline Dialog - Error Shown when Off Time < On Time



Full Screen Dialog - Error Shown when Off Time < On Time

 


Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/touchui-date-picker-validator

2) Create clientlib (type cq:ClientLibraryFolder/apps/touchui-date-picker-validator/clientlib and set a property categories of String type to cq.authoring.dialogdependencies of type String[] with value underscore

3) Create file ( type nt:file ) /apps/touchui-date-picker-validator/clientlib/js.txt, add the following

                         date-validator.js

4) Create file ( type nt:file ) /apps/touchui-date-picker-validator/clientlib/date-validator.js, add the following code

(function ($) {
var EAEM_CHECK_DATE_AFTER = "eaemcheckdateafter",
fieldErrorEl = $("<span class='coral-Form-fielderror coral-Icon coral-Icon--alert coral-Icon--sizeS'" +
"data-init='quicktip' data-quicktip-type='error' />");

$.validator.register({
selector: "input",
validate: validate, //if validate() returns a non empty value, show() is called
show: show,
clear: clear
});

function validate($el){
//if not date widget or widget value is empty, return
if(!$el.parent().hasClass("coral-DatePicker") || _.isEmpty($el.val())){
return;
}

var $datePicker = $el.parent(),
$form = $datePicker.closest("form"),
checkDateAfter = $datePicker.data(EAEM_CHECK_DATE_AFTER);

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

var $toCompareField = $form.find("[name='" + checkDateAfter + "']");

if(_.isEmpty($toCompareField) || _.isEmpty($toCompareField.val())){
return;
}

var toCompareMillis = new Date($toCompareField.val()).getTime(),
compareWithMillis = new Date($el.val()).getTime(),
text = $toCompareField.closest(".coral-Form-fieldwrapper").find(".coral-Form-fieldlabel").html();

return ( compareWithMillis < toCompareMillis) ? "Should not be less than '" + text + "'" : null;
}

function show($el, message){
if(!$el.parent().hasClass("coral-DatePicker")){
return;
}

var $datePicker = $el.parent();

this.clear($el);

var arrow = $datePicker.closest("form").hasClass("coral-Form--vertical") ? "right" : "top";

$el.attr("aria-invalid", "true").toggleClass("is-invalid", true);

fieldErrorEl.clone()
.attr("data-quicktip-arrow", arrow)
.attr("data-quicktip-content", message)
.insertAfter($datePicker);
}

function clear($el){
if(!$el.parent().hasClass("coral-DatePicker")){
return;
}

var $datePicker = $el.parent();

$el.removeAttr("aria-invalid").removeClass("is-invalid");

$datePicker.nextAll(".coral-Form-fielderror").tooltip("hide").remove();
}
}(jQuery));

AEM 61 - This And That

$
0
0


AEM_61_RECOMPILE_JSPS

1)  In AEM <= 6.0 the compiled classes are placed in CRX /var/classes - http://localhost:4502/crx/de/index.jsp#/var/classes. To force recompile any jsp files, deleting the node in /var/classes helped; with AEM 61, the compiled class files are NOT placed in /var/classes any more, so to recompile jsps, use Felix Console - http://localhost:4502/system/console/slingjsp or to be 100% sure to the naked eye, follow these steps

             a. Stop bundle org.apache.sling.commons.fsclassloader
             b. Search with keyword classes in CQ install folder <author>\crx-quickstart\launchpad\felix
             c. Delete generated java/class files from <author>\crx-quickstart\launchpad\felix\<bundleXYZ>\data\classes
             d. Delete generated java files from /var/classes in CRX (sightly generated java files are placed here)
             e. Restart bundle org.apache.sling.commons.fsclassloader




AEM_61_OSGI_INSTALL_ACTION

2)  OSGI action when updating packages in AEM. Packages with SNAPSHOT in name have special meaning; SNAPSHOT packages are only for Dev environment. Thanks to Ian Boston for the tip

Current VersionNew VersionAction
1.0.221.0.23Install
1.0.221.0.23-SNAPSHOTInstall
1.0.231.0.23-SNAPSHOTIgnore
1.0.23-SNAPSHOT1.0.23-SNAPSHOTInstall
1.0.23-SNAPSHOT1.0.23-r231423423Install
1.0.23-r2314234231.0.23-r231423423Ignore
1.0.23-r2314234231.0.23-r231423424Install (last digit 4 > 3)
1.0.23-SNAPSHOT1.0.23-T1r231423423Install
1.0.23-T1r2314234231.0.23-T2r231423423Install (Patch revision number - T2r > T1r)




AEM_61_ADD_WORKFLOW_ADMINISTRATORS

3)  By default, only administrators can view all workflow instancesto add users/groups for administering workflows (view, terminate, monitor workflows created by other users), give necessary read/write permissions on /etc/workflow/instances and add user/group to Superuser of Adobe Granite Workflow Service - http://localhost:4502/system/console/configMgr/com.adobe.granite.workflow.core.WorkflowSessionFactory







4)  By default, when html tags like hyperlink from an external website are copied into Rich Text Editor (not source edit mode) all attributes are NOT copied (so you may only see href copied and not others ). To copy other attributes like say class, role etc. htmlPasteRules have to be defined in the edit plugin of RTE. For more options check the documentation


To copy css classes, make sure cssMode is defined










AEM 61 - Classic UI Limit Multifield Widget

$
0
0

Goal


Extend CQ.form.Multifield to create a LimitMultfield to limit the number of items added in the widget

For TouchUI check this post

Demo | Package Install


Configuration



Error



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classic-ui-multi-field-limit-items

2) Create node /apps/classic-ui-multi-field-limit-items/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.widgets

3) Create file (nt:file) /apps/classic-ui-multi-field-limit-items/clientlib/js.txt and add

                       multi-field-limit-items.js

4) Create file (nt:file) /apps/classic-ui-multi-field-limit-items/clientlib/multi-field-limit-items.js and add the following code.

CQ.Ext.ns("ExperienceAEM");

ExperienceAEM.LimitMultiField = CQ.Ext.extend(CQ.form.MultiField, {
initComponent: function () {
ExperienceAEM.LimitMultiField.superclass.initComponent.call(this);

if(!this.limit){
return;
}

this.on("beforeadd", function(){
var items = this.findByType(this.fieldConfig.xtype);

if(items.length < parseInt(this.limit)){
return;
}

CQ.Ext.Msg.alert('Error', 'More than ' + this.limit + " not allowed");

return false;
});
}
});

CQ.Ext.reg("limit-multifield", ExperienceAEM.LimitMultiField);


AEM 61 - TouchUI Asset Finder Default To Pages

$
0
0

Goal


When loaded in browser, Asset Finder of TouchUI Editor shows Images by default; this post is on changing the default to Pages

A sample Asset Finder group and registering controller is available here

Demo | Package Install




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touchui-asset-finder-default-page

2) Create node /apps/touchui-asset-finder-default-page/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.editor.hook.assetfinder

3) Create file (nt:file) /apps/touchui-asset-finder-default-page/clientlib/js.txt and add

                       default-to-page.js

4) Create file (nt:file) /apps/touchui-asset-finder-default-page/clientlib/default-to-page.js and add the following code

(function ($, $document) {
"use strict";

//id assetfinder-filter and class .assetfilter.type are defined in
///libs/wcm/core/content/editor/jcr:content/sidepanels/edit/items/assetsTab/items/filterPanel/items/views/items/search/items/searchpanel
var PAGE_CONTROLLER = "Pages",
ASSET_FINDER_FILTER = "#assetfinder-filter",
ASSET_FILTER_SELECTOR = ".assetfilter.type";

$document.on("cq-content-frame-loaded", makePageOptionDefault);

function makePageOptionDefault(){
var $assetFinderFilter = $(ASSET_FINDER_FILTER),
$assetFinderType = $assetFinderFilter.find(ASSET_FILTER_SELECTOR),
cuiSelect = $assetFinderType.data("select");

cuiSelect.setValue(PAGE_CONTROLLER);

$assetFinderType.trigger($.Event('selected', {
selected: PAGE_CONTROLLER
}));
}
})(jQuery, jQuery(document));

AEM 61 - Classic UI Add Assets Count Column to Dam Admin Grid

$
0
0

Goal


Add a new column Asset Count, showing the count of assets in a folder (non-recursive) in DAM Admin console grid of Classic UI

For similar extension in Touch UI check this post

Demo | Package Install





Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/classicui-damadmin-show-asset-count

2) Create clientlib (type cq:ClientLibraryFolder/apps/classicui-damadmin-show-asset-count/clientlib and set property categories of String type to cq.widgets and dependencies to underscore

3) Create file ( type nt:file ) /apps/classicui-damadmin-show-asset-count/clientlib/js.txt, add the following

                         show-count.js

4) Create file ( type nt:file ) /apps/classicui-damadmin-show-asset-count/clientlib/show-count.js, add the following code

(function(){
if(window.location.pathname !== "/damadmin"){
return;
}

var DA_GRID = "cq-damadmin-grid", DA_TREE = "cq-damadmin-tree",
ASSET_COUNT = "assetCount", assetCountCache = {};

var DA_INTERVAL = setInterval(function(){
var grid = CQ.Ext.getCmp(DA_GRID);

if(grid && ( grid.rendered == true)){
var cm = grid.getColumnModel(),
damAdmin = grid.findParentByType("siteadmin");

if(!cm || !cm.columns) {
return;
}

clearInterval(DA_INTERVAL);

addCountColumn(grid, damAdmin);

handleGridClick(grid, damAdmin);

handleTreeClick(damAdmin)
}
}, 250);

function addCountColumn(grid, damAdmin) {
var cm = grid.getColumnModel();

var cColumn = new CQ.Ext.grid.Column({
"header": "Asset Count",
"id": "assetCount",
width: 100,
"renderer": function (v, params, record) {
if (_.isEmpty(assetCountCache[record.data.label])) {
return "0";
}

var assets = _.reject(assetCountCache[record.data.label], function (value, key) {
return (key.indexOf("jcr:") == 0) || value["jcr:primaryType"] == "sling:OrderedFolder";
});

return Object.keys(assets).length;
}
});

cm.columns.splice(4, 0, cColumn);

cm.lookup[ASSET_COUNT] = cColumn;

fetchData(location.hash.substr(1), damAdmin);
}

function fetchData(path, damAdmin){
$.ajax(path + ".2.json").done(function(data){
assetCountCache = data;
damAdmin.reloadPages();
});
}

function handleGridClick(grid, damAdmin){
grid.on("rowdblclick", function(){
fetchData(this.getSelectionModel().getSelected().id, damAdmin);
});
}

function handleTreeClick(damAdmin){
var tree = CQ.Ext.getCmp(DA_TREE);

tree.on("click", function(node){
fetchData(node.getPath(), damAdmin);
});
}
})();


AEM 61 - Classic UI Limit Components Added in Parsys

$
0
0

Goal


Limit the number of Components added in a Parsys of Classic UI

For Touch UI check this post

Demo | Package Install

For simplicity, lets assume user admin can only configure the component limit on a parsys; In the demo user admin (logged in Firefox) configures the component limit and user author (logged in Chrome) adds components to the limit parsys


Set Component Limit Menu Item




Set Component Limit Dialog

 


Component Limit on Parsys in CRX



Limit Exceeded - New Menu Item Disabled



Limit Exceeded - Error on Component Drop




Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/classicui-limit-parsys

2) Create clientlib (type cq:ClientLibraryFolder/apps/classicui-limit-parsys/clientlib and set property categories of String type to cq.widgets and dependencies to underscore

3) Create file ( type nt:file ) /apps/classicui-limit-parsys/clientlib/js.txt, add the following

                         limit-parsys.js

4) Create file ( type nt:file ) /apps/classicui-limit-parsys/clientlib/limit-parsys.js, add the following code

(function(){
var pathName = window.location.pathname;

if( ( pathName !== "/cf" ) && ( pathName.indexOf("/content") !== 0)){
return;
}

var EAEM_COMPONENT_LIMIT = "eaemComponentLimit";

CQ.Ext.onReady(function() {
CQ.WCM.on("editablesready", applyLimitAndExtendDrop, this);
});

function isParsysNew(editable){
return _.isObject(editable.params)
&& (editable.params["./sling:resourceType"] == CQ.wcm.EditBase.PARSYS_NEW);
}

function addMenuItem(){
var component = this.element.linkedEditComponent;

if (!component || !component.menuComponent) {
return;
}

var menu = component.menuComponent;

if (menu.eaemLimitSet) {
return;
}

menu.eaemLimitSet = true;

var path = this.getParentPath(),
limit = isWithinLimit(path);

var newItem = menu.findBy(function(comp){
return comp.text === "New...";
})[0];

if(!limit.isWithin){
newItem.setDisabled(true);
}

//for simplicity lets assume only "admin" has set limit access
if(CQ.User.getCurrentUser().getUserID() === "admin"){
menu.addSeparator();

menu.add({
text: "Set Component Limit",
handler: function () {
var dialog = getSetLimitDialog(path, limit.currentLimit);
dialog.show();
}
});
}
}

function extendDrop(dropFn){
return function(dragSource, e, data){
var limit = isWithinLimit(this.editComponent.getParentPath());

if(!limit.isWithin){
this.editComponent.hideTarget();
CQ.Ext.Msg.alert('Error', 'More than ' + limit.currentLimit + ' components not allowed');
return false;
}

return dropFn.call(this, dragSource, e, data);
}
}

function applyLimitAndExtendDrop() {
var editables = CQ.utils.WCM.getEditables();

_.each(editables, function (editable) {
if (isParsysNew(editable)) {
editable.emptyComponent.el.on("contextmenu", addMenuItem, editable);
}

var dropTargets = editable.getDropTargets();

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

dropTargets[0].notifyDrop = extendDrop(dropTargets[0].notifyDrop);
});
}

function isWithinLimit(path){
var isWithin = true, currentLimit = "";

$.ajax( { url: path + ".2.json", async: false } ).done(function(data){
if(_.isEmpty(data) || !data[EAEM_COMPONENT_LIMIT]){
return;
}

currentLimit = data[EAEM_COMPONENT_LIMIT];

var compObjects = [], limit = parseInt(data[EAEM_COMPONENT_LIMIT]);

_.each(data, function(value, key){
if(!_.isObject(value)){
return;
}

if(!value["sling:resourceType"]){
return;
}

compObjects.push(value);
});

isWithin = compObjects.length < limit;
});

return {
isWithin: isWithin,
currentLimit: currentLimit
};
}

function getSetLimitDialog(path, currentLimit){
var dialogConfig = {
"jcr:primaryType": "cq:Dialog",
title: "Components Limit",
modal: true,
width: 400,
height: 150,
items: [{
xtype: "panel",
layout: "form",
bodyStyle :"padding: 20px",
items: [{
value: currentLimit,
xtype: "textfield",
fieldLabel: "Limit to "
}]
}],
ok: function () {
var limitField = this.findByType("textfield")[0],
data = {};

data[EAEM_COMPONENT_LIMIT] = limitField.getValue();

$.ajax({
url: path,
data: data,
type: 'POST'
});

this.close();
}
};

return CQ.WCM.getDialog(dialogConfig);
}
})();

AEM 61 - Touch UI Limit the Number of Components Added in Parsys

$
0
0

Goal


Limit the number of Components added in a Parsys of Touch UI

For Classic UI check this post

Demo | Package Install


Bug Fixes

Applying limit to copy / paste of components - Demo | Package Install


Set the Limit - Design Mode



Stored in CRX



Limit Exceeded Error on Component Drop or Insert - Edit Mode



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/touchui-limit-parsys

2) Create clientlib (type cq:ClientLibraryFolder/apps/touchui-limit-parsys/clientlib and set property categories of String[] type to cq.authoring.dialog, cq.compat.authoring.widgets and dependencies to underscore

3) Create file ( type nt:file ) /apps/touchui-limit-parsys/clientlib/js.txt, add the following

                         limit-parsys.js

4) Create file ( type nt:file ) /apps/touchui-limit-parsys/clientlib/limit-parsys.js, add the following code


// for touchui design mode
(function(){
var pathName = window.location.pathname,
EAEM_COMPONENT_LIMIT = "eaemComponentLimit";

if( !pathName.endsWith("dialogwrapper.html") ){
return;
}

CQ.Ext.onReady(function () {
findDesignDialogWindow();
});

function findDesignDialogWindow(){
var wMgr = CQ.Ext.WindowMgr, winId;

var W_INTERVAL = setInterval(function () {
wMgr.each(function (win) {
if(!win || !win.id){
return;
}

clearInterval(W_INTERVAL);

addLimitTextField(win);
});
}, 250);
}

function addLimitTextField(win){
var compSelector = win.findByType("componentselector");

if(compSelector.length == 0){
return;
}

compSelector = compSelector[0];

var dialog = compSelector.findParentByType("dialog");

$.ajax( dialog.path + ".2.json" ).done(handler);

function handler(data){
var limitField = new CQ.Ext.form.TextField({
value: data[EAEM_COMPONENT_LIMIT] || "",
fieldLabel: "Limit Components to ",
name: "./" + EAEM_COMPONENT_LIMIT,
style: {
marginBottom: '10px'
}
});

compSelector.ownerCt.insert(2, limitField);

compSelector.ownerCt.doLayout();
}
}
}());

// for touchui edit mode
(function ($document, gAuthor) {
var pathName = window.location.pathname;

if( pathName.endsWith("dialogwrapper.html") ){
return;
}

var EAEM_COMPONENT_LIMIT = "eaemComponentLimit";

$(extendComponentDrop);

function getDesignPath(editable){
var parsys = editable.getParent(),
designSrc = parsys.config.designDialogSrc,
result = {}, param;

designSrc = designSrc.substring(designSrc.indexOf("?") + 1);

designSrc.split(/&/).forEach( function(it) {
if (_.isEmpty(it)) {
return;
}
param = it.split("=");
result[param[0]] = param[1];
});

return decodeURIComponent(result["content"]);
}

function extendComponentDrop(){
var dropController = gAuthor.ui.dropController,
compDragDrop = dropController.get(gAuthor.Component.prototype.getTypeName());

//handle drop action
compDragDrop.handleDrop = function(dropFn){
return function (event) {
if(showError(event.currentDropTarget.targetEditable)){
return;
}

return dropFn.call(this, event);
};
}(compDragDrop.handleDrop);

//handle insert action
gAuthor.edit.actions.openInsertDialog = function(openDlgFn){
return function (editable) {
if(showError(editable)){
return;
}

return openDlgFn.call(this, editable);
}
}(gAuthor.edit.actions.openInsertDialog);

//handle paste action
var insertAction = gAuthor.edit.Toolbar.defaultActions["INSERT"];

insertAction.handler = function(insertHandlerFn){
return function(editableBefore, param, target){
if(showError(editableBefore)){
return;
}

return insertHandlerFn.call(this, editableBefore, param, target)
}
}(insertAction.handler);

function showError(editable){
var limit = isWithinLimit(editable);

if(!limit.isWithin){
showErrorAlert("Limit exceeded, allowed - " + limit.currentLimit);
return true;
}

return false;
}
}

function getChildEditables(parsys){
var editables = gAuthor.edit.findEditables(),
children = [], parent;

_.each(editables, function(editable){
parent = editable.getParent();

if(parent && (parent.path === parsys.path)){
children.push(editable);
}
});

return children;
}

function showErrorAlert(message, title){
var fui = $(window).adaptTo("foundation-ui"),
options = [{
text: "OK",
warning: true
}];

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

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

function isWithinLimit(editable){
var path = getDesignPath(editable),
children = getChildEditables(editable.getParent()),
isWithin = true, currentLimit = "";

$.ajax( { url: path + ".2.json", async: false } ).done(function(data){
if(_.isEmpty(data) || !data[EAEM_COMPONENT_LIMIT]){
return;
}

currentLimit = data[EAEM_COMPONENT_LIMIT];

var limit = parseInt(data[EAEM_COMPONENT_LIMIT]);

isWithin = children.length <= limit;
});

return {
isWithin: isWithin,
currentLimit: currentLimit
};
}
})($(document), Granite.author);


AEM 61 - TouchUI Extending Side Panel for Adding new Tab Page Tree

$
0
0

Goal


Authoring interface of Touch UI comes with two tabs Assets and Components in Side Panel; This post is on adding a new tab Page Tree for browsing the site structure and opening pages in new browser tab (Asset Finder allows search and open of pages)

For Classic UI check this post

Demo | Package Install



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touchui-sidepanel-pagetree

2) Create node /apps/touchui-sidepanel-pagetree/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.dialog

3) Create file (nt:file) /apps/touchui-sidepanel-pagetree/clientlib/js.txt and add

                       pagetree.js

4) Create file (nt:file) /apps/touchui-sidepanel-pagetree/clientlib/pagetree.js and add the following code

(function ($, $document) {
var PAGE_BROWSER = "/apps/touchui-sidepanel-pagetree/page-browser/content/tree-wrapper.html",
pageTreeAdded = false;

$document.on('cq-layer-activated', addPageTree);

function addPageTree(event){
if(pageTreeAdded || (event.layer !== "Edit")){
return;
}

var $sidePanelEdit = $("#SidePanel").find(".js-sidePanel-edit"),
$tabs = $sidePanelEdit.data("tabs");

//add the page itree iframe in new tab
$tabs.addItem({
tabContent: "Page Browser",
panelContent: getPageContent(),
active: false
});

pageTreeAdded = true;
}

function getPageContent(){
return "<iframe src='" + PAGE_BROWSER + "' style='border:none' height='800px'></iframe>";
}
})(jQuery, jQuery(document));


5) #16 in the above code adds new tab with iframe content loading the page /apps/touchui-sidepanel-pagetree/page-browser/content/tree-wrapper.html, containing necessary logic to show classic ui page tree

6) For the tree html page, create nt:folder /apps/touchui-sidepanel-pagetree/page-browser/wrapper

7) Create nt:file /apps/touchui-sidepanel-pagetree/page-browser/wrapper/html.jsp, add the following code

<%@page session="false" %>
<%@include file="/libs/foundation/global.jsp" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">

<title>Page Tree</title>

<style type="text/css">
#CQ .x-tab-panel-body, #CQ .x-panel-body {
background-color: transparent !important;
}
</style>

<cq:includeClientLib categories="cq.compat.authoring.widgets"/>

<script type="text/javascript">
CQ.I18n.init({ "locale": "<%= request.getLocale() %>" });

CQ.Ext.onReady(function () {
var pageTree = CQ.wcm.ContentFinderTab.getBrowseTree({
"treeRoot":{
"text": CQ.I18n.getMessage("Content")
}
});

pageTree.on('beforedblclick', function(node){
window.open("/editor.html" + node.getPath() + ".html", '_blank');
});

var config = {
items: [ pageTree ],
xtype: "dialogwrapper"
};

var dialog = CQ.Util.build(config, null, null, false, null);

dialog.setWidth(300);
dialog.setHeight(700);
});

</script>
</head>
<body style="margin:10px 0 0 15px; overflow: hidden">
<div id="CQ"></div>
</body>
</html>


8) Create sling:OrderedFolder /apps/touchui-sidepanel-pagetree/page-browser/content and nt:unstructured node /apps/touchui-sidepanel-pagetree/page-browser/content/tree-wrapper with property sling:resourceType=/apps/touchui-sidepanel-pagetree/page-browser/wrapper for page html content


AEM 61 - JcrPayloadPathBuilder Implementation to change Inbox Item Url from Touch to Classic

$
0
0

Goal


When a user clicks on Inbox item of Classic UI, by default the asset opens in Touch UI editor - /assetdetails.html

This post is on changing the url from Touch (/assetdetails.html) to Classic (/damadmin) for workflow started on assets in sample folder /content/dam/experience-aem

Demo | SourcePackage Install


Solution


1) Create a OSGI service apps.experienceaem.inbox.ClassicUIAssetUrlPathBuilder implementing com.day.cq.workflow.ui.JcrPayloadPathBuilder with much lower ranking say -1306, installed in /apps/classicui-inbox-asset-url/install

package apps.experienceaem.inbox;

import com.day.cq.dam.api.Asset;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.ui.JcrPayloadPathBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.*;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

@Component(metatype = false)
@Service
@Properties({
@Property(name = Constants.SERVICE_DESCRIPTION, value = "Experience AEM Classic UI Inbox Asset Url"),
@Property(name = "service.ranking", intValue = -1306, propertyPrivate = false)
})
public class ClassicUIAssetUrlPathBuilder implements JcrPayloadPathBuilder {
private static final Logger log = LoggerFactory.getLogger(ClassicUIAssetUrlPathBuilder.class);

private static String EAEM_FOLDER = "/content/dam/experience-aem";

@Reference
private ResourceResolverFactory resolverFactory;

public String buildPath(WorkItem item) {
ResourceResolver srvResolver = null;

try {
if (!item.getWorkflowData().getPayloadType().equals("JCR_PATH")) {
return null;
}

String path = item.getWorkflowData().getPayload().toString();

Map<String, Object> authMap = new HashMap<String, Object>();
authMap.put(ResourceResolverFactory.SUBSERVICE, "pathbuilder");

srvResolver = resolverFactory.getServiceResourceResolver(authMap);

Resource res = srvResolver.getResource(path);

if (res == null || !StringUtils.startsWith(res.getPath(), EAEM_FOLDER)) {
return null;
}

if (res.adaptTo(Asset.class) != null) {
return "/damadmin#" + res.getPath();
}
} catch (Exception e) {
log.warn("Error creating inbox asset url", e);
} finally {
if (srvResolver != null) {
srvResolver.close();
}
}

return null;
}
}

2) Create a service user mapping for bundle apps.experienceaem.inbox.classicui-inbox-asset-url-bundle and sub service pathbuilder in folder /apps/classicui-inbox-asset-url/config, node org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-eaem of type sling:OsgiConfig

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
user.mapping="[apps.experienceaem.inbox.classicui-inbox-asset-url-bundle:pathbuilder=targetservice]"/>

3) Check the component registered in http://localhost:4502/system/console/components



AEM 61 - TouchUI Configure Parsys Placeholder Text, Background & Border Colors

$
0
0

Goal


Extension to Configure Touch UI Parsys Placeholder text and colors in Design Dialog

For simple implementation check this post

Demo | Package Install


Configure Parsys - Design Mode



Stored in CRX



Parsys - Edit Mode



Component Drop


Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/touchui-configure-parsys-text-color

2) Create clientlib (type cq:ClientLibraryFolder/apps/touchui-configure-parsys-text-color/clientlib and set property categories of String[] type to cq.authoring.dialog, cq.compat.authoring.widgets and dependencies to underscore

3) Create file ( type nt:file ) /apps/touchui-configure-parsys-text-color/clientlib/js.txt, add the following

                         configure-parsys.js

4) Create file ( type nt:file ) /apps/touchui-configure-parsys-text-color/clientlib/configure-parsys.js, add the following code

(function(){
var pathName = window.location.pathname,
PARSYS_PLACEHOLDER_TEXT = "parsysPlaceholderText",
PARSYS_TEXT_COLOR = "parsysTextColor",
PARSYS_BG_COLOR = "parsysBackgroundColor",
PARSYS_BORDER_COLOR = "parsysBorderColor",
PARSYS_DROP_BG_COLOR = "parsysDropBackgroundColor",
PARSYS_DROP_BORDER_COLOR = "parsysDropBorderColor";

// for touchui design mode
(function(){
if( !pathName.endsWith("dialogwrapper.html") ){
return;
}

CQ.Ext.onReady(function () {
findDesignDialogWindow();
});

function findDesignDialogWindow(){
var wMgr = CQ.Ext.WindowMgr, winId;

var W_INTERVAL = setInterval(function () {
wMgr.each(function (win) {
if(!win || !win.id){
return;
}

clearInterval(W_INTERVAL);

addParsysConfiguration(win);
});
}, 250);
}

function addParsysConfiguration(win){
var compSelector = win.findByType("componentselector");

if(compSelector.length == 0){
return;
}

compSelector = compSelector[0];

var dialog = compSelector.findParentByType("dialog");

$.ajax( dialog.path + ".2.json" ).done(handler);

function handler(data){
var parsysPlaceholderText = new CQ.Ext.form.TextField({
value: data[PARSYS_PLACEHOLDER_TEXT] || "",
fieldLabel: "Parsys Text",
name: "./" + PARSYS_PLACEHOLDER_TEXT,
style: {
marginBottom: '10px'
}
});

var colorConfig = {
showHexValue: true,
editable: true,
style: {
marginBottom: '10px'
}
};

var parsysTextColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Text Color",
name: "./" + PARSYS_TEXT_COLOR
}, colorConfig));

var parsysBackgroundColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Background Color",
name: "./" + PARSYS_BG_COLOR
}, colorConfig));

var parsysBorderColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Border Color",
name: "./" + PARSYS_BORDER_COLOR
}, colorConfig));

var parsysDropBackgroundColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Drop Background Color",
name: "./" + PARSYS_DROP_BG_COLOR
}, colorConfig));

var parsysDropBorderColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Drop Border Color",
name: "./" + PARSYS_DROP_BORDER_COLOR
}, colorConfig));

var ownerCt = compSelector.ownerCt;

ownerCt.insert(2, parsysDropBorderColor);
ownerCt.insert(2, parsysDropBackgroundColor);
ownerCt.insert(2, parsysBorderColor);
ownerCt.insert(2, parsysBackgroundColor);
ownerCt.insert(2, parsysTextColor);
ownerCt.insert(2, parsysPlaceholderText);

ownerCt.doLayout();

parsysTextColor.setValue(data[PARSYS_TEXT_COLOR] || "");
parsysBackgroundColor.setValue(data[PARSYS_BG_COLOR] || "");
parsysBorderColor.setValue(data[PARSYS_BORDER_COLOR] || "");
parsysDropBackgroundColor.setValue(data[PARSYS_DROP_BG_COLOR] || "");
parsysDropBorderColor.setValue(data[PARSYS_DROP_BORDER_COLOR] || "");
}
}
}());

// for touchui edit mode
(function ($, $document, gAuthor) {
if( pathName.endsWith("dialogwrapper.html") ){
return;
}

var PARSYS = "foundation/components/parsys/new",
IPARSYS = "foundation/components/iparsys/new",
PARSYS_SELECTOR = "[data-path$='/*']",
PLACE_HOLDER = "cq-Overlay--placeholder",
configCache = {};

$document.on('cq-layer-activated', extendParsys);

$document.on("cq-overlay-hover.cq-edit-layer", function (event) {
if(!event.inspectable){
return;
}

configureParsys(event.inspectable, event.originalEvent.type);
});

function extendParsys(event){
if(event.layer !== "Edit"){
return;
}

_.each(getParsyses(), function(parsys){
configureParsys(parsys);
})
}

function configureParsys(parsys, type){
if(!parsys || !parsys.getParent() || !parsys.getParent().overlay){
return;
}

var $overlay = $(parsys.getParent().overlay.dom),
$placeholder = $overlay.find(PARSYS_SELECTOR);

if(!$placeholder.hasClass(PLACE_HOLDER)){
return;
}

if(_.isEmpty(configCache[parsys.getParent().path])){
$.ajax( getDesignPath(parsys) + ".2.json" ).done(configure);
}else{
configure(configCache[parsys.getParent().path]);
}

function configure(data){
if(_.isEmpty(data)){
return;
}

configCache[parsys.getParent().path] = data;

var color;

if(!_.isEmpty(data[PARSYS_PLACEHOLDER_TEXT])){
$placeholder.attr("data-text", data[PARSYS_PLACEHOLDER_TEXT]);
}

if(!_.isEmpty(data[PARSYS_TEXT_COLOR])){
$placeholder.css("color", getColor(data[PARSYS_TEXT_COLOR]));
}

if(!_.isEmpty(data[PARSYS_BG_COLOR])){
$placeholder.css("background-color", getColor(data[PARSYS_BG_COLOR]));
}

if(!_.isEmpty(data[PARSYS_BORDER_COLOR])){
$placeholder.css("border-color", getColor(data[PARSYS_BORDER_COLOR]));
}

if(!_.isEmpty(data[PARSYS_DROP_BG_COLOR]) && type && (type === 'mouseover')){
$placeholder.css("background-color", getColor(data[PARSYS_DROP_BG_COLOR]));
}

if(!_.isEmpty(data[PARSYS_DROP_BORDER_COLOR]) && type && (type === 'mouseover')){
$placeholder.css("border-color", getColor(data[PARSYS_DROP_BORDER_COLOR]));
}
}

function resetColors(){
$placeholder.css("color" , "");
$placeholder.css("background-color" , "");
$overlay.css("border-color" , "");
}
}

function getColor(color){
color = color.trim();

if(color.indexOf("#") !== 0){
color = "#" + color;
}

return color;
}

function getDesignPath(editable){
var parsys = editable.getParent(),
designSrc = parsys.config.designDialogSrc,
result = {}, param;

designSrc = designSrc.substring(designSrc.indexOf("?") + 1);

designSrc.split(/&/).forEach( function(it) {
if (_.isEmpty(it)) {
return;
}
param = it.split("=");
result[param[0]] = param[1];
});

return decodeURIComponent(result["content"]);
}

function isParsys(editable){
return editable && (editable.type === PARSYS || editable.type === IPARSYS);
}

function getParsyses(){
var editables = gAuthor.edit.findEditables(),
parsys = [];

_.each(editables, function(editable){
if(isParsys(editable)){
parsys.push(editable);
}
});

return parsys;
}
})(jQuery, jQuery(document), Granite.author);
}());


AEM 61 - ClassicUI Configure Parsys Placeholder Text, Background & Border Colors

$
0
0

Goal


Extend Parsys (/libs/foundation/components/parsys) to configure placeholder text, background and border colors

For a similar extension in Touch UI check this post

A simple version for ClassicUI is here

Demo | Package Install


Parsys - Design Mode



Stored in CRX



Parsys - Edit Mode



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/classicui-configure-parsys-text-color

2) Create clientlib (type cq:ClientLibraryFolder/apps/classicui-configure-parsys-text-color/clientlib and set property categories of String type to cq.widgets and dependencies to underscore

3) Create file ( type nt:file ) /apps/classicui-configure-parsys-text-color/clientlib/js.txt, add the following

                         configure-parsys.js

4) Create file ( type nt:file ) /apps/classicui-configure-parsys-text-color/clientlib/configure-parsys.js, add the following code

(function(){
var PARSYS_DESIGN_DIALOG = "/libs/foundation/components/parsys/design_dialog",
PARSYS_PLACEHOLDER_TEXT = "parsysPlaceholderText",
PARSYS_TEXT_COLOR = "parsysTextColor",
PARSYS_BG_COLOR = "parsysBackgroundColor",
PARSYS_BORDER_COLOR = "parsysBorderColor",
designExtended = false;

CQ.Ext.onReady(function () {
if(CQ.WCM.isEditMode()){
handleEditMode();
}

if(CQ.WCM.isDesignMode()){
handleDesignMode();
}
});

function handleDesignMode(){
if( designExtended === true ){
return;
}

extendShowDialog();
}

function handleEditMode(){
CQ.WCM.on("editablesready", configureParsys, this);
}

function isParsysNew(editable){
return _.isObject(editable.params)
&& (editable.params["./sling:resourceType"] == CQ.wcm.EditBase.PARSYS_NEW);
}

function configureParsys(){
var parsyses = getParsyses(), placeholder,
$placeholder, $pContainer, designConfig;

_.each(parsyses, function(parsys){
if(!parsys.emptyComponent) {
return;
}

designConfig = getConfiguration(parsys);

placeholder = parsys.emptyComponent.findByType("static")[0];

$placeholder = $(placeholder.el.dom);

$pContainer = $placeholder.closest(".cq-editrollover-insert-container");

if(designConfig[PARSYS_PLACEHOLDER_TEXT]){
$placeholder.html(designConfig[PARSYS_PLACEHOLDER_TEXT]);
}

if(designConfig[PARSYS_TEXT_COLOR]){
$placeholder.css("color", getColor(designConfig[PARSYS_TEXT_COLOR]));
}

if(designConfig[PARSYS_BG_COLOR]){
$pContainer.css("background-color", getColor(designConfig[PARSYS_BG_COLOR]));
}

if(designConfig[PARSYS_BORDER_COLOR]){
var color = getColor(designConfig[PARSYS_BORDER_COLOR]);

parsys.highlight.on("beforeshow", function(highlight){
$("#" + highlight.id).css("background-color", color);
})
}
});
}

function getColor(color){
color = color.trim();

if(color.indexOf("#") !== 0){
color = "#" + color;
}

return color;
}

function getConfiguration(editComponent) {
var pageInfo = CQ.utils.WCM.getPageInfo(editComponent.path),
designConfig = {}, cellSearchPath, parentPath, parName;

if (!pageInfo || !pageInfo.designObject) {
return;
}

try {
cellSearchPath = editComponent.cellSearchPath;
parentPath = editComponent.getParent().path;

cellSearchPath = cellSearchPath.substring(0, cellSearchPath.indexOf("|"));
parName = parentPath.substring(parentPath.lastIndexOf("/") + 1);

designConfig = pageInfo.designObject.content[cellSearchPath][parName];
} catch (err) {
console.log("Error getting parsys configuration", err);
}

return designConfig;
}

function getParsyses(){
var parsyses = {};

_.each(CQ.WCM.getEditables(), function(e){
if(!isParsysNew(e)){
return;
}

parsyses[e.path] = e;
});

return parsyses;
}

function extendShowDialog(){
CQ.wcm.EditBase.showDialog = (function(showDialogFn) {
return function(editComponent, type, ignoreIsContainer){
if(editComponent.dialog !== PARSYS_DESIGN_DIALOG){
return;
}

var isFirstRun = !editComponent.dialogs[CQ.wcm.EditBase.EDIT];

showDialogFn.call(this, editComponent, type, ignoreIsContainer);

if(isFirstRun){
editComponent.getEditDialog().on("loadcontent", extendParsysDialog);
}
}
}(CQ.wcm.EditBase.showDialog));
}

function extendParsysDialog(dialog){
$.ajax( dialog.path + ".2.json" ).done(handler);

function handler(data){
dialog.un("loadcontent", extendParsysDialog);

var parsysPlaceholderText = new CQ.Ext.form.TextField({
value: data[PARSYS_PLACEHOLDER_TEXT] || "",
fieldLabel: "Parsys Text",
name: "./" + PARSYS_PLACEHOLDER_TEXT,
style: {
marginBottom: '10px'
}
});

var colorConfig = {
showHexValue: true,
editable: true,
style: {
marginBottom: '10px'
}
};

var parsysTextColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Text Color",
name: "./" + PARSYS_TEXT_COLOR
}, colorConfig));

var parsysBackgroundColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Background Color",
name: "./" + PARSYS_BG_COLOR
}, colorConfig));

var parsysBorderColor = new CQ.form.ColorField(_.extend({
fieldLabel: "Parsys Border Color",
name: "./" + PARSYS_BORDER_COLOR
}, colorConfig));

var compSelector = dialog.findByType("componentselector")[0],
ownerCt = compSelector.ownerCt;

ownerCt.insert(2, parsysBorderColor);
ownerCt.insert(2, parsysBackgroundColor);
ownerCt.insert(2, parsysTextColor);
ownerCt.insert(2, parsysPlaceholderText);

ownerCt.doLayout();

parsysTextColor.setValue(data[PARSYS_TEXT_COLOR] || "");
parsysBackgroundColor.setValue(data[PARSYS_BG_COLOR] || "");
parsysBorderColor.setValue(data[PARSYS_BORDER_COLOR] || "");
}
}
}());


AEM 61 - Classic UI Configure Parsys Components Limit in Design Dialog

$
0
0

Goal


Limit number of components added in a parsys, where the limit is configurable in design dialog

For a different implementation (configure component limit in each page) - check this post

For TouchUI check this post

Demo | Package Install


Parsys Limit - Design Mode



Stored in CRX



Limit Exceeded - Drag and Drop



Limit Exceeded - New menu item disabled

 



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/classicui-design-configurable-limit-parsys

2) Create clientlib (type cq:ClientLibraryFolder/apps/classicui-design-configurable-limit-parsys/clientlib and set property categories of String type to cq.widgets and dependencies to underscore

3) Create file ( type nt:file ) /apps/classicui-design-configurable-limit-parsys/clientlib/js.txt, add the following

                         limit-parsys.js

4) Create file ( type nt:file ) /apps/classicui-design-configurable-limit-parsys/clientlib/limit-parsys.js, add the following code

(function(){
var PARSYS_DESIGN_DIALOG = "/libs/foundation/components/parsys/design_dialog",
PARSYS_LIMIT = "eaemComponentLimit";

CQ.Ext.onReady(function () {
if(CQ.WCM.isEditMode()){
handleEditMode();
}

if(CQ.WCM.isDesignMode()){
handleDesignMode();
}
});

function handleDesignMode(){
extendShowDialog();
}

function handleEditMode(){
CQ.WCM.on("editablesready", applyLimitAndExtendDrop, this);
}

function getSiblings(editable){
var parent, siblings = [];

_.each(CQ.WCM.getEditables(), function(e){
parent = e.getParent();

if(!parent || (parent.path !== editable.getParent().path)){
return;
}

siblings.push(e);
});

return siblings;
}

function isWithinLimit(editComponent){
var pageInfo = CQ.utils.WCM.getPageInfo(editComponent.path),
isWithin = true, currentLimit = "",
cellSearchPath, parentPath, parName;

if(!pageInfo || !pageInfo.designObject){
return;
}

try{
cellSearchPath = editComponent.cellSearchPath;
parentPath = editComponent.getParent().path;

cellSearchPath = cellSearchPath.substring(0, cellSearchPath.indexOf("|"));
parName = parentPath.substring(parentPath.lastIndexOf("/") + 1);
currentLimit = pageInfo.designObject.content[cellSearchPath][parName][PARSYS_LIMIT];

if(currentLimit){
isWithin = getSiblings(editComponent).length <= parseInt(currentLimit);
}
}catch(err){
console.log("Experience AEM - Error getting the component limit", err);
}

return {
isWithin: isWithin,
currentLimit: currentLimit
};
}

function extendDrop(dropFn){
return function(dragSource, e, data){
var limit = isWithinLimit(this.editComponent);

if(!limit.isWithin){
this.editComponent.hideTarget();
CQ.Ext.Msg.alert('Error', "Limit exceeded, allowed - " + limit.currentLimit);
return false;
}

return dropFn.call(this, dragSource, e, data);
};
}

function disableNewMenuItem(){
var component = this.element.linkedEditComponent;

if (!component || !component.menuComponent) {
return;
}

var menu = component.menuComponent;

if (menu.eaemLimitSet) {
return;
}

menu.eaemLimitSet = true;

var limit = isWithinLimit(this);

var newItem = menu.findBy(function(comp){
return comp.text === "New...";
})[0];

if(!limit.isWithin){
newItem.setDisabled(true);
}
}

function isParsysNew(editable){
return _.isObject(editable.params)
&& (editable.params["./sling:resourceType"] == CQ.wcm.EditBase.PARSYS_NEW);
}

function applyLimitAndExtendDrop() {
var editables = CQ.utils.WCM.getEditables();

_.each(editables, function (editable) {
if (isParsysNew(editable)) {
editable.emptyComponent.el.on("contextmenu", disableNewMenuItem, editable);
}

var dropTargets = editable.getDropTargets();

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

dropTargets[0].notifyDrop = extendDrop(dropTargets[0].notifyDrop);
});
}

function extendShowDialog(){
CQ.wcm.EditBase.showDialog = (function(showDialogFn) {
return function(editComponent, type, ignoreIsContainer){
if(editComponent.dialog !== PARSYS_DESIGN_DIALOG){
return;
}

var isFirstRun = !editComponent.dialogs[CQ.wcm.EditBase.EDIT];

showDialogFn.call(this, editComponent, type, ignoreIsContainer);

if(isFirstRun){
editComponent.getEditDialog().on("loadcontent", extendParsysDialog);
}
}
}(CQ.wcm.EditBase.showDialog));
}

function extendParsysDialog(dialog){
$.ajax( dialog.path + ".2.json" ).done(handler);

function handler(data){
dialog.un("loadcontent", extendParsysDialog);

var limitField = new CQ.Ext.form.TextField({
value: data[PARSYS_LIMIT] || "",
fieldLabel: "Component Limit",
name: "./" + PARSYS_LIMIT,
style: {
marginBottom: '10px'
}
});

var compSelector = dialog.findByType("componentselector")[0],
ownerCt = compSelector.ownerCt;

ownerCt.insert(2, limitField);

ownerCt.doLayout();
}
}
}());



AEM 61 - TouchUI Add Items to Multifield based on Select (Drop Down) value

$
0
0

Goal


In a Touch UI dialog, add items to Multifield dynamically, based on the value selected in Drop Down (Select) widget. In this sample, countries are added in multifield /libs/granite/ui/components/foundation/form/multifield based on the language selected /libs/granite/ui/components/foundation/form/select

Demo | Package Install




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touchui-dynamic-select-multifield

2) Create node /apps/touchui-dynamic-select-multifield/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.dialog

3) Create file (nt:file) /apps/touchui-dynamic-select-multifield/clientlib/js.txt and add

                       dynamic-multifield.js

4) Create file (nt:file) /apps/touchui-dynamic-select-multifield/clientlib/dynamic-multifield.js and add the following code

(function ($, $document) {
"use strict";

var LANGUAGE = "./language", COUNTRY = "./country";

$document.on("dialog-ready", function() {
var langCountries = {},
$language = $("[name='" + LANGUAGE + "']"),
$countryDelete = $("[name='" + COUNTRY + "@Delete']"),
cuiLanguage = $language.closest(".coral-Select").data("select"),
cuiCountry = $countryDelete.closest(".coral-Multifield").data("multifield"),
$countryAdd = cuiCountry.$element.find(".js-coral-Multifield-add");

cuiLanguage.on('selected.select', function(event){
var $country;

cuiCountry.$element.find(".js-coral-Multifield-remove").click();

_.each(langCountries[event.selected], function(country){
$countryAdd.click();
$country = cuiCountry.$element.find("[name='" + COUNTRY + "']:last");
$country.val(country);
});
});

function fillCountries(data){
var countries;

_.each(data, function(country, code){
if(!_.isObject(country) || (country.country === "*") ){
return;
}

code = getLangCode(code);

countries = langCountries[code] || [];

countries.push(country.country);

langCountries[code] = countries;
});
}

$.getJSON("/libs/wcm/core/resources/languages.2.json").done(fillCountries);
});

function getLangCode(code){
if(code.indexOf("_") != -1){
code = code.substring(0, code.indexOf("_"));
}

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

5) Create a simple datasource for languages - /apps/touchui-dynamic-select-multifield/datasource/language/language.jsp


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

<%@ page import="com.adobe.granite.ui.components.ds.DataSource" %>
<%@ page import="com.adobe.granite.ui.components.ds.ValueMapResource" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.sling.api.wrappers.ValueMapDecorator" %>
<%@ page import="com.adobe.granite.ui.components.ds.SimpleDataSource" %>
<%@ page import="org.apache.commons.collections.iterators.TransformIterator" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.LinkedHashMap" %>
<%@ page import="org.apache.commons.collections.Transformer" %>
<%@ page import="org.apache.sling.api.resource.*" %>

<%
final Map<String, String> languages = new LinkedHashMap<String, String>();

languages.put("", "Select");
languages.put("ar", "Arabic");
languages.put("en", "English");
languages.put("de", "German");

final ResourceResolver resolver = resourceResolver;

DataSource ds = new SimpleDataSource(new TransformIterator(languages.keySet().iterator(), new Transformer() {
public Object transform(Object o) {
String language = (String) o;
ValueMap vm = new ValueMapDecorator(new HashMap<String, Object>());

vm.put("value", language);
vm.put("text", languages.get(language));

return new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm);
}
}));

request.setAttribute(DataSource.class.getName(), ds);
%>

6) Touch UI dialog xml - /apps/touchui-dynamic-select-multifield/fill-multifield-on-select-component/cq: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="nt:unstructured"
jcr:title="Sample Dynamic Select Multifield Component"
sling:resourceType="cq/gui/components/authoring/dialog"
helpPath="en/cq/current/wcm/default_components.html#Text">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<fieldset
jcr:primaryType="nt:unstructured"
jcr:title="Sample Select"
sling:resourceType="granite/ui/components/foundation/form/fieldset">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<language
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/select"
fieldLabel="Language"
name="./language">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/touchui-dynamic-select-multifield/datasource/language"
addNone="{Boolean}true"/>
</language>
<country
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
fieldLabel="Country">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
name="./country"/>
</country>
</items>
</column>
</items>
</fieldset>
</items>
</column>
</items>
</content>
</jcr:root>

AEM 61 - Extract Illustrator Artboards as PDF Assets in DAM Update Asset Workflow

$
0
0

Goal


Add a workflow step in DAM Update Asset workflow to extract the Artboards of an Illustrator file and save them asPDFs

Demo | Package Install | Source code

Thanks to my fantastic Adobe colleagues for code snippets


Artboards in Illustrator 



Artboards extracted as AEM PDF Assets



Solution


1) Create a workflow Process Step apps.experienceaem.pdf.CreatePDFRendition and add the code below

package apps.experienceaem.pdf;

import com.adobe.internal.io.ByteReader;
import com.adobe.internal.io.ByteWriter;
import com.adobe.internal.io.InputStreamByteReader;
import com.adobe.internal.io.RandomAccessFileByteWriter;
import com.adobe.internal.pdftoolkit.core.exceptions.PDFException;
import com.adobe.internal.pdftoolkit.pdf.document.PDFDocument;
import com.adobe.internal.pdftoolkit.pdf.document.PDFOpenOptions;
import com.adobe.internal.pdftoolkit.pdf.document.PDFSaveFullOptions;
import com.adobe.internal.pdftoolkit.pdf.page.PDFPage;
import com.adobe.internal.pdftoolkit.pdf.page.PDFPageTree;
import com.adobe.internal.pdftoolkit.services.manipulations.PMMOptions;
import com.adobe.internal.pdftoolkit.services.manipulations.PMMService;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.AssetManager;
import com.day.cq.dam.commons.util.DamUtil;
import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.apache.felix.scr.annotations.*;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.jcr.resource.JcrResourceResolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.Session;
import java.io.*;

@Component
@Service
@Properties({@Property(name = "process.label", value = "Experience AEM - Generate PDF Assets Process")})
public class CreatePDFRendition implements WorkflowProcess {
private static final Logger log = LoggerFactory.getLogger(CreatePDFRendition.class);

@Reference(policy = ReferencePolicy.STATIC)
private JcrResourceResolverFactory jcrResolverFactory;

public void execute(WorkItem workItem, WorkflowSession workflowSession,
MetaDataMap metaDataMap) throws WorkflowException {
long startTime = System.currentTimeMillis();

Asset asset = getAssetFromPayload(workItem, workflowSession.getSession());

try{
if(asset != null && asset.getName().endsWith(".ai")){
addPDFRendition(asset);
}
}catch(Exception e){
log.warn("Error generating pdf", e);
}

long endTime = System.currentTimeMillis();

log.info("CreatePDFRendition took {} seconds" , (endTime - startTime) / 1000);
}

private void addPDFRendition(Asset asset) throws Exception{
File tmpFile = null;
ByteWriter tmpFileWriter = null;

FileInputStream tmpFileReader = null;
InputStream assetOrigIS = null;

PDFDocument pdfDocument = null;
PDFDocument vectorFile = null;
PDFPage pdfPage = null;

try {
assetOrigIS = asset.getOriginal().getStream();

vectorFile = parseDocument(assetOrigIS);

PMMService pmmService = new PMMService(vectorFile);

PDFPageTree pages = vectorFile.requirePages();

int count = pages.getCount();

for(int i = 0; i < count; i++){
pdfPage = pages.getPage(i);

pdfDocument = pmmService.extractPages(pdfPage, 1,
PMMOptions.newInstance(PMMOptions.AllOptions),
PDFOpenOptions.newInstance());

tmpFile = File.createTempFile(asset.getName(), ".pdf");

tmpFileWriter = getTempFileWriter(tmpFile);

pdfDocument.save(tmpFileWriter, PDFSaveFullOptions.newInstance());

tmpFileWriter.close();

tmpFileReader = new FileInputStream(tmpFile);

AssetManager assetMgr = asset.getOriginal().getResourceResolver()
.adaptTo(AssetManager.class);

String folder = asset.adaptTo(Node.class).getParent().getPath();

assetMgr.createAsset( folder + "/" + asset.getName() + "-"
+ pdfPage.getPageNumber() + ".pdf",
tmpFileReader, "application/pdf", true);

pdfDocument.close();
tmpFileWriter.close();
tmpFileReader.close();
}
}catch(Exception e){
log.warn("Error generating pdf for - " + asset.getPath(), e);
}finally{
if (pdfDocument != null) {
pdfDocument.close();
}

if (tmpFileWriter != null) {
tmpFileWriter.close();
}

if(tmpFileReader !=null ){
tmpFileReader.close();
}

if(assetOrigIS != null){
assetOrigIS.close();
}
}
}

private static PDFDocument parseDocument(InputStream input) throws Exception {
ByteReader byteReader = null;
PDFDocument pdfDoc = null;

byteReader = new InputStreamByteReader(input);

try {
pdfDoc = PDFDocument.newInstance(byteReader, PDFOpenOptions.newInstance());
} catch (PDFException e) {
log.warn("Error while reading vector file", e);
throw e;
}

return pdfDoc;
}

private ByteWriter getTempFileWriter(File file) throws IOException {
file.delete();

File parent = file.getParentFile();

if (parent != null) {
parent.mkdirs();
}

file.createNewFile();

return new RandomAccessFileByteWriter(new RandomAccessFile(file, "rw"));
}

private Asset getAssetFromPayload(WorkItem item, Session session) {
Asset asset = null;

if(item.getWorkflowData().getPayloadType().equals("JCR_PATH")) {
String path = item.getWorkflowData().getPayload().toString();
Resource resource = getResourceResolver(session).getResource(path);

if(null != resource) {
asset = DamUtil.resolveToAsset(resource);
} else {
log.error("getAssetFromPaylod: asset [{}] in payload of workflow [{}] does not exist.", path, item.getWorkflow().getId());
}
}

return asset;
}

private ResourceResolver getResourceResolver(final Session session) {
return jcrResolverFactory.getResourceResolver(session);
}
}


2) #85 to #106 loop through pages, write the page stream to a temp file and create pdf asset by reading from the temp file

3) For compilation, Gibson libraries (com.adobe.internal.pdftoolkit) are not available on Adobe public maven repo Nexus; so create a private repo - eaemrepo and add the location in repositories section of project pom.xml (the jars can be found in CQ install eg. author\crx-quickstart\launchpad\felix\bundle446\data\install\pdfcore-3.0.568820.jar or you can get them from this sample's source code)

<repositories>
<repository>
<id>eaemrepo</id>
<name>Experience AEM Internal Repo</name>
<url>file://${project.basedir}/../libs</url>
</repository>

<repository>
<id>adobe</id>
<name>Adobe Public Repository</name>
<url>http://repo.adobe.com/nexus/content/groups/public/</url>
<layout>default</layout>
</repository>
</repositories>

       jars added in private maven repo eaemrepo



4) Add the Process step apps.experienceaem.pdf.CreatePDFRendition to DAM Update Asset workflow. For demonstration purposes, the workflow step was added to ootb DAM Update Asset workflow (/etc/workflow/models/dam/update_asset), but in real world it is always recommended to copy and create a new workflow model for adding any additional workflow steps




AEM 61 - Add "Save as PDF" button to the Assets Console Action bar

$
0
0

Goal


In DAM Assets console of Touch UI, add button Save as PDF to the Action Bar for downloading selected assets as PDF

Demo | Package Install | Source Code


Save as PDF button added to Action Bar



Downloaded PDF

 


Solution


1) Create a OSGI servlet apps.experienceaem.assets.CreatePDFFromAssetsServlet in CRX folder /apps/eaem-save-assets-as-pdf with the following code

package apps.experienceaem.assets;

import com.adobe.internal.io.ByteWriter;
import com.adobe.internal.io.RandomAccessFileByteWriter;
import com.adobe.internal.pdftoolkit.core.types.ASMatrix;
import com.adobe.internal.pdftoolkit.pdf.document.PDFDocument;
import com.adobe.internal.pdftoolkit.pdf.document.PDFOpenOptions;
import com.adobe.internal.pdftoolkit.pdf.document.PDFSaveFullOptions;
import com.adobe.internal.pdftoolkit.pdf.graphics.PDFExtGState;
import com.adobe.internal.pdftoolkit.pdf.graphics.PDFRectangle;
import com.adobe.internal.pdftoolkit.pdf.graphics.xobject.PDFXObjectImage;
import com.adobe.internal.pdftoolkit.pdf.page.PDFPage;
import com.adobe.internal.pdftoolkit.pdf.page.PDFPageTree;
import com.adobe.internal.pdftoolkit.services.imageconversion.ImageManager;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.AssetManager;
import com.day.cq.dam.commons.util.DamUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import javax.jcr.Node;
import javax.servlet.ServletException;
import java.awt.image.BufferedImage;
import java.io.*;

@Component(metatype = true, label = "Experience AEM Create PDF From Assets", description = "")
@Service
@Properties({
@Property(name = "sling.servlet.methods", value = {"GET" }, propertyPrivate = true),
@Property(name = "sling.servlet.paths", value = "/bin/eaem/createpdf", propertyPrivate = true)})
public class CreatePDFFromAssetsServlet extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(CreatePDFFromAssetsServlet.class);

protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
try{
String assetPaths = request.getParameter("assetPaths");

if(StringUtils.isEmpty(assetPaths)){
response.getWriter().print("No asset paths provided");
}else{
Asset pdf = createPDF(assetPaths, request);

if(pdf == null){
response.getWriter().print("Error creating pdf");
}else{
writePDF(response, pdf);
}
}
}catch(Exception e){
log.error("Error creating pdf", e);
}
}

private void writePDF(SlingHttpServletResponse response, Asset pdf) throws Exception{
response.setContentType("application/pdf");
response.addHeader("Content-Disposition", "attachment; filename=" + pdf.getName());
response.setContentLength((int)pdf.getOriginal().getSize());

InputStream is = null;

try{
is = pdf.getOriginal().getStream();
OutputStream responseOutputStream = response.getOutputStream();
int bytes;

while ((bytes = is.read()) != -1) {
responseOutputStream.write(bytes);
}
}finally{
if(is != null){
is.close();
}
}
}

private Asset createPDF(String assetPaths, SlingHttpServletRequest request) throws Exception{
String pdfPath = null;
File tmpFile = null;
ByteWriter tmpFileWriter = null;

FileInputStream tmpFileReader = null;
Asset asset = null, pdf = null;
ResourceResolver resolver = request.getResourceResolver();

PDFDocument pdfDocument = null;
InputStream assetStream = null;

try{
Node folder = null;

pdfDocument = PDFDocument.newInstance(PDFOpenOptions.newInstance());
PDFPageTree pageTree = pdfDocument.requireCatalog().getPages();

for(String assetPath : assetPaths.split(",")){
asset = DamUtil.resolveToAsset(resolver.getResource(assetPath));

if(folder == null){
folder = asset.adaptTo(Node.class).getParent();
}

assetStream = asset.getOriginal().getStream();

BufferedImage bImage = ImageIO.read(assetStream);

PDFXObjectImage image = ImageManager.getPDFImage(bImage, pdfDocument);

PDFPage newPage = PDFPage.newInstance(pdfDocument,
PDFRectangle.newInstance(pdfDocument,0, 0, image.getWidth(), image.getHeight()));

ImageManager.insertImageInPDF(image, newPage,
PDFExtGState.newInstance(pdfDocument),
new ASMatrix(image.getWidth(), 0, 0, image.getHeight(), 0, 0));

if (pageTree == null){
pageTree = PDFPageTree.newInstance(pdfDocument, newPage);
}else{
pageTree.getPage(0).prependPage(newPage);
}

assetStream.close();
}

tmpFile = File.createTempFile(folder.getName(), ".pdf");

tmpFileWriter = getTempFileWriter(tmpFile);

pdfDocument.save(tmpFileWriter, PDFSaveFullOptions.newInstance());

tmpFileReader = new FileInputStream(tmpFile);

AssetManager assetMgr = resolver.adaptTo(AssetManager.class);

pdfPath = folder.getPath() + "/" + folder.getName() + ".pdf";

Resource resource = resolver.getResource(pdfPath);

if(resource != null){
resource.adaptTo(Node.class).remove();
}

pdf = assetMgr.createAsset( pdfPath, tmpFileReader, "application/pdf", true);
}catch(Exception e){
log.warn("Error generating pdf", e);
}finally{
if(assetStream != null){
assetStream.close();
}

if (pdfDocument != null) {
pdfDocument.close();
}

if (tmpFileWriter != null) {
tmpFileWriter.close();
}

if(tmpFileReader !=null ){
tmpFileReader.close();
}
}

return pdf;
}

private ByteWriter getTempFileWriter(File file) throws IOException {
file.delete();

File parent = file.getParentFile();

if (parent != null) {
parent.mkdirs();
}

file.createNewFile();

return new RandomAccessFileByteWriter(new RandomAccessFile(file, "rw"));
}
}


2) For compilation, Gibson libraries (com.adobe.internal.pdftoolkit) are not available on Adobe public maven repo Nexus; so create a private repo - eaemrepo and add the location in repositories section of project pom.xml (the jars can be found in CQ install eg. author\crx-quickstart\launchpad\felix\bundle446\data\install\pdfcore-3.0.568820.jar or you can get them from this sample's source code)

<repositories>
<repository>
<id>eaemrepo</id>
<name>Experience AEM Internal Repo</name>
<url>file://${project.basedir}/../libs</url>
</repository>
<repository>
<id>adobe</id>
<name>Adobe Public Repository</name>
<url>http://repo.adobe.com/nexus/content/groups/public/</url>
<layout>default</layout>
</repository>
</repositories>

        jars added in eaemrepo

 

3) Create a clientlib (type cq:ClientLibraryFolder) /apps/eaem-save-assets-as-pdf/clientlib with categories - cq.gui.damadmin.admin

4) Create file /apps/eaem-save-assets-as-pdf/clientlib/js.txt with the following content

                                                save-as-pdf.js

5) Create file /apps/eaem-save-assets-as-pdf/clientlib/save-as-pdf.js with the following code

(function ($, $document) {
var DOWNLOAD_ACTIVATOR = "cq-damadmin-admin-actions-download-activator",
TEXT = "Save as PDF",
CREATE_PDF_URL = "/bin/eaem/createpdf?assetPaths=",
added = false;

$document.on("foundation-mode-change", addButton);

function addButton(e, mode){
if(added || (mode !== "selection") ){
return;
}

added = true;

var $cFolder = $("." + DOWNLOAD_ACTIVATOR);

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

var $downloadPdf = $cFolder.after($($cFolder[0].outerHTML));

$downloadPdf.attr("title", TEXT)
.removeClass(DOWNLOAD_ACTIVATOR)
.removeAttr("href")
.click(downloadPDF)
.find("span").html(TEXT);

$document.on("foundation-selections-change", ".foundation-collection", function() {
var $selectedItems = $(".foundation-selections-item");

if($selectedItems.length > 0){
$downloadPdf.removeAttr("hidden");
}
});

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

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

window.open(CREATE_PDF_URL + assetPaths.join(","), "_blank");
}
}
})(jQuery, jQuery(document));

AEM 61 - Classic UI show User initiating Activation via Workflow in DAM Admin grid

$
0
0

Goal


Activations initiated by clicking Activate button on DAM Admin UI show the user name in Published column of grid, but activations via workflow show Administrator as the publisher. This post is on showing initiating user as well in the grid (assuming the workflow Request for Activation is used for activation)

The solution here uses CQ querybuilder, and can be improved to send a single query with each page load; also create oak indexes to avoid the traversing cursor

29.02.2016 11:48:22.174 *WARN* [0:0:0:0:0:0:0:1 [1456768102069] GET /bin/querybuilder.json HTTP/1.1] org.apache.jackrabbit.oak.spi.query.Cursors$TraversingCursor Traversed 12000 nodes with filter Filter(query=select [jcr:path], [jcr:score], * from [nt:base] as a where [modelId] = '/etc/workflow/models/request_for_activation/jcr:content/model' and [data/payload/path] = '/content/dam/geometrixx/portraits/alison_parker.jpg' and isdescendantnode(a, '/etc/workflow/instances') order by [endTime] desc /* xpath: /jcr:root/etc/workflow/instances//*[@modelId = '/etc/workflow/models/request_for_activation/jcr:content/model' and data/payload/@path = '/content/dam/geometrixx/portraits/alison_parker.jpg'] order by @endTime descending */, path=/etc/workflow/instances//*, property=[modelId=[/etc/workflow/models/request_for_activation/jcr:content/model], data/payload/path=[/content/dam/geometrixx/portraits/alison_parker.jpg]]); consider creating an index or changing the query

Demo | Package Install



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classicui-show-publish-workflow-initiator

2) Create node /apps/classicui-show-publish-workflow-initiator/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.widgets and dependencies - underscore

3) Create file (nt:file) /apps/classicui-show-publish-workflow-initiator/clientlib/js.txt and add

                       show-workflow-initiator.js

4) Create file (nt:file) /apps/classicui-show-publish-workflow-initiator/clientlib/show-workflow-initiator.js and add the following code

(function(){
if(window.location.pathname !== "/damadmin"){
return;
}

//id defined in /libs/wcm/core/content/damadmin
var SA_GRID = "cq-damadmin-grid",
PUBLISHED_ID = "published",
PUBLISHING_WORKFLOW = '/etc/workflow/models/request_for_activation/jcr:content/model',
ALLOWED_CUSHION = 5000,
QUERY_PREFIX = "/bin/querybuilder.json?path=/etc/workflow/instances" +
"&1_property=modelId&1_property.value=" + PUBLISHING_WORKFLOW +
"&orderby=@endTime&orderby.sort=desc&p.limit=1&p.nodedepth=1&p.hits=full" +
"&2_property=@data/payload/path&2_property.value=";

var modifyPublished = function(grid){
var pColumn = grid.getColumnModel().getColumnById(PUBLISHED_ID);

if(!pColumn){
return;
}

var renderer = pColumn.renderer;

pColumn.renderer = function(v, params, record) {
var html = renderer.call(this, v, params, record);

var replication = record.data.replication;

if ( !replication || (replication.action != "ACTIVATE")) {
return html;
}

var query = QUERY_PREFIX + record.id;

//WARNING - for demonstration only - individual queries are not very efficient
//may take its toll on your damadmin performance
$.ajax({ url : query, async : false }).done(findInitiator);

function findInitiator(data){
if(_.isEmpty(data) || _.isEmpty(data.hits)){
return;
}

var hit = data.hits[0];

if( (replication.published - new Date(hit.endTime).getTime()) < ALLOWED_CUSHION ){
html = $(html).append(", Requested by - <span style='color:red'>"
+ hit.initiator + "</span>")[0].outerHTML;
}
}

return html;
};

grid.store.load();
};

var SA_INTERVAL = setInterval(function(){
var grid = CQ.Ext.getCmp(SA_GRID);

if(!grid || ( grid.rendered != true)){
return;
}

var cm = grid.getColumnModel();

if(!cm || !cm.columns){
return;
}

clearInterval(SA_INTERVAL);

try{
modifyPublished(grid);
}catch(err){
console.log("Error executing modify published column extension");
}
}, 250);
})();


AEM 61 - TouchUI Extending Side Panel for Removing Assets Browser

$
0
0

Goal


Authoring interface of Touch UI comes with two tabs Assets and Components in Side Panel; This post is on removing Assets Browser 

For adding a new tab check this post

Demo | Package Install


Product



Extension



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touchui-sidepanel-remove-asset-browser

2) Create node /apps/touchui-sidepanel-remove-asset-browser/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.dialog

3) Create file (nt:file) /apps/touchui-sidepanel-remove-asset-browser/clientlib/js.txt and add

                       remove-asset-browser.js

4) Create file (nt:file) /apps/touchui-sidepanel-remove-asset-browser/clientlib/remove-asset-browser.js and add the following code

(function ($, $document) {
var assetBrowserRemoved = false;

$document.on('cq-layer-activated', removeAssetsBrowser);

function removeAssetsBrowser(event){
if(assetBrowserRemoved){
return;
}

assetBrowserRemoved = true;

var $sidePanelEdit = $("#SidePanel").find(".js-sidePanel-edit"),
$tabs = $sidePanelEdit.data("tabs");

//first tab is asset browser, remove it
$tabs.removeItem(0);
}
})(jQuery, jQuery(document));


Viewing all 513 articles
Browse latest View live




Latest Images