Monday, August 27, 2018

Adobe Experience Manager (AEM) Examples and Notes

Personal AEM Notes

I just recently started a project at work using Adobe Experience Manager (AEM). Here's my random, rambling collection of notes. If you know of a better way to do any of these examples please let me know by adding a comment.

Good resources:
  1. Jeff's Youtube series Programmer Vs World AEM Series
  2. HTL guide: https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/master/SPECIFICATION.md#225-test
  3. https://github.com/pankajchhatri/AEM

General Tips for Working with AEM

  1. Save often when creating objects - all the time. Create an object, then "Save All", or Ctl-S. All the time. Delete something? Save immediately.
  2. Remember to always to a hard reload of a browser after changing AEM content, Ctl-Shift-R.
  3. Coming from Visual Studio, I found the IntelliJ "Live Edit" plugin and the Chrome plugin very useful.

Jeff's overview of AEM components:

Apache Felix - osgi framework - forces modular sw development - service orentation
Jackrabbit - JCR content repo, JSR-170,283, workspaces, nodes, properties
Sling - Restful Web Framework
Sencha EXT JS 4 - js application framework
Lucene - search engine
Quartz Scheduler - cron system programmatically accessable
CRX - Tar Journal, Package Manager, deploy and share, Servlet Engine CQSE
CRXDE Lite - ide
WCM (Web Content Manager) - this is what content writers use

Folder Structure

/apps: custom templates, components,
/content: stores all content
/etc: client library, design dialog info
/libs: standard components - do not modify
   example:  /libs/wcm/foundation/components/page/
/conf: All configuration
/var: locks,
/home: info about users and groups
/oak:index: Jackrabbit Oak index definitions

Wierd Strings to Put in the URL

Strings to put after "http://localhost:4502" to do wonderful things:


/cf#/ - sets the old interface, e.g., http://localhost:4502/cf#/content/...
/editor.html/ - uses the new interface, e.g., http://localhost:4502/editor.html/content/...
/crx/de - goes straight to the CRXDE page e.g., http://localhost:4502/crx/de
/welcome - go to the Websites/DAM page e.g., http://localhost:4502/libs/cq/core/content/welcome.html
/siteadmin - go to the Websites e.g., http://localhost:4502/siteadmin
/system/console/bundles - shows versions and the console - e.g., http://localhost:4502/system/console/bundles

How to limit use of a template?

    allowed paths: /content(/.*)? /* anywhere under content */
    allowed paths: /content/sports/baseball  /* only under baseball */

How to get rid of <p>&nbsp;</p>?

Make sure the text you entered into the "text" element does not contain an extra carriage return.

How to only iterate a fixed number of times?

<ul data-sly-list="${thelist}">
    <li data-sly-test="${itemList.index < 4}">${itemList}</li>
</ul>

Set default values with the 'or' operator

  ${properties.pageTitle || properties.jcr:title || resource.name}

How to find what version of AEM you're running

http://localhost:4502/system/console/status-productinfo

To skip the AEM link check

add this class

<a x-cq-linkchecker="skip" href="..." />

Chrome screen creeping up?

In Chrome do you ever get CRX/DE screen creeping up the bottom left panel til it takes over the whole screen? The best solution I've found is to right-click and select "inspect" and make sure the Chrome browser is not in full screen mode.

How to get a list of tags and iterate over them

var categories = [];
var tags = resolver.getResource("/etc/tags/mysection").getChildren();
for (var i=1;i<tags.length;i++) {
   categories.push(tags[i].name);//do something
}

What is a "Client Library"?

It organizes resources that we send to the browser (client side) - things like css and JavaScript.

How to include a resource in a page

<div data-sly-resource="${ 'myname' @ resourceType='wcm/foundation/components/parsys'}"></div>

To show a page title

${properties.jcr:title}

How to include other files

<sly data-sly-include="header.html"></sly>

How to iterate over properties

 <ul data-sly-list.child="${currentPage.getProperties}">
    <li>${child}</li>
 </ul>

 or
 
<ul data-sly-repeat.child="${currentPage.getParent.getProperties}">
    <li>${child}</li>
</ul>

How to place a page property on the HTML page

Here's an example of reading a property from the page dialog in JavaScript and putting the value on the page.

<div data-sly-use.bannerTitle="getBannerTitle.js">
    ${bannerTitle}
</div>
getBannerTitle.js
"use strict";
use(function () {
    return  resourcePage.getProperties().get("bannerTitle");
});

To See the Actual jcr Nodes

/content/....

To show an array as text with a seperator

${['apple', 'pear', 'papaya'] @ join=' - '}
generates
apple - pear - papaya

How to format strings

${'person {0} of {1}' @ format=[7,9]}

How to encode a string for a URL

