Difference between useQuery and useQueries in React.JS to read data from AEM

In React JS useQuery and useQueries are two different hooks that serve distinct purposes when for fetching and managing data.

useQuery:

  • useQuery is used to fetch and manage data for a single query.
  • It takes a single query key as its primary argument and an optional configuration object.
  • You use useQuery when you want to fetch data for a specific resource or endpoint.
  • It automatically manages caching, fetching, and re-fetching based on your configuration settings.

Below is an example of useQuery: (Where url is dam json path)

import {useQuery} from "@transtack/react-query";

export default function ReadData{

let url = "/content/dam/myproject/profile.json";

let {data, error, isError, isLoading, isState, ...otherParams} = useQuery ( 
[url],
fetchUrlSynchronously
);

if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      {/* Display data */}
    </div>
  );
}

useQueries:

  • useQueries is used to fetch multiple queries simultaneously.
  • It takes an array of query objects as its primary argument. Each query object includes a query key and a fetching function.
  • You use useQueries when you need to fetch data for multiple resources or endpoints in parallel.
  • It returns an array of query result objects, each containing data, loading status, and errors for the corresponding query.

Below is an example of useQueries:

import {useQueries} from "@transtack/react-query";

export default function ReadData{

const urls = [
    { url: 'content/dam/myproject/profileOne.json' },
    { url: '/content/dam/myproject/profileTwo.json' }
  ];


let {data, error, isError, isLoading, isState, ...otherParams} = useQuery ( 
[url],
fetchUrlSynchronously
);


let queryResults: any []= useQueries({
 queries: Object.keys(urls).map((key)  => {
  return {
    queryKey: [urls[key].url],
    queryFn: fetchUrlAsynchronously,
    ...{ enabled: true}
}
  })
});

return (
    <div>
      {queryResults.map((result, index) => (
        <div key={index}>
          {result.isLoading ? 'Loading...' : null}
          {result.error ? `Error: ${result.error.message}` : null}
          {result.data ? /* Display data */ : null}
        </div>
      ))}
    </div>
  );

}

In summary, the key difference lies in the purpose and scope of these hooks. useQuery is for fetching data for a single query, while useQueries is for handling multiple queries concurrently. Depending on your use case, you can choose the hook that best fits your requirements. React Query’s documentation provides comprehensive information on both hooks and how to configure them for optimal use.

Thank you for reading.
Happy Coding!

Quick Read | What is ContentPolicyManager in AEM?

ContentPolicyManager is a service that facilitates the management and application of content policies to components. Content policies are configurations that define how components should behave and appear on a page. They help enforce consistency in design, styling, and behavior across different instances of the same component.

So if you want to pick up different value for a component based on different policy attached to a component we can use ContentPolicyManager which gives us the exact value attached to that component in design dialog based on the policy.

Let’s see how we can write the code to get the values

ResourceResolver resourceResolver = request.getResourceResolver();
Resource currentResource = resourceResolver.resolve(request.getRequestPathInfo().getResourcePath());
ContentPolicyManager contentPolicyManager = resourceResolver.adaptTo(ContentPolicyManager.class);
ContentPolicy contentPolicy = contentPolicyManager.getPolicy(currentResource);

String deignDialogValue = contentPolicy.getProperties().get("nameProperty",String.class);

To get the object of the content policy manager we need to have resourceResolver. So in first step I am getting the resource resolver object from the request and later in step two i am getting the current resource .
Once we have Content Policy manager we can get the content policy for the current resource which will be specific to ONLY current resource i.e current component.
Once I have content policy object we get which ever design dialog property we want to fetch.

Some of the useful APIs of Content Policy are:

Some of the useful APIs of Content Policy Manager are:


NOTE: Above implementations works when you have a policy correctly attaches to a resource i.e component in AEM.

Thank you for reading.
Happy Coding!

Select All and De-Select All fields in Dropdown using Coral UI JavaScript in AEM

In this article we will try out a custom Coral UI Javascript in AEM and all a functionality to a dropdown where, dropdown will have n number of values and will have Select All and De-select All option as well in the dropdown. This is really helpful when you have lot’s of options in a dropdown and you have to select all and de-select all.

First create a simple AEM dropdown field with multiselect functionality and add some 5-6 options in the dropdown and then add two option with following text and value combination which will help us select all fields in dropdown as well as de-select all fields in dropdown.


NOTE: The name property used in this article is ./country for the dropdown.



Select All option:

text: Select All (You can keep any name as it just shows on the UI).
value: all (We are keeping all here because we will be using the same value in JS)

Deselect All option:

text: De-Select All (You can keep any name as it just shows on the UI).
value: none (We are keeping none here because we will be using the same value in JS)

Now create a clientlibrary in AEM with category name as cq.myproject.select-deselect. And then attach this clientlib at the cq:dialog node of the component by adding the following property at cq:dialog node.

extraClientlibs – String – cq.myproject.select-deselect

Once we are done setting up everything then paste the following code in your JS file of the clientlibs and checkout the functionality in the dropdown.

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

