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

AEM 62 - Touch UI Validator for restricting Tags Count in a Dialog Field

$
0
0

Goal


Granite Validator for restricting the count of tags selected in a Touch UI Dialog field

For Classic UI - check this post

Dialog of foundation Page Properties modified for demonstration only - /libs/foundation/components/page/cq:dialog/content/items/tabs/items/basic/items/column/items/title/items/tags

Demo | Package Install


Configuration



Error



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/eaem-max-tags-in-tags-picker

2) Create node /apps/eaem-max-tags-in-tags-picker/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.dialog and dependencies to underscore

3) Create file (nt:file) /apps/eaem-max-tags-in-tags-picker/clientlib/js.txt and add

                       max-tags.js

4) Create file (nt:file) /apps/eaem-max-tags-in-tags-picker/clientlib/max-tags.js and add the following code

(function ($) {
var EAEM_MAX_TAGS_VALIDATOR = "eaem.max.tags",
EAEM_MAX_TAGS = "eaemMaxTags",
foundationReg = $(window).adaptTo("foundation-registry");

foundationReg.register("foundation.validation.validator", {
selector: "[data-validation='" + EAEM_MAX_TAGS_VALIDATOR + "'] input",
validate: function(el) {
var $tagsPicker = $(el).closest(".js-cq-TagsPickerField"),
maxTagsAllowed = $tagsPicker.data(EAEM_MAX_TAGS.toLowerCase());

if(!maxTagsAllowed){
console.log(EAEM_MAX_TAGS + " number not set");
return;
}

var $tagList = $tagsPicker.next(".coral-TagList");

return ($tagList.find(".coral-TagList-tag").length > maxTagsAllowed
? "Max exceeded, allowed : " + maxTagsAllowed : undefined);
}
});
}(jQuery));



AEM 61 - Classic UI Limit the number of Tags selected in xtype:tags widget

$
0
0

Goal


Limit the number of tags selected in Classic UI Tags widget

For limiting the number of tags per namespacecheck this post

For Touch UI - check this post

Dialog of foundation Page Properties modified for demonstration only - /libs/foundation/components/page/tab_basic/items/basic/items/tags



Configuration

                     eaemMaxTags  : 2



Error Alert



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-classic-ui-max-tags-in-tags-widget

2) Create clientlib (type cq:ClientLibraryFolder/apps/eaem-classic-ui-max-tags-in-tags-widget/clientlib and set property categories of String type to cq.tagging and dependencies to underscore

3) Create file ( type nt:file ) /apps/eaem-classic-ui-max-tags-in-tags-widget/clientlib/js.txt, add the following

                         max-tags.js

4) Create file ( type nt:file ) /apps/eaem-classic-ui-max-tags-in-tags-widget/clientlib/max-tags.js, add the following code

(function(){
var EAEM_MAX_TAGS_CONFIG = "eaemMaxTags";

var EAEM_TAG_INPUT_FIELD = CQ.Ext.extend(CQ.tagging.TagInputField, {
checkMaximum: function(tag) {
var limit = this.initialConfig[EAEM_MAX_TAGS_CONFIG];

if(limit && this.tags.length >= parseInt(limit)){
var msgBox = CQ.Ext.Msg.alert('Limit exceeded', "Maximum tags allowed of any namespace: " + limit);
msgBox.getDialog().setZIndex(99999);
return false;
}

return EAEM_TAG_INPUT_FIELD.superclass.checkMaximum.call(this, tag);
}
});

CQ.Ext.reg("tags", EAEM_TAG_INPUT_FIELD);
}());

AEM 62 - InDesign CC 2015 - Create PDF Catalog in InDesign with Assets downloaded from AEM

$
0
0

Goal


This post is on developing a Adobe CC HTML Extension to login to AEM 62, search and download AEM assets (content fragments, images) and place them on a new InDesign document page,  effectively creating PDF catalogs with the assets uploaded (and managed in AEM); CC HTML Extensions run on Common Extensibility Platform (CEP) of CC products like InDesign, Photoshop, Illustrator...

Check this post for detailed instructions on how to create and debug CEP panels (MAC and Windows) and these CEP samples

For integrating AEM with InDesign Server to extract media etc. check documentationCatalog Producer to generate product catalogs in AEM Assets is documented here

Signing toolkit ZXPSignCmd can be downloaded at Adobe Labs (windows version available in the extension source)

Download the Extension Manager command line tool for installing ZXP extensions (Adobe Extension Manager CC no longer supported in CC 2015)
           
             Windows - https://www.adobeexchange.com/ExManCmd_win.zip
             MAC - https://www.adobeexchange.com/ExManCmd_mac.zip

Some of the logic in this post was coded by me, some copied shamelessly (written by various Adobe colleagues)

Demo | InDesign AEM ZXP | AEM Sling Referrer Package Install | Source Code


Sling Referrer Filter "Allow Empty" set to "true"

             Configuration - http://localhost:4502/system/console/configMgr/org.apache.sling.security.impl.ReferrerFilter



ZXP Installation

             Self-Signed Certificates - For dev purposes use OpenSSL and generate a self-signed certificate for signing the ZXP

                          openssl req -x509 -days 3650 -newkey rsa:2048 -keyout experience-aem-cep-key.pem -out experience-aem-cep-cert.pem

                          openssl pkcs12 -export -in  experience-aem-cep-cert.pem -inkey experience-aem-cep-key.pem -out experience-aem-cep.p12

             Build - Use ant to build the zxp using command >ant zxp

             Install - ExManCmd.exe /install "C:\dev\code\projects\cq62-extensions\eaem-aem-assets-on-indesign-page\temp\eaem-aem-assets-on-indesign-page.zxp"

             Installed to location (windows) - C:\Program Files (x86)\Common Files\Adobe\CEP\extensions


             Remove - ExManCmd.exe /remove com.experience.aem.cep.idsn



Place AEM Assets Panel - Login



Place AEM Assets Panel - Search, Download and & Place

              Assets selected are downloaded to C:\Users\<user>\Documents\eaem folder (created if not exists) on windows; The downloaded assets are placed on new InDesign page



Place AEM Assets Panel - Generate & Upload PDF

              PDF generated and saved to C:\Users\<user>\Documents\eaem folder (created if not exists) on windows



PDF Catalog in AEM



Solution


1) Folder structure:

                           For creating InDesign CEP extensions (html panels), the following is a simple source code folder structure

                           eaem-aem-assets-on-indesign-page
                                         css
                                                     style.css
                                         CSXS
                                                     manifest.xml
                                         html
                                                     place-aem-assets.html
                                         img
                                                     txt.png
                                         js
                                                     aem-service.js
                                                     init-service.js
                                                     place-controller.js
                                                     CSInterface-5.2.js
                                         jsx
                                                     place-assets.jsx
                                         lib
                                                     exmancmd_win
                                                     experience-aem-cep.p12
                                                     ZXPSignCmd
                                         .debug
                                         build.xml

2) Debugging Panels:

                a) Debug url for the panel Place AEM Assets as specified in .debug file is http://localhost:8098/ (available only when the panel is open in InDesign).

                    Check this post for detailed instructions on how to code & debug CEP panels (MAC and Windows)

<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="com.experience.aem.cep.idsn.place">
<HostList>
<Host Name="IDSN" Port="8098"/>
</HostList>
</Extension>
</ExtensionList>

                b) Logging available in C:\Users\<user>\AppData\Local\Temp\CEP6-IDSN.log. Default is INFO, for debugging make sure you have the PlayerDebugMode set to 1 and LogLevel 4 (DEBUG) in

                          MAC - /Users/<user>/Library/Preferences/com.adobe.CSXS.6.plist
                          Windows registry key  - Computer\HKEY_CURRENT_USER\Software\Adobe\CSXS.6




                c) ZXP when installed using ExManCmd gets installed to C:\Program Files (x86)\Common Files\Adobe\CEP\extensions (Windows)

3) CSXS/manifest.xml file is required for every extension and provides necessary configuration information for the extension

<Resources>
<MainPath>./html/place-aem-assets.html</MainPath>
<ScriptPath>./jsx/place-assets.jsx</ScriptPath>
<CEFCommandLine>
<Parameter>--enable-nodejs</Parameter>
<Parameter>--mixed-context</Parameter>
</CEFCommandLine>
</Resources>

          a) #2 specifies the home page/splash screen of the CEP html extension

          b) #3 contains the path to file containing extendscript logic for interacting with the host, here InDesign (to create document and place assets on page)

          c) #4 to #7 direct CEP engine to make nodejs module available for the extension panel (used for upload/download of relatively large assets in chunks eg. to download a 1 GB image from AEM)

4) Place AEM Assets extension is a SPA (Single Page Application) using angularjs for building the application and give it dynamic behavior, so the single html page place-aem-assets.html consists of angular directives and controller to provide login screen, show search page etc.

<!DOCTYPE html>
<html lang="en" ng-app="SearchAEM">

<head>
<meta charset="utf-8">

<title>Place AEM Assets</title>

<link rel="stylesheet" href="../css/style.css">
</head>

<body>
<div ng-controller="placeController">
<div class="sign-in" ng-show="showLogin">
<div>
<h3>DIGITAL ASSET MANAGEMENT</h3>
</div>
<div>
<label>User Name</label>
<input type="text" ng-model="j_username" ng-enter="login()"/>
</div>
<div>
<label>Password</label>
<input type="password" ng-model="j_password" ng-enter="login()"/>
</div>
<div>
<label>DAM Host</label>
<input type="text" value="{{damHost}}" ng-model="damHost" ng-enter="login()"/>
</div>

<button type="button" ng-click="login()">Sign In</button>
</div>

<div ng-show="!showLogin">
<div class="top">
<div class="top-left">
<input type="text" placeholder="AEM PDF Upload Path" ng-model="uploadPath"/>
<input type="text" placeholder="Search text" ng-model="term" ng-enter="search()"/>
</div>
<div class="top-right">
<button ng-click="search()">Search</button>
</div>
</div>
<div class="results">
<div class="result-block" ng-class="{ selected : result.selected } "
ng-repeat="result in results" ng-click="select(result)">
<div>
<img ng-src="{{result.imgPath}}"/>
</div>
<div>
{{result.name}}
</div>
</div>
</div>
<div class="bottom">
<div class="bottom-left" ng-show="results.length > 0">
<button ng-click="place()">Place</button>
</div>
<div class="bottom-right">
<button ng-click="generatePDFAndUpload()">Generate PDF & Upload</button>
</div>
</div>
</div>
</div>

<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.js"></script>
<script src="../js/CSInterface-5.2.js"></script>
<script src="../js/init-service.js"></script>
<script src="../js/aem-service.js"></script>
<script src="../js/place-controller.js"></script>
</body>
</html>

5) The necessary modules and services are defined in js/init-service.js
'use strict';

(function () {
var underscore = angular.module('underscore', []);
underscore.factory('_', function () {
return window._;
});

var cep = angular.module('cep', []);
cep.service('csi', CSInterface);
cep.factory('cep', ['$window', function ($window) {
return $window.cep;
}]);
cep.factory('fs', ['cep', function (cep) {
return cep.fs;
}]);
cep.factory('nfs', ['cep', function (cep) {
return require("fs");
}]);
cep.factory('nhttp', function () {
return require("http");
});
cep.factory('nqs', function () {
return require('querystring')
});
cep.factory('nbuffer', function () {
return require('buffer');
});
}());

6) js/aem-service.js defines all the necessary services and functions for interacting with AEM (login, search, download, upload etc.)

                     a) Use CSInterface.evalScript() function to execute extend script functions in the host engine (InDesign) eg. #42 EAEM.placeAssets(), #59 EAEM.exportAsPDF() ES functions

                     b) #95 loginWithNJS() for AEM login, read the login_token from cookies (#122), get the csrfToken (#132) and make them available for other services (search, download, upload etc)
                         by setting in angular $rootScope (#130)

                     c) #159 downloadWithNJS() for downloading large files in chunks (tested with files upto 10GB)

                     d) #220 uploadWithNJS() to upload generated PDFs in 5MB chunks (utilizing AEM chunk upload feature)

                     e) #307 SearchService() with necessary functions to search for assets in AEM using Query Builder

'use strict';

(function () {
var aem = angular.module('aem', ['underscore', 'cep']);
aem.service('aemService', AEMService);
aem.factory('searchService', SearchService);

AEMService.$inject = [ '_', 'csi', 'fs', '$http', '$rootScope' ,'nhttp', 'nqs', 'nfs', 'nbuffer', '$q' ];

function AEMService(_, csi, fs, $http, $rootScope, nhttp, nqs, nfs, nbuffer, $q){
return {
getFilename: getFilename,
loginWithNJS: loginWithNJS,
appendLoginToken: appendLoginToken,
getDownloadPath: getDownloadPath,
downloadWithNJS: downloadWithNJS,
uploadWithNJS: uploadWithNJS,
placeAssets: placeAssets,
generatePDF: generatePDF
};

function isCEPError(error){
return _.isEmpty(error) || (error.toUpperCase() == "ERROR");
}

function placeAssets(filePaths) {
if (_.isEmpty(filePaths)) {
return $q.when({});
}

var deferred = $q.defer();

function handler(result) {
if (isCEPError(result)) {
deferred.reject("Error placing assets");
return;
}

deferred.resolve(result);
}

csi.evalScript("EAEM.placeAssets('" + _.values(filePaths).join(",") + "')", handler);

return deferred.promise;
}

function generatePDF() {
var deferred = $q.defer();

function handler(result) {
if (isCEPError(result)) {
deferred.reject("Error generating PDF");
return;
}

deferred.resolve(result);
}

csi.evalScript("EAEM.exportAsPDF()", handler);

return deferred.promise;
}

function getFilename(path) {
path = path ? path.replace(/\\/g, '/') : '';
return path.substring(path.lastIndexOf('/') + 1);
}

function getDownloadPath(){
var folderPath = csi.getSystemPath(SystemPath.MY_DOCUMENTS) + "/eaem";
fs.makedir(folderPath);

return folderPath;
}

function getHostPort(host){
var arr = [];

if(host.indexOf("/") >= 0){
host = host.substring(host.lastIndexOf("/") + 1);
}

if(host.indexOf(":") < 0){
arr.push(host);
arr.push("80")
}else{
arr.push(host.split(":")[0]);
arr.push(host.split(":")[1]);
}

return arr;
}

//login with nodejs to capture login cookie
function loginWithNJS(username, password, damHost){
var hp = getHostPort(damHost);

if(!_.isEmpty($rootScope.dam)){
return $q.when($rootScope.dam);
}

var deferred = $q.defer(), dam = { host : damHost };

var options = {
hostname: hp[0],
port: hp[1],
path: "/libs/granite/core/content/login.html/j_security_check",
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
method: 'POST'
};

var req = nhttp.request(options, function(res) {
var cookies = res.headers["set-cookie"];

_.each(cookies, function(cookie){
if(cookie.indexOf("login-token") == -1){
return;
}

dam.loginToken = cookie.split('login-token=')[1];
});

if(_.isEmpty(dam.loginToken)){
deferred.reject("Trouble logging-in, Invalid Credentials?");
return;
}

$rootScope.dam = dam;

$http.get( appendLoginToken(dam.host + '/libs/granite/csrf/token.json')).then(function(data){
dam.csrfToken = data.data.token;
deferred.resolve(dam);
})
});

req.on('error', function(e) {
deferred.reject("Trouble logging-in, Invalid Credentials?");
});

var data = nqs.stringify({
_charset_: "UTF-8",
j_username: username,
j_password: password,
j_validate: true
});

req.write(data);
req.end();

return deferred.promise;
}

function appendLoginToken(url){
return url + (url.indexOf("?") == -1 ? "?" : "&") + "j_login_token=" + $rootScope.dam.loginToken;
}

function downloadWithNJS(damPaths){
if(_.isEmpty(damPaths)){
return $q.when({});
}

damPaths = _.uniq(damPaths);

var deferred = $q.defer(), filePaths = {},
count = damPaths.length, dam = $rootScope.dam;

_.each(damPaths, handler);

return deferred.promise;

function handler(damPath){
damPath = decodeURIComponent(damPath);

var url = appendLoginToken(dam.host + damPath),
filePath = getDownloadPath() + "/" + getFilename(damPath);

if (nfs.existsSync(filePath)) {
nfs.unlinkSync(filePath);
}

var file = nfs.openSync(filePath, 'w');

var req = nhttp.get(url, function(res) {
if(res.statusCode == 404){
handle404(damPath);
return;
}

res.on('data', function(chunk) {
nfs.writeSync(file, chunk, 0, chunk.length);
});

res.on('end', function() {
nfs.closeSync(file);

count--;

filePaths[damPath] = filePath;

if(count != 0){
return;
}

deferred.resolve(filePaths);
});
});

req.on('error', function(e) {
deferred.reject("Error downloading file");
});
}

function handle404(damPath){
alert("Asset Not Found - " + damPath);
}
}

function uploadWithNJS(localPath, damFolderPath){
if(_.isEmpty(localPath) || _.isEmpty(damFolderPath)){
return $q.when( { "error" : "Empty paths"} );
}

var BUFFER_SIZE = 5 * 1024 * 1024, // 5MB
dam = $rootScope.dam,
uploadPath = appendLoginToken(dam.host + damFolderPath + ".createasset.html"),
file = nfs.openSync(localPath, 'r'),
deferred = $q.defer();

readNextBytes(0);

return deferred.promise;

function readNextBytes(offset){
var buffer = nbuffer.Buffer(BUFFER_SIZE, 'base64'),
bytes = nfs.readSync(file, buffer, 0, BUFFER_SIZE, null),
complete = false;

if (bytes < BUFFER_SIZE) {
buffer = buffer.slice(0, bytes);
complete = true;
}

uploadBlob(getBlob(buffer), offset, complete);
}

function uploadBlob(blob, offset, complete) {
var fd = new FormData();

fd.append('file', blob);
fd.append("fileName", getFilename(localPath));
fd.append("file@Offset", offset);
fd.append("file@Length", 0);
fd.append("file@Completed", complete);
fd.append("_charset_", "utf-8");

return $http.post(uploadPath, fd, {
transformRequest: angular.identity, //no transformation return data as-is
headers: {
'CSRF-Token' : dam.csrfToken,
'Content-Type': undefined //determine based on file type
}
}).then(function () {
if (complete) {
nfs.closeSync(file);
deferred.resolve(damFolderPath + "/" + getFilename(localPath));
return;
}
readNextBytes(offset + BUFFER_SIZE);
}, failure);

function failure() {
nfs.closeSync(file);
alert("Error upoading");
}
}

function getBlob(fileOrBytes){
var bytes = fileOrBytes.data ? atob(decodeURIComponent(escape(fileOrBytes.data)).replace(/\s/g, ''))
: fileOrBytes;

var bArrays = [];
var SLICE_LEN = 1024, end, slice, nums;

for (var offset = 0; offset < bytes.length; offset = offset + SLICE_LEN) {
end = offset + SLICE_LEN;
slice = bytes.slice(offset, end < bytes.length ? end : bytes.length);
nums = new Array(slice.length);

for (var i = 0; i < slice.length; i++) {
nums[i] = fileOrBytes.data ? slice.charCodeAt(i) : slice[i];
}

bArrays.push(new Uint8Array(nums));
}

return new Blob(bArrays, {
type: "application/octet-binary"
});
}
}
}

SearchService.$inject = [ '_', '$http', '$rootScope' ];

function SearchService(_, $http, $rootScope){
return function (defaults) {
this.aem = "http://localhost:4502";
this.params = _.extend( { j_login_token : $rootScope.dam.loginToken }, defaults);
this.numPredicates = 0;

this.host = function(aem){
this.aem = aem;
return this;
};

this.fullText = function (value) {
if (!value) {
return this;
}

this.params[this.numPredicates + '_fulltext'] = value;
this.numPredicates++;

return this;
};

this.http = function(){
var builder = this;

return $http({
method: 'GET',
url: builder.aem + "/bin/querybuilder.json",
params: builder.params
});
}
}
}
}());