This will replace non-URL friendly characters like ' ' with '%20'.

  ${properties.jcr:title @ context='uri'}
  or
  ${'This is a test' @ context='uri'}
url:This%20is%20a%20test

How to encode a string for simple html content

This will replace non-URL friendly characters like ' ' with '%20'.

${'Texas A & M University' @ context='text'}
generates
&lt;x&gt;Texas A &amp; M University&lt;/x&gt;  

How to put in an AEM HTL comment

<!--/* This is an example comment that will not appear in the browser. */-->

How to use Sly Element so no "div" element is sent to the browser

Use the "sly" element when you don't want to have the extra "div" element

  <div data-sly-include="head.html"></div>
  /* this produces: <div>myhead.html</div> */
  use only the "sly" tag and the surrounding divs are not created.
  <sly data-sly-include="head.html"></sly>
  /* this produces: myhead.html */

How to replace content of HTML Element

  <div data-sly-text="${currentPage.title}">This will not be seen</div>

Simple Example of "use" to Call Server-side JavaScript and Pass Parameters

For example, suppose we need a social icon component, "sharethis", which will accept two variables, url encode them, and return an object with two url-encoded variables. The returned object is named "site". ( I have since learned the right way to do this is with the "context='uri'"):

  ${'This is a test' @ context='uri'} //url:This%20is%20a%20test

In sharethis.html:

<div class="iconContainer" data-sly-use.site="${'sharethis.js' @ title=currentPage.title, url=request.requestURL.toString}">
    <p class="shareThisTitle">Share this:</p>
    <a href="https://www.facebook.com/sharer/sharer.php?href=${site.url}&title=${site.title}" alt="share on facebook" target="_blank" class="nodecoration">
        <img src="/content/dam/design/facebook.png" title="share on facebook" alt="share on facebook" />
    </a>
...
</div>

The variables passed in are accessed by prefacing them with "this.". In the file sharethis.js:

"use strict";
use(function () {
    return {
        title: encodeURIComponent(this.title),
        url: encodeURIComponent(this.url)
       }
});

To Have AEM Automatically Bundle Css And Javascript Files

clientlib
create node / cq:clientlibraryfolder

create clientlib named zurbfoundation
create property 'categories' string[], zurb.foundation
create property 'dependencies' "+" cq.jquery

js.txt
#base=style   //subdirectory name

css.txt
#base=source

<html data-sly-use.clientlib='/libs/granite/slightly/templates/clientlib.html'>

<sly data-sly-call="${clientlib.css @ categories='zurb.foundation'}" />
<sly data-sly-call="${clientlib.js @ categories='zurb.foundation'}" />
or to do both:
<sly data-sly-call="${clientlib.all @ categories='zurb.foundation'}" />

How to do a REST call to get json objects in server-side JavaScript within a proxy

"use strict";
function geturlcontents(url) {
    //put the following values in a config file 
    var proxyHost = "proxy.district13.gov",
    proxyPort = 80,
    username = "katniss",
    password = "ilovearchery";

    var method = new org.apache.commons.httpclient.methods.GetMethod(url),
    client = new org.apache.commons.httpclient.HttpClient(),
    status = new org.apache.commons.httpclient.HttpStatus(),
    inputStream;

    var hostConfiguration = new org.apache.commons.httpclient.HostConfiguration();
    hostConfiguration.setProxy(proxyHost,proxyPort);
    client.setHostConfiguration(hostConfiguration);

    var credentials = new org.apache.commons.httpclient.UsernamePasswordCredentials(username, password);
    var authScope =   new org.apache.commons.httpclient.auth.AuthScope(proxyHost, proxyPort);
    client.getState().setProxyCredentials(authScope, credentials);

    try {
 var statusCode = client.executeMethod(method);
 if (statusCode == status.SC_OK) {
  inputStream = method.getResponseBodyAsString();
 } else {
  console.log(':::Failed to execute http method. status code = '+statusCode + ' for url '+ url);
 }
    } catch (e) {
       console.log(":::exception: "+e);
    } finally {
       method.releaseConnection();
    }
return inputStream;
};

use(function() {
  var inputStream = geturlcontents(this.api);
  return JSON.parse(inputStream).slice(0, this.maxitems);
});

How to do a REST call to get json objects in server-side in JSP within a proxy


<%@include file="/libs/foundation/global.jsp"%>
<%@page import="org.apache.commons.httpclient.*,
org.apache.commons.httpclient.methods.*,
org.apache.commons.httpclient.params.HttpMethodParams,
org.apache.commons.httpclient.auth.*,
org.slf4j.Logger,
org.slf4j.LoggerFactory,
org.apache.sling.commons.json.*,
java.util.Date,
java.io.*"%>

<%
String proxyHost = "myproxy.mycompany.com";
int proxyPort = 80;
String username = "steven";
String password = "spielburg";
String url = "http://www.omdbapi.com/?t=alien&plot=full";

HttpClient client = new HttpClient();
GetMethod method = new GetMethod(url);
HttpStatus status = new HttpStatus();
HostConfiguration hostConfiguration = new HostConfiguration();

hostConfiguration.setProxy(proxyHost, proxyPort);
client.setHostConfiguration(hostConfiguration);
UsernamePasswordCredentials credentials = new org.apache.commons.httpclient.UsernamePasswordCredentials(username, password);
AuthScope authScope = new AuthScope(proxyHost, proxyPort);
client.getState().setProxyCredentials(authScope, credentials);

String jsonString = "[]";
try {
int statusCode = client.executeMethod(method);
application.log(" *** statusCode : " +statusCode);

if (statusCode != HttpStatus.SC_OK) {
       application.log(" *** Method failed: " + method.getStatusLine());
       throw new Exception(" *** client.executeMethod failed for url "+ url + " with http error code " + statusCode);
}
      // Read the response body.
      byte[] responseBody = method.getResponseBody();
      jsonString = new String(responseBody);
 } catch (HttpException e) {
      application.log(" *** Fatal protocol violation: " + e.getMessage());
      e.printStackTrace();
    } catch (IOException e) {
      application.log(" *** Fatal transport error: " + e.getMessage());
      e.printStackTrace();
    } catch (Exception e) {
      application.log(" *** Fatal error: " + e.getMessage());
      e.printStackTrace();
    } finally {
      method.releaseConnection();
    }  

 JSONArray jsonArray = new JSONArray(jsonString);

String countofmoviesString = (String)properties.get("countofmovies");
int countofmovies = Integer.parseInt(countofmoviesString);

%>
<div class="movieTileContainer">
     <ul class="movieTileList" >
<%
int maxMovies = jsonArray.length() > countofmovies ? countofmovies : jsonArray.length();
for(int i=0;i<maxMovies;i++) {
   JSONObject jsonObject = jsonArray.getJSONObject(i);
    String title = (String)jsonObject.get("title");
    String jurl = (String)jsonObject.get("url");
    JSONObject image = (JSONObject) jsonObject.get("movieEntryImage");
    String thumbnailLocation = (String)image.get("thumbnailLocation");
    JSONArray prices = (JSONArray) jsonObject.get("price");

    String price = (String)prices.getJSONObject(0).get("price");

   %>
            <li class="movieTileListItem">
                <div class="content">
                    <div class="movieTileBox">
                        <a class="movieTileLink" title="<%= title %> " href="https://www.MyMovieSite.com<%= jurl %>">
                            <p class="movieTileTitle"><%= title %> </p>
                            <img class="movieTileImage" width="185" height="101" src="https://www.MyMovieSite.com<%= thumbnailLocation %>"
                                 alt="<%= title %>">
                                     <p class="movieTilePrice" <%= price %>
                            </p>
                        </a>
                    </div>
                </div>
            </li>
<% } %>
    </ul>
</div>

Common Resource Types:

text field: granite/ui/components/coral/foundation/form/textfield

number: /libs/granite/ui/components/foundation/form/numberfield

DAM file: granite/ui/components/foundation/form/pathbrowser

How to lowercase a variable

More generally, how to invoke a JavaScript method on an htl variable. Call the method without parenthesis.

<a href="${myvariable.toLowerCase}/mypage.html">

Important AEM directories

/libs/granite/ui/components/foundation - new components
/libs/wcm/foundation/components/

To Add An Editable Area

<div data-sly-resource="${ 'content' @ resourceType='wcm/foundation/components/parsys'}"></div>

To Show AEM Quickstart Frame

add this immediately after body tag and it will present the AEM quickstart frame

<div data-sly-call="/libs/wcm/core/components/init/init.jsp"></div>

How to Create an Editable Button

/components/materialize-button/dialog/items/items/tab1/items
fieldDescription / What do you want the button to say
fieldLabel / Button Text
jscr:primaryType cq:Widget
name / ./buttontext
xtype / textfield
<a>${properties.buttontext}</a>

Unorganized Snippets of Forgotten Thoughts

on components, "group" is category.
to add dialog: rt-click/create...dialog
on tab1, create a node with name of "items" and type of cq:WidgetCollection
  inside items create another node named "buttontext" of type cq:Widget
  in Properties add "xtype/String/textfield"
                add "fieldDescription/String/What do you want the button to say"
  add "fieldLabel/String/Button Text"
  add "name/String/./buttontext"  --name is where it puts the value

from component:
${properties.buttontext}
mainpage.tidy.4.json

(to get a list of entities, API Documentation/CQ/Ext/form)