$(document).on("foundation-field-change", "coral-select[name='./country']", function (e) {
let postSelect = $("coral-tag[aria-selected='true']").val();
let allDropdownselect=($(this).Find("coral-selectlist-item(value='all']")).is(':selected');
let nodropdownSelect = ($(this).find("coral-selectlist-item[value='none']")).is(':selected');
        if (!!allDropdownselect  && !postSelect) {
            $(this) .find("coral-selectlist-item[value! ='none']").attr("selected", true);
            $(this). find("coral-tag[value='all']"). remove();
        }
        if (!!nodropdownSelect  && !postSelect) {
            $(this).find("coral-selectlist-item[value!='none']").attr("selected", false);
            $(this).find("coral-tag[value='none' ]") .remove();
        }
    });
}) (document, Granite.$);



Also if you want to customise the above Coral UI JS as per your name property and dropdown options then you need to update the following props.

Field NamePurpose
./countryUsed as the name property of the dropdown field
allUsed as the value of Select All dropdown field
noneUsed as the value of De-Select All dropdown field

Configuring Maven in MacOS for AEM Build

In this article we will see how we can setup java and maven in our MacOS so that we can give a successful build of our AEM Archetype project.

As we know we can easily setup the Environmental variables in windows but the process in MacOS is bit different.

Firstly we need to simple install the Java 11 using the .dmg file installer which you can get at https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html.



And once you are done installing the Java 11 successfully you can check for the Java version in your terminal using java -version and check if it shows the correct Java version.

Now download the latest or required maven version from https://maven.apache.org/download.cgi and unzip it at any of your local system location. (In my case I have kept it at /Users/NikhilKumar/Downloads/apache-maven-3.9.0).




Now once you are done with the above placement of the unzipped apache-maven-3.9.0 folder, you can open a new terminal which point to the users location (in my case it’s /Users/NikhilKumar).


Now next thing is to check for /.bash_profile file in the users path or you can simply open the terminal of macOs and type vi ~/.bash_profile. Now you will get an empty vi editor and now just add the below set of lines which will set the maven path to the path of the dowloaded maven version.

export M2_HOME="/Users/NikhilKumar/Downloads/apache-maven-3.9.0"
PATH="${M2_HOME}/bin:${PATH}"
export PATH

Now once you are done adding the above code just press Esc then Shift + Colon(:) and then type wq and press enter. (This means we are saving the piece of lines that we have added in bash_profile).

Once you are done with above steps you can either re-open a new terminal to check for maven version or just do source .bash_profile and then check for maven version and it should show you the corresponding maven version.

Now we can go ahead to our AEM Maven project and give a maven build and it should successfully download the dependencies and build should be success.

Thank you for reading.
Happy Coding!

Create and Use Service User using ACL Scripts | AEMaaCs

In this article we will learn how to create and use Service Users in your AEM code to provide controlled, programmatic access to the AEM repository.

As we all know we used to create the system users using the /crx/explorer and used to provide the required permissions from /useradmin dashboard. But now as we are moving towards AEMaaCs and the recommended way is to push the system users and it’s permissions through code. So let’s see how we can achieve that.

So let’s go ahead and update the OSGI config which comes OOTB as part of recent archetypes i.e, org.apache.sling.jcr.repoinit.RepositoryInitializer-myproject-statistics.config which can be found in you project structure at /ui.config/src/main/content/jcr_root/apps/myproject/osgiconfig.


1. Service User – permission to single path

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject
         allow jcr:all for myprojectUserService
    end
"]

2. Service User – permission to multiple paths

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject,/conf/myproject
         allow jcr:all for myprojectUserService
    end
"]

We can give different permissions as per our requirement, here I have given jcr:all for the above paths but if you want to give only read/write permissions you can do something like below:

3. Service User – permission to single path with only read permission

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject
         allow jcr:read for myprojectUserService
    end
"]

4. Service User – permission to multiple paths with only write permission

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject
         allow jcr:write for myprojectUserService
    end
"]

The recommended way is to create the service user under system/cq:services/<my-project> when using the principle ACL. You do it my using the following script:

scripts=["
    create service user myprojectUserService with forced path system/cq:services/my-project

    
    set principal ACL for myprojectUserService
        allow jcr:read on /content/dam/myproject
    end
"]

So once we are done creating the service user we will map it to a subServiceName which will be used in our java code to get the proper resolver to access the AEM content.

We can create our OSGI config i.e, org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-myproject.cfg.json

{
  "user.mapping": [
    "myproject.core:myprojectUserService=[myprojectUserService]"
  ]
}

So once we are done creating the above configurations we can utilise the above service user to get our resource resolver which will help us access the AEM content.

final Map<String, Object> authInfo = Collections.singletonMap(
                ResourceResolverFactory.SUBSERVICE,
                "myprojectUserService");

    
        try (ResourceResolver serviceResolver = resourceResolverFactory.getServiceResourceResolver(authInfo)) {
            // Do some work with the service user's resource resolver and underlying resources.
        } catch (LoginException e) {
            log.error("Login Exception when obtaining a User for the Bundle Service: {} ", e);
        }


Thank you for reading.
Happy Coding!