Introduction

Building an AEM Admin interface, also known as a console, is not a trivial task at first glance. This post describes how to create an admin console for AEM 6.2 to list, sort, create, and delete centralized data.

Source Code: available here

What are we building?

A method to create content in centralized location that can be shared across many different sites and locales. In our case, we will be creating calendar entries to convey data center uptime for our Pug Ranch's Live Video Feed.

Why are we building this?

Typically components get localized across many locales. This creates live copies of data across our Oak repository. Our data does not need to be localized because our data center uptime covers all locales. By centralizing the data, we remove it from the localization process and remove unnecessary localized nodes.

High Level Design

The design can be broken down into three high-level pieces.

  1. Admin Console Menu Item
  2. Admin Console Content, Components, and Clientlibs
  3. Servlet to handle creating and editing of data

Creating Console Menu Item

Below is the structure of our menu item. It's important to know that we need to overlay the existing libs/cq/core/content/nav/tools/operations nodes. 

Notes:

  • The id corresponds to the consoleId (defined later)
  • Icon should be labeled with a corresponding Coral UI icon name.
  • Href should point to the location of your console (defined later)


The full uptime menu item JCR node description:

Once you have made the above nodes, you will be able navigate to AEM (1) Tools (2) Operations (3) and Data Center Uptime (4).

Admin Console Content, Components, and Clientlibs

The next piece we need to build is the actual console and the components within the console. This is the most complex piece, so let's break this down further:

  1. Content
    1. Admin - This is the main view to list our entries. It's defined by a .content.xml
    2. createEntry - This is the create view. It's is also defined by a .content.xml
    3. editEntry - This is the edit view. Surprise, also defined by a .content.xml
  2. Components
    1. Datasource - This is a JSP that provides data (our entries) to our list.
    2. Uptimeentry - As we iterate through our list of entries, this defines the view for each entry.
    3. Uptimeform - This handles both create and edit operations.
  3. Clientlibs - This single JS will provide:
    1. A Granite Delete Confirmation Dialog
    2. AJAX Call to delete an entry. It leverages the SlingPostServlet for simplicity.
    3. Pass a path parameter to our editEntry page.

Content / Admin

This file is huge, so go take a look here. If you've ever worked with ui.content this file should feel familiar as we're declaring almost all of our layout in XML.

Notes:

  • consoleId should match our Menu Item "id" we created above.
  • The clientlibs node allows us to include clientlibs. In our case, Coral 3 and our own uptime clientlib.
  • Our list and datasource are what pull in the list of entries. The datasource component will be covered later.
  • itemResourceType is used to display each entry. It will also be covered later.
  • Within actions, the selection nodes (delete, edit) are used when selecting an item in the list. 
  • Within actions, the secondary action always shows and is used for create. It's uses a plain href property since no params need to be passed.

Content / createEntry & Content / editEntry

You can find createEntry here. You can find editEntry here.

Notes:

  • These files are identical with the exception of one line: the text property of the title node.
  • The content node that has a property of granite/ui/components/coral/foundation/form. This has a property of action that submits to our servlet, /bin/pugranch/uptimeEntry which will be defined later.
  • The node uptimeform has a sling:resourceType of pugranch/admin/ext/uptime/components/uptimeform which we will build later using Sightly, Coral, and HTML.

Components / datasource

This component is not a typical front-end style component. We only need the folder and the JSP. You can find the source here.

Notes:

  • The goal of the data source is to find resources, and build an iterable list of those resources.
  • It's important to know that for any value you want exposed, you must add that to the ValueMap of the child. You cannot rely on typical resource methods I.E. resource.getPath, resource.getName if you do not include those values in the datasource.
  • Working in JSP is not fun, but it's a bit more direct than Sightly + Java.

Components / uptimeentry

This component takes the entries from our data source and uses the values provided to display each entry. Source here.

Notes:

  • Again, it's important to know that the only values we have available to us are coming directly from the datasource. Typical methods you would use to get values from a resource are not available here.
  • Note data-id and data-path. Our Javascript will be acting on these properties as needed.
  • A lot of the Coral UI classes and naming conventions are using 6.2 conventions. Your mileage may very.

Components / uptimeform

This component is the inside of our createEntry and editEntry form. In the case of editEntry, we have a use class to provide additional logic to pull in information. Source here.