7) Angular controller placeController for binding the logic to html page is defined in js/place-controller.js

'use strict';

(function () {
var app = angular.module('SearchAEM', ['aem']);
app.directive('ngEnter', ngEnterFn);
app.controller('placeController', PlaceController);

function ngEnterFn(){
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if (event.which === 13) {
scope.$apply(function() {
scope.$eval(attrs.ngEnter);
});

event.preventDefault();
}
});
};
}

PlaceController.$inject = [ '$scope', 'aemService', 'searchService', '$http', 'csi', 'cep' ];

function PlaceController($scope, aemService, searchService, $http, csi, cep){
$scope.damHost = "localhost:4502";
$scope.showLogin = true;

var searchDefaults = {
'path': "/content/dam",
'type': 'dam:Asset',
'orderby': '@jcr:content/jcr:lastModified',
'orderby.sort': 'desc',
'p.hits': 'full',
'p.nodedepth': 2,
'p.limit': 25,
'p.offset': 0
};

$scope.login = login;

$scope.search = search;

$scope.select = select;

$scope.place = place;

$scope.generatePDFAndUpload = generatePDFAndUpload;

function login() {
if (!$scope.j_username || !$scope.j_password || !$scope.damHost) {
alert("Enter credentials");
return;
}

$scope.damHost = $scope.damHost.trim();

if ($scope.damHost.indexOf("http://") == -1) {
$scope.damHost = "http://" + $scope.damHost;
}

function success(){
$scope.showLogin = false;
}

function error(message){
alert(message);
}

aemService.loginWithNJS($scope.j_username, $scope.j_password, $scope.damHost)
.then(success, error);
}

function search(){
if (!$scope.term) {
alert("Enter search term");
return;
}

$scope.results = [];

var mapHit = function(hit) {
var result;

result = {};

result.selected = false;
result.name = aemService.getFilename(hit["jcr:path"]);
result.path = hit["jcr:path"];
result.imgPath = aemService.appendLoginToken($scope.damHost + hit["jcr:path"]
+ "/jcr:content/renditions/cq5dam.thumbnail.140.100.png");
result.format = hit["jcr:content"]["metadata"]["dc:format"];

if(result.format == "text/html"){
result.imgPath = "../img/txt.png";
}

return result;
};

new searchService(searchDefaults).host($scope.damHost)
.fullText($scope.term)
.http()
.then(function(resp) {
$scope.results = _.compact(_.map(resp.data.hits, mapHit));
});
}

function select(result){
result.selected = !result.selected;
}

function place(){
var toDownload = _.reject($scope.results, function(result){
return !result.selected;
});

aemService.downloadWithNJS(_.pluck(toDownload, 'path'))
.then(setDownloadedPaths)
.then(aemService.placeAssets)
.then(aemService.uploadWithNJS)
}

function setDownloadedPaths(filePaths){
_.each($scope.results, function(result){
result.localPath = filePaths[result.path] || '';
});

return filePaths;
}

function generatePDFAndUpload(){
if(_.isEmpty($scope.uploadPath)){
alert("Enter PDF upload location in AEM");
return;
}

aemService.generatePDF()
.then(upload)
.then(function(damFilePath){
alert("Uploaded - " + damFilePath);
});

function upload(pdfPath){
return aemService.uploadWithNJS(pdfPath, $scope.uploadPath);
}
}
}
}());

8) InDesign extend script logic for creating a new document and place the downloaded assets on pages is defined in jsx/place-assets.jsx
(function () {
if (typeof EAEM == "undefined") {
EAEM = {
COLUMNS_PER_SPREAD: 3,
ROWS_PER_SPREAD: 2
};
}

function collectionToArray(theCollection) {
return (theCollection instanceof Array) ? theCollection.slice(0)
: theCollection.everyItem().getElements().slice(0);
}

function getContainerAssetCount(spreadOrGroup){
var pageItems = collectionToArray(spreadOrGroup.pageItems),
count = 0;

for (var pageItemIdx = 0; pageItemIdx < pageItems.length; pageItemIdx++) {
var pageItem = pageItems[pageItemIdx];

if (pageItem instanceof Group) {
count = count + getContainerAssetCount(pageItem);
}else {
count++;
}
}

return count;
}

function getPlaceSpread(document){
var lastSpread = document.spreads.lastItem();

var count = getContainerAssetCount(lastSpread),
spread;

if (count < (EAEM.COLUMNS_PER_SPREAD * EAEM.ROWS_PER_SPREAD)) {
spread = lastSpread;
}else{
spread = document.spreads.add();
}

return spread;
}

function getNextGridPos(spread) {
var gridPos = {
row: 0,
column: 0
};

var count = getContainerAssetCount(spread);

if(count > 0){
gridPos.row = Math.floor(count / EAEM.COLUMNS_PER_SPREAD);
gridPos.column = count % EAEM.COLUMNS_PER_SPREAD;
}

return gridPos;
}

function createPageItem(spread) {
var rect = spread.textFrames.add();

var y1 = 0; // upper left Y-Coordinate
var x1 = 0; // upper left X-Coordinate
var y2 = 275; // lower right Y-Coordinate
var x2 = 160; // lower right X-Coordinate

rect.geometricBounds = [ y1 , x1 , y2 , x2 ];

return rect;
}

function movePageItem(document, spread, gridPos, rect){
var marginTop = document.marginPreferences.top;
var marginBottom = document.marginPreferences.bottom;
var marginLeft = document.marginPreferences.left;
var marginRight = document.marginPreferences.right;

var spreadLeftTop = spread.pages.firstItem().resolve(
AnchorPoint.TOP_LEFT_ANCHOR, CoordinateSpaces.SPREAD_COORDINATES)[0];
var spreadRightBottom = spread.pages.lastItem().resolve(
AnchorPoint.BOTTOM_RIGHT_ANCHOR, CoordinateSpaces.SPREAD_COORDINATES)[0];

var spreadWidth = spreadRightBottom[0] - spreadLeftTop[0] - marginLeft - marginRight;
var spreadHeight = spreadRightBottom[1] - spreadLeftTop[1] - marginTop - marginBottom;

var stepH = spreadWidth / EAEM.COLUMNS_PER_SPREAD;
var stepV = spreadHeight / EAEM.ROWS_PER_SPREAD;

var xPos = spreadLeftTop[0] + gridPos.column * stepH + marginLeft + 10;
var yPos = spreadLeftTop[1] + gridPos.row * stepV + marginTop + 25;

var rectTop = rect.resolve(AnchorPoint.TOP_LEFT_ANCHOR, CoordinateSpaces.SPREAD_COORDINATES)[0];

var deltaX = xPos - rectTop[0];
var deltaY = yPos - rectTop[1];

rect.move(null,[deltaX, deltaY]);
}

function placeImage(rect, pdfPath){
rect.contents = "";
rect.contentType = ContentType.UNASSIGNED;
rect.place(pdfPath);
rect.fit(FitOptions.PROPORTIONALLY);
}

EAEM.placeAssets = function(commaSepPaths){
var result = "ERROR", document,
units = app.scriptPreferences.measurementUnit;

try{
app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;

if(app.documents.length == 0){
document = app.documents.add();
}else{
document = app.activeDocument;
}

var assetsArray = commaSepPaths.split(",");

for(var i = 0; i < assetsArray.length; i++){
var spread = getPlaceSpread(document);

var gridPos = getNextGridPos(spread);

var rect = createPageItem(spread);

movePageItem(document, spread, gridPos, rect);

placeImage(rect, assetsArray[i]);
}

result = "SUCCESS";
}catch(err){
result = "ERROR";
}

app.scriptPreferences.measurementUnit = units;

return result;
};

EAEM.exportAsPDF = function(){
var document, result = "ERROR";

try{
if(app.documents.length == 0){
document = app.documents.add();
}else{
document = app.activeDocument;
}

var pdfPath = Folder.myDocuments.fsName.replace(/\\/g, '/') + "/eaem/" + document.name + ".pdf";

document.exportFile(ExportFormat.pdfType, new File(pdfPath), false,
app.pdfExportPresets.item("[High Quality Print]"));

result = pdfPath;
}catch(err){
result = "ERROR";
}

return result;
};
})();



AEM 62 - Touch UI Rich Text Editor InPlace Editing perform Spell Check before Save

$
0
0

Goal


Touch UI Rich Text Editor In Place Editing - perform Spell Check on the edited content before saving it to CRX.

Out of the box spell check plugin is available in InPlace editing full screen mode (enable the plugin in rtePlugins eg. /libs/foundation/components/text/dialog/items/tab1/items/text/rtePlugins/spellcheck); With the plugin enabled, user has to explicitly click on the following plugin button before saving, to do the spell check on content...



This extension listens to the beforeFinish event of InPlace Editing to do the spellcheck, relieving user from the manual task of clicking spellcheck button each time; provides spell check functionality in inline mode as well

Spellcheck DOESN'T need to be enabled (in rtePlugins) for this extension to work

Demo | Package Install




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/eaem-touchui-rte-spellcheck-before-save

2) Create node /apps/eaem-touchui-rte-spellcheck-before-save/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.dialog and dependencies to underscore

3) Create file (nt:file) /apps/eaem-touchui-rte-spellcheck-before-save/clientlib/js.txt and add

                       inline-edit-spellcheck-before-save.js

4) Create file (nt:file) /apps/eaem-touchui-rte-spellcheck-before-save/clientlib/inline-edit-spellcheck-before-save.js and add the following code

(function ($, $document, gAuthor) {
if(!gAuthor){
return;
}

var SPELL_CHECK_URL = "/libs/cq/ui/rte/spellcheck",
currentEditable = null, doSpellCheck = true;

$document.on("cq-editables-loaded", function(event){
$.each(event.editables, function(index, editable){
if(!editable.dom || !isInPlaceEditingEnabled(editable)){
return;
}

editable.dom.on("editing-start", getEditStartedListener(editable));
});
});

$document.on("inline-edit-finish", function (event) {
event.editable.dom.on("editing-start", getEditStartedListener(event.editable));
});

function isInPlaceEditingEnabled(editable){
try{
var editConfig = editable.config.editConfig;
return editConfig && editConfig.inplaceEditingConfig && editConfig.inplaceEditingConfig.active;
}catch(err){
return false;
}
}

function getEditStartedListener(editable){
var gRegistry = Granite.author.editor.registry,
emptyFn = function(){};

if(_.isEmpty(gRegistry)){
console.log("EAEM - Granite author registry not available");
return emptyFn;
}

var inlineTextEditor = gRegistry["text"];

if(!inlineTextEditor){
console.log("EAEM - Granite author rte not available");
return emptyFn;
}

return function eaemEditStartedListener(){
if(!inlineTextEditor.rte){
return;
}

currentEditable = editable;

doSpellCheck = true;

var listeners = inlineTextEditor.rte.options.listeners,
beforeFinishFn = listeners["beforeFinish"],
onStartedFn = listeners["onStarted"];

if(!beforeFinishFn){
listeners["beforeFinish"] = eaemBeforeFinishListener;
}else{
listeners["beforeFinish"] = function(){
eaemBeforeFinishListener();
beforeFinishFn();
}
}

if(!onStartedFn){
listeners["onStarted"] = eaemEditStartedListener;
}else{
listeners["onStarted"] = function(){
eaemEditStartedListener();
onStartedFn();
}
}
}
}

function eaemBeforeFinishListener(){
return performSpellCheck(this.getContent());
}

function performSpellCheck(content){
if(!doSpellCheck){
return false;
}

var doNotSave = false;

$.ajax({
async: false,
url: SPELL_CHECK_URL,
data: {
"_charset_": "utf-8",
"mode": "text",
"html": "true",
"text": content
}
}).done(handler);

function handler(spellCheckResults){
if(_.isEmpty(spellCheckResults.words)){
return;
}

var spellCheckPlugin = getMockSpellCheckPlugin();
spellCheckPlugin.checkSuccess(spellCheckResults);

showMessageBox("Spell check found mistakes...", "Spellcheck");

doNotSave = true;
}

return doNotSave;
}

function getMockSpellCheckPlugin(){
var inlineTextEditor = Granite.author.editor.registry["text"],
spellCheckPlugin = new CUI.rte.plugins.SpellCheckerPlugin(inlineTextEditor.rte.editorKernel, "spellcheck");

spellCheckPlugin.config = {
"invalidStyle": "border-bottom: dotted red;"
};

spellCheckPlugin.checkTextUI = {
setHighlighted: function(){}
};

return spellCheckPlugin;
}

function showMessageBox(message, title){
var fui = $(window).adaptTo("foundation-ui"),
options = [{
id: "RE-EDIT",
text: "RE-EDIT",
primary: true
},{
id: "SAVE",
text: "SAVE",
warning: true
}];

message = message || "Message";
title = title || "Title";

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

function handler(btnId){
var inlineTextEditor = Granite.author.editor.registry["text"];

doSpellCheck = false;

if (btnId === "SAVE") {
inlineTextEditor.rte.editorKernel.execCmd("save");
}else{
_.debounce(startInlineEdit, 500)();
}
}
}

function startInlineEdit(){
var inlineTextEditor = Granite.author.editor.registry["text"];
inlineTextEditor.startInlineEdit(currentEditable);
}
}(jQuery, jQuery(document), Granite.author));

AEM 62 - Touch UI Rich Text Editor InPlace Editing Open in FullScreen

$
0
0

Goal


Open Rich Text Editor InPlace Editing in Full Screen mode by default, listening to the editing-start event

Demo | Package Install


Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touchui-rte-edit-in-fullscreen

2) Create node /apps/eaem-touchui-rte-edit-in-fullscreen/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.authoring.dialog, String property dependencies with value underscore

3) Create file (nt:file) /apps/eaem-touchui-rte-edit-in-fullscreen/clientlib/js.txt, add

                       edit-in-fullscreen.js

4) Create file (nt:file) /apps/eaem-touchui-rte-edit-in-fullscreenclientlib/edit-in-fullscreen.js, add the following code

(function ($, $document, gAuthor) {
if(!gAuthor){
return;
}

$document.on("cq-editables-loaded", function(event){
$.each(event.editables, function(index, editable){
if(!editable.dom || !isInPlaceEditingEnabled(editable)){
return;
}

editable.dom.on("editing-start", getEditStartedListener());
});
});

$document.on("inline-edit-finish", function (event) {
event.editable.dom.on("editing-start", getEditStartedListener());
});

function isInPlaceEditingEnabled(editable){
try{
var editConfig = editable.config.editConfig;
return editConfig && editConfig.inplaceEditingConfig && editConfig.inplaceEditingConfig.active;
}catch(err){
return false;
}
}

function getEditStartedListener(){
var gRegistry = Granite.author.editor.registry,
emptyFn = function(){};

if(_.isEmpty(gRegistry)){
console.log("EAEM - Granite author registry not available");
return emptyFn;
}

var inlineTextEditor = gRegistry["text"];

if(!inlineTextEditor){
console.log("EAEM - Granite author rte not available");
return emptyFn;
}

return function eaemEditStartedListener(){
if(!inlineTextEditor.rte){
return;
}

inlineTextEditor.rte.editorKernel.execCmd("fullscreen-start");
}
}
}(jQuery, jQuery(document), Granite.author));

AEM 62 - Touch UI Content Fragment Editor RTE Spell Check Plugin

$
0
0

Goal


Add Spell Check to the Rich Text Editor of Content Fragments. New in AEM 62, learn more about content fragments here

Demo | Package Install


The Plugin



Mistakes Underlined Red



No Mistakes



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touchui-cfm-rte-spellcheck-plugin

2) Create node /apps/eaem-touchui-cfm-rte-spellcheck-plugin/clientlib of type cq:ClientLibraryFolder, add String property categories with value dam.cfm.authoring.editors, String[] property dependencies with value underscore, granite.shared

3) Create file (nt:file) /apps/eaem-touchui-cfm-rte-spellcheck-plugin/clientlib/js.txt, add

            /etc/clientlibs/granite/coralui2/optional/rte/js/components/rte/plugins/AbstractSpellCheckerPlugin.js
            /libs/cq/gui/components/authoring/editors/clientlibs/core/js/editors/RTE.SpellCheckerPlugin.js
            cfm-rte-spellcheck.js

4) Create file (nt:file) /apps/eaem-touchui-cfm-rte-spellcheck-plugin/clientlib/cfm-rte-spellcheck.js, add the following code

(function ($, $document) {
var SPELL_CHECK_URL = "/libs/cq/ui/rte/spellcheck",
SPELL_CHECK_PLUGIN = "spellcheck",
SPELL_CHECK_CHECK_TEXT = "checktext",
pluginAdded = false;

$document.on("cfm:contentchange", addSpellCheckPlugin);

function addSpellCheckPlugin(event, data) {
if (pluginAdded) {
return;
}

var editor = data.editor;

if (!(editor instanceof Dam.CFM.StyledTextEditor)) {
return;
}

pluginAdded = true;

var ek = editor.rte.getEditorKernel(),
$toolbar = editor.$toolbar,
spellCheckPlugin = getMockSpellCheckPlugin(ek);

$toolbar.append(getSpellCheckHtml());

var scElem = new CUI.rte.ui.stub.ElementImpl(SPELL_CHECK_CHECK_TEXT, spellCheckPlugin, true);

scElem.notifyToolbar(ek.toolbar);

editor.$editor.on("click", function () {
scElem.plugin.clearInvalidationMarks(ek.editContext);
});

scElem.$el.on("click.rte-handler", function () {
performSpellCheck(ek.getProcessedHtml());
});
}

function getSpellCheckHtml(){
return '<div>' +
'<button is="coral-button" variant="quiet" icon="'
+ SPELL_CHECK_PLUGIN + '" iconsize="S" data-rte-command="'
+ SPELL_CHECK_CHECK_TEXT + '">' +
'</button>' +
'</div>';
}

function getMockSpellCheckPlugin(editorKernel){
var spellCheckPlugin = new CUI.rte.plugins.SpellCheckerPlugin(editorKernel, SPELL_CHECK_PLUGIN);

spellCheckPlugin.config = {
method: "POST",
spellcheckerUrl: SPELL_CHECK_URL,
invalidStyle: "border-bottom: dotted red;"
};

spellCheckPlugin.checkTextUI = {
setHighlighted: function(){}
};

return spellCheckPlugin;
}

function performSpellCheck(content){
$.ajax({
url: SPELL_CHECK_URL,
data: {
"_charset_": "utf-8",
"mode": "text",
"html": "true",
"text": content
}
}).done(handler);

function handler(spellCheckResults){
if(!_.isEmpty(spellCheckResults.words)){
return;
}

showMessageBox("No mistakes found", "Spellcheck");
}
}

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

message = message || "Ok";
title = title || "Ok";

fui.prompt(title, message, "success", options);
}
}(jQuery, jQuery(document)));

AEM 62 - Touch UI Assets Console add Metadata while Uploading Files

$
0
0

Goal


Show simple Metadata Form in the Upload Dialog of Touch UI Assets Console; With this extension users can add metadata while uploading files

Demo | Package Install


Metadata Schema

Add a new tab to Image metadata schema form (how-to documentation) for showing the metadata entered while uploading Image assets



Metadata Form in Upload Dialog



Metadata Display



Metadata in CRX



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-assets-file-upload-with-metadata

2) Metadata form shown in Upload Dialog is Authoring Dialog; Create the dialog /apps/eaem-assets-file-upload-with-metadata/dialog, add metadata form nodes




      Dialog XML

<?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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
margin="{Boolean}false"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldLabel="Title"
name="./eaemTitle"/>
<alt
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="(leave empty to use the title defined above)"
fieldLabel="Alt Text"
name="./eaemAltText"/>
</items>
</column>
</items>
</jcr:root>


3) Create node /apps/eaem-assets-file-upload-with-metadata/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value dam.gui.coral.fileupload, cq.authoring.dialog String[] property dependencies with value underscore

4) Create file (nt:file) /apps/eaem-assets-file-upload-with-metadata/clientlib/js.txt, add

            fileupload-with-metadata.js

5) Create file (nt:file) /apps/eaem-assets-file-upload-with-metadata/clientlib/fileupload-with-metadata.js, add the following code

(function($, $document) {
var METADATA_DIALOG = "/apps/eaem-assets-file-upload-with-metadata/dialog.html",
METADATA_PREFIX = "eaem",
url = document.location.pathname,
metadataDialogAdded = false;

if( url.indexOf("/assets.html") == 0 ){
handleAssetsConsole();
}else if(url.indexOf(METADATA_DIALOG) == 0){
handleMetadataDialog();
}

function handleAssetsConsole(){
$document.on("foundation-contentloaded", handleFileAdditions);
}

function handleMetadataDialog(){
$document.on("foundation-contentloaded", styleMetadataIframe);
}

function registerReceiveDataListener(handler) {
if (window.addEventListener) {
window.addEventListener("message", handler, false);
} else if (window.attachEvent) {
window.attachEvent("onmessage", handler);
}
}

function styleMetadataIframe(){
$(".cq-dialog-actions").remove();
registerReceiveDataListener(sendMetadata);
}

function sendMetadata(event){
var message = JSON.parse(event.data);

var $dialog = $(".cq-dialog"),
$fields = $dialog.find("[name^='./']"),
data = {}, $field, name, value;

$fields.each(function(index, field){
$field = $(field);
name = $field.attr("name");
value = $field.val();

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

name = name.substr(2);

if(!name.indexOf(METADATA_PREFIX) === 0){
return
}

data[name] = value;
});

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

function handleFileAdditions(){
var $fileUpload = $("coral-chunkfileupload"),
$metadataIFrame;

$fileUpload.on('coral-fileupload:fileadded', addMetadataDialog);

$fileUpload.on('coral-fileupload:loadend', postMetadata);

function sendDataMessage(message){
$metadataIFrame[0].contentWindow.postMessage(JSON.stringify(message), "*");
}

function addMetadataDialog(){
if(metadataDialogAdded){
return;
}

metadataDialogAdded = true;

_.debounce(addDialog, 500)();
}

function addDialog(){
var $dialog = $("coral-dialog-header:contains('Upload')").closest("coral-dialog"),
iFrame = '<iframe width="550px" height="250px" src="' + METADATA_DIALOG + '"/>',
$dialogContent = $dialog.find("coral-dialog-content");

$metadataIFrame = $(iFrame).appendTo($dialogContent.css("max-height", "500px"));
$dialogContent.find("input").css("width", "28rem");
$dialogContent.closest(".coral-Dialog-wrapper").css("top", "40%").css("left", "40%");
}

function postMetadata(event){
var detail = event.originalEvent.detail,
folderPath = detail.action.replace(".createasset.html", ""),
assetMetadataPath = folderPath + "/" + detail.item._file.name + "/jcr:content/metadata";

var data = {
path: assetMetadataPath
};

sendDataMessage(data);
}
}
})(jQuery, jQuery(document));


AEM 62 - Touch UI Nested ( Multi-Multi ) Composite Multifield storing data as JSON

$
0
0

Goal


Create a 62 Touch UI Nested Composite Multifield aka Multi-Multi Field storing the entered data as JSON. Package Install contains a sample component using this multifield extension

PS: If you are using any other Touch UI multifield extension the re-registering of multifield using CUI.Widget.registry.register("multifield", CUI.Multifield); may cause issues

Demo | Package Install


Nested Multifield



Stored as JSON



Sample Dialog XML

#45 empty valued flag eaem-nested makes the multifield widget, nested composite multifield

<?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="EAEM Multifield TouchUI 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 Dashboard"
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">
<dashboard
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Enter Dashboard name"
fieldLabel="Dashboard name"
name="./dashboard"/>
<countries
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
class="full-width"
fieldDescription="Click '+' to add a new page"
fieldLabel="Countries">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/fieldset"
eaem-nested=""
name="./countries">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
method="absolute"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<country
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Name of Country"
fieldLabel="Country Name"
name="./country"/>
<states
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
class="full-width"
fieldDescription="Click '+' to add a new page"
fieldLabel="States">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/fieldset"
name="./states">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
method="absolute"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<state
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Name of State"
fieldLabel="State Name"
name="./state"/>
<path
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
fieldDescription="Select Path"
fieldLabel="Path"
name="./path"
rootPath="/content"/>
</items>
</column>
</items>
</field>
</states>
</items>
</column>
</items>
</field>
</countries>
</items>
</column>
</items>
</fieldset>
</items>
</column>
</items>
</content>
</jcr:root>


Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touch-ui-nested-multi-field-panel

2) Create node /apps/eaem-touch-ui-nested-multi-field-panel/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.authoring.dialog, String property dependencies with value underscore

3) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-panel/clientlib/js.txt, add

                       nested-multifield.js

4) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-panel/clientlib/nested-multifield.js, add the following code

(function ($, $document) {
var DATA_EAEM_NESTED = "data-eaem-nested",
CFFW = ".coral-Form-fieldwrapper";

//reads multifield data from server, creates the nested composite multifields and fills them
function addDataInFields() {
$document.on("dialog-ready", dlgReadyHandler);

function dlgReadyHandler() {
var mName = $("[" + DATA_EAEM_NESTED + "]").data("name");

if(!mName){
return;
}

//strip ./
mName = mName.substring(2);

var $fieldSets = $("[" + DATA_EAEM_NESTED + "][class='coral-Form-fieldset']"),
$form = $fieldSets.closest("form.foundation-form"),
actionUrl = $form.attr("action") + ".json";

$.ajax(actionUrl).done(postProcess);

function postProcess(data){
if(!data || !data[mName]){
return;
}

var mValues = data[mName], $field, name;

if(_.isString(mValues)){
mValues = [ JSON.parse(mValues) ];
}

_.each(mValues, function (record, i) {
if (!record) {
return;
}

if(_.isString(record)){
record = JSON.parse(record);
}

_.each(record, function(rValue, rKey){
$field = $($fieldSets[i]).find("[name='./" + rKey + "']");

if(_.isArray(rValue) && !_.isEmpty(rValue)){
fillNestedFields( $($fieldSets[i]).find("[data-init='multifield']"), rValue);
}else{
$field.val(rValue);
}
});
});
}

//creates & fills the nested multifield with data
function fillNestedFields($multifield, valueArr){
_.each(valueArr, function(record, index){
$multifield.find(".js-coral-Multifield-add").click();

_.each(record, function(value, key){
var $field = $($multifield.find("[name='./" + key + "']")[index]);
$field.val(value);
})
})
}
}
}

function fillValue($field, record){
var name = $field.attr("name");

if (!name) {
return;
}

//strip ./
if (name.indexOf("./") == 0) {
name = name.substring(2);
}

record[name] = $field.val();

//remove the field, so that individual values are not POSTed
$field.remove();
}

//for getting the nested multifield data as js objects
function getRecordFromMultiField($multifield){
var $fieldSets = $multifield.find("[class='coral-Form-fieldset']");

var records = [], record, $fields, name;

$fieldSets.each(function (i, fieldSet) {
$fields = $(fieldSet).find("[name]");

record = {};

$fields.each(function (j, field) {
fillValue($(field), record);
});

if(!$.isEmptyObject(record)){
records.push(record)
}
});

return records;
}

//collect data from widgets in multifield and POST them to CRX as JSON
function collectDataFromFields(){
$document.on("click", ".cq-dialog-submit", collectHandler);

function collectHandler() {
var $form = $(this).closest("form.foundation-form"),
mName = $("[" + DATA_EAEM_NESTED + "]").data("name"),
$fieldSets = $("[" + DATA_EAEM_NESTED + "][class='coral-Form-fieldset']");

var record, $fields, $field, name, $nestedMultiField;

$fieldSets.each(function (i, fieldSet) {
$fields = $(fieldSet).children().children(CFFW);

record = {};

$fields.each(function (j, field) {
$field = $(field);

//may be a nested multifield
$nestedMultiField = $field.find("[data-init='multifield']");

if($nestedMultiField.length == 0){
fillValue($field.find("[name]"), record);
}else{
name = $nestedMultiField.find("[class='coral-Form-fieldset']").data("name");

if(!name){
return;
}

//strip ./
name = name.substring(2);

record[name] = getRecordFromMultiField($nestedMultiField);
}
});

if ($.isEmptyObject(record)) {
return;
}

//add the record JSON in a hidden field as string
$('<input />').attr('type', 'hidden')
.attr('name', mName)
.attr('value', JSON.stringify(record))
.appendTo($form);
});
}
}

$document.ready(function () {
addDataInFields();
collectDataFromFields();
});

//extend otb multifield for adjusting event propagation when there are nested multifields
//for working around the nested multifield add and reorder
CUI.Multifield = new Class({
toString: "Multifield",
extend: CUI.Multifield,

construct: function (options) {
this.script = this.$element.find(".js-coral-Multifield-input-template:last");
},

_addListeners: function () {
this.superClass._addListeners.call(this);

//otb coral event handler is added on selector .js-coral-Multifield-add
//any nested multifield add click events are propagated to the parent multifield
//to prevent adding a new composite field in both nested multifield and parent multifield
//when user clicks on add of nested multifield, stop the event propagation to parent multifield
this.$element.on("click", ".js-coral-Multifield-add", function (e) {
e.stopPropagation();
});

this.$element.on("drop", function (e) {
e.stopPropagation();
});
}
});

CUI.Widget.registry.register("multifield", CUI.Multifield);
})(jQuery, jQuery(document));


AEM 62 - Touch UI URL Parameter Layer Mode (Preview, Edit, Developer...)

$
0
0

Goal


Load the layer specified in eaemMode URL Parameter

http://localhost:4502/editor.html/content/geometrixx/en.html?eaemMode=Preview
http://localhost:4502/editor.html/content/geometrixx/en.html?eaemMode=Edit
http://localhost:4502/editor.html/content/geometrixx/en.html?eaemMode=Developer
etc...

Demo | Package Install | Github



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touchui-mode-url-parameter

2) Create node /apps/eaem-touchui-mode-url-parameter/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.authoring.dialog, String property dependencies with value underscore

3) Create file (nt:file) /apps/eaem-touchui-mode-url-parameter/clientlib/js.txt, add

                       mode-parameter.js

4) Create file (nt:file) /apps/eaem-touchui-mode-url-parameter/clientlib/mode-parameter.js, add the following code

(function ($, $document) {
var EAEM_MODE = "eaemMode", pageLoaded = false;

$document.on("cq-page-info-loaded", loadDesiredLayer);

function loadDesiredLayer(){
if(pageLoaded){
return;
}

pageLoaded = true;

var layerManager = Granite.author.layerManager,
queryParams = queryParameters(),
eaemMode = queryParams[EAEM_MODE],
currentLayerName = layerManager.getCurrentLayerName();

if(_.isEmpty(eaemMode) || _.isEmpty(currentLayerName)
|| (currentLayerName.toLowerCase() === eaemMode.toLowerCase())) {
return;
}

layerManager.activateLayer(eaemMode);
}

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

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

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

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


AEM 62 - Touch UI Nested ( Multi-Multi ) Composite Multifield storing data as Child Nodes

$
0
0

Goal


Create a 62 Touch UI Nested Composite Multifield aka Multi-Multi Field storing the entered data as Child Nodes. Package Install contains a sample component using this multifield extension

For storing the nested multifield data as json - check this post

PS: If you are using any other Touch UI multifield extension the re-registering of multifield using CUI.Widget.registry.register("multifield"CUI.Multifield); may cause issues

Demo | Package Install | Github


Nested Multifield



Stored as Child Nodes



Sample Dialog XML

#45 eaem-nested=NODE_STORE makes the multifield widget, nested composite multifield

<?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="EAEM TouchUI Nested Multifield"
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 Dashboard"
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">
<dashboard
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Enter Dashboard name"
fieldLabel="Dashboard name"
name="./dashboard"/>
<countries
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
class="full-width"
fieldDescription="Click '+' to add a new page"
fieldLabel="Countries">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/fieldset"
eaem-nested="NODE_STORE"
name="./countries">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
method="absolute"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<country
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Name of Country"
fieldLabel="Country Name"
name="./country"/>
<states
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
class="full-width"
fieldDescription="Click '+' to add a new page"
fieldLabel="States">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/fieldset"
name="./states">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
method="absolute"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<state
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Name of State"
fieldLabel="State Name"
name="./state"/>
<path
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
fieldDescription="Select Path"
fieldLabel="Path"
name="./path"
rootPath="/content"/>
<startDate
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/datepicker"
class="field"
displayedFormat="YYYY-MM-DD HH:mm"
fieldLabel="Start Date"
name="./startDate"
type="datetime"/>
<show
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/checkbox"
name="./show"
text="Show?"
value="yes"/>
<type
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/select"
fieldDescription="Select Size"
fieldLabel="Size"
name="./size">
<items jcr:primaryType="nt:unstructured">
<def
jcr:primaryType="nt:unstructured"
text="Select Size"
value=""/>
<small
jcr:primaryType="nt:unstructured"
text="Small"
value="small"/>
<medium
jcr:primaryType="nt:unstructured"
text="Medium"
value="medium"/>
<large
jcr:primaryType="nt:unstructured"
text="Large"
value="large"/>
</items>
</type>
<tags
jcr:primaryType="nt:unstructured"
sling:resourceType="cq/gui/components/common/tagspicker"
allowCreate="{Boolean}true"
fieldLabel="Tags"
name="./tags"/>
</items>
</column>
</items>
</field>
</states>
</items>
</column>
</items>
</field>
</countries>
</items>
</column>
</items>
</fieldset>
</items>
</column>
</items>
</content>
</jcr:root>


Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touch-ui-nested-multi-field-node-store

2) Create node /apps/eaem-touch-ui-nested-multi-field-node-store/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.authoring.dialog, String property dependencies with value underscore

3) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-node-store/clientlib/js.txt, add

                       nested-multifield.js

4) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-node-store/clientlib/nested-multifield.js, add the following code

(function ($, $document) {
var EAEM_NESTED = "eaem-nested",
DATA_EAEM_NESTED = "data-" + EAEM_NESTED,
CFFW = ".coral-Form-fieldwrapper",
NODE_STORE = "NODE_STORE";

function isNodeStoreMultifield(type) {
return (type === NODE_STORE);
}

function isSelectOne($field) {
return !_.isEmpty($field) && ($field.prop("type") === "select-one");
}

function setSelectOne($field, value) {
var select = $field.closest(".coral-Select").data("select");

if (select) {
select.setValue(value);
}
}

function isCheckbox($field) {
return !_.isEmpty($field) && ($field.prop("type") === "checkbox");
}

function setCheckBox($field, value) {
$field.prop("checked", $field.attr("value") === value);
}

function isDateField($field) {
return !_.isEmpty($field) && $field.parent().hasClass("coral-DatePicker");
}

function setDateField($field, value) {
var date = moment(new Date(value)),
$parent = $field.parent();

$parent.find("input.coral-Textfield").val(date.format($parent.data("displayed-format")));

$field.val(date.format($parent.data("stored-format")));
}

function isTagsField($fieldWrapper) {
return !_.isEmpty($fieldWrapper) && ($fieldWrapper.children(".js-cq-TagsPickerField").length > 0);
}

function getTagsFieldName($fieldWrapper) {
return $fieldWrapper.children(".js-cq-TagsPickerField").data("property-path").substr(2);
}

function getTagObject(tag){
var tagPath = "/etc/tags/" + tag.replace(":", "/");
return $.get(tagPath + ".tag.json");
}

function setTagsField($fieldWrapper, tags) {
if(_.isEmpty(tags)){
return;
}

var cuiTagList = $fieldWrapper.find(".coral-TagList").data("tagList");

_.each(tags, function(tag){
getTagObject(tag).done(function(data){
cuiTagList._appendItem( { value: data.tagID, display: data.titlePath} );
});
});
}

function isMultifield($formFieldWrapper){
return ($formFieldWrapper.children("[data-init='multifield']").length > 0);
}

function setWidgetValue($field, value) {
if (_.isEmpty($field)) {
return;
}

if(isSelectOne($field)) {
setSelectOne($field, value);
}else if(isCheckbox($field)) {
setCheckBox($field, value);
}else if(isDateField($field)) {
setDateField($field, value);
}else {
$field.val(value);
}
}

function getMultifields($formField, isInner){
var mNames = {}, mName, $multifield, $template,
$multiTemplates = $formField.find(".js-coral-Multifield-input-template");

$multiTemplates.each(function (i, template) {
$template = $(template);
$multifield = $($template.html());

if(!isInner && !isNodeStoreMultifield($multifield.data(EAEM_NESTED))){
return;
}

mName = $multifield.data("name").substring(2);

mNames[mName] = $template.closest(".coral-Multifield");
});

return mNames;
}

function buildMultifield(data, $multifield, mName){
var $formFieldWrapper, $field, $fieldSet, name,
innerMultifields;

_.each(data, function (value, key) {
if(key.indexOf("jcr:") === 0){
return;
}

$multifield.children(".js-coral-Multifield-add").click();

$fieldSet = $multifield.find(".coral-Form-fieldset").last();

_.each($fieldSet.find(CFFW), function (formFieldWrapper) {
$formFieldWrapper = $(formFieldWrapper);

if(isMultifield($formFieldWrapper)){
innerMultifields = getMultifields($formFieldWrapper, true);

_.each(innerMultifields, function($innerMultifield, nName){
buildMultifield(value[nName], $innerMultifield, nName);
});

return;
}else if(isTagsField($formFieldWrapper)){
setTagsField($formFieldWrapper, value[getTagsFieldName($formFieldWrapper)]);
return;
}

$field = $formFieldWrapper.find("[name]");

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

name = $field.attr("name").substr(2);

if(_.isEmpty(value[name])){
return;
}

setWidgetValue($field, value[name]);
});
})
}

function addDataInFields() {
$document.on("dialog-ready", dlgReadyHandler);

function dlgReadyHandler() {
var outerMultifields = getMultifields($(this), false),
$form = $("form.cq-dialog"),
actionUrl = $form.attr("action") + ".infinity.json";

$.ajax(actionUrl).done(postProcess);

function postProcess(data){
_.each(outerMultifields, function($outerMultifield, mName){
buildMultifield(data[mName], $outerMultifield, mName);
});
}
}
}

function fillValue($form, fieldSetName, $field, counter){
var name = $field.attr("name"), value;

if (!name) {
return;
}

if (name.indexOf("./") === 0) {
name = name.substring(2);
}

value = $field.val();

if (isCheckbox($field)) {
value = $field.prop("checked") ? $field.val() : "";
}

//remove the field, so that individual values are not POSTed
$field.remove();

$('<input />').attr('type', 'hidden')
.attr('name', fieldSetName + "/" + counter + "/" + name)
.attr('value', value)
.appendTo($form);
}

function addNestedMultifieldData($form, outerMultiName, $nestedMultiField){
var $fieldSets = $nestedMultiField.find("[class='coral-Form-fieldset']"),
nName = $fieldSets.data("name"), $fields;

if(!nName){
return;
}

nName = outerMultiName + "/" + nName.substring(2);

$fieldSets.each(function (iCounter, fieldSet) {
$fields = $(fieldSet).find("[name]");

$fields.each(function (counter, field) {
fillValue($form, nName, $(field), (iCounter + 1));
});
});
}

function collectDataFromFields(){
$document.on("click", ".cq-dialog-submit", collectHandler);

function collectHandler() {
var $form = $(this).closest("form.foundation-form"),
mName = $("[" + DATA_EAEM_NESTED + "]").data("name"),
$fieldSets = $("[" + DATA_EAEM_NESTED + "][class='coral-Form-fieldset']");

var $fields, $field, name, $nestedMultiField;

$fieldSets.each(function (oCounter, fieldSet) {
$fields = $(fieldSet).children().children(CFFW);

$fields.each(function (counter, field) {
$field = $(field);

//may be a nested multifield
$nestedMultiField = $field.find("[data-init='multifield']");

if($nestedMultiField.length == 0){
fillValue($form, mName, $(field).find("[name]"), (oCounter + 1));
}else{
addNestedMultifieldData($form, mName + "/" + (oCounter + 1) , $nestedMultiField);
}
});
});
}
}

$document.ready(function () {
addDataInFields();
collectDataFromFields();
});

//extend otb multifield for adjusting event propagation when there are nested multifields
//for working around the nested multifield add and reorder
CUI.Multifield = new Class({
toString: "Multifield",
extend: CUI.Multifield,

construct: function () {
this.script = this.$element.find(".js-coral-Multifield-input-template:last");
},

_addListeners: function () {
this.superClass._addListeners.call(this);

//otb coral event handler is added on selector .js-coral-Multifield-add
//any nested multifield add click events are propagated to the parent multifield
//to prevent adding a new composite field in both nested multifield and parent multifield
//when user clicks on add of nested multifield, stop the event propagation to parent multifield
this.$element.on("click", ".js-coral-Multifield-add", function (e) {
e.stopPropagation();
});

this.$element.on("drop", function (e) {
e.stopPropagation();
});
}
});

CUI.Widget.registry.register("multifield", CUI.Multifield);
})(jQuery, jQuery(document));


5) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-node-store/sample-nested-multi-field/sample-nested-multi-field.jsp for rendering the stored multifield data

<%@ page import="java.io.PrintWriter" %>
<%@ page import="org.apache.commons.lang.StringUtils" %>
<%@include file="/libs/foundation/global.jsp" %>
<%@page session="false" %>

<div style="display: block; border-style: solid; border-width: 1px; margin: 10px; padding: 10px">
<b>Countries and States</b>

<%
try {
if (currentNode.hasNode("countries")) {
Node countriesNode = currentNode.getNode("countries"), cNode;
int counter = 1; PropertyIterator itr = null; Property property;

while(true){
if(!countriesNode.hasNode(String.valueOf(counter))){
break;
}

cNode = countriesNode.getNode(String.valueOf(counter));

itr = cNode.getProperties();

while(itr.hasNext()){
property = itr.nextProperty();

if(property.getName().equals("jcr:primaryType")){
continue;
}
%>
<%=property.getName()%> : <b><%=property.getString()%></b>
<%
}

if(cNode.hasNode("states")){
Node statesNode = cNode.getNode("states"), sNode;
int sCounter = 1; PropertyIterator sTtr = null; Property sProperty;

while(true){
if(!statesNode.hasNode(String.valueOf(sCounter))){
break;
}

sNode = statesNode.getNode(String.valueOf(sCounter));

itr = sNode.getProperties();

while(itr.hasNext()){
sProperty = itr.nextProperty();

if(sProperty.getName().equals("jcr:primaryType")){
continue;
}

String value = null;

if (sProperty.isMultiple()) {
Value[] values = sProperty.getValues();
value = StringUtils.join(values, ",");
} else {
value = sProperty.getString();
}

%>
<div style="margin-left:30px">
<%=sProperty.getName()%> : <b><%=value%></b>
</div>
<%
}

%>

<%

sCounter = sCounter + 1;
}
}

counter = counter + 1;
}
} else {
%>
Add countries and states in dialog</b>
<%
}
} catch (Exception e) {
e.printStackTrace(new PrintWriter(out));
}
%>
</div>

AEM CC - Sample CC Scripts for handling Metadata

$
0
0

Goal


Sample Extend Scripts for handling metadata from CEP panels. Check the following posts for more information on coding HTML panels for CC usign CEP

http://experience-aem.blogspot.com/2015/01/aem-6-search-and-download-dam-assets-in-adobe-cc-like-illustrator.html


GitHub

Solution


For testing the extend script pieces (before including them in CEP Panels) use Adobe Extendscript Toolkit CC. In the following screenshots toolkit connects to InDesign CC / Illustrator CC, runs the script to set metadata. From the CEP Panels, JS object parameters to ES functions are passed as strings by calling JSON.stringify() and converted to objects using JSON.parse() - get the json2.jsx script from github)






Script - Set Metadata in InDesign CC

#include "json2.jsx"

(function () {
if (typeof EAEM == "undefined") {
EAEM = {};
}

EAEM.NS_EAEM = "NS_EAEM";
EAEM.NS_URI = "http://experience-aem.blogspot.com/eaem";
EAEM.NS_PREFIX = "eaem";

var ERROR = "ERROR",
SUCCESS = "SUCCESS";

function init() {
ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
XMPMeta.registerNamespace(EAEM.NS_URI, EAEM.NS_PREFIX);
}

EAEM.setMetaData = setMetaData;

function setMetaData(namespace, metadataJSONstr) {
var result = ERROR;

try {
var doc = app.activeDocument,
metadataObj = JSON.parse(metadataJSONstr),
value, nUri = (namespace === EAEM.NS_EAEM) ? EAEM.NS_URI : XMPConst[namespace],
xmp = doc.metadataPreferences;

for (var x in metadataObj) {
if (!metadataObj.hasOwnProperty(x)) {
continue;
}

value = metadataObj[x];

xmp.setProperty(nUri, x, '');

if (!value) {
continue;
}

if (value instanceof Array) {
if (value.length > 0) {
xmp.createContainerItem(nUri, x, undefined, ContainerType.BAG);

for (var y = 1; y <= value.length; y++) {
xmp.setProperty(nUri, x + "[" + y + "]", value[y - 1]);
}
}
} else {
xmp.setProperty(nUri, x, value);
}
}

result = SUCCESS;
} catch (err) {
result = ERROR + "-" + err.message;
}

return result;
}

function testSetMetadata(){
var metadata = {
"abbr" : "AEM",
"title" : "Adobe Experience Manager"
};

var result = setMetaData("NS_EAEM", JSON.stringify(metadata));

$.writeln("result -> " + result);
}

function testSetMetadataArray(){
var metadata = {
"products" : [ "Sites", "Assets", "Mobile"]
};

var result = setMetaData("NS_EAEM", JSON.stringify(metadata));

$.writeln("result -> " + result);
}

init();

testSetMetadata();

testSetMetadataArray();
})();


Script - Get Metadata in InDesign CC

#include "json2.jsx"

(function () {
if (typeof EAEM == "undefined") {
EAEM = {};
}

EAEM.NS_EAEM = "NS_EAEM";
EAEM.NS_URI = "http://experience-aem.blogspot.com/eaem";
EAEM.NS_PREFIX = "eaem";

var ERROR = "ERROR",
SUCCESS = "SUCCESS";

function init() {
ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
XMPMeta.registerNamespace(EAEM.NS_URI, EAEM.NS_PREFIX);
}

EAEM.getMetaData = getMetaData;

function getMetaData(props) {
var propValues = {};

try {
var propsArr = JSON.parse(props), nUri, propertyValue,
xmp = app.activeDocument.metadataPreferences;

for (var x = 0; x < propsArr.length; x++) {
propertyValue = xmp.getProperty(EAEM.NS_URI, propsArr[x]);

if(!propertyValue){
propertyValue = getXMPArrayData(xmp, EAEM.NS_URI, propsArr[x]);
}

if(propertyValue){
propValues[propsArr[x]] = propertyValue;
}
}
}
catch (err) {
propValues = ERROR + "-" + err.message;
}

return JSON.stringify(propValues);
}

function getXMPArrayData(xmp, nUri, propertyName){
var index = 1, values = [],
propertyValue = xmp.getProperty(nUri, propertyName + "[" + index++ + "]");

//its an array
if(!propertyValue) {
return values;
}

do{
values.push(propertyValue);

propertyValue = xmp.getProperty(nUri, propertyName + "[" + index++ + "]");

if(!propertyValue){
break;
}
}while(true);

return values;
}

function testGetMetadata(){
var metadata = [ "abbr", "title", "products" ];

var result = getMetaData(JSON.stringify(metadata));

$.writeln("result -> " + result);
}

init();

testGetMetadata();
})();


Script - Set Metadata in Illustrator CC

#include "json2.jsx"

(function () {
if (typeof EAEM == "undefined") {
EAEM = {};
}

EAEM.NS_EAEM = "NS_EAEM";
EAEM.NS_URI = "http://experience-aem.blogspot.com/eaem";
EAEM.NS_PREFIX = "eaem";

var ERROR = "ERROR",
SUCCESS = "SUCCESS";

function init() {
ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
XMPMeta.registerNamespace(EAEM.NS_URI, EAEM.NS_PREFIX);
}

EAEM.setMetaData = setMetaData;

function setMetaData(namespace, metadataJSONstr) {
var result = ERROR;

try {
var doc = app.activeDocument,
metadataObj = JSON.parse(metadataJSONstr),
value, nUri = (namespace === EAEM.NS_EAEM) ? EAEM.NS_URI : XMPConst[namespace],
xmp = new XMPMeta(doc.XMPString);

for (var x in metadataObj) {
if (!metadataObj.hasOwnProperty(x)) {
continue;
}

value = metadataObj[x];

xmp.deleteProperty(nUri, x);

if (!value) {
continue;
}

if (value instanceof Array) {
for (var y = 0; y < value.length; y++) {
xmp.appendArrayItem(nUri, x, value[y], 0, XMPConst.ARRAY_IS_ORDERED);
}
} else {
xmp.setProperty(nUri, x, value);
}
}

doc.XMPString = xmp.serialize(XMPConst.SERIALIZE_USE_COMPACT_FORMAT);

result = SUCCESS;
} catch (err) {
result = ERROR + "-" + err.message;
}

return result;
}

function testSetMetadata(){
var metadata = {
"abbr" : "AEM",
"title" : "Adobe Experience Manager"
};

var result = setMetaData("NS_EAEM", JSON.stringify(metadata));

$.writeln("result -> " + result);
}

function testSetMetadataArray(){
var metadata = {
"products" : [ "Sites", "Assets", "Mobile"]
};

var result = setMetaData("NS_EAEM", JSON.stringify(metadata));

$.writeln("result -> " + result);
}

init();

testSetMetadata();

testSetMetadataArray();
})();


Script - Get Metadata in Illustrator CC

#include "json2.jsx"

(function () {
if (typeof EAEM == "undefined") {
EAEM = {};
}

EAEM.NS_EAEM = "NS_EAEM";
EAEM.NS_URI = "http://experience-aem.blogspot.com/eaem";
EAEM.NS_PREFIX = "eaem";

var ERROR = "ERROR",
SUCCESS = "SUCCESS";

function init() {
ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
XMPMeta.registerNamespace(EAEM.NS_URI, EAEM.NS_PREFIX);
}

EAEM.getMetaData = getMetaData;

function getMetaData(props) {
var propValues = {};

try {
var propsArr = JSON.parse(props), nUri, property,
xmp = new XMPMeta(app.activeDocument.XMPString);

for (var x = 0; x < propsArr.length; x++) {
if (!xmp.doesPropertyExist(EAEM.NS_URI, propsArr[x])) {
continue;
}

property = xmp.getProperty(EAEM.NS_URI, propsArr[x]);

if (property.options & XMPConst.PROP_IS_ARRAY) {
var count = xmp.countArrayItems(EAEM.NS_URI, propsArr[x]),
resArr = [];

for (var k = 1; k <= count; k++) {
resArr.push(xmp.getArrayItem(EAEM.NS_URI, propsArr[x], k).toString());
}

propValues[propsArr[x]] = resArr;
}else{
propValues[propsArr[x]] = property.value;
}
}
}
catch (err) {
propValues = ERROR + "-" + err.message;
}

return JSON.stringify(propValues);
}

function testGetMetadata(){
var metadata = [ "abbr", "title", "products" ];

var result = getMetaData(JSON.stringify(metadata));

$.writeln("result -> " + result);
}

init();

testGetMetadata();
})();

AEM 62 - Touch UI Create Page Wizard Set Tags Widget Required

$
0
0

Goal


Make the Tags field required in Touch UI Create Page wizard

Demo | Package Install | Github




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touchui-create-page-tag-required

2) Create node /apps/eaem-touchui-create-page-tag-required/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.gui.common.tagspicker, String property dependencies with value underscore

3) Create file (nt:file) /apps/eaem-touchui-create-page-tag-required/clientlib/js.txt, add

                       tag-required.js

4) Create file (nt:file) /apps/eaem-touchui-create-page-tag-required/clientlib/tag-required.js, add the following code

(function ($, $document) {
var CREATE_PAGE_WIZARD_URL = "/mnt/overlay/wcm/core/content/sites/createpagewizard.html",
TAGS_FIELD = "./cq:tags";

if(window.location.pathname.indexOf(CREATE_PAGE_WIZARD_URL) !== 0){
return;
}

$document.on("foundation-contentloaded", function(){
var $tagsPicker = $("[data-property-path='" + TAGS_FIELD + "']");

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

var $tagsTextField = $tagsPicker.find("input:text"),
cuiPathBrowser = $tagsPicker.data("pathBrowser");

cuiPathBrowser.$picker.on("coral-pathbrowser-picker-confirm.tagspicker", triggerChange);

cuiPathBrowser.dropdownList.on("selected.tagspicker", triggerChange);

$document.on("click", ".coral-TagList-tag-removeButton", triggerChange);

function triggerChange(){
setTimeout(function(){
$tagsTextField.trigger("change");
}, 250);
}
});

$.validator.register({
selector: ".js-cq-TagsPickerField input:text",

validate: function ($textField) {
var $tagsPicker = $textField.closest(".js-cq-TagsPickerField"),
$tagList = $tagsPicker.parent().find(".coral-TagList");

$tagsPicker.prev("label").html("Tags *");

if ($tagList.find(".coral-TagList-tag").length <= 0) {
return "Please fill out this field";
}
}
});
}(jQuery, jQuery(document)));

AEM 62 - Create a Sample 3 Step Wizard for Processing Selected Assets

$
0
0

Goal


Create a simple 3 step wizard, say for selecting assets in a path, provide additional information in steps and submit for processing; useful for creating custom screens the "AEM way"

Demo | Package Install


The Wizard - http://localhost:4502/apps/eaem-sample-wizard/eaem-3-step-wizard.html



Wizard Structure in CRX




Solution


1) Login to CRXDE Lite - http://localhost:4502/crx/de/index.jsp and create nt:folder /apps/eaem-sample-wizard

2) Create /apps/eaem-sample-wizard/eaem-3-step-wizard of type cq:Page with the following content

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Page">
<jcr:content
jcr:mixinTypes="[sling:VanityPath]"
jcr:primaryType="nt:unstructured"
jcr:title="EAEM 3 Step Wizard"
sling:resourceType="/apps/eaem-sample-wizard/page">
<head jcr:primaryType="nt:unstructured">
<viewport
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
<favicon
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="[coralui3,granite.ui.coral.foundation,eaem.wizard]"/>
</head>
<body
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/body">
<items jcr:primaryType="nt:unstructured">
<form
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
action="/apps/eaem-sample-wizard/eaem-3-step-wizard/jcr:content"
foundationForm="{Boolean}true"
maximized="{Boolean}true"
method="post"
novalidate="{Boolean}true"
style="vertical">
<successresponse
jcr:primaryType="nt:unstructured"
jcr:title="Success"
sling:resourceType="granite/ui/components/coral/foundation/form/responses/openprompt"
open="/apps/eaem-sample-wizard/eaem-3-step-wizard.html"
redirect="/apps/eaem-sample-wizard/eaem-3-step-wizard.html"
text="Your data was submitted"/>
<items jcr:primaryType="nt:unstructured">
<charset
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/hidden"
name="_charset_"
value="utf-8"/>
<wizard
jcr:primaryType="nt:unstructured"
jcr:title="Experience AEM 3 Step Wizard"
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<step1
jcr:primaryType="nt:unstructured"
jcr:title="Step One"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/section">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
margin="{Boolean}false"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<assetsPath
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
fieldLabel="Assets Path"
name="eaemAssetsPath"
rootPath="/content/dam"/>
</items>
</column>
</items>
</title>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
text="Next"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</step1>
<step2
jcr:primaryType="nt:unstructured"
jcr:title="Step Two"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<list
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/include"
path="/libs/dam/gui/content/assets/jcr:content/views/list"/>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
text="Next"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</step2>
<step3
jcr:primaryType="nt:unstructured"
jcr:title="Step Three"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<footer
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/section">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
margin="{Boolean}false"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<text
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
fieldLabel="Text"
name="eaemText"/>
</items>
</column>
</items>
</footer>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
text="GO"
type="submit"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</step3>
</items>
<granite:data
jcr:primaryType="nt:unstructured"
suffix="${requestPathInfo.suffix}"/>
</wizard>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>

3) The extension clientlib #19, for page created above, eaem.wizard is added in next steps

4) #69, in step 1, select the folder path from where assets are to be picked




5) #98, in step 2, provide a list view of the assets in folder, selected in step 1; list view here includes otb list view from Assets Console http://localhost:4502/assets.html/content/dam -  /libs/dam/gui/content/assets/jcr:content/views/list




6)  #133, in step 3, provide a text area widget for entering additional information



7) #28 has the wizard form action /apps/eaem-sample-wizard/eaem-3-step-wizard/jcr:content of sling:resourceType /apps/eaem-sample-wizard/page

8) Create /apps/eaem-sample-wizard/page with the following content

<?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:Folder"
sling:resourceSuperType="granite/ui/components/coral/foundation/page"/>

9) Create nt:file /apps/eaem-sample-wizard/page/POST.jsp with the necessary logic to process selected assets, entered content submitted in step 3 of the wizard

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

<%
String eaemSelectedAssets = request.getParameter("eaemSelectedAssets");
String eaemText = request.getParameter("eaemText");

//PROCESS THE SELECTED ASSETS

log.info("eaemText-------" + eaemText);
log.info("eaemSelectedAssets-------" + eaemSelectedAssets);
%>


10) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-sample-wizard/clientlib with categories=eaem.wizard and dependencies=underscore to add some dynamic behavior to the wizard while navigating between the steps

11) Create nt:file /apps/eaem-sample-wizard/clientlib/js.txt and add the following content

               handle-wizard.js

12) Create nt:file /apps/eaem-sample-wizard/clientlib/handle-wizard.js, add the following logic


(function ($, $document) {
var WIZARD_URL = "/apps/eaem-sample-wizard/eaem-3-step-wizard.html",
SUFFIX = "suffix",
EAEM_ASSETS_PATH = "eaemAssetsPath",
EAEM_SELECTED_ASSETS = "eaemSelectedAssets";

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

$document.on("foundation-wizard-stepchange", handleSteps);

function moveToStep2IfSuffixAvailable(){
var $wizard = $(".foundation-wizard");

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

var suffix = $wizard.data(SUFFIX);

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

var wizardApi = $wizard.adaptTo("foundation-wizard");
wizardApi.next();
}

function getStepNumber(){
var $wizard = $(".foundation-wizard"),
currentStep = $wizard.find(".foundation-wizard-step-active"),
wizardApi = $wizard.adaptTo("foundation-wizard");

return wizardApi.getPrevSteps(currentStep).length + 1;
}

function isSecondStep(){
return (getStepNumber() === 2);
}

function isThirdStep(){
return (getStepNumber() === 3);
}

function handleSteps(event, nextStep, currentStep){
if(_.isUndefined(currentStep)){
return;
}

var $eaemAssetsPath = $("[name=" + EAEM_ASSETS_PATH + "]");

if(isSecondStep() && !_.isEmpty($eaemAssetsPath.val())){
$(nextStep).hide();
redirectTo(WIZARD_URL + $eaemAssetsPath.val());
}else if(isThirdStep()){
addSelectedAssetsToForm();
}
}

function addSelectedAssetsToForm(){
var $form = $("form"), assetPaths = [],
eaemSelectedAssets = $("[name=" + EAEM_SELECTED_ASSETS + "]");

if(_.isEmpty(eaemSelectedAssets)){
eaemSelectedAssets = $('<input />').attr('type', 'hidden')
.attr('name', EAEM_SELECTED_ASSETS)
.appendTo($form);
}

$(".foundation-selections-item").each(function(index, asset){
assetPaths.push($(asset).data("foundation-collection-item-id"));
});

eaemSelectedAssets.val(assetPaths.join(","));
}

function redirectTo(url){
var ui = $(window).adaptTo("foundation-ui");

ui.wait($("form"));

window.location = url;
}
}(jQuery, jQuery(document)));



AEM 62 - Add Multifield to Assets Metadata Schema Editor

$
0
0

Goal


This post is a no-code simple hack method to add multifield (with pathbrowser) to metadata schema editor

Package Install

Solution


1) To add a metadata field to the image/jpeg editor; access jpeg metadata schema editor

http://localhost:4502/mnt/overlay/dam/gui/content/metadataschemaeditor/schemadetails.html/default/image/jpeg?formPath=/conf/global/settings/dam/adminui-extension/metadataschema

2) Drag a Text Field with label Related Images mapping to the property ./jcr:content/metadata/relatedImages in new tab, here EAEM



3) Saved in CRX eg. /conf/global/settings/dam/adminui-extension/metadataschema/default/image/jpeg/items/tabs/items/tab4/items/col1/items/1474558552884



4) Manually, change the saved metadata field to multifield (with pathbrowser) - granite/ui/components/foundation/form/multifield

<_x0031_474558552884
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
fieldLabel="Related Images"
renderReadOnly="true">
<granite:data
jcr:primaryType="nt:unstructured"
metaType="text"/>
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
name="./jcr:content/metadata/relatedImages"/>
</_x0031_474558552884>





4) The user can now select multiple paths saved to jpeg image metadata eg. /content/dam/geometrixx-outdoors/activities/snowboarding/PDP_1_c05.jpg/jcr:content/metadata/relatedImages



AEM 62 - Touch UI Composite Multifield with Rich Text Editor (RTE) Required Validator

$
0
0

Goal


Create a composite multifield comprised of rich text editors (widgets of type  cq/gui/components/authoring/dialog/richtext) with the editors set as required in multifield

For AEM 61 (no validator)  check this post

Demo | Package Install


Composite Multifield



RTE Set as Required



Error when Empty



Saved Data in CRX



Solution


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

2) Create clientlib (type cq:ClientLibraryFolder/apps/touchui-rte-multifield/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-rte-multifield/clientlib/js.txt, add the following

                         rte-multifield.js

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

(function ($, $document) {
var DATA_EAEM_NESTED = "data-eaem-nested",
CFFW = ".coral-Form-fieldwrapper",
RTE_CONTAINER = "richtext-container",
RTE_EDITABLE = ".coral-RichText-editable",
FIELD_ERROR_EL = $("<span class='coral-Form-fielderror coral-Icon coral-Icon--alert coral-Icon--sizeS'" +
"data-init='quicktip' data-quicktip-type='error' />");

function addValidator($multifield){
var $rteContainer, requiredField;

_.each($multifield.find("." + RTE_CONTAINER), function(rteContainer){
$rteContainer = $(rteContainer);

if(invisibleFieldAdded($rteContainer)){
return;
}

requiredField = $rteContainer.find("[aria-required='true']");

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

$rteContainer.children(RTE_EDITABLE).css("height", "5rem");

//coral validation framework ignores hidden and contenteditable fields, so add an invisible text field
//the text field is just for registering a validator
$rteContainer.append("<input type=text style='display:none' value='"
+ $rteContainer.find(RTE_EDITABLE).text() + "'" +
"data-eaem-invisible='true' aria-required='true'/>");

$rteContainer.children().on("input", function() {
var $invisibleText = $(this).nextAll("input:text").val($(this).text().trim());

$invisibleText.checkValidity();
$invisibleText.updateErrorUI();
})
});
}

function invisibleFieldAdded($rteContainer){
return !_.isEmpty($rteContainer.find("[data-eaem-invisible='true']"));
}

function validateSubmittables(){
var $submittables = $("[" + DATA_EAEM_NESTED + "]").find(":-foundation-submittable");

return Array.prototype.every.call($submittables, function(submittable) {
var api = $(submittable).adaptTo("foundation-validation");

return api.checkValidity({
suppressEvent: true
});
});
}

function setSelect($field, value){
var select = $field.closest(".coral-Select").data("select");

if(select){
select.setValue(value);
}
}

function setHiddenOrRichText($field, value){
$field.val(value);

var $rteContainer = $field.parent();

if(!$rteContainer.hasClass(RTE_CONTAINER)){
return;
}

$rteContainer.children(RTE_EDITABLE).empty().append(value);
}

function setCheckBox($field, value){
$field.prop( "checked", $field.attr("value") == value);
}

function getMultiFieldNames($multifields){
var mNames = {}, mName;

$multifields.each(function (i, multifield) {
mName = $(multifield).children("[name$='@Delete']").attr("name");

mName = mName.substring(0, mName.indexOf("@"));

mName = mName.substring(2);

mNames[mName] = $(multifield);
});

return mNames;
}

function buildMultiField(data, $multifield, mName){
$multifield.find(".js-coral-Multifield-add").click(function(){
var $multifield = $(this).parent();

setTimeout(function(){
addValidator($multifield);
}, 500);
});

if(_.isEmpty(mName) || _.isEmpty(data)){
return;
}

_.each(data, function(value, key){
if(key == "jcr:primaryType"){
return;
}

$multifield.find(".js-coral-Multifield-add").click();

_.each(value, function(fValue, fKey){
if(fKey == "jcr:primaryType"){
return;
}

var $field = $multifield.find("[name='./" + fKey + "']").last(),
type = $field.prop("type");

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

if(type == "select-one"){
setSelect($field, fValue);
}else if(type == "checkbox"){
setCheckBox($field, fValue);
}else if(type == "hidden"){
setHiddenOrRichText($field, fValue);
}else{
$field.val(fValue);
}
});
});
}

//reads multifield data from server, creates the nested composite multifields and fills them
function addDataInFields() {
$(document).on("dialog-ready", readyHandler);

function readyHandler(){
var $multifields = $("[" + DATA_EAEM_NESTED + "]");

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

var mNames = getMultiFieldNames($multifields),
$form = $(".cq-dialog"),
actionUrl = $form.attr("action") + ".infinity.json";

$.ajax(actionUrl).done(postProcess);

function postProcess(data){
_.each(mNames, function($multifield, mName){
buildMultiField(data[mName], $multifield, mName);
});
}
}
}

function fillValue($form, fieldSetName, $field, counter){
var name = $field.attr("name");

if (!name) {
return;
}

//strip ./
if (name.indexOf("./") == 0) {
name = name.substring(2);
}

var value = $field.val();

if( $field.prop("type") == "checkbox" ){
value = $field.prop("checked") ? $field.val() : "";
}

$('<input />').attr('type', 'hidden')
.attr('name', fieldSetName + "/" + counter + "/" + name)
.attr('value', value )
.appendTo($form);

//remove the field, so that individual values are not POSTed
$field.remove();
}

//collect data from widgets in multifield and POST them to CRX
function collectDataFromFields(){
$(document).on("click", ".cq-dialog-submit", submitHandler);

function submitHandler(){
var $multifields = $("[" + DATA_EAEM_NESTED + "]");

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

var $form = $(this).closest("form.foundation-form"),
$fieldSets, $fields;

if(!validateSubmittables()){
return;
}

$multifields.each(function(i, multifield){
$fieldSets = $(multifield).find("[class='coral-Form-fieldset']");

$fieldSets.each(function (counter, fieldSet) {
$fields = $(fieldSet).children().children(CFFW);

$fields.each(function (j, field) {
fillValue($form, $(fieldSet).data("name"), $(field).find("[name]"), (counter + 1));
});
});
});
}
}

$.validator.register({
selector: "[data-eaem-invisible='true']",

validate: function ($invisibleText) {
var cuiRichText = $invisibleText.prevAll(RTE_EDITABLE).data("richText");

if(!cuiRichText || !cuiRichText.editorKernel){
return;
}

var isRequired = ($invisibleText.attr("aria-required") === "true");

if (isRequired && _.isEmpty(cuiRichText.editorKernel.getProcessedHtml())) {
return "Please fill this field";
}

return null;
},

show: function ($invisibleText, message) {
var $field = $invisibleText.prevAll(RTE_EDITABLE),
arrow = $invisibleText.closest("form").hasClass("coral-Form--vertical") ? "right" : "top";

FIELD_ERROR_EL.clone()
.attr("data-quicktip-arrow", arrow)
.attr("data-quicktip-content", message)
.insertAfter($field);

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

clear: function ($invisibleText) {
var $field = $invisibleText.prevAll(RTE_EDITABLE);

$field.removeAttr("aria-invalid").removeClass("is-invalid")
.nextAll(".coral-Form-fielderror").tooltip("hide").remove();
}
});

$document.ready(function () {
addDataInFields();
collectDataFromFields();
});
}(jQuery, jQuery(document)));


AEM 62 - Adding Dynamic Columns in Asset Reports

$
0
0

Goal


Give AEM admin the flexibility to add additional asset metadata columns in Reporting Console http://localhost:4502/mnt/overlay/dam/gui/content/reports/reportspage.html

The custom column configuration is stored in cookies, so the configuration is per report and per user

For more information on Assets Reporting check documentation

Thank you ACS Commons for the Generic Lists

Demo | Package Install | Github


Configure Columns

            http://localhost:4502/etc/experience-aem/report-columns.html



Enable Columns



Columns in Report



Report Exported as CSV



Solution


1) Create the extension nt:folder /apps/eaem-asset-reports-dynamic-columns

2) Add the ACS Commons GenericList in nt:folder /apps/eaem-asset-reports-dynamic-columns/components, /apps/eaem-asset-reports-dynamic-columns/templates (available in Package Install if installing the extension)

3) Create sling:Folder /apps/eaem-asset-reports-dynamic-columns/dialog/columns and nt:unstructured /apps/eaem-asset-reports-dynamic-columns/dialog/columns/column with the following properties for creating column html

<?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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="cq/gui/components/siteadmin/admin/pages/headers/deflt"
class="type small-col"
modalResourceType="granite/ui/components/foundation/form/checkbox"
name="./columns"
sort-selector=".label .eaemCol"
text="eaemCol"
title="eaemCol"/>


4) Create nt:unstructured /apps/eaem-asset-reports-dynamic-columns/dialog/modal with following properties, for showing the column names in Configure Columns modal (set in property columnspath)

<?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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="dam/gui/coral/components/admin/reports/customcolumnmodal"
columnspath="/apps/eaem-asset-reports-dynamic-columns/dialog/columns"/>

5) Create cq:ClientLibraryFolder /apps/eaem-asset-reports-dynamic-columns/clientlib with categories cq.dam.admin.reports.customcolumns.coral and dependencies underscore

6) Create nt:file /apps/eaem-asset-reports-dynamic-columns/clientlib/js.txt with the following content

                  dynamic-report-columns.js

7) Create nt:file /apps/eaem-asset-reports-dynamic-columns/clientlib/dynamic-report-columns.js with the following code

(function ($, $document) {
var VIEW_SETTINGS_COLUMN_CONFIG = {},
FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
REPORTS_FORM = "#customcolumnsForm",
DAM_ADMIN_REPORTS_PAGE = ".cq-damadmin-admin-reportspage",
METADATA_COL_MAPPING = "data-eaem-col-mapping",
EXPORT_BUTTON = ".dam-admin-reports-export",
QB = "/bin/querybuilder.json?",
EXPORT_URL = "/apps/eaem-asset-reports-dynamic-columns/dialog/export.html",
REPORT_RESULT_URL = "/mnt/overlay/dam/gui/content/reports/reportsresult.html",
EAEM_COL = "eaemCol",
FORM_FIELD_WRAPPER = ".coral-Form-fieldwrapper",
COOKIE_REPORT_LIST_VIEW_COLUMNS = "eaem.report.listview.columns",
REPORTS_CONFIGURE_COLUMNS_DIALOG = "reports-configure-columns-dialog",
COLUMN_URL = "/apps/eaem-asset-reports-dynamic-columns/dialog/columns/column.html",
COLUMNS_MODAL = "/apps/eaem-asset-reports-dynamic-columns/dialog/modal.html",
DYNAMIC_COLS_CONFIG_URL = "/etc/experience-aem/report-columns/jcr:content/list.infinity.json",
MAIN_COLUMN = "dam/gui/coral/components/admin/reports/columns/main",
MAIN_COLUMN_WIDTH = 22,
COLUMN_CACHE = [], COLUMN_WIDTH = "0%", CELL_DATA = [], searchUrl;

if (typeof EAEM == "undefined") {
EAEM = { REPORT : {} };
}

EAEM.REPORT.storeEnabledColumns = storeEnabledColumns;

loadColumnsConfiguration();

$document.on(FOUNDATION_CONTENT_LOADED, function(event){
_.defer(function(){
handleContentLoad(event);
});
});

$document.on("submit", "form.foundation-form", function(e) {
searchUrl = getSearchUrl();
});

function loadColumnsConfiguration() {
if(!_.isEmpty(VIEW_SETTINGS_COLUMN_CONFIG)){
return;
}

$.ajax(DYNAMIC_COLS_CONFIG_URL).done(function(data){
if(_.isEmpty(data)){
return;
}

_.each(data, function(obj, key){
if(key.indexOf("item") !== 0){
return;
}

VIEW_SETTINGS_COLUMN_CONFIG[obj.value] = obj["jcr:title"];
});
});
}

function handleContentLoad(event){
var target = event.target;

if(isConfigureColumnsDialog(target)){
addDynamicColumnsInModal();
}else if(isReportsPage(event)){
handleReportsPage();
}
}

function handleReportsPage(){
var $reportsPage = $(DAM_ADMIN_REPORTS_PAGE);

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

var enabledColumns = getEnabledColumnsObj()[getReportPath()];

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

handleHeaders();
}

function handleHeaders(){
var $reportsPage = $(DAM_ADMIN_REPORTS_PAGE),
$hContainers = $reportsPage.find("header > .label"),
$aContainers = $reportsPage.find("article");

if(customHeadersAdded($hContainers)){
handlePagination($aContainers);
return;
}

handleExport();

var enabledColumns = getEnabledColumnsObj()[getReportPath()];

$.ajax(COLUMN_URL).done(function(colHtml) {
_.each(enabledColumns, function(colMetaPath){
addColumnHeaders($hContainers, $aContainers, colHtml, colMetaPath);
});

if(!searchUrl){
return;
}

$.get(searchUrl).done(function(data){
if(_.isEmpty(data.hits)){
return;
}

CELL_DATA = data.hits;

addCellValues();
});
});
}

function handlePagination($aContainers){
var $labelContainers = $aContainers.find(".label"),
$lContainer;

$labelContainers.each(function(index, aContainer){
$lContainer = $(aContainer);

if(customHeadersAdded($lContainer)){
return;
}

_.each(COLUMN_CACHE, function(cellHtml){
$lContainer.append(cellHtml);
})
});

fixCellWidths($labelContainers, COLUMN_WIDTH);

addCellValues();
}

function addColumnHeaders($hContainers, $aContainers, colHtml, colMetaPath){
var $columnHeader, $labelContainers = $aContainers.find(".label"),
cellHtml;

$columnHeader = $(colHtml).appendTo($hContainers);

$columnHeader.attr(METADATA_COL_MAPPING, colMetaPath)
.html(VIEW_SETTINGS_COLUMN_CONFIG[colMetaPath]);

COLUMN_WIDTH = fixHeaderWidths($hContainers);

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

cellHtml = getCellHtml(colMetaPath, "");

$labelContainers.append(cellHtml);

fixCellWidths($labelContainers, COLUMN_WIDTH);

COLUMN_CACHE.push(cellHtml);
}

function addCellValues( ){
var $reportsPage = $(DAM_ADMIN_REPORTS_PAGE),
$aContainers = $reportsPage.find("article"),
$aParent = $aContainers.parent(),
$article, $cell,
enabledColumns = getEnabledColumnsObj()[getReportPath()];

_.each(CELL_DATA, function(hit){
$article = $aParent.find("article[data-path='" + hit["jcr:path"] + "']");

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

_.each(enabledColumns, function(colMetaPath){
$cell = $article.find("[" + METADATA_COL_MAPPING + "='" + colMetaPath + "']");
$cell.html(nestedPluck(hit, colMetaPath));
})
})
}

function fixCellWidths($lContainers, colWidth){
$lContainers.children("div").removeClass("small-col large-col")
.css("width", MAIN_COLUMN_WIDTH + "%");

$lContainers.children("p").removeClass("small-col large-col")
.css("width", colWidth).css("float", "left");
}

function getCellHtml(colMapping, colText){
return "<p "
+ METADATA_COL_MAPPING + "='" + colMapping + "'>"
+ colText +
"</p>";
}

function fixHeaderWidths($hContainer){
var $hDivs = $hContainer.children("div"), $hDiv,
colWidth = ((100 - MAIN_COLUMN_WIDTH) / ($hDivs.length - 1)) + "%";

$hDivs.each(function(index, hDiv){
$hDiv = $(hDiv);

$hDiv.removeClass("small-col large-col");

if( $hDiv.data("itemresourcetype") === MAIN_COLUMN){
$hDiv.css("width", MAIN_COLUMN_WIDTH + "%");
return;
}

$hDiv.css("width", colWidth);
});

return colWidth;
}

function customHeadersAdded($hContainers){
return !_.isEmpty($hContainers.find("[" + METADATA_COL_MAPPING + "]"));
}

function isReportsPage(event){
var target = event.target;

if(!target){
return false;
}

var $target = (!target.$ ? $(target) : target.$);

return (!_.isEmpty($target.find(DAM_ADMIN_REPORTS_PAGE))
|| $target.hasClass(DAM_ADMIN_REPORTS_PAGE.substr(1)));
}

function isConfigureColumnsDialog(target){
if(!target || (target.tagName !== "CORAL-DIALOG")){
return false;
}

var $target = (!target.$ ? $(target) : target.$);

return $target.hasClass(REPORTS_CONFIGURE_COLUMNS_DIALOG);
}

function addDynamicColumnsInModal(){
var url = COLUMNS_MODAL + getReportPath();

$.ajax(url).done(handler);

function handler(html){
if(_.isEmpty(html)){
return;
}

var $html, $column, $input, $form = $(REPORTS_FORM),
enabledColumns = getEnabledColumnsObj()[getReportPath()];

_.each(VIEW_SETTINGS_COLUMN_CONFIG, function(colTitle, colPath){
$html = $(html);

$input = $html.find("input[title='" + EAEM_COL + "']")
.attr(METADATA_COL_MAPPING, colPath)
.val("");

if(contains(enabledColumns, colPath)){
$input.attr("checked", "checked");
}

$input.attr("onchange", "EAEM.REPORT.storeEnabledColumns()");

$column = $input.closest(FORM_FIELD_WRAPPER);

$column.find(".coral-Checkbox-description").html(colTitle);

$form.append($column[0].outerHTML);
});

styleDialog();
}
}

function handleExport(){
$document.off("click", EXPORT_BUTTON).fipo("tap", "click", EXPORT_BUTTON, handler);

function handler(){
var currReportPath = getReportPath();

if(currReportPath.indexOf("default") !== -1) {
return;
}

var $form = $(".dam-admin-reports").find("[report-path='" + currReportPath + "']").find('form'),
$sliderrange = $form.find(".sliderrange");

setSliderValue($sliderrange);

var url = Granite.HTTP.externalize(EXPORT_URL + currReportPath + "?" + $form.serialize());

var downloadURL = function (url) {
$('<iframe>', {
id: 'idown',
src: url
}).hide().appendTo('body');
};

downloadURL(url);
}

function setSliderValue($form){
var tickValues = $form.find(".coral-Slider").attr("data-tickvalues").split(","),
lowerInd = $form.find(".coral-Slider.lower").attr('value'),
upperInd = $form.find(".coral-Slider.upper").attr('value'),
order = $form.find(".coral-Slider").attr("data-order"), tmp;

if (order && order === "increasing") {
if (lowerInd > upperInd) {
tmp = lowerInd;
lowerInd = upperInd;
upperInd = tmp;
}
} else if (lowerInd < upperInd) {
tmp = lowerInd;
lowerInd = upperInd;
upperInd = tmp;
}

$form.find(".coral-Slider.lower .lowervalue").val(tickValues[lowerInd]);
$form.find(".coral-Slider.upper .uppervalue").val(tickValues[upperInd]);
}
}

function getReportPath(){
return $('input[name=dam-asset-report]:checked').attr("report-path");
}

function getEnabledColumnsObj(){
var cookieValue = getCookie(COOKIE_REPORT_LIST_VIEW_COLUMNS);

if(!cookieValue){
cookieValue = {};
}else{
cookieValue = JSON.parse(decodeURIComponent(cookieValue));
}

return cookieValue;
}

function storeEnabledColumns(){
var $input, columns = [], colMapping;

$(REPORTS_FORM).find("input:checked").each(function(index, input){
$input = $(input);

colMapping = $input.attr(METADATA_COL_MAPPING);

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

columns.push(colMapping);
});

var cookieObj = getEnabledColumnsObj();

cookieObj[getReportPath()] = columns;

addCookie(COOKIE_REPORT_LIST_VIEW_COLUMNS, JSON.stringify(cookieObj));
}

function getCookie(cookieName){
var cookieValue = "";

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

var cookies = document.cookie.split(";"), tokens;

_.each(cookies, function(cookie){
tokens = cookie.split("=");

if(tokens[0].trim() !== cookieName){
return;
}

cookieValue = tokens[1].trim();
});

return cookieValue;
}

function addCookie(cookieName, value){
if(_.isEmpty(cookieName)){
return;
}

$.cookie(cookieName, value, { expires: 365, path: "/" } );
}

function contains(arrOrObj, key){
var doesIt = false;

if(_.isEmpty(arrOrObj) || _.isEmpty(key)){
return doesIt;
}

if(_.isArray(arrOrObj)){
doesIt = (arrOrObj.indexOf(key) !== -1);
}

return doesIt;
}

function styleDialog(){
var $form = $(REPORTS_FORM);

$form.css("max-height", "21.5rem").css("overflow-y", "auto");
}

function getSearchUrl(){
var $form = $("form[action='" + REPORT_RESULT_URL + getReportPath() + "']");
return QB + $form.serialize() + "&p.nodedepth=2&p.hits=full";
}

function nestedPluck(object, key) {
if (!_.isObject(object) || _.isEmpty(object) || _.isEmpty(key)) {
return [];
}

if (key.indexOf("/") === -1) {
return object[key];
}

var nestedKeys = _.reject(key.split("/"), function(token) {
return token.trim() === "";
}), nestedObjectOrValue = object;

_.each(nestedKeys, function(nKey) {
if(_.isUndefined(nestedObjectOrValue)){
return;
}

if(_.isUndefined(nestedObjectOrValue[nKey])){
nestedObjectOrValue = undefined;
return;
}

nestedObjectOrValue = nestedObjectOrValue[nKey];
});

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

8) For exporting the report with configured custom columns, create nt:unstructured /apps/eaem-asset-reports-dynamic-columns/dialog/export with property sling:resourceType set to /apps/eaem-asset-reports-dynamic-columns/export

9) Create /apps/eaem-asset-reports-dynamic-columns/export/export.jsp with the following code

<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.StringWriter" %>
<%@ page import="org.apache.commons.lang3.StringUtils" %>
<%@ page import="org.apache.sling.api.SlingHttpServletRequest" %>
<%@ page import="org.apache.sling.commons.json.JSONObject" %>
<%@ page import="org.slf4j.Logger" %>
<%@ page import="org.slf4j.LoggerFactory" %>
<%@ page import="java.net.URLDecoder" %>
<%@ page import="org.apache.sling.commons.json.JSONArray" %>
<%@ page import="org.apache.sling.api.resource.ResourceResolver" %>
<%@ page import="org.apache.sling.api.resource.Resource" %>
<%@ page import="java.util.*" %>
<%@ page import="org.apache.sling.api.resource.ValueMap" %>
<%@ page import="javax.jcr.Session" %>
<%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.2"%>
<%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0"%>

<cq:defineObjects />

<%!
final Logger log = LoggerFactory.getLogger(this.getClass());
final String EXPORT_JSP = "/libs/dam/gui/content/reports/export";
final String COLUMNS_CONFIG = "/etc/experience-aem/report-columns/jcr:content/list";

static String COOKIE_REPORT_LIST_VIEW_COLUMNS = "eaem.report.listview.columns";
static String PATH_HEADER = "\"PATH\"";
%>

<%
HttpServletResponseWrapper responseWrapper = getWrapper(response);

request.getRequestDispatcher(EXPORT_JSP).include(request, responseWrapper);

String csvContent = responseWrapper.toString();

csvContent = addCustomColumns(slingRequest, csvContent);

PrintWriter o = response.getWriter();

o.write(csvContent);

o.flush();
%>

<%!
private String addCustomColumns(SlingHttpServletRequest sRequest, String csvContent){
Cookie cookie = sRequest.getCookie(COOKIE_REPORT_LIST_VIEW_COLUMNS);

if(cookie == null){
return csvContent;
}

String enabledColumns = cookie.getValue();

if(StringUtils.isEmpty(enabledColumns)){
return csvContent;
}

String reportSelected = sRequest.getRequestPathInfo().getSuffix();

try{
JSONObject enabled = new JSONObject(URLDecoder.decode(enabledColumns, "UTF-8"));

if(!enabled.has(reportSelected)){
return csvContent;
}

JSONArray reportCustomColumns = enabled.getJSONArray(reportSelected);
String[] lines = csvContent.split("\r\n");

StringWriter customCsvContent = new StringWriter();

if(lines.length == 0){
return csvContent;
}

Map<String, String> colConfig = getCustomColumnsConfig(sRequest.getResourceResolver());

addHeaderLine(colConfig, reportCustomColumns, customCsvContent, lines[0]);

addAssetLines(sRequest, reportCustomColumns, customCsvContent, lines);

csvContent = customCsvContent.toString();
}catch(Exception e){
log.warn("Error adding columns", e);
}

return csvContent;
}

private void addAssetLines(SlingHttpServletRequest sRequest, JSONArray reportCustomColumns,
StringWriter gieCsvContent, String[] csvLines) throws Exception {
if(csvLines.length == 1){
return;
}

int pathIndex = -1;

String[] headers = csvLines[0].split(",");

for(int i = 0; i < headers.length; i++){
if(headers[i].equalsIgnoreCase(PATH_HEADER)){
pathIndex = i;
}
}

if(pathIndex == -1){
return;
}

String line = null, assetPath = null, colMapPath = null, colMetaName;
ValueMap assetMetadata = null;

ResourceResolver resolver = sRequest.getResourceResolver();
Session session = resolver.adaptTo(Session.class);

for(int l = 1; l < csvLines.length; l++){
line = csvLines[l];

try{
assetPath = line.split(",")[pathIndex];

assetPath = assetPath.replaceAll("\"", "");

assetPath = assetPath + "/jcr:content/metadata";

if(!session.nodeExists(assetPath)){
continue;
}

assetMetadata = resolver.getResource(assetPath).getValueMap();

for(int index = 0, len = reportCustomColumns.length(); index < len; index++){
colMapPath = reportCustomColumns.getString(index);
colMetaName = colMapPath.substring(colMapPath.lastIndexOf("/") + 1);
line = line + "," + assetMetadata.get(colMetaName, "");
}
}catch(Exception e){
}

gieCsvContent.append(line);

gieCsvContent.append("\r\n");
}
}

private Map<String, String> getCustomColumnsConfig(ResourceResolver resolver){
Resource mlResource = resolver.getResource(COLUMNS_CONFIG);
Map<String, String> colConfig = new HashMap<String, String>();

if(mlResource == null){
return colConfig;
}

try{
Iterator<Resource> managedList = mlResource.listChildren();
Resource resource;
ValueMap vm;

while (managedList.hasNext()) {
resource = managedList.next();
vm = resource.getValueMap();

colConfig.put(vm.get("value", ""), vm.get("jcr:title", ""));
}
}catch(Exception e){
log.warn("Error reading managed list");
}

return colConfig;
}


private void addHeaderLine(Map<String, String> colConfig, JSONArray customColumns,
StringWriter customCsvContent, String headerLine) throws Exception{
String colMapPath = null;

if(headerLine.indexOf("\"\"") != -1){
headerLine = headerLine.substring(0, headerLine.indexOf("\"\"") - 1);
}

for(int index = 0, len = customColumns.length(); index < len; index++){
colMapPath = customColumns.getString(index);
headerLine = headerLine + ",\"" + colConfig.get(colMapPath) + "\"";
}

customCsvContent.append(headerLine).append("\r\n");;
}

private static HttpServletResponseWrapper getWrapper(HttpServletResponse response){
return new HttpServletResponseWrapper(response) {
public final StringWriter sw = new StringWriter();

@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(sw);

}

@Override
public String toString() {
return sw.toString();
}
};
}
%>

AEM 62 - Touch UI Inbox Complete all Selected Items

$
0
0

Goal


Add Complete all functionality to Touch UI Inbox, to complete the current step of all selected items with one click

Demo | Package Install | GitHub




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-inbox-complete-all

2) Create node /apps/eaem-inbox-complete-all/button/complete-all-selected of type nt:unstructured, with the following properties, for button html

<?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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/endor/actionbar/button"
align="right"
class="button primary"
formId="updateworkitemform"
icon="coral-Icon--checkCircle"
text="Complete all Selected Items"/>


3) Create node /apps/eaem-inbox-complete-all/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.workflow.gui.inbox, String property dependencies with value underscore

4) Create file (nt:file) /apps/eaem-inbox-complete-all/clientlib/js.txt, add

                       complete-all-selected.js

5) Create file (nt:file) /apps/eaem-inbox-complete-all/clientlib/complete-all-selected.js, add the following code

(function ($, $document) {
var FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
FOUNDATION_DATA_MODEL_LOADED = "foundation-model-dataloaded",
UPDATE_TASK_FORM = "#updatetaskform,#updateworkitemform",
COMPLETE_TASK = "[type=submit]",
completeAllAdded = false,
doCompleteAll = false, taskData = {},
fui = $(window).adaptTo("foundation-ui"),
COMPLETE_ALL_BUT_URL = "/apps/eaem-inbox-complete-all/button/complete-all-selected.html";

$document.on(FOUNDATION_CONTENT_LOADED, function(){
var $form = $(UPDATE_TASK_FORM);

if(_.isEmpty($form) || completeAllAdded){
return;
}

completeAllAdded = true;

$.ajax(COMPLETE_ALL_BUT_URL).done(addCompleteAllButton);

$form.on(FOUNDATION_DATA_MODEL_LOADED, completeTasks);
});

function setWidgetValue($field, value){
if(isSelectOne($field)){
setSelectOne($field, value);
}else{
$field.val(value);
}
}

function fillFormData(){
if(_.isEmpty(taskData)){
return;
}

var $form = $(UPDATE_TASK_FORM), $field;

_.each(taskData, function(value, name){
$field = $form.find("[name='" + name + "']");

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

setWidgetValue($field, value);
})
}

function completeTasks(){
if(!doCompleteAll) {
return;
}

fillFormData();

$(UPDATE_TASK_FORM).submit();

var store = Granite.UI.Extension.DataStore;

if(!store.hasItems()){
doCompleteAll = false;

fui.clearWait();
}
}

function addCompleteAllButton(html){
doCompleteAll = false;

var $completeTask = $(COMPLETE_TASK),
$completeAll = $(html).insertAfter($completeTask);

$completeTask.closest("div").removeAttr("style");

$completeAll.click(function(){
doCompleteAll = true;

var $form = $(UPDATE_TASK_FORM),
store = Granite.UI.Extension.DataStore;

if(store.getSize() > 1){
fui.wait();
taskData = queryParameters($form.serialize());
}

$form.submit();
})
}

function isSelectOne($field) {
return !_.isEmpty($field) && ($field.prop("type") === "select-one");
}

function setSelectOne($field, value) {
var select = $field.closest(".coral-Select").data("select");

if (select) {
select.setValue(value);
}
}

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

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

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

return result;
}
}($, $(document)));


AEM 62 - Touch UI Replace Asset Binary with AEM Asset or Local File Upload

$
0
0

Goal


Replace the binary (not metadata) of an AEM Asset with binary of another AEM Asset or Local File Upload

Thank you Rahul Singh Rawat, Drew Robinson for the code snippets

Demo | Package Install | Source Code | Git Hub


Replace Options


Replace with AEM File



Solution


1) Create a servlet apps.experienceaem.assets.ReplaceBinary with following code for performing the binary replace by adding original rendition

package apps.experienceaem.assets;

import com.day.cq.dam.api.Asset;
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.jcr.Session;
import javax.servlet.ServletException;
import java.io.InputStream;

@Component(metatype = true, label = "Experience AEM Replace Binary Servlet")
@Service
@Properties({
@Property(name = "sling.servlet.methods", value = { "POST" }, propertyPrivate = true),
@Property(name = "sling.servlet.paths", value = "/bin/eaem/replace-binary", propertyPrivate = true)
})
public class ReplaceBinary extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(ReplaceBinary.class);

@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException {
ResourceResolver resourceResolver = request.getResourceResolver();

String toBeReplacedAssetPath = request.getParameter("toBeReplacedAssetPath");
String replaceWithAssetPath = request.getParameter("replaceWithAssetPath");
String deleteSource = request.getParameter("deleteSource");

replaceBinary(resourceResolver, toBeReplacedAssetPath, replaceWithAssetPath, deleteSource);
}

private void replaceBinary(ResourceResolver resourceResolver, String toBeReplacedAssetPath,
String replaceWithAssetPath, String deleteSource) {
Resource toBeReplacedResource = resourceResolver.getResource(toBeReplacedAssetPath);
Resource replacingResource = resourceResolver.getResource(replaceWithAssetPath);

Asset toBeReplacedAsset = toBeReplacedResource.adaptTo(Asset.class);
Asset replacingAsset = replacingResource.adaptTo(Asset.class);

String mimeType = toBeReplacedAsset.getMimeType();

Resource original = replacingAsset.getOriginal();
InputStream stream = original.adaptTo(InputStream.class);

toBeReplacedAsset.addRendition("original", stream, mimeType);

if(!"true".equals(deleteSource)){
return;
}

try{
Session session = resourceResolver.adaptTo(Session.class);
session.removeItem(replacingResource.getPath());

session.save();
}catch(Exception e){
log.warn("Error removing asset - " + replacingResource.getPath());
}
}
}

2) Create nt:unstructured node /apps/eaem-assets-replace-binary/button/replace-binary with the following xml for generating pull down html, added to Action Bar, showing the Replace options - Replace with Local File, Replace with AEM File

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
granite:id="eaem-binary-replace"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/pulldown"
icon="globe"
text="Replace"
variant="actionBar">
<items jcr:primaryType="nt:unstructured">
<replace-with-local-file
granite:class="replace-with-local-file"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/collection/actionlink"
target=".eaem-placeholder"
text="Replace with Local File"/>
<replace-with-aem-file
granite:class="replace-with-aem-file"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/collection/actionlink"
target=".eaem-placeholder"
text="Replace with AEM File"/>
</items>
</jcr:root>

3) Create nt:unstructured node /apps/eaem-assets-replace-binary/button/replace-with-local-fileupload with following xml, for generating the file upload button html added to action bar (as hidden, to workaround the IE/Firefox issue)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
granite:id="replace-with-local-fileupload"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/fileupload"
accept="image/*"
async="{Boolean}true"
autoStart="{Boolean}false"
chunkuploadsupported="{Boolean}true"
multiple="{Boolean}false"
name="file"/>

4) Create the wizard /apps/eaem-assets-replace-binary/wizard/select-assets of type cq:Page with following xml, to select an AEM Asset (source binary) for the Replace with AEM File option

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Page">
<jcr:content
jcr:mixinTypes="[sling:VanityPath]"
jcr:primaryType="nt:unstructured"
jcr:title="Select Replacement File"
sling:resourceType="granite/ui/components/coral/foundation/page">
<head jcr:primaryType="nt:unstructured">
<viewport
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
<favicon
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="[coralui3,granite.ui.coral.foundation,cq.listview.coral.columns.personalization,dam.gui.admin.util]"/>
</head>
<body
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/body">
<items jcr:primaryType="nt:unstructured">
<form
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
foundationForm="{Boolean}true"
maximized="{Boolean}true"
method="post"
novalidate="{Boolean}true"
style="vertical">
<items jcr:primaryType="nt:unstructured">
<charset
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/hidden"
name="_charset_"
value="utf-8"/>
<wizard
jcr:primaryType="nt:unstructured"
jcr:title="Select Replacement File"
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<step1
jcr:primaryType="nt:unstructured"
jcr:title="Select Assets"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/include"
path="/libs/dam/gui/content/assets/jcr:content/views/column"/>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
text="Next"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</step1>
<step2
jcr:primaryType="nt:unstructured"
jcr:title="Confirm"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
<parentConfig jcr:primaryType="nt:unstructured">
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
text="Replace"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
<items jcr:primaryType="nt:unstructured">
<column1
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<text1
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/heading"
level="{Long}3"
text="you are going to replace 'PLACEHOLDER_SRC' with 'PLACEHOLDER_DEST'"/>
<text2
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/heading"
level="{Long}3"
text="Select 'Replace' to confirm."/>
</items>
</column1>
</items>
</step2>
</items>
<granite:data
jcr:primaryType="nt:unstructured"
suffix="${requestPathInfo.suffix}"/>
</wizard>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>

5) Create /apps/eaem-assets-replace-binary/clientlib of type cq:ClientLibraryFolder with categories dam.gui.admin.util and dependencies underscore

6) Create file /apps/eaem-assets-replace-binary/clientlib/js.txt with the following content

                      replace-binary.js

7) Create file /apps/eaem-assets-replace-binary/clientlib/replace-binary.js with the following code

(function ($, $document) {
var FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
FOUNDATION_MODE_CHANGE = "foundation-mode-change",
FOUNDATION_SELECTIONS_CHANGE = "foundation-selections-change",
FOUNDATION_WIZARD_STEPCHANGE = "foundation-wizard-stepchange",
EDIT_ACTIVATOR = "aem-assets-admin-actions-edit-activator",
REPLACE_WITH_LOCAL_FILE = ".replace-with-local-file",
REPLACE_WITH_LOCAL_FILE_UPLOAD = "#replace-with-local-fileupload",
REPLACE_WITH_AEM_FILE = ".replace-with-aem-file",
EAEM_BINARY_REPLACE = "#eaem-binary-replace",
DAM_ADMIN_CHILD_PAGES = ".cq-damadmin-admin-childpages",
PLACEHOLDER_DEST = "PLACEHOLDER_DEST",
PLACEHOLDER_SRC = "PLACEHOLDER_SRC",
REPLACE_WITH_ASSET_PATH = "replaceWithAssetPath",
TO_BE_REPLACED_ASSET_PATH = "toBeReplacedAssetPath",
DELETE_SOURCE = "deleteSource",
fui = $(window).adaptTo("foundation-ui"),
SUBMIT_URL = "/bin/eaem/replace-binary?",
SELECT_ASSET_URL = "/apps/eaem-assets-replace-binary/wizard/select-assets.html",
REPLACE_PULL_DOWN_URL = "/apps/eaem-assets-replace-binary/button/replace-binary.html",
FILE_UPLOAD_BUT_URL = "/apps/eaem-assets-replace-binary/button/replace-with-local-fileupload.html";

var pathName = window.location.pathname;

if(pathName.indexOf("/assets.html") === 0){
handleAssetsConsole();
}else if (pathName.indexOf(SELECT_ASSET_URL) === 0){
handleSelectAssetsWizard();
}

function handleSelectAssetsWizard(){
var replaceWithAssetPath, submitHandlerAdded = false,
toBeReplacedAssetPath = queryParameters()[TO_BE_REPLACED_ASSET_PATH] ;

$document.on(FOUNDATION_CONTENT_LOADED, handleFirstStep);

function handleFirstStep(){
var stepNumber = getWizardStepNumber();

if(stepNumber !== 1){
return;
}

var $cancelBut = getWizardCancelButton();

$cancelBut.on("click", function(){
window.parent.location.reload();
});

$document.on(FOUNDATION_SELECTIONS_CHANGE, ".foundation-collection", disableNextIfNoAssetsSelected);

$document.on(FOUNDATION_WIZARD_STEPCHANGE, handleWizardSteps);
}

function handleWizardSteps(event, nextStep){
var stepNumber = getWizardStepNumber();

if(stepNumber !== 2){
return;
}

var $nextStep = $(nextStep), html = $nextStep.html(),
dest = getStringAfterLastSlash(replaceWithAssetPath),
src = getStringAfterLastSlash(toBeReplacedAssetPath);

$nextStep.html(html.replace(PLACEHOLDER_DEST, dest).replace(PLACEHOLDER_SRC, src));

if(submitHandlerAdded){
return;
}

submitHandlerAdded = true;

var $nextButton = getCurrentStepNextButton();

$nextButton.on("click", registerSubmitHandler);
}

function registerSubmitHandler(){
var url = SUBMIT_URL + REPLACE_WITH_ASSET_PATH + "=" + replaceWithAssetPath + "&"
+ TO_BE_REPLACED_ASSET_PATH + "=" + toBeReplacedAssetPath;

fui.wait();

$.ajax({url : url, type: "POST"}).done(function(){
fui.clearWait();

var dest = getStringAfterLastSlash(replaceWithAssetPath),
src = getStringAfterLastSlash(toBeReplacedAssetPath);

showAlert("File '" + src + "' replaced with '" + dest + "' binary", 'Replaced', callback);
});

function callback(){
window.parent.location.reload();
}
}

function disableNextIfNoAssetsSelected(){
var stepNumber = getWizardStepNumber();

if(stepNumber !== 1){
return;
}

disableWizardNext();

var fSelections = $(".foundation-collection").adaptTo("foundation-selections");

if(fSelections && (fSelections.count() > 1)){
showAlert("Select one asset", 'Error');
return;
}

replaceWithAssetPath = getToBeReplacedPaths();

replaceWithAssetPath = _.isEmpty(replaceWithAssetPath) ? "" : replaceWithAssetPath[0];

if(_.isEmpty(replaceWithAssetPath) || _.isEmpty(toBeReplacedAssetPath)){
return;
}

var destExtn = getStringAfterLastDot(replaceWithAssetPath),
srcExtn = getStringAfterLastDot(toBeReplacedAssetPath);

if (destExtn !== srcExtn) {
var dest = decodeURIComponent(getStringAfterLastSlash(replaceWithAssetPath)),
src = decodeURIComponent(getStringAfterLastSlash(toBeReplacedAssetPath));

showAlert("'" + dest + "' and '" + src + "' donot have the same extension", 'Error');

replaceWithAssetPath = "";

return;
}

if(!_.isEmpty(replaceWithAssetPath)){
enableWizardNext();
}
}

function disableWizardNext(){
toggleWizard(false);
}

function enableWizardNext(){
toggleWizard(true);
}

function toggleWizard(isEnable){
var $wizard = $(".foundation-wizard");

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

var wizardApi = $wizard.adaptTo("foundation-wizard");
wizardApi.toggleNext(isEnable);
}

function getWizardCancelButton(){
var $wizard = $(".foundation-wizard");

return $($wizard.find("[data-foundation-wizard-control-action=cancel]")[0]);
}

function getWizardStepNumber(){
var $wizard = $(".foundation-wizard"),
currentStep = $wizard.find(".foundation-wizard-step-active"),
wizardApi = $wizard.adaptTo("foundation-wizard");

return wizardApi.getPrevSteps(currentStep).length + 1;
}

function getCurrentStepNextButton(){
var stepNumber = getWizardStepNumber(),
$wizard = $(".foundation-wizard");

return $($wizard.find("[data-foundation-wizard-control-action=next]")[stepNumber - 1]);
}
}

function handleAssetsConsole(){
var replaceDialog = null, folderPath;

$document.on(FOUNDATION_MODE_CHANGE, function(e, mode){
if(mode !== "selection" ){
return;
}

var $replaceButton = $(EAEM_BINARY_REPLACE);

if(!_.isEmpty($replaceButton)){
return;
}

$.ajax(REPLACE_PULL_DOWN_URL).done(addPullDown);

$.ajax(FILE_UPLOAD_BUT_URL).done(addFileUpload);
});

function addFileUpload(html){
var $abContainer = $("coral-actionbar-container:first"),
$childPage = $(DAM_ADMIN_CHILD_PAGES),
folderPath = $childPage.data("foundation-collection-id");

var $fileUpload = $(html).appendTo($abContainer).attr("hidden", "hidden");

$fileUpload.off('coral-fileupload:fileadded')
.on('coral-fileupload:fileadded', uploadHandler)
.off('coral-fileupload:loadend')
.on('coral-fileupload:loadend', fileUploaded);

function fileUploaded(){
var replaceWithAssetPath = folderPath + "/" + getStringAfterLastSlash(this.value),
toBeReplacedAssetPath = getToBeReplacedPaths()[0];

var url = SUBMIT_URL + REPLACE_WITH_ASSET_PATH + "="
+ replaceWithAssetPath + "&"
+ TO_BE_REPLACED_ASSET_PATH + "=" + toBeReplacedAssetPath + "&"
+ DELETE_SOURCE + "=true";

fui.wait();

$.ajax({url : url, type: "POST"}).done(function(){
fui.clearWait();

var dest = getStringAfterLastSlash(replaceWithAssetPath),
src = getStringAfterLastSlash(toBeReplacedAssetPath);

showAlert("File '" + src + "' replaced with '" + dest + "' binary", 'Replaced', function(){
window.location.reload();
});
});
}

function uploadHandler(){
var toBeReplacedAssetPath = getToBeReplacedPaths()[0],
replaceWithAssetPath = this.value,
destExtn = getStringAfterLastDot(toBeReplacedAssetPath),
srcExtn = getStringAfterLastDot(replaceWithAssetPath);

if (destExtn !== srcExtn) {
var dest = decodeURIComponent(getStringAfterLastSlash(toBeReplacedAssetPath)),
src = decodeURIComponent(getStringAfterLastSlash(replaceWithAssetPath));

showAlert("'" + dest + "' and '" + src + "' donot have the same extension", 'Error');

return;
}

this.action = folderPath + ".createasset.html";
this.upload();
}
}

function addPullDown(html){
var $eActivator = $("." + EDIT_ACTIVATOR);

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

$(html).insertBefore( $eActivator );

handleReplaceWithLocalFile();

handleReplaceWithAEMFile();
}

function handleReplaceWithLocalFile(){
var $replaceWithLocalFile = $(REPLACE_WITH_LOCAL_FILE);

$replaceWithLocalFile.click(function(event){
event.preventDefault();

var toBeReplacedAssetPath = getToBeReplacedPaths();

if(toBeReplacedAssetPath.length > 1){
showAlert("Select one asset...", "Error");
return;
}

//upload not added in pulldown to workaround IE11/firefox issue
$(REPLACE_WITH_LOCAL_FILE_UPLOAD).find("button").click();
});
}

function handleReplaceWithAEMFile(){
var $childPage = $(DAM_ADMIN_CHILD_PAGES),
foundationLayout = $childPage.data("foundation-layout"),
$replaceAEMFile = $(REPLACE_WITH_AEM_FILE);

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

folderPath = $childPage.data("foundation-collection-id");

$replaceAEMFile.click(function(event){
event.preventDefault();

var toBeReplacedAssetPath = getToBeReplacedPaths();

if(toBeReplacedAssetPath.length > 1){
showAlert("Select one asset...", "Error");
return;
}

replaceDialog = getReplaceAEMFileDialog(folderPath + "?toBeReplacedAssetPath=" + toBeReplacedAssetPath[0]);

replaceDialog.show();
});
}

function getReplaceAEMFileDialog(path){
return new Coral.Dialog().set({
closable: "on",
header: {
innerHTML: 'File Replace'
},
content: {
innerHTML: getReplaceAEMFileDialogContent(path)
}
});
}

function getReplaceAEMFileDialogContent(path){
var url = SELECT_ASSET_URL + path;

return "<iframe width='1300px' height='700px' frameBorder='0' src='" + url + "'></iframe>";
}
}

function getToBeReplacedPaths(){
var toBeReplacedAssetPaths = [];

$(".foundation-selections-item").each(function(index, asset){
toBeReplacedAssetPaths.push($(asset).data("foundation-collection-item-id"));
});

return toBeReplacedAssetPaths;
}

function getStringAfterLastSlash(str){
if(!str){
return "";
}

var find = "";

if(str.indexOf("/") !== -1){
find = "/";
}else if(str.indexOf("\\") !== -1){
find = "\\";
}

return str.substr(str.lastIndexOf(find) + 1);
}

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

return str.substr(str.lastIndexOf(".") + 1);
}

function showAlert(message, title, callback){
var fui = $(window).adaptTo("foundation-ui"),
options = [{
id: "ok",
text: "OK",
primary: true
}];

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

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

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

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

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

return result;
}
}($, $(document)));

AEM 62 - Touch UI Move Asset Wizard Set Default Path for Rename

$
0
0

Goal


Handy extension for setting the default path in move asset wizard/mnt/overlay/dam/gui/content/assets/moveassetwizard.html, saving few clicks while renaming assets

Demo | Package Install | Git Hub



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-assets-move-set-default-path

2) Create clientlib (type cq:ClientLibraryFolder/apps/eaem-assets-move-set-default-path/clientlib and set a property categories of String type to cq.gui.damadmin.moveassetwizarddependencies of type String[] with value underscore

3) Create file ( type nt:file ) /apps/eaem-assets-move-set-default-path/clientlib/js.txt, add the following

                         set-default-path.js

4) Create file ( type nt:file ) /apps/eaem-assets-move-set-default-path/clientlib/set-default-path.js, add the following code

(function () {
var oneTime = false;

Granite.UI.MovePageWizard.prototype.getDestinationPath = function() {
if(oneTime){
return this.destinationPath;
}

oneTime = true;

this.destinationPath = getStringBeforeLastSlash(decodeURIComponent(queryParameters()["item"]));

return this.destinationPath;
};

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

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

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

return result;
}

function getStringBeforeLastSlash(str){
if(!str){
return str;
}

return str.substring(0,str.lastIndexOf("/"));
}
}());

AEM 62 - Touch UI Add Zoom in Assets Card View

$
0
0

Goal


Add Zoom functionality in Assets Card view, so the user doesn't need to go into Asset Detail - /assetdetails.html for zoom in/out the asset

Demo | Package Install | GitHub


Zoom Quick Action



Asset Zoom



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/experience-aem-asset-zoom

2) Create clientlib (type cq:ClientLibraryFolder/apps/experience-aem-asset-zoom/clientlib and set a property categories of String type to dam.gui.actions.coraldependencies of type String[] with value underscore

3) Create file ( type nt:file ) /apps/experience-aem-asset-zoom/clientlib/js.txt, add the following

                         card-zoom.js

4) Create file ( type nt:file ) /apps/experience-aem-asset-zoom/clientlib/card-zoom.js, add the following code

(function ($, $document) {
var FOUNDATION_MODE_CHANGE = "foundation-mode-change",
DAM_ADMIN_CHILD_PAGES = ".cq-damadmin-admin-childpages",
META_TYPE = "data-foundation-collection-meta-type",
LAYOUT_CARD_VIEW = "card",
ASSET_DETAIL = "/libs/dam/gui/content/assetdetails/jcr:content/content/items/assetdetail.html";

$document.on(FOUNDATION_MODE_CHANGE, function(event){
_.defer(function(){
contentLoaded(event);
});
});

function contentLoaded(){
var $childPage = $(DAM_ADMIN_CHILD_PAGES),
foundationLayout = $childPage.data("foundation-layout");

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

var layoutId = foundationLayout.layoutId;

if(layoutId !== LAYOUT_CARD_VIEW){
return;
}

var $items = $("coral-masonry-item"), $item,
dialog = getZoomDialog();

$items.each(function(){
$item = $(this);

if($item.find("[" + META_TYPE + "]").attr(META_TYPE) !== "asset"){
return;
}

var $action = getZoomAction(dialog).appendTo($item);

$item.mouseenter(function(){
$action.show();
});

$item.mouseleave(function(){
$action.hide();
})
});
}

function getZoomDialog(){
return new Coral.Dialog().set({
closable: "on",
header: {
innerHTML: 'Zoom'
},
content: {
innerHTML: getZoomDialogContent()
}
});
}

function getZoomDialogContent(){
return "<iframe width='1300px' height='800px' frameBorder='0'></iframe>";
}

function getZoomAction(dialog){
return $(getHtml()).hide().click(clickHandler);

function clickHandler(event){
event.stopPropagation();

var assetPath = $(this).closest(".foundation-collection-item").data("foundationCollectionItemId");

dialog.$.find("iframe").attr("src", ASSET_DETAIL + assetPath);

dialog.show();
}
}

function getHtml(){
return '<button style="position: absolute;top: 35%;left: 35%;z-index: 10000;cursor: zoom-in;"' +
' class="coral-Button coral-Button--square coral-QuickActions-button">' +
'<coral-icon icon="zoomIn"></coral-icon>' +
'</button>';
}
})(jQuery, jQuery(document));

Viewing all 525 articles
Browse latest View live