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

AEM CQ 6 - Adding Create Child Page to TouchUI Page Info Trigger

$
0
0

Goal


In Classic UI, there is a convenient option in Page tab of Sidekick to create a child page of current page (in Edit mode)





Using this extension, we provide the same action on Touch UI Page Info Trigger. Check Demo, Download Package. Please leave a comment if you find bugs or there is a better way




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touch-ui-child-page

2) Create node /apps/touch-ui-child-page/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.editor.hook

3) Create file (nt:file) /apps/touch-ui-child-page/clientlib/js.txt and add

                       create-child-page.js

4) Create file (nt:file) /apps/touch-ui-child-page/clientlib/create-child-page.js and add the following code

(function(document, $, page) {
document.on("ready", function() {
var trigger = $('#pageinfo-trigger');

trigger.on('click', function(){
var INTERVAL = setInterval(function(){
var classicUI = $(".pageinfo-pageactions .classicui-switcher");

if(classicUI && classicUI.length > 0){
clearInterval(INTERVAL);

var createChildPage = "<li class='coral-List-item experience-aem-create-child-page'>" +
"<i class='coral-Icon coral-Icon--add coral-Icon--sizeS' title='Create Child Page'></i>" +
"Create Child Page</li>";

$("ul.pageinfo-pageactions").append(createChildPage);

document.fipo('tap', 'click', ".experience-aem-create-child-page", function () {
window.location = Granite.HTTP.externalize("/libs/wcm/core/content/sites/createpagewizard.html" + page.path);
});
}
});
});

});
})(jQuery(document), Granite.$, Granite.author.page);




AEM 61 - Touch UI Filtering Pages by Template in Asset Finder

$
0
0

Goal


Asset Finder in Touch UI is Content Finder of Classic UI. In AEM 61 ( not 60 ) we can search for pages in Asset Finder. In this extension we provide additional filtering of pages by providing Search by Template (Granite/TouchUI/Coral UI sample Select)

Demo | Source Code | Package Install

A similar extension for Classic UI is here



Solution


1) We need a servlet to return templates as json. So code one and install as OSGI bundle

package apps.experienceaem.pagefilters;

import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.servlet.ServletException;
import java.io.IOException;

@SlingServlet(
paths="/bin/experience-aem/touch-ui/page-filters/templates",
methods = "GET",
metatype = false,
label = "Get Templates Servlet"
)
public class GetTemplates extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(GetTemplates.class);

@Override
protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");

JSONWriter jw = new JSONWriter(response.getWriter());

try{
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
QueryManager qm = session.getWorkspace().getQueryManager();

String stmt = "//element(*,cq:Template)[jcr:like(@jcr:path, '/apps/%')] order by @jcr:title";

Query q = qm.createQuery(stmt, Query.XPATH);

NodeIterator results = q.execute().getNodes();
Node node = null, tNode = null; String path = null;

jw.array();

while(results.hasNext()){
node = results.nextNode();
path = node.getProperty("jcr:content/sling:resourceType").getString();

if(path.startsWith("/apps/")){
path = path.substring(6);//remove /apps/
}

jw.object();
jw.key("id").value(path);
jw.key("name").value(node.getProperty("jcr:title").getString());
jw.endObject();
}

jw.endArray();
}catch(Exception e){
log.error("Error getting templates",e);
throw new ServletException(e);
}
}
}

2) JS logic for showing templates is added using Clientlibs. Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touch-ui-page-filters

2) Create node /apps/touch-ui-page-filters/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.editor.hook.assetfinder and String property dependencies with value cq.jquery.ui

3) Create file (nt:file) /apps/touch-ui-page-filters/clientlib/js.txt and add

                       page-filters.js

4) Create file (nt:file) /apps/touch-ui-page-filters/clientlib/page-filters.js and add the following code

(function (document, $, assetFinder) {
var TEMPLATE_SELECT_ID = "experience-aem-filter-template";
var c$templateSelect = null;

//id assetfinder-filter and class .assetfilter.type are defined in
///libs/wcm/core/content/editor/jcr:content/sidepanels/edit/items/assetsTab/items/filterPanel/items/views/items/search/items/searchpanel
var $assetFinderFilter = $('#assetfinder-filter');
var $assetFinderType = $assetFinderFilter.find(".assetfilter.type");

var addTemplateSelect = function () {
var templateMarkup = '<span class="coral-Select" data-init="select" id="' + TEMPLATE_SELECT_ID + '">' +
'<button type="button" class="coral-Select-button coral-MinimalButton">' +
'<span class="coral-Select-button-text">Select</span>' +
'</button>' +
'<select class="coral-Select-select">' +
'<option value="ALL">Of All Templates</option>' +
'</select>' +
'</span>';

var $optionTemplate = $('<script type="text/x-jquery-tmpl">' +
'<option value="${id}">${name}</option>' +
'</script>').appendTo($assetFinderType);

var promise = $.ajax({
type: 'GET',
url: "/bin/experience-aem/touch-ui/page-filters/templates"
});

promise.done(function (data) {
$("<div/>").appendTo($assetFinderType).append($(templateMarkup));

$optionTemplate.tmpl(data).appendTo("#" + TEMPLATE_SELECT_ID + " .coral-Select-select");

c$templateSelect = new CUI.Select({
element: "#" + TEMPLATE_SELECT_ID,
visible: true
});

c$templateSelect.hide();
});

promise.fail(function(){
alert("error");
});

$assetFinderType.find("select").on('change', function (event) {
var type = $(event.target).find("option:selected").val();

if (type == "Pages") {
c$templateSelect.show();
}else{
c$templateSelect.hide();
}
});
};

var pagesController = assetFinder.registry["Pages"];
var loadAssets = pagesController.loadAssets;

$.extend(pagesController, {
loadAssets: function(query, lowerLimit, upperLimit){
var template = c$templateSelect.getValue();

if(template && ( template != "ALL")){
query = '"jcr:content/sling:resourceType":"' + template + '"' + query;
}

return loadAssets.call(this, query, lowerLimit, upperLimit);
}
});

addTemplateSelect();
}(document, jQuery, Granite.author.ui.assetFinder));

5) In the above logic #32 we use JQuery templates to fill options in Select

$optionTemplate.tmpl(data).appendTo("#" + TEMPLATE_SELECT_ID + " .coral-Select-select");


6) The following logic #34 creates Touch UI Select Widget

c$templateSelect = new CUI.Select({
element: "#" + TEMPLATE_SELECT_ID,
visible: true
});

7) Structure in CRXDE Lite



AEM 6 - Showing Asset Metadata in Touch UI Asset Finder

$
0
0

Goal


Extend Asset Finder to show asset metadata in a tooltip in Touch UI.

Demo | Package Install

A similar extension for Classic UI is here




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/asset-meta-data

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

3) Create file (nt:file) /apps/asset-meta-data/clientlib/js.txt and add

                       metadata.js

4) Create file (nt:file) /apps/asset-meta-data/clientlib/metadata.js and add the following code

(function (document, $, assetFinder) {
// class assetfinder-content-container is defined in
// libs/wcm/core/content/editor/jcr:content/sidepanels/edit/items/assetsTab/items/contentPanel/items/assetfinder
var aContainer = assetFinder.$el.find('.assetfinder-content-container');

// id assetfinder-filter and class assetfilter.type are defined in
// libs/wcm/core/content/editor/jcr:content/sidepanels/edit/items/assetsTab/items/filterPanel/items/views/items/search/items/searchpanel
var $assetFinderFilter = $('#assetfinder-filter');
var $assetFinderType = $assetFinderFilter.find(".assetfilter.type");

var showMeta = { "dam:FileFormat": "File format", "dam:MIMEtype": "Mime type",
"dam:size": "Size in bytes", "jcr:lastModified": "Last Modified",
"tiff:ImageLength": "Length", "tiff:ImageWidth": "Width" };

var c$cardView = CUI.CardView.get(aContainer);

c$cardView.$element.on("change:insertitem", function (event) {
if (event.moreItems) {
return;
}

var type = $assetFinderType.find("select").find("option:selected").val();

if (type !== "Images") {
return;
}

var $cards = $(event.target).find(".card-asset");

$.each($cards, function (i, card) {
addToolTip(card);
});
});

var addToolTip = function (card) {
var $card = $(card), nodeValue = "";

var addHtml = function (key, value) {
return "<div style='display: block;'>" +
"<span style='font-family:adobe-clean; font-weight: bold'>" + key + "</span>: "
+ value +
"</div>";
};

$.ajax({
url: $card.data("path") + "/jcr:content/metadata.json",
dataType: "json"
}).done(function (data) {
nodeValue = addHtml("Name", $card.find("h4").text());

for (var x in data) {
if (!data.hasOwnProperty(x) || !showMeta.hasOwnProperty(x)) {
continue;
}

if (data[x]) {
nodeValue = nodeValue + addHtml(showMeta[x], data[x]);
}
}

nodeValue = nodeValue + "</div>";

$card.mouseenter(function () {
var tooltip = new CUI.Tooltip({
type: "info",
target: $(card),
content: nodeValue,
arrow: "bottom",
interactive: true
});
});
});
}
}(document, jQuery, Granite.author.ui.assetFinder));

5) Tooltip widget can be attached using this simple JS code 

var tooltip = new CUI.Tooltip({
type: "info",
target: $(card),
content: nodeValue,
arrow: "bottom",
interactive: true
});

AEM 6 - Classic UI Starting Workflow From Asset Editor

$
0
0

Goal


This post is for AEM users who wish to Start Workflow on assets from Asset Editor after adding or modifying metadata

Demo | Package Install

Starting a Workflow




If the Asset is already in Workflow




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/asset-editor-start-workflow

2) Create node /apps/asset-editor-start-workflow/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.dam.admin

3) Create file (nt:file) /apps/asset-editor-start-workflow/clientlib/js.txt and add

                       add-workflow.js

4) Create file (nt:file) /apps/asset-editor-start-workflow/clientlib/add-workflow.js and add the following code. The dialog config for workflow was copied from \libs\cq\ui\widgets\source\widgets\wcm\SiteAdmin.Actions.js

(function(){
var isAlreadyInWorkflow = function(pagePath){
var url = CQ.HTTP.noCaching("/bin/workflow.json");
url = CQ.HTTP.addParameter(url, "isInWorkflow", pagePath);
url = CQ.HTTP.addParameter(url, "_charset_", "UTF-8");

var response = CQ.HTTP.get(url);
var isInWorkflow = false;

if (CQ.HTTP.isOk(response)) {
var data = CQ.Util.eval(response);
isInWorkflow = data.status;
}

return isInWorkflow;
};

var showWorkflowDialog = function(pagePath){
var id = CQ.Util.createId("cq-workflowdialog");

//this dialog config was copied from \libs\cq\ui\widgets\source\widgets\wcm\SiteAdmin.Actions.js
var startWorkflowDialog = {
"jcr:primaryType": "cq:Dialog",
"title":CQ.I18n.getMessage("Start Workflow"),
"id": id,
"formUrl":"/etc/workflow/instances",
"params": {
"_charset_":"utf-8",
"payloadType":"JCR_PATH"
},
"items": {
"jcr:primaryType": "cq:Panel",
"items": {
"jcr:primaryType": "cq:WidgetCollection",
"model": {
"xtype":"combo",
"name":"model",
"id": id + "-model",
"hiddenName":"model",
"fieldLabel":CQ.I18n.getMessage("Workflow"),
"displayField":"label",
"valueField":"wid",
"title":CQ.I18n.getMessage("Available Workflows"),
"selectOnFocus":true,
"triggerAction":"all",
"allowBlank":false,
"editable":false,
"store":new CQ.Ext.data.Store({
"proxy":new CQ.Ext.data.HttpProxy({
"url":"/libs/cq/workflow/content/console/workflows.json",
"method":"GET"
}),
"baseParams": { tags: 'wcm' },
"reader":new CQ.Ext.data.JsonReader({
"totalProperty":"results",
"root":"workflows"
},
[ {"name":"wid"}, {"name":"label"}, {"name": CQ.shared.XSS.getXSSPropertyName("label")} ]
)
}),
"tpl": new CQ.Ext.XTemplate(
'<tpl for=".">',
'<div class="x-combo-list-item">',
'{[CQ.I18n.getVarMessage(CQ.shared.XSS.getXSSTablePropertyValue(values, \"label\"))]}',
'</div>',
'</tpl>'
)
},
"comment": {
"jcr:primaryType": "cq:TextArea",
"fieldLabel":CQ.I18n.getMessage("Comment"),
"name":"startComment",
"height":200
},
"title": {
xtype: 'textfield',
name:'workflowTitle',
fieldLabel:CQ.I18n.getMessage('Workflow Title')
}
}
},
"okText":CQ.I18n.getMessage("Start")
};

var dialog = CQ.WCM.getDialog(startWorkflowDialog);

dialog.addHidden({ "payload": pagePath } );

dialog.success = function(){
CQ.Ext.Msg.alert("Workflow","Workflow started on asset - " + pagePath);
};

dialog.show();
};

var addWorkflowButton = function(bbar, pagePath){
var wButton = new CQ.Ext.Button({
"text": "Start Workflow",
"cls": "cq-btn-save",
"handler": function() {
var isInWorkflow = isAlreadyInWorkflow(pagePath);

if (isInWorkflow) {
CQ.Ext.Msg.alert("Workflow", CQ.I18n.getMessage("Asset is already subject to a workflow!"));
return;
}

showWorkflowDialog(pagePath);
}
});

bbar.insertButton(1, wButton);
};

var INTERVAL = setInterval(function(){
var tabPanel = CQ.Ext.getCmp("cq-damadmin-tabpanel");

if(tabPanel){
clearInterval(INTERVAL);

tabPanel.on("add", function(t, assetEditor){
addWorkflowButton(assetEditor.formPanel.getBottomToolbar(), assetEditor.path);
});
}
}, 250);
})();



AEM 6 - Classic UI Disable Set Password

$
0
0

Goal


Disable Set Password if a user doesn't have necessary permissions. Without this extension a user will still not be able to change the password if he doesnt have necessary permissions, but user can click on Set Password button and the dialog shows up giving user impression that he/she can change password

Demo | Package Install

If a user doesn't have necessary permissions (here user author doesn't have modify permission on node /home/users/geometrixx/author which stores user password )...



The Set Password is disabled for author




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classic-ui-disable-set-password

2) Create node /apps/classic-ui-disable-set-password/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.security

3) Create file (nt:file) /apps/classic-ui-disable-set-password/clientlib/js.txt and add

                       disable.js

4) Create file (nt:file) /apps/classic-ui-disable-set-password/clientlib/disable.js and add the following code.

(function(){
//id set in \libs\cq\security\widgets\source\widgets\security\UserAdmin.js
var USER_ADMIN_ID = "cq-useradmin";

if(window.location.pathname == "/useradmin"){
var UA_INTERVAL = setInterval(function(){
var userAdmin = CQ.Ext.getCmp(USER_ADMIN_ID);

if(userAdmin && userAdmin.userProperties && userAdmin.userProperties.pwdButtons){
clearInterval(UA_INTERVAL);

var pwdButtons = userAdmin.userProperties.pwdButtons;
var setPassword = pwdButtons.filter("text", "Set Password").get(0);
var authorizableList = userAdmin.list;

authorizableList.getSelectionModel().on('selectionchange', function(sm){
var selected = sm.getSelected();

if(!selected){
return;
}

//var hasPerm = CQ.User.getCurrentUser().hasPermissionOn("modify", selected.data.home);

var user = CQ.User.getCurrentUser();

setPassword.setDisabled(true);

$.ajax({
url: "/.cqactions.json",
data: {
path: selected.data.home,
authorizableId: user.getUserID()
}
}).done(function(data){
setPassword.setDisabled(!data.entries[0].modify);
});
});
}
}, 250);
}
})();




AEM 6 - TouchUI Asset Search Select Path Predicate

$
0
0

Goal


Restrict results to assets from certain folder by default when user opens the Search Inner Rail in Touch UI. Projects assets are generally placed in certain root folder (eg. /content/dam/MyProject) and it'd be nice if the search rail by default returns assets from this folder and not entire DAM

Demo | Package Install

Solution


1) Create a Search Facet with Path Predicate

              a. Open Search Facets console  (http://localhost:4502/libs/dam/gui/content/customsearch/searchfacetformlister.html/dam/gui/content/facets) and Edit Assets search facets



              b. Drag and Drop a Path Predicate



              c. Select the project path for path predicate



              d. The predicate shows up in search inner rail




2) The next step is to code a small extension to select path predicate by default when user opens search rail, so that we save author additional 2 clicks each time, for getting the assets results from configured path

          a) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touchui-select-path-predicates

          b) Create node /apps/touchui-select-path-predicates/clientlib of type cq:ClientLibraryFolder and add a String property categories with value dam.admin.search.predicates

          c) Create file (nt:file) /apps/touchui-select-path-predicates/clientlib/js.txt and add

                       select.js

          d) Create file (nt:file) /apps/touchui-select-path-predicates/clientlib/select.js and add the following code.

(function(document, $) {
$(document).ready(function(){
var $path = $("[data-type='path']");

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

//defined in /libs/dam/gui/content/assets/jcr:content/body/content/aside/items/search
var ASSET_RAIL_SEARCH = "#aem-assets-rail-search";
var $button = $path.find("[role='button']");
var $checkbox = $path.find("input[type='checkbox']");

var doSearch = function(){
$button.click();
$checkbox.click();
};

if($.cookie("endor.innerrail.current") == ASSET_RAIL_SEARCH){
doSearch();
}

$(document).on('click', '.js-endor-innerrail-toggle', function(e) {
doSearch();
});
});
})(document, Granite.$);


AEM 6 - Touch UI Add new Action to Assets Action Bar

$
0
0

Goal


Adding a new menu item to the Assets console Action bar, for creating unique folders

Demo | Package Install




Solution


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

2 Create node /apps/create-random-asset-folder/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.gui.damadmin.admin

3) Create file (nt:file) /apps/create-random-asset-folder/clientlib/js.txt and add

                       create-folder.js

4) Create file (nt:file) /apps/create-random-asset-folder/clientlib/create-folder.js and add the following code.

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

var CREATE_FOLDER_ACTIVATOR = ".cq-damadmin-admin-actions-createfolder-activator";
var UNIQUE_FOLDER_ELEMENT_ID = "experience-aem-unique-folder-create";

//code picked up from the internet
var getUniqueFolderName = function(){
var d = new Date().getTime();

return 'xxxx-xxxx-xxxx-xxxx'.replace(/[x]/g, function(c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
});
};

var createFolder = function(parentPath) {
var options = {
"./jcr:primaryType": "sling:OrderedFolder",
"./jcr:content/jcr:primaryType": "nt:unstructured",
"_charset_": "utf-8"
};

return $.ajax({
url: parentPath + "/" + getUniqueFolderName(),
data: options,
type: 'POST'
});
};

$(document).on("foundation-contentloaded", function(e){
if($("#" + UNIQUE_FOLDER_ELEMENT_ID).length > 0 ){
return;
}

var $cFolder = $(CREATE_FOLDER_ACTIVATOR);

$cFolder.after($($cFolder[0].outerHTML).attr("id", UNIQUE_FOLDER_ELEMENT_ID)
.css("opacity", ".5").removeAttr("href").attr("title", "Create Unique Folder").click(function(){
var parentPath = $(".foundation-content-path").data("foundationContentPath");

createFolder(parentPath).done(function(){
$(".foundation-content").adaptTo("foundation-content").refresh();
});
}));
})
})(document, Granite.$);

AEM 6 - Touch UI Assets Console show Tooltip for Cards

$
0
0

Goal


Extend Assets Console to show tooltip with sub folder names on mouse over. A sample Coral tooltip CUI.Tooltip({})

Demo | Package Install



Solution


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

2 Create node /apps/touchui-show-tooltip/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.gui.damadmin.admin

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

                       tooltip.js

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

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

var SITE_ADMIN_TREE = "/bin/wcm/siteadmin/tree.json";

$(document).on("foundation-contentloaded", function(e){
var addSubFoldersToTooltip = function(data){
var subFolders = [];

$.each(data, function(i, f) {
subFolders.push(f["text"]);
});

if(subFolders.length == 0){
subFolders.push("No Sub Folders");
}

return "<div style='display: block;'>" +
"<span style='font-family:adobe-clean; font-weight: bold'>" + subFolders.join("<USE_BR_HERE>") + "</span></div>";
};

var getSubFolders = function (folderPath) {
return $.ajax({
url: SITE_ADMIN_TREE,
data: { path: folderPath },
dataType: "json",
async: false,
type: 'GET'
});
};

var assets = $("article.foundation-collection-item");

assets.each(function(index, card){
var $card = $(card);

$card.one('mouseenter', function () {
var $this = $(this);

var tooltip = new CUI.Tooltip({
type: "info",
target: $(card),
content: (function(){
var html;

getSubFolders($this.data("path")).done( function(data){
html = addSubFoldersToTooltip(data);
});

return html;
})(),
arrow: "left",
interactive: true
});
});
});
});
})(document, Granite.$);




AEM 6 SP1 - Servlet for Encryption Decryption

$
0
0

Goal


A servlet to return encrypt/decrypt words

Package Install

Encryption Request: http://localhost:4502/bin/experienceaem/encrypt?words=experience,aem



Decryption Request: http://localhost:4502/bin/experienceaem/decrypt?words={1e5cfddfb039af2da8116f84580fac6e35139bf235f156b070916232dbe3d117},{6f847af0f9858cfc3601eb4e740b4f1ee054bbb6321e3607fd0cc41651cce77a}



Solution


Create and deploy EncryptDecryptServlet with following code

package apps.experienceaem;

import com.adobe.granite.crypto.CryptoSupport;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.*;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import java.io.IOException;

@Component(label = "Experience AEM Encryption Decryption Servlet")
@Service
@Properties({
@Property(name = "sling.servlet.methods", value = { "GET" }, propertyPrivate = true),
@Property(name = "sling.servlet.paths", value = {
"/bin/experienceaem/encrypt",
"/bin/experienceaem/decrypt"
})
})
public class EncryptDecryptServlet extends SlingAllMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(EncryptDecryptServlet.class);

@Reference
protected CryptoSupport cryptoSupport;

@Override
protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");

String words = request.getParameter("words");
String uri = request.getRequestURI();

if(StringUtils.isEmpty(words)){
throw new ServletException("Need words");
}

String[] wordsArr = words.split(",");

try{
JSONWriter jw = new JSONWriter(response.getWriter());

jw.object();

for(String word : wordsArr){
jw.key(word).value(uri.endsWith("encrypt") ? cryptoSupport.protect(word)
: cryptoSupport.unprotect(word));
}

jw.endObject();
}catch(Exception e){
LOG.error("Error encrypting/decrypting", e);
}
}
}



AEM 6 SP1 - Hiding Attributes in CRXDE Lite

$
0
0

Goal


Hide attribute of a node in CRXDE Lite (specially passwords). In this post we hide the attribute testPassword for user author

For adding a Password type to CRXDE Lite (masking attributes)check this post

Thanks to Justin Edelson and Ravi Kiran for their ideas...

Solution


1) Browse to the path in CRXDE Lite (http://localhost:4502/crx/de) eg. /apps/config.author/com.experienceaem.crxde.lite.ExtensionConfig



2) Here we add a Deny on node /apps/config.author/com.experienceaem.crxde.lite.ExtensionConfig with a glob expression for hiding attribute testPassword

        a) Click on Access Control -> Access Control List -> +



         b) Search for author, provide Deny privilege with glob expression (rep:glob) /testPassword, click OK



       c) Here is the expression



3) Login to CRXDE Lite (http://localhost:4502/crx/de) as author and browse to /apps/config.author/com.experienceaem.crxde.lite.ExtensionConfig; testPassword attribute is not visible



AEM 6 SP1 - Extending CRXDE Lite to store Password Type

$
0
0

This is NOT a supported way to extend CRXDE Lite, infact afaik there is no supported/documented way to extend CRXDE Lite. If you always truly :) follow best practices in a project, STOP here, do not read further


Goal


Create a Password Type in CRXDE Lite for users to enter passwords (mask characters while typing password type attribute values). Password type values can be stored in plain text or Encrypted on file system. Please comment if you find bugs and a possible fix...

For a supported way to hide passwords (or other attributes) check this post

Demo | Package Install

Password Type in CRXDE Lite




Sample password stored as plain text (eg. author\crx-quickstart\launchpad\config\com\experienceaem\crxde\lite)

service.pid="com.experienceaem.crxde.lite.ExtensionConfig"
testMulti=["hi","there"]
testPassword="Password:password_changed"
testString="hithere"
testLong=L"1"

Sample password stored Encrypted

service.pid="com.experienceaem.crxde.lite.ExtensionConfig"
testMulti=["hi","there"]
testPassword="Password:{d498726918ffd35347bd536b4421915082c97e26074b499d136084ab905ded2c}"
testString="hithere"
testLong=L"1"


Solution


Follow the two steps below to extend CRXDE Lite and add necessary JS code to create Password type. First step is Not Upgrade-Proof, so when you upgrade CQ, the first step may have to be performed again

Step 1 - Update CRXDE Lite Jar

All we do in this step is copy (back it up just in case if something goes wrong) the serialized CRXDE lite jar, open it and add a small chunk of JS code in it so that any extensions we code are loaded by the added JS logic when lite is opened in browser. The following steps are also shown in demo

1) Access bundles console http://localhost:4502/system/console/bundles and find the CRXDE Support bundle





2) Search for the serialized bundle on filesystem and copy it to a temp location (take a backup before you modify). On my AEM 6 SP1 its available in author\crx-quickstart\launchpad\installer (rsrc-com.adobe.granite.crxde-lite-1.0.66-CQ600-B0001.jar-1415034571045.ser)

3) Rename the copied .ser file to .jar (eg. rsrc-com.adobe.granite.crxde-lite-1.0.66-CQ600-B0001.jar-1415034571045.ser -> rsrc-com.adobe.granite.crxde-lite-1.0.66-CQ600-B0001.jar)

4) Open the jar using zip executable (say winrar), open file docroot\js\start.js in any text editor and add following code at the end. Save file and a winrar confirmation should popup asking if the jar should be updated with saved file.

Ext.onReady(function() {
var loadLiteExtns = function(){
Ext.Ajax.request({
url: "/apps/ext-crx-delite/files.txt",
success: function(response, options) {
var js = response.responseText;

if(!js){
return;
}

js = js.split("\n");

Ext.each(js, function(jsPath) {
Ext.Ajax.request({
url: jsPath,
success: function(response, options) {
eval(response.responseText);
}
});
});
}
});
};

CRX.util.Util.on("reinit", function(){
location.reload();
});

loadLiteExtns();
});


5) In the above steps we add necessary code to load the extension files entered in /apps/ext-crx-delite/files.txt. So whenever a new CRXDE Lite extension is needed a new line with extension file path can be added in /apps/ext-crx-delite/files.txt

6) Access http://localhost:4502/system/console/bundles, click Install/Update... to upload and update CQ with the new CRXDE Support jar having necessary code to load the CRXDE Lite extension files.

Step 2 - Add extension files in CRX

In this step we add the JS file containing logic to create a Password type (like String type)

1) Access http://localhost:4502/crx/de

2) Create node /apps/ext-crx-delite of type nt:folder

3) Create node /apps/ext-crx-delite/files.txt of type nt:file and add the following line. The logic added in Step 1 reads this file for loading JS extension files added as paths

                                 /apps/ext-crx-delite/hide-password.js

4) Create node /apps/ext-crx-delite/hide-password.js of type nt:file and add the following code

Ext.ns("ExperienceAEM");

ExperienceAEM.PASSWORD = "Password";

ExperienceAEM.PropertyPanel = Ext.extend(CRX.ide.PropertyPanel,{
MASK: "****",

constructor: function(config){
ExperienceAEM.PropertyPanel.superclass.constructor.call(this, config);

var eThis = this;

this.types.store.loadData([ExperienceAEM.PASSWORD],true);

var valueColumn = this.getColumnModel().getColumnById("value");

valueColumn.renderer = function(value, p, record) {
var rValue = Ext.grid.Column.prototype.renderer.call(this, value, p, record);

if( (typeof rValue) != "string"){
return rValue;
}

var indexOfPassword = rValue.indexOf(ExperienceAEM.PASSWORD + ":");

if( indexOfPassword == 0){
record.data.type = ExperienceAEM.PASSWORD;
}

return (record.data.type == ExperienceAEM.PASSWORD) ? eThis.MASK : rValue;
};
},

initValue: function() {
ExperienceAEM.PropertyPanel.superclass.initValue.call(this);

this.value[ExperienceAEM.PASSWORD] = {
xtype: "textfield",
inputType: "password",
allowBlank: true,
disableKeyFilter: true,
cls: "x-form-text",
tabIndex: 102
};
}
});

Ext.reg("propertypanel", ExperienceAEM.PropertyPanel);

(function(){
var handler = CRX.ide.SaveAllAction.initialConfig.handler;

CRX.ide.SaveAllAction.initialConfig.handler = function(){
var workspaces = CRX.Util.getWorkspaces();
var dirtyPassRecs = [];

workspaces.each(function(workspace) {
var paths = CRX.State.getChangedPaths("/" + workspace + "/");

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

Ext.each(paths, function(path){
var records = CRX.State.getPropertyRecords(path);

Ext.each(records, function(record) {
if ( (record.data.type !== ExperienceAEM.PASSWORD) || !record.dirty){
return;
}

record.data.type = CRX.util.STRING;
//comment the below line if you use encryption
record.data.value = ExperienceAEM.PASSWORD + ":" + record.data.value;

dirtyPassRecs.push(record);
});
});
});

var words = [];

Ext.each(dirtyPassRecs, function(record) {
words.push(record.data.value);
});

/*
uncomment this block to use encryption
//http://experience-aem.blogspot.com/2014/11/aem-6-sp1-servlet-for-encryption-decryption.html
Ext.Ajax.request({
url: "/bin/experienceaem/encrypt?words=" + words.join(","),
success: function(response) {
var words = JSON.parse(response.responseText);

Ext.each(dirtyPassRecs, function(record) {
record.data.value = ExperienceAEM.PASSWORD + ":" + words[record.data.value];
});

handler();

Ext.each(dirtyPassRecs, function(record) {
record.data.type = ExperienceAEM.PASSWORD;
});
}
});*/

//start: comment the below lines to use encryption
handler();

Ext.each(dirtyPassRecs, function(record) {
record.data.type = ExperienceAEM.PASSWORD;
});
//end:
}
})();


5) Make sure logic strips off the leading Password: string when reading a stored password, from CRX, to get the actual password (further if password is encrypted, make sure you decrypt it before using...)

AEM 6 SP1 - Classic UI change Page Properties Dialog Ok button to Save

$
0
0

Goal


Just some quick sample JS code to change the text of OK button to Save in Page Properties Dialog


No Extension



With Extension




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classic-page-properties-buttons

2 Create node /apps/classic-page-properties-buttons/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.widgets

3) Create file (nt:file) /apps/classic-page-properties-buttons/clientlib/js.txt and add

                      change-ok-to-save.js

4) Create file (nt:file) /apps/classic-page-properties-buttons/clientlib/change-ok-to-save.js and add the following code.

(function(){
if( ( window.location.pathname !== "/cf" ) && ( window.location.pathname.indexOf("/content") !== 0)){
return;
}

var SK_INTERVAL = setInterval(function(){
var sk = CQ.WCM.getSidekick();

if(sk){
clearInterval(SK_INTERVAL);

var dialog = CQ.WCM.getDialog(sk.initialConfig.propsDialog);

if(dialog){
dialog.success = function(form, action) {
CQ.Util.reload(CQ.WCM.getContentWindow());
};

var okButton = dialog.buttons[0];

okButton.setText("Save");
}
}
}, 250);
})();

AEM 6 SP1 - Rich Text Editor Classic UI Color Palette

$
0
0

Goal


Add a Color Picker to Rich Text Editor of Classic UI ( not Touch UI )

Demo | Package Install

Demo shows dialog of foundation text component modified to add the color picker config. This is just for demonstration only (on Geometrixx pages), ideally the foundation components should never be altered...

Please leave a comment if you find bug / fix. The plugin has to be enhanced to support removal of added colors...


Picker with Default Colors


Configuration




Button



Picker





Custom Colors 


Configuration





Picker





Solution


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

2) Create clientlib (type cq:ClientLibraryFolder/apps/rte-color-picker/clientlib and set a property categories of String type to cq.widgets

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

                         color-picker.js

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

CQ.Ext.ns("ExperienceAEM");

ExperienceAEM.ColorPicker = {
ADD_COLOR_CMD : "addcolor"
};

ExperienceAEM.ColorPicker.Plugin = new Class({
toString: "ColorPickerPlugin",
extend: CUI.rte.plugins.Plugin,
P: ExperienceAEM.ColorPicker,

addPickerUI: null,

getFeatures: function() {
return [ this.P.ADD_COLOR_CMD ];
},

initializeUI: function(tbGenerator) {
var plg = CUI.rte.plugins;

if (this.isFeatureEnabled(this.P.ADD_COLOR_CMD)) {
this.addPickerUI = tbGenerator.createElement(this.P.ADD_COLOR_CMD, this, true, "Add Color");
tbGenerator.addElement("format", plg.Plugin.SORT_FORMAT,this.addPickerUI,1000);
}
},

execute: function(cmd, value, env) {
if (cmd == this.P.ADD_COLOR_CMD) {
this.showDialog(env.editContext);
}
},

showDialog: function(context) {
var editorKernel = this.editorKernel, dm = editorKernel.getDialogManager();
var config = this.config;

var colorPalette = {
xtype: "colorpalette"
};

if(config){
if(config.defaultColor){
colorPalette.value = config.defaultColor;
}

if(config.colors && config.colors.length > 0){
colorPalette.colors = config.colors;
}
}

var dialogConfig = {
"jcr:primaryType": "cq:Dialog",
title: "Color Picker",
modal: true,
width: 300,
height: 200,
items: [ {
xtype: "panel",
layout: "form",
padding: "20px 0 0 10px",
items: [ colorPalette ]
}],
ok: function() {
var palette = this.findByType("colorpalette")[0];

this.close();

if(palette.value){
editorKernel.relayCmd(ExperienceAEM.ColorPicker.ADD_COLOR_CMD, palette.value);
}
}
};

dm.show(CQ.WCM.getDialog(dialogConfig));
},

notifyPluginConfig: function(pluginConfig) {
pluginConfig = pluginConfig || { };

CUI.rte.Utils.applyDefaults(pluginConfig, {
"tooltips": {
"addcolor": {
"title": "Add Color",
"text": "Add Color"
}
}
});

this.config = pluginConfig;
},

updateState: function(selDef) {
if(this.addPickerUI){
this.addPickerUI.setSelected(false);
}
}
});

CUI.rte.plugins.PluginRegistry.register("colorpicker", ExperienceAEM.ColorPicker.Plugin);

ExperienceAEM.ColorPicker.Cmd = new Class({
toString: "ColorPicker",
extend: CUI.rte.commands.Command,

P: ExperienceAEM.ColorPicker,

isCommand: function(cmdStr) {
return (cmdStr == this.P.ADD_COLOR_CMD);
},

getProcessingOptions: function() {
var cmd = CUI.rte.commands.Command;
return cmd.PO_SELECTION | cmd.PO_NODELIST;
},

addColor: function(execDef){
var nodeList = execDef.nodeList;

if(!nodeList || !execDef.value){
return;
}

//todo: handle color remove
//add your custom tags here with pre defined css classes or styles
try{
nodeList.surround(execDef.editContext, "span", { style: "color:#" + execDef.value } );
}catch(err){
}
},

execute: function(execDef) {
if(execDef.command == this.P.ADD_COLOR_CMD){
this.addColor(execDef);
}
}
});

CUI.rte.commands.CommandRegistry.register("colorpicker", ExperienceAEM.ColorPicker.Cmd);

5) Add necessary CSS. Create folder (nt:folder) /apps/rte-color-picker/clientlib/themes

6) Create clientlib (type cq:ClientLibraryFolder/apps/rte-color-picker/clientlib/themes/default and set a property categories of String type to cq.widgets

7) Create file (nt:file) /apps/rte-color-picker/clientlib/themes/default/css.txt, add the following

                    css/color-picker.css

8) Create folder (nt:folder) /apps/rte-color-picker/clientlib/themes/default/css and add the icon addcolor.png

9) Create file (nt:file) /apps/rte-color-picker/clientlib/themes/default/css/color-picker.css, add the following code. RTE looks for css classes x-edit-addcolor (for the plugin toolbar button addcolor)

#CQ .x-html-editor-tb .x-edit-addcolor {
background: url(addcolor.png) center no-repeat;
}

10) Add any text component with RichText editor and in the rtePlugins path of dialog add colorpicker node to enable color picker plugin



11) The extension structure in CRX DE Lite



AEM 6 SP1 - Touch UI Sort and Show Tags in Alphabetical Order

$
0
0

Goal


Sort the tags alphabetically in Asset Metadata Editor of Touch UI

Package Install


Product





Extension





Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/sort-metadata-editor-tags

2 Create node /apps/sort-metadata-editor-tags/clientlib of type cq:ClientLibraryFolder and add a String property categories with value dam.gui.metadataeditor

3) Create file (nt:file) /apps/sort-metadata-editor-tags/clientlib/js.txt and add

                      sort-tags.js

4) Create file (nt:file) /apps/sort-metadata-editor-tags/clientlib/sort-tags.js and add the following code.

$(document).on('cui-contentloaded.data-api', function (e) {
var $tagsWidget = $('[data-metaType=tags]');
var $selectList = $tagsWidget.find('.js-coral-Autocomplete-selectList');

$selectList.html($selectList.find("li").sort(function(a, b) {
a = $(a).text();
b = $(b).text();

//this piece was copied from underscore.js sortBy
if (a > b || a === void 0){
return 1;
}else if (a < b || b === void 0){
return -1;
}

return 1;
}));
});

AEM 6 SP1 - Sort Assets in Touch UI

$
0
0

Goal


Sort folders and assets in Touch UI Assets Console

Column view is more dynamic and needs additional logic for sorting all columns shown in the view. This extension sorts only first column

Please leave a comment if you find bugs / have fix...

Demo | Package Install


Product





Extension




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touch-ui-sort-assets

2 Create node /apps/touch-ui-sort-assets/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.gui.damadmin.admin

3) Create file (nt:file) /apps/touch-ui-sort-assets/clientlib/js.txt and add

                  sort-assets.js

4) Create file (nt:file) /apps/touch-ui-sort-assets/clientlib/sort-assets.js and add the following code.

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

var FOUNDATION_CONTENT_LOADED = "foundation-contentloaded";

var sort = function($articles, isColumn){
$articles.sort(function(a, b) {
a = $(a).find(isColumn ? ".foundation-collection-item-title" : "h4").text();
b = $(b).find(isColumn ? ".foundation-collection-item-title" : "h4").text();

//this piece was copied from underscore.js sortBy
if (a > b || a === void 0){
return 1;
}else if (a < b || b === void 0){
return -1;
}

return 1;
});

return $articles;
};

var sortCards = function(){
var execFn = function(){
var $cards = $.find(".foundation-layout-card");

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

var $grids = $("div[class^='grid-']"),
clazz = $grids.prop("class"),
gIndex = parseInt(clazz.substr(clazz.indexOf("-") + 1), 10),
$articles = sort($("article"));

$grids.html("");

$articles.each(function(index, article){
$($grids[index % gIndex]).append($(article));
});
};

setTimeout(function(){
try { execFn() } catch(err){ console.log("Error executing sort..." + err);}
},250);
};

var sortList = function(){
setTimeout(function(){
var $items = $.find(".foundation-layout-list");

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

$(".grid-0").html(sort($("article")));
}, 250);
};

var sortColumns = function(){
var columns = $.find(".foundation-layout-columns .coral-ColumnView-column-content");

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

//sort just the first column, more logic needed for sorting miller columns
$(columns[0]).html(sort( $(columns[0]).find(".coral-ColumnView-item"), true));
};

$(document).on(FOUNDATION_CONTENT_LOADED, function(e){
$(document).on(FOUNDATION_CONTENT_LOADED, ".foundation-layout-columns", function(e) {
sortColumns();
});

//event not thrown on .foundation-layout-list, .foundation-layout-card sometimes, bug???
$(document).on(FOUNDATION_CONTENT_LOADED, ".foundation-layout-util-maximized", function(e) {
sortCards();
sortList();
});
});
})(document, jQuery);


AEM 6 SP1 - Classic UI Restrict Large or Small Files Upload

$
0
0

Goal


In Classic UI of AEM 6 SP1 Restrict Upload of Large Files (or file sizes that do not fall in a min-max range)

Large Uploads using Drag and Drop is currently not restricted... wip

Please leave a comment if you find bug, have fix...

Demo | Package Install


Upload File Limit Set to 2MB





Error shown when > 2 MB files are uploaded





Upload File Limit Set to Min 2KB, Max 2 MB





Error shown when > 2 MB or < 2 KB files are uploaded





Solution



1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classic-ui-dam-set-upload-limit

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

3) Create file (nt:file) /apps/classic-ui-dam-set-upload-limit/clientlib/js.txt and add

                       upload-limit.js

4) Create file (nt:file) /apps/classic-ui-dam-set-upload-limit/clientlib/upload-limit.js and add the following code.

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

//id set in /libs/wcm/core/content/damadmin
var DAM_ADMIN_ID = "cq-damadmin";
var CREATE_FILE_ICON = "cq-damadmin-create-file-icon";
var PROP_ALLOWED_FILE_SIZE_BYTES = "allowedFileSizeBytes";

var addFileSizeHandler = function(button){
var attach = function(uploadWin, afsb){
var isRange = afsb.indexOf("[") !== -1, min, max;

if(isRange){
afsb = afsb.replace("[", "").replace("]", "");

min = parseInt(afsb.substring(0,afsb.lastIndexOf("-")), 10);
max = parseInt(afsb.substring(afsb.lastIndexOf("-") + 1), 10);
}else{
min = 0;
max = parseInt(afsb, 10);
}

uploadWin.on('fileselected', function(uploadField, files){
var message = "", errorFiles = [];

for (var i = 0; i < files.length; i++) {
if(files[i].size > max){
message = message + "File " + files[i].name
+ " size must be less than " + max + " bytes (" + (max/1024) + " kb)" + "<br>";
errorFiles.push(files[i].name);
}else if(files[i].size < min){
message = message + "File " + files[i].name
+ " size must be greater than " + min + " bytes (" + (min/1024) + " kb)" + "<br>";
errorFiles.push(files[i].name);
}
}

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

CQ.Ext.Msg.alert("Error", message, function(){
var uploadFields = uploadWin.findByType("html5fileuploadfield");

for (var i = 0; i < uploadFields.length; i++) {
if(!uploadFields[i].file){
continue;
}

if(errorFiles.indexOf(uploadFields[i].file.name) != -1){
uploadWin.onFileRemoved(uploadFields[i].file.name);
uploadFields[i].clearFile();
}
}
});
});
};

button.on("click", function(){
var wMgr = CQ.Ext.WindowMgr, uWin;

var W_INTERVAL = setInterval(function(){
try{
wMgr.each(function(win){
if(win.xtype !== "html5uploaddialog"){
return;
}

clearInterval(W_INTERVAL);

//make sure you get the last (active) upload dialog window, if there are multiple
uWin = win;
});

CQ.Ext.Ajax.request({
url: uWin.displayPath + ".json",
success: function(response) {
var obj = $.parseJSON(response.responseText);

if(!obj[PROP_ALLOWED_FILE_SIZE_BYTES]){
return;
}

attach(uWin, obj[PROP_ALLOWED_FILE_SIZE_BYTES].trim());
}
});
}catch(err){}
}, 250);
});
};

var addToNewButton = function(grid){
var toolBar = grid.getTopToolbar();

var newMenu = toolBar.findBy(function(comp){
return comp["iconCls"] == CREATE_FILE_ICON;
}, toolBar);

if(!newMenu || newMenu.length == 0){
return;
}

addFileSizeHandler(newMenu[0]);

var newFileButton = newMenu[0].menu.findBy(function(comp){
return comp["iconCls"] == CREATE_FILE_ICON;
}, toolBar);

if(!newFileButton || newFileButton.length == 0){
return;
}

addFileSizeHandler(newFileButton[0]);
};

var INTERVAL = setInterval(function(){
var grid = CQ.Ext.getCmp(DAM_ADMIN_ID + "-grid");

if(!grid){
return;
}

clearInterval(INTERVAL);

addToNewButton(grid);
}, 250);

})();

AEM 6 SP1 - Classic UI Add Help Button to Page Properties Dialog

$
0
0

Goal


Add Help button to Page Properties dialog of Classic UI

Demo | Package Install





Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classic-ui-page-properties-help

2) Create node /apps/classic-ui-page-properties-help/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.widgets

3) Create file (nt:file) /apps/classic-ui-page-properties-help/clientlib/js.txt and add

                       add-help.js

4) Create file (nt:file) /apps/classic-ui-page-properties-help/clientlib/add-help.js and add the following code.

(function(){
if( ( window.location.pathname !== "/cf" ) && ( window.location.pathname.indexOf("/content") !== 0)){
return;
}

var SK_INTERVAL = setInterval(function(){
var sk = CQ.WCM.getSidekick();

if(!sk){
return;
}

clearInterval(SK_INTERVAL);

try{
var dialog = CQ.WCM.getDialog(sk.initialConfig.propsDialog);

if(!dialog){
return;
}

dialog.addButton(new CQ.Ext.Button({
"text":"Help",
"tooltip":{
"title":"Help",
"text":"Open help page in new tab",
"autoHide":true
},
handler : function(){
CQ.Ext.Msg.alert("Help", "Click ok to open helpx.adobe.com in new tab", function(){
var win = window.open("http://helpx.adobe.com", '_blank');
win.focus();
}
);
}
}));

dialog.buttons.unshift(dialog.buttons.pop());

dialog.doLayout();
}catch(err){
console.log("Error executing extension")
}
}, 250);
})();

AEM 6 - Classic UI Modify SiteAdmin Grid Column

$
0
0

Goal


Modify the SiteAdmin Grid Published column of Classic UI to show Still in Queue... status if a page takes more than 10 secs to get published

Demo | Package Install





Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/classic-ui-modify-column-published

2) Create node /apps/classic-ui-modify-column-published/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.widgets

3) Create file (nt:file) /apps/classic-ui-modify-column-published/clientlib/js.txt and add

                       modify-published.js

4) Create file (nt:file) /apps/classic-ui-modify-column-published/clientlib/modify-published.js and add the following code.

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

//id defined in /libs/wcm/core/content/siteadmin
var SA_GRID = "cq-siteadmin-grid";
var PUBLISHED_ID = "published";
var WAIT_MILLS = 10000;

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

if(!pColumn){
return;
}

var renderer = pColumn.renderer;

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

var replication = record.data.replication;

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

if( (new Date().getTime() - replication.published) > WAIT_MILLS ){
html = $(html).find("span").css("color", "red").html("Still in Queue...").parent()[0].outerHTML;
}

return html;
};

grid.store.load();
};

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

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

var cm = grid.getColumnModel();

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

clearInterval(SA_INTERVAL);

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

AEM 6 SP1 - Show Folders only in Assets Console

$
0
0

Goal


Show Folders only in Touch UI Assets Console of AEM 6 SP1

This extension is not too friendly with Miller columns view, the show folder checkbox works only on first column

Demo | Package Install





Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/touch-ui-assets-show-folders

2 Create node /apps/touch-ui-assets-show-folders/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.gui.damadmin.admin

3) Create file (nt:file) /apps/touch-ui-assets-show-folders/clientlib/js.txt and add

             /libs/cq/gui/components/authoring/clientlibs/editor/js/util/debounce.jquery.js
             show-folders.js

4) Create file (nt:file) /apps/touch-ui-assets-show-folders/clientlib/show-folders.js and add the following code.

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

var FOUNDATION_CONTENT_LOADED = "foundation-contentloaded";
var CREATE_FOLDER_ACTIVATOR = ".cq-damadmin-admin-actions-createfolder-activator";
var E_AEM_SHOW_FOLDERS = "experience-aem-show-folders";
var AEM_ASSETS_CREATE = "#aem-assets-create";

$(document).on(FOUNDATION_CONTENT_LOADED, function(e){
if($("#" + E_AEM_SHOW_FOLDERS).length > 0){
return;
}

//if dynamic media is enabled /etc/dam/dynamicmediaconfig, the create folder changes to dropdown
var $cFolder = $(AEM_ASSETS_CREATE) ;

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

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

var $showFolderCheckBox = $("<span class='endor-ActionBar-item' style='line-height: 2.25em'>" +
"<label class='coral-Checkbox coral-Form-field'>" +
"<input type='checkbox' id='" + E_AEM_SHOW_FOLDERS + "' value='true' class='coral-Checkbox-input' />" +
"<span class='coral-Checkbox-checkmark'></span>" +
"<span class='coral-Checkbox-description'><b>Show Folders Only</b></span>" +
"</label></span>");

var $articles, $assets, gridsHtml;

$showFolderCheckBox.insertAfter($cFolder).find("#" + E_AEM_SHOW_FOLDERS).change(function(){
if(!$articles){
$articles = $("article");
$assets = $("[data-type='asset']");
}

var hide = this.checked, $grids = $("div[class^='grid-']");

if($grids.length > 0 && !gridsHtml){
gridsHtml = $grids.parent().html();
}

if(hide){
$assets.hide();
}else{
$assets.show();
}

var $cards = $.find(".foundation-layout-card");

//adjust the cards to fit in layout
if($cards.length == 0){
return;
}

var clazz = $grids.prop("class"),
gIndex = parseInt(clazz.substr(clazz.indexOf("-") + 1), 10),
assetType, index = 0;

if(!hide && gridsHtml){
$grids.parent().html(gridsHtml);
return;
}

$grids.html("");

var inGrid = Math.ceil( ( hide ? ($articles.length - $assets.length) : $articles.length ) / gIndex);

$articles.each(function(i, article){
if(hide){
assetType = $(article).find("[itemprop='assettype']");

if(assetType.length > 0 && assetType[0].innerHTML !== "FOLDER"){
return;
}
}

$($grids[ Math.floor(index++ / inGrid)]).append($(article));
});
});

$(document).on(FOUNDATION_CONTENT_LOADED, function(e){
$(document).on(FOUNDATION_CONTENT_LOADED, ".foundation-layout-columns", function(e) {
$articles = $assets = gridsHtml = undefined;
$("#" + E_AEM_SHOW_FOLDERS).prop( "checked", false );
});

//event not thrown on .foundation-layout-list, .foundation-layout-card sometimes, bug???
$(document).on(FOUNDATION_CONTENT_LOADED, ".foundation-layout-util-maximized", function(e) {
$articles = $assets = gridsHtml = undefined;
$("#" + E_AEM_SHOW_FOLDERS).prop( "checked", false );
});
});
});
})(document, jQuery);

AEM 6 - Attaching Listeners to Asset Metadata Editor Form Fields

$
0
0

Goal


Create a new Asset Metadata Editor Form field and attach Listeners

This post demonstrates adding a new form field to Asset metadata editor using the Metadata Schema feature of AEM 6

Demo | Package Install

Thank you Dave Chang for the PredicateBuilder logic


listeners node added under Serial metadata form field node






keypress listener shows error tooltip when user types-in a space







Solution


1) Add a new form field to Image asset metadata editor by accessing the Metadata schema form of image (http://localhost:4502/libs/dam/gui/content/metadataschemaeditor/schemadetails.html/dam/content/schemaeditors/forms/default/image)

2) Add a new form field Serial with property ./jcr:content/metadata/serial





3) Click done and the form field node can be found under /apps/dam/content/schemaeditors/forms/default/image in CRXDE Lite (http://localhost:4502/crx/de). In my CQ the node got created at /apps/dam/content/schemaeditors/forms/default/image/items/tabs/items/tab1/items/col2/items/1418939382969

4) Add a new node listeners of type nt:unstructured with property

                       Name: keypress
                       Type: String
                       Value: function(event) { ExperienceAEM.textOnly(event) }




5) The keypress event function attached to editor form field Serial executes when user keys-in any character. The javascript function ExperienceAEM.textOnly added in next steps checks for whitespace and shows error tooltip if user tries to enter spaces

6) In CRXDE Lite (http://localhost:4502/crx/de) create folder /apps/touch-ui-metadata-listeners

7) Create node /apps/touch-ui-metadata-listeners/clientlib of type cq:ClientLibraryFolder and add a String property categories with value dam.gui.metadataeditor

8) Create file (nt:file) /apps/touch-ui-metadata-listeners/clientlib/js.txt and add

             attach-listeners.js
             text-only.js

9) Create file (nt:file) /apps/touch-ui-metadata-listeners/clientlib/attach-listeners.js and add the following code. Here we are reading form fields, querying CRX for any listener nodes and attach event listeners. This is sample code for adding listeners on input elements and not guaranteed to work on every metadata editor element. Based on the form field element type (say select) the code needs to be adjusted...

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

var SCHEMA_EDITOR_PATH = "/apps/dam/content/schemaeditors";
var QUERY_BUILDER = "/bin/querybuilder.json";
var NODE_LISTENERS = "listeners";
var SEARCH_TEXT_IN_LISTENERS = "function";

function PredicateBuilder(defaults) {
this.params = $.extend({}, defaults);
this.numPredicates = 0;
}

PredicateBuilder.prototype = {
fullText: function(value, relPath) {
if (!value) {
return this;
}

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

if (relPath) {
this.params[this.numPredicates + '_fulltext.relPath'] = relPath;
}

this.numPredicates++;

return this;
},

prop: function(name, value) {
if (name && value) {
this.params[this.numPredicates + '_property'] = name;

if($.isArray(value)) {
var that = this;

$.each(value, function(i, item) {
that.params[that.numPredicates + '_property.' + i + '_value'] = item;
});
}else{
this.params[this.numPredicates + '_property.value'] = value;
}

this.numPredicates++;
}

return this;
},

http: function(){
var builder = this;

return $.ajax({
method: 'GET',
url: QUERY_BUILDER,
data: builder.params
});
}
};

var attachListeners = function(hits){
if(!$.isArray(hits)){
return;
}

var $ele;

$.each(hits, function(i, hit){
$ele = $("[name='" + hit["name"] + "']");

$.each(hit.listeners, function(key, value){
if(key == "jcr:primaryType"){
return;
}

try{
$ele.on(key, eval("(" + value+ ")" ) );
}catch(err){
console.log("Error attaching listener : " + key + " - " + value);
}
})
});
};

$(document).on("foundation-contentloaded", function(e) {
var $editables = $(".foundation-field-edit");

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

var builder = new PredicateBuilder({
'path': SCHEMA_EDITOR_PATH,
'p.hits': 'full',
'p.nodedepth': 2,
'p.limit': 100
});

var values = [];

$editables.find("input").each(function(i, value){
values.push($(value).attr("name"));
});

builder.prop("name", values).fullText(SEARCH_TEXT_IN_LISTENERS, NODE_LISTENERS)
.http().done(function(resp) {
attachListeners(resp.hits);
});
});
})(document, Granite.$);

10) Add the event listener fired on keypress (added in a different js file). Create file (nt:file) /apps/touch-ui-metadata-listeners/clientlib/text-only.js and add the following code

(function() {
if (typeof window.ExperienceAEM == "undefined") {
window.ExperienceAEM = {};
}

var tooltip = null;

ExperienceAEM.textOnly = function(event){
if(!tooltip){
tooltip = new CUI.Tooltip({
type: "error",
target: event.target,
content: "No spaces allowed",
visible:false,
arrow: "left",
interactive: false
});
}

if(event.which === 32){
tooltip.show();
event.preventDefault();
}else{
tooltip.hide();
}
}
}());


Viewing all 525 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>