Notes:

  • We're again using some Coral Kung Fu to create our form elements.
  • We are using a Sling convention, :redirect to provide our servlet with the location we want to be redirected to upon successful entry creation.
  • Remember, our form action and method were declared in our content nodes, so the do not need to be declared here.
  • The step value (.01) seems to have a bug with rouding, so we fix this in our servlet.

Clientlibs / pugranch.uptime

The client library only contains on Javascript file. It's relatively straight-forward with only four functions. The source is here.

Functions to Note:

  • Register the buttons during a selection change.
  • Toggle the Delete Dialog when "Delete" is clicked.
  • Handle the Confirm Deletion - Uses an AJAX call and Sling Post Servlet conventions to delete the node.
  • Handle the "Edit" action - Passes the path to the editEntry.

Servlet


UptimeEntryServlet.java

The Uptime Entry Servlet Performs the functions listed below. Source code here.

  1. Create an uptimeCalendar sling:OrderedFolder if needed.
  2. Create or update an uptime entry. The naming convention is YEAR-Month.
  3. Redirect to the location based on :redirect.

Conclusion

In the end, you should have something that resembles the screenshot below. It can seem daunting at first, but if you walk through each piece, creating an admin console can be very similar to a lot of core AEM concepts.

Resources

With xpath being deprecated since JCR 2, SQL2 remains the most effective way to perform raw queries against Jackrabbit. The two ways I've used are described below:

  1. Using resourceResolver.findResources - I.E. The Sling Way
  2. Using queryManager.createQuery - I.E. The Jackrabbit Way

There's pros and cons to both, so let's dive in...

ResourceResolver.findResources

Here we can see a simple query being executed and returning an iterable that can be adapted to any object. This works brilliantly for simple queries. Now what if we want to paginate or limit these posts? Enter queryManager...

QueryManager.createQuery

With a few changes, we can now leverage the methods query.setOffset and query.setLimit. 

While there are many advantages to this approach, there is one big disadvantage: There's no simple way to adapt a plain JCR NodeIterator into a resource without writing a custom adapter. However, we can do something very clever in Sightly...

By using data-sly-resource, we can grab each child node using our path and render it using our given resourceType.

JCROakDatabaseSQL2Query

Introduction

AEM 6.1 introduced new CSRF protections for Servlets. If you're using OOTB AEM jQuery, this is mostly handled for you. I want to cover the use case for not using jquery or the granite.csrf.standalone ClientLib.

Why

I've been making it a point to reduce my dependency on jQuery. With an AEM author, you'll never get 100% away from it, but it's possible to do on publish if you're doing typical WCM type sites. Also, with my current project, we are keeping the site as dependency free as humanly possible.

How

The trick is to send an async XHR request to the token endpoint (/libs/granite/csrf/token.json), pass that token on to your servlet as a header property called "CSRF-Token".

Below you see a fairly basic Sightly Component. Comments are inline.

And a a simple servlet to return the information back...

Update: I've added a proper github project here.

In classic UI, it was easy to specify the options property on a selection xtype. 

This made getting dynamic data into a dropdown very easy. Simply point the options property to /path/to/resource.infinity.json and you were done. In Granite, things don't work this easy.

There are two approaches that I found. 1) Use a listener. 2) Use ACS Commons GenericList / Datasource solution.

The ACS commons solution works great, but it's a bit rigid on the data structure. I used it as my starting point for the solution below. 

My solution is a 3 step process, requires no JS and no external dependencies.

Step 1

Make a datasource component. It only needs to have one jsp file in it...

Step 2

Add a datasource node to your dialog property. Read the gist for pertinent info.

Step 3

Profit.

Scripting AEM Oak Compaction

January, 10 2016

Offline compaction is still the Adobe recommended way of compacting Oak. Below is a script to help automate the entire process. Please keep in mind that you will need to download a version of Oak Run that matches your repository version.

  1. Shutdown AEM
  2. Find Old Checkpoints
  3. Remove Unreferenced Checkpoints
  4. Compact Oak
  5. Restart AEM

Each step will log basic information into a file using the current date. The stop and start processes specifically write the exact time they were kicked off.

Note: I've taken the liberty of adding a few arguments to the compaction process to help with memory issues. You can read about tar memory mapping here.

oakaemshellscriptcompaction