Showing posts with label Nested Multifield. Show all posts
Showing posts with label Nested Multifield. Show all posts

May 9, 2020
Estimated Post Reading Time ~

Custom xtype in AEM/Creating Multifield in AEM

In AEM we can achieve this by creating a widgets clienlibrary, which holds the custom multifield structure and it should call before the comps/page loads in CQ.

Create a clientlibrary in the root folder called apps. Here i am creating a folder called advtraining in apps. In this im creating the clientlibrary called "cqwidgets" having categories as "cq.widgets".

Now after creating this , place our custom multifield code in the created js file.

And Create a component for placing our custom xtype . And just drag the component in to the page and start using it.



By aem4beginner

May 8, 2020
Estimated Post Reading Time ~

How to Use ACS Commons Multifield With Datepicker

This article details on how to create a component that contains multiple fields, Date being one of them

ACS Commons allows 2 ways to store the data into the repository.
  1. JSON Store
  2. NODE Store
Using JSON_STORE
Details:
1. cq_dialog.xml

<scheduleEvents jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/multifield” class="full-width">  
    <field jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/fieldset” acs-commons-nested=“JSON_STORE" name="./scheduleEvents”>  
    <layout jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns” method="absolute”/>  
    <items jcr:primaryType="nt:unstructured”>  
      <column jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/container”>  
         <items jcr:primaryType="nt:unstructured”>  
            <eventDate jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/datepicker” type=“date" displayedFormat="YYYY-MM-DD” fieldLabel="Event Date” fieldDescription="Scheduled date of the event.” name="./eventDate”/>  
         </items>

Note acs-commons-nested=“JSON_STORE”

2. Backend code snippet

@ValueMapValue(name="scheduleEvents")  
protected String[] scheduleEventsArray = null;  
...  
if (scheduleEventsArray != null && scheduleEventsArray.length > 0) {  
  for( String section : scheduleEventsArray ) {  
    Map<String, String> props = (Map<String, String>) new Gson().fromJson(section, Map.class);  
      eventDate = props.get("eventDate”);

Using NODE_STORE
Details:
1. cq_dialog.xml

<scheduleEvents jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/multifield” class="full-width">  
    <field jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/fieldset” acs-commons-nested=“NODE_STORE" name="./scheduleEvents”>  
    <layout jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns” method="absolute”/>  
    <items jcr:primaryType="nt:unstructured”>  
      <column jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/container”>  
         <items jcr:primaryType="nt:unstructured”>  
            <eventDate jcr:primaryType=“nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/datepicker” type=“date" displayedFormat="YYYY-MM-DD” fieldLabel="Event Date” fieldDescription="Scheduled date of the event.” name="./eventDate”/>  
         </items>
b. Note acs-commons-nested=“NODE_STORE”

2. Backend code snippet
Resource eventsResource = resource.getChild( "scheduleEvents" );  
Iterator<Resource> allEventsResources = eventsResource.listChildren();  
  while ( allEventsResources.hasNext() ){  
     Resource eventResource = allEventsResources.next();  
      valuemap = eventResource.getValueMap();  
       eventDate = valuemap.get("eventDate", Calendar.class);

Source: https://techinpieces.com/how-to-use-acs-commons-multifield-with-datepicker-skip-to-end-of-metadata/


By aem4beginner

April 26, 2020
Estimated Post Reading Time ~

Create a Nested Multi-Field CQ Dialog Widget

Nested multi-field cq dialog widget or custom Adobe Experience Manager (AEM) component is one that uses a nested multi-field control located in a dialog. A nested multi-field control is an inner multi-field control within an outer multi-field control and lets an author dynamically enter data.

For example, assume the AEM control lists developers and each developer has an unknown number of skills to display. That is, within the inner multi-field, the author enter details such as professional skill set. The outer multi-field determines how may developers to display.

Let's create Nested multi-field cq dialog widget as shown below:


Read More


By aem4beginner

Nested Multifield (coral 3) with Sling Model in AEM

Create an AEM multi-module project using archetype 11
Create a new component with cq:dialog for Touch UI as shown below.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="Country Details"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
        <items jcr:primaryType="nt:unstructured">
            <column
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/container">
                <items jcr:primaryType="nt:unstructured">
                    <countries
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                        composite="{Boolean}true"
                        fieldLabel="Countries">
                        <field
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/container"
                            name="./countries">
                            <items jcr:primaryType="nt:unstructured">
                                <column
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <countryName
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                            fieldLabel="Country Name"
                                            name="./countryName"/>                                        
                                        <states
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                                            composite="{Boolean}true"
                                            fieldLabel="States">
                                            <field
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/container"
                                                name="./states">
                                                <items jcr:primaryType="nt:unstructured">
                                                    <column
                                                        jcr:primaryType="nt:unstructured"
                                                        sling:resourceType="granite/ui/components/coral/foundation/container">
                                                        <items jcr:primaryType="nt:unstructured">
                                                            <stateName
                                                                jcr:primaryType="nt:unstructured"
                                                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                                fieldLabel="State Name"
                                                                name="./stateName"/>
                                                            <statePostal
                                                                jcr:primaryType="nt:unstructured"
                                                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                                fieldLabel="State Postal"
                                                                name="./statePostal"/>
                                                            <statePopulation
                                                                jcr:primaryType="nt:unstructured"
                                                                sling:resourceType="granite/ui/components/coral/foundation/form/numberfield"
                                                                fieldLabel="State Population"
                                                                name="./statePopulation"/>
                                                            <stateDensity
                                                                jcr:primaryType="nt:unstructured"
                                                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                                                fieldDescription="Select State Density"
                                                                fieldLabel="State Density"
                                                                name="./stateDensity">
                                                                <items jcr:primaryType="nt:unstructured">
                                                                    <high
                                                                        jcr:primaryType="nt:unstructured"
                                                                        text="High"
                                                                        value="high"/>
                                                                    <medium
                                                                        jcr:primaryType="nt:unstructured"
                                                                        text="Medium"
                                                                        value="medium"/>
                                                                    <low
                                                                        jcr:primaryType="nt:unstructured"
                                                                        text="Low"
                                                                        value="low"/>                                                                    
                                                                </items>
                                                            </stateDensity>
                                                        </items>
                                                    </column>
                                                </items>
                                            </field>
                                        </states>
                                    </items>
                                </column>
                            </items>
                        </field>
                    </countries>
                </items>
            </column>
        </items>
    </content>
</jcr:root>

Now create sling models to get the authored values.
  1. CountriesModel.java
  2. Country.java
  3. State.java
CountriesModel.java
package com.aemquickstart.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.ArrayList;
import java.util.List;


@Model(adaptables = Resource.class)
public class CountriesModel {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    @Named("sling:resourceType")
    @Default(values = "No resourceType")
    protected String resourceType;

    @Inject
    @Optional
    private List<Resource> countries;

    private List<Country> countriesList = new ArrayList<>();

 public List<Country> getCountriesList() {
  return countriesList;
 }

 public void setCountriesList(List<Country> countriesList) {
  this.countriesList = countriesList;
 }
 @PostConstruct
    protected void init() {
        logger.debug("In init of CountriesModel");
        if (!countries.isEmpty()) {
            for (Resource resource : countries) {
                Country student = resource.adaptTo(Country.class);
                countriesList.add(student);
            }
        }
    }
 
}

Country.java
package com.aemquickstart.core.models;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The type Country.
 */
@Model(adaptables = Resource.class)
public class Country {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    @Optional
    private String stateName;

    @Inject
    @Optional
    private List<Resource> states;

    @Optional
    private List<State> stateList = new ArrayList<>();

    public List<State> getStateList() {
        return stateList;
    }

    public String getStateName() {
  return stateName;
 }

 public void setStateName(String stateName) {
  this.stateName = stateName;
 }

 public void setStateList(List<State> stateList) {
        this.stateList = stateList;
    }

    @PostConstruct
    protected void init() {
        logger.debug("In init method of Country model.");
        if(!states.isEmpty()) {
            for (Resource resource : states) {
                State state = resource.adaptTo(State.class);
                stateList.add(state);
            }
        }
    }
}

State.java
package com.aemquickstart.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

@Model(adaptables = Resource.class)
public class State {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    @Optional
    private String stateDensity;

    @Inject
    @Optional
    private String statePopulation;

    @Inject
    @Optional
    private String stateName;

    @Inject
    @Optional
    private String statePostal;

 public String getStateDensity() {
  return stateDensity;
 }

 public void setStateDensity(String stateDensity) {
  this.stateDensity = stateDensity;
 }

 public String getStatePopulation() {
  return statePopulation;
 }

 public void setStatePopulation(String statePopulation) {
  this.statePopulation = statePopulation;
 }

 public String getStateName() {
  return stateName;
 }

 public void setStateName(String stateName) {
  this.stateName = stateName;
 }

 public String getStatePostal() {
  return statePostal;
 }

 public void setStatePostal(String statePostal) {
  this.statePostal = statePostal;
 }

 public Logger getLogger() {
  return logger;
 }
    @PostConstruct
    protected void init() {
        logger.debug("In init of State Model");
    }
}

Add below html code in CountryDetails.html
<div>
    <b>Countries Details</b>
    <br><br>
    <div data-sly-use.countryModel="com.aemquickstart.core.models.CountriesModel" data-sly-unwrap>
        <div data-sly-test="${!countryModel || wcmmode.edit}">
            Add country and state details using component dialog
        </div>

        <div data-sly-test="${countryModel.countriesList}">
            <div data-sly-list.country="${countryModel.countriesList}">
                <div>
                    <div>Country Name: ${country.countryName}</div>
                    <br>
                    <div data-sly-list.state="${country.stateList}" style="margin-left:40px">
                        <div>State No.: ${stateList.count}</b></div>
                        <div>Name: ${state.stateName}</b></div>
                        <div>Postal: ${state.statePostal}</div>
                        <div>Population: ${state.statePopulation}</div>
                        <div>Density: ${state.stateDensity}</div>
                        <br>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Deploy code using the below maven command.
mvn clean install -PautoInstallPackage

Author CountryDetails component in touch dialog.
Authored details are visible on the page.


By aem4beginner

April 24, 2020
Estimated Post Reading Time ~

Using the ACS AEM Commons Nested Multifield



Creating dialogs in Adobe Experience Manager, or AEM, is key to granting content authors the ability to create dynamic, fully featured sites within a CMS framework. With AEM’s move to the Touch UI, authors now have a more modern and robust environment to create content.

Developers are able to tap into the power of the Touch UI to construct more powerful and dynamic functionality in order to enhance the authoring experience. Despite the expanded feature set, AEM’s new UI still lacks a handful of decidedly useful features.

One of the biggest sources of frustration that developers face is the multifield resource type. Out of the box, this Granite resource type is only able to contain a single field. In order to add multiple fields, a developer would need to create multiple multifields, each containing a single field, then write a bunch of logic to keep each of those in sync. Quickly, that will become a development nightmare, and an even bigger nightmare to support or enhance later on.

Thankfully, AEM’s open source community has come to the rescue. One of the additions in ACS AEM Commons provides for a nested multifield that allows developers to create a multifield of a fieldset. The rest of this post will go into how to configure a dialog to utilize the acs-commons-nested property and read in the JSON value saved to the JCR.

This guide was written using AEM 6.1 with Service Pack 1 installed and has ACS-Commons version 2.2.4 installed.

It is Just a Property
Adding this functionality to a dialog couldn’t be simpler. All you need to do is add the property acs-commons-nested to a fieldset within a multifield. Let’s look at the snippet below.

<example-multifield
jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/multifield"
fieldLabel="Example Multifield with Long Label">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/fieldset"
acs-commons-nested=""
name="./example">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
method="absolute"/>
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<examplePath
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
fieldLabel="Example Path"
name="./examplePath"/>
<exampleText
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldLabel="Example Text"
name="./exampleText"/>
</items>
</column>
</items>
</field>
</example-multifield>

First, we have the example-multifield node with its sling:resourceTypeset to be the standard Granite multifield. Nothing unusual so far. That node then contains the field node, which has its sling:resourceType set to be Granite’s fieldset. Contained within this node is the property called acs-commons-nested=””, which is what ACS Commons is looking to enable the multi-multifield functionality.

From there, the rest is just your standard container of fields. In this example, I’ve added a path browser and a textfield.

The multifield on the dialog ends up functioning just like the standard AEM version, with the Add field button, along with the reordering and delete controls.

Make it Look Better

While the previous section did result in a functional dialog, the usability of the various form fields is quite low. Thankfully, this can be easily resolved by adding a couple of classes in three different locations in your XML.

The classes we will add are foundation-layout-util-maximized-alt and long-label. They will extend the length of the fieldLabel properties when they render in the dialog, as well as place the fieldLabel above the form fields.

Below is what the nodes look like in the XML with classes added to them:<example-multifield
class="foundation-layout-util-maximized-alt long-label"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
fieldLabel="Example Multifield with Long Label">
 
<examplePath
class="foundation-layout-util-maximized-alt long-label"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
fieldLabel="Example Path"
name="./examplePath"/>
 
<exampleText
class="foundation-layout-util-maximized-alt long-label"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldLabel="Example Text"
name="./exampleText"/>

And this is the much more author-friendly dialog that will now display:

SaveAs JSONArray

When an author fills out the new nested multifield dialog, the data entered ends up being saved into the JCR as a String array, with the value of each array being a JSON object. If you were to view the example.infinity.json page for this node, you would see the following JSON returned:
{
"example": [
"{\"examplePath\":\"/content/dam/geometrixx\",\"exampleText\":\"Example Text Field Text\"}",
"{\"examplePath\":\"/content/geometrixx/en\",\"exampleText\":\"Second Example Text Field\"}"
]
}

And if you were to look at it in CRX/DE, the values look like the following:

Read In the JSON

I feel this guide wouldn’t be complete if I didn’t include how to actually read in the JSON from the JCR. This example is going to do this all within the JSP so that you can have a self contained, a fully functional component that you can use in the Geometrixx demo site. In practice, however, I have used custom JSP Tags that utilize reusable utility classes to handle the retrieval of the node’s properties and create JSONObject or JSONArrayobjects.

First, we will need a simple POJO to hold our data.

class ExamplePojo {
 
private String examplePath;
private String exampleText;
 
public ExamplePojo(String examplePath, String exampleText) {
 
this.examplePath = examplePath;
this.exampleText = exampleText;
}
 
public String getExamplePath() {
return this.examplePath;
}
 
public void setExamplePath(String examplePath) {
this.examplePath = examplePath;
}
 
public String getExampleText() {
return this.exampleText;
}
 
public void setExampleText(String exampleText) {
this.exampleText = exampleText;
}
}

Next would be to read in the property from the component’s node and convert that into a JSONObject. From there, we can use the key/value pairs to create an ExamplePojo object, and then add that to an ArrayList. It should be noted that there is not a way to deserialize the JSONObjectdirectly into your POJO, which is why ExamplePojo’s constructor is used to create the object. 

Value[] values = new Value[]{};
PropertyIterator propItr = resource.adaptTo(Node.class).getProperties("example");
if (propItr.hasNext()) {
Property prop = propItr.nextProperty();
if (prop.isMultiple()) {
values = prop.getValues();
} else {
values = (new Value[]{prop.getValue()});
}
}
 
List<ExamplePojo> exampleList = new ArrayList<ExamplePojo>();
for (Value value : values) {
JSONObject jsonObj = new JSONObject(
new JSONTokener(value.getString()));
ExamplePojo pojo = new ExamplePojo(
jsonObj.getString("examplePath"),
jsonObj.getString("exampleText"));
exampleList.add(pojo);
}

You can drop this component onto any Geometrixx page after it is added to the dropzone via Design Mode.

Hopefully, this guide has been helpful and has expanded your ability to develop dialogs in AEM’s Touch UI.



By aem4beginner

April 16, 2020
Estimated Post Reading Time ~

Create a Nested Multi-Field CQ Dialog Widget

Nested multi-field cq dialog widget or custom Adobe Experience Manager (AEM) component is one that uses a nested multi-field control located in a dialog. A nested multi-field control is an inner multi-field control within an outer multi-field control and lets an author dynamically enter data.

For example, assume the AEM control lists developers and each developer has an unknown number of skills to display. That is, within the inner multi-field, the author enters details such as a professional skill set. The outer multi-field determines how may developers to display.

Let's create Nested multi-field cq dialog widget as shown below:




Steps to create an AEM component that uses a nested multi-field:
Create a CQ application folder structure.
Create a template on which the page component is based.
Create the page component based on the template.
Create an AEM column component.
Add a dialog to the component.
Create a CQ web page that uses the new component.
Creating a CQ application folder structure

Let's create an Adobe CQ application folder structure that contains templates, components, and pages by using CRXDE Lite.




CQ Application Folder Structure

The following describes each application folder:
application name: contains all of the resources that an application uses. The resources can be templates, pages, components, and so on.
components: contains components that your application uses.
page: contains page components. A page component is a script such as a JSP file.
global: contains global components that your application uses.
template: contains templates on which you base page components.
src: contains source code that comprises an OSGi component (this development article does not create an OSGi bundle using this folder).
install: contains a compiled OSGi bundles container.

For creating an application folder structure, follow the below steps:
To view the CQ welcome page, enter the URL http://[host name]:[port] into a web browser. For example, http://localhost:4502.
Select CRXDE Lite.
Right-click the apps folder (or the parent folder), select Create, Create Folder.
Enter the folder name into the Create Folder dialog box. Enter Nested_Multifield.



Repeat steps 1-4 for each folder specified in the previous illustration.
Click the Save All button.

Note: Never forget to click on SaveAll while working on CRXDE.
Creating a Template

Let's create a template by using CRXDE Lite. A CQ template enables you to define a consistent style for the pages in your application. A template comprises nodes that specify the page structure.

For creating a template, follow the below steps:
1. To view the CQ welcome page, enter the URL http://[host name]:[port] into a web browser. For example, http://localhost:4502.
2. Select CRXDE Lite.
3. Right-click the template folder (within your application), select Create, Create
Template.
4. Enter the following information into the Create Template dialog box:
Label: The name of the template to create. Enter multifieldTemplate.
Title: The title that is assigned to the template.
Description: The description that is assigned to the template.
Resource Type: The component’s path is assigned to the template and copied to implementing pages. Enter /apps/nested_multifield/components/page/multifieldTemplate.
Ranking: The order (ascending) in which this template will appear in relation to other templates. Setting this value to 1 ensures that the template appears first in the list.



Create Template using crxde
5. Add a path to Allowed Paths. Click on the plus sign and enter the following value: /content(/.*)?.
6. Click Next for Allowed Parents.
7. Select OK on Allowed Children.

Creating a render component that uses the template
By default, a component has at least one default script, identical to the name of the component. To create a render component, perform these tasks:
To view the CQ welcome page, enter the URL http://[host name]:[port] into a web browser. For example, http://localhost:4502.
Select CRXDE Lite.
Right-click /apps/nested_multifield/components/page, then select
Create, Create Component.
Enter the following information into the Create Component dialog box:
Label: The name of the component to create. Enter multifieldTemplate.
Title: The title that is assigned to the component.
Description: The description that is assigned to the template.
Super Type:foundation/components/page.



Creating a Page Component in AEM
5. Select Next for Advanced Component Settings and Allowed Parents.
6. Select OK on Allowed Children.
7. Open the templateMultifield.jsp located at: /apps/nested_multifield/components/page/multifieldTemplate/multifieldTemplate.jsp.



8. Enter the following JSP code.
<html>
<%@include file="/libs/foundation/global.jsp" %>
<cq:include script="/libs/wcm/core/components/init/init.jsp"/>
<body>

<h1>Here is where your custom AEM component will go</h1>

<cq:include path="par" resourceType="foundation/components/parsys" />
</body>
</html>




Creating an AEM custom component that uses a nested multifield
After you set up the AEM folder structure, create the AEM custom multi-field component by performing the below steps:

1. Right-click on /apps/nested_multifield/components and then select New, Component.

2. Enter the following information into the Create Component dialog box:
Label: The name of the component to create. Enter developer-profile-setup.
Title: The title that is assigned to the component. Enter developer-profile-setup.
Description: The description that is assigned to the template. Enter developer-profile-setup.
Super Resource Type: Enter foundation/components/parbase.
Group: The group in the side rail or side kick where the component appears. Enter Adobe. (The developers component is located under the Adobe heading in the Touch UI side rail. It also appears in Adobe in the classic view sidekick.)
Allowed parents: Enter */*parsys.

3. Click Ok.
Add a dialog to Custom Nested Multi-Field AEM component
A dialog lets an author click on the component in the Touch UI (or Classic UI) view.

To create the dialog, perform these tasks:
Select /apps/nested_multifield/components/developer-profile-setup and select Create, Create Dialog.
In the Title field, enter the column.
Click Ok.
Delete all nodes under /apps/nested_multifield/components/developer-profile-setup/dialog.

Create the Dialog tab
Follow the below steps to create a dialog tab:
1. Click on the following node: /apps/nested_multifield/components/developer-profile-setup/dialog.
2. Right-click and select Create, Create Node
3. Enter the following values:
Name: items
Type: cq:Widget



4. Add the following property:
xtype (String) – tabpanel




5. Select the /apps/nested_multifield/components/developer-profile-setup/dialog/items node.
6. Right-click and select Create, Create Node.
7. Enter the following values:
Name: items
Type: cq:WidgetCollection




8. Select the /apps/nested_multifield/components/developer-profile-setup/dialog/items/items node.
9. Right-click and select Create, Create Node.
10. Enter the following values:
Name: developer
Type: cq:Widget
11. Add the following properties:
title (String) -Developer (a title that appears on the tab)
xtype (String) – panel (defines the data type of this field.)



12. Click on the following node:/apps/nested_multifield/components/developer-profile-setup/dialog/items/items/developer.
13. Right-click and select Create, Create Node.
14. Enter the following values:
Name: items
Type: cq:WidgetCollection

15. Select the Select the /apps/nested_multifield/components/developer-profile-setup/dialog/items/items/developer/items.
16. Right click and select Create, Create Node.

17. Enter the following values:
Name: developernode
Type: cq:Widget

18. Add the following properties:
fieldLabel (String) – Developer’s Data
name (String) – ./devdata
xtype (String) – multifield

19 . Select the /apps/nested_multifield/components/developer-profile-setup/dialog/items/items/developer/items/developernode.

20. Right-click and select Create, Create Node.

21. Enter the following values:
Name: fieldConfig
Type: cq:Widget

22. Add the following properties:
xtype (String) – devprofile



Add the JS file to the ClientLibs folder after performing below steps:
Right-click /apps/nested_multifield/components then select New, Node.
Make sure that the node type is cq:ClientLibraryFolder and name the node clientlibs.
Right-click on clientlibs and select Properties. Add the categories property to this node. Specify simplycracked.custom.widget.devprofile and ensure the type is String[].
Create a file named dev-profile.js to the clientlibs node. Add the code shown in this section to the file.
Add a TXT file to the clientlibs node named js.txt. Add dev-profile.js to this text file.

Adding JavaScript file defines a custom xtype to a cq:ClientLibraryFolder node
dev-profile.js file

try {
if (typeof SimplyCracked == 'undefined') {
SimplyCracked = {}; // creating namespace
}
SimplyCracked.SitemapDatacollection = CQ.Ext.extend(CQ.form.CompositeField, {
/**
* @private
* @type CQ.Ext.form.TextField
*/
hiddenField: null,
/**
* @private
* @type CQ.Ext.form.PathField
*/
developerName: null,
/**
* @private
* @type CQ.Ext.form.PathField
*/
developerDesc: null,
/**
* @private
* @type CQ.Ext.form.MultiField
*/
replaceMulti: null,
/**
* @private
* @type CQ.Ext.form.CheckBox
*/
lastMod: null,
/**
* @private
* @type CQ.Ext.form.ComboBox
*/
changeFreq: null,
/**
* @private
* @type CQ.Ext.form.ComboBox
*/
priority: null,
/**
* @private
* @type CQ.Ext.form.MultiField
*/
skillSet: null,
/**
* @private
* @type CQ.Ext.form.CheckBox
*/
temporaryDisable: null,
constructor: function(config) {
config = config || {};
var defaults = {
"border": true,
"padding": 10,
"style": "padding:10px 0 0 5px;",
"layout": "form",
"labelWidth": 200
};
config = CQ.Util.applyDefaults(config, defaults);
SimplyCracked.SitemapDatacollection.superclass.constructor
.call(this, config);
},
// overriding CQ.Ext.Component#initComponent
initComponent: function() {
SimplyCracked.SitemapDatacollection.superclass.initComponent
.call(this);
// Hidden field
this.hiddenField = new CQ.Ext.form.Hidden({
name: this.name
});
this.add(this.hiddenField);
this.developerName = new CQ.Ext.form.TextField({
fieldLabel: "Developer's Name",
allowBlank: false,
width: 400,
listeners: {
change: {
scope: this,
fn: this.updateHidden
},
dialogclose: {
scope: this,
fn: this.updateHidden
}
}
});
this.add(this.developerName);
this.developerDesc = new CQ.Ext.form.TextArea({
fieldLabel: "About Developer",
fieldDescription: "Provide a detail description about developer",
allowBlank: false,
width: 400,
listeners: {
change: {
scope: this,
fn: this.updateHidden
},
dialogclose: {
scope: this,
fn: this.updateHidden
},
}
});
this.add(this.developerDesc);
//define the inner multifield
this.skillSet = new CQ.form.MultiField({
fieldLabel: "Add Skills",
fieldDescription: "Click '+' to add your Skills",
width: 400,
fieldConfig: {
"xtype": "textfield",
allowBlank: false,
},
listeners: {
change: {
scope: this,
fn: this.updateHidden
}
}
});
this.add(this.skillSet);
},
// overriding CQ.form.CompositeField#setValue
setValue: function(value) {
var readVal = '';
var storeVal = '';
var skillSetValues = '';
if (value) {
var colValue = value.split('|');
if (colValue.length > 0) {
readVal = colValue[0];
storeVal = colValue[1];
skillSetValues = colValue[2];
}
}
this.developerName.setValue(readVal);
this.developerDesc.setValue(storeVal);
this.skillSet.setValue(skillSetValues.split(','));
},
// overriding CQ.form.CompositeField#getValue
getValue: function() {
return this.getRawValue();
},
getRawValue: function() {
//var temporaryDisableVal = this.temporaryDisable.getValue() || "";
var readVal = this.developerName.getValue() || "";
var storeVal = this.developerDesc.getValue() || "";
//var replaceMultiValues = this.replaceMulti.getValue() || "";
// var lastModVal = this.lastMod.getValue() || "";
//var changeFreqVal = this.changeFreq.getValue() || "";
//var priorityVal = this.priority.getValue() || "";
var skillSetValues = this.skillSet.getValue() || "";
// if (temporaryDisableVal == '')
// temporaryDisableVal = " ";
var value = readVal + "|" + storeVal + "|" +
skillSetValues;
this.hiddenField.setValue(value);
return value;
},
updateHidden: function() {
this.hiddenField.setValue(this.getValue());
},
destroyRichText: function() {
this.el.dom = {};
}
});
CQ.Ext.reg('devprofile', SimplyCracked.SitemapDatacollection);
} catch (e) {
// suppressing error.
// error occurs for CQ.form.CompositeField in mobile devices.
}


Update developer-profile-setup JSP file

The developer-profile-setup.jsp is the main JSP file for the component and is located at:

/apps/nested_multifield/components/developer-profile-setup/developer-profile-setup.jsp

<jsp:directive.include file="/libs/foundation/global.jsp" />
<cq:includeClientLib categories="simplycracked.custom.widget.devprofile" />
AEM Custom Nested Multifieid Component
<%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0" %>
<%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<div id="wrapper" style="height:auto;border:1px solid grey">
Developer's Data
<c:forEach var="items" items="${properties.devdata}" varStatus="status">
<c:set var="listItem" value="${fn:split(items,'|')}" />
<c:set var="developerName" value="${fn:trim(listItem[0])}" />
<c:set var="about" value="${fn:trim(listItem[1])}" />
<c:set var="skills" value="${fn:trim(listItem[2])}" />

<div class="dev-profile" style="background-color:#E0F8F7;height:auto;border:1px solid grey;margin-top:15px">
Name : ${developerName} About : ${about} Skills : ${skills}
</div>

</c:forEach>
</div>

Creating EditConfig node

Perform below steps to define the behavior of our nested multi-filed cq dialog widget or AEM Component:-

Perform the following tasks:
1. Select /apps/nested_multifield/components/developer-profile-setup.
2. Right-click and select Create, Create Node
3. Enter the following values:
Name: cq:editConfig
Type: cq:EditConfig

4. Add the following property:
cq:actions (String[]) – EDITANNOTATECOPYMOVEDELETEINSERT
cq:dialogMode (String) – floating
5. Select /apps/nested_multifield/components/developer-profile-setup/cq:editConfig.
6. Right-click and select Create, Create Node.
7. Enter the following values:
Name: cq:listeners
Type: cq:EditListenersConfig
8. Add the following property:
afteredit (String) – REFRESH_PAGE

Create a CQ web page that uses the nested multi-field component
Follow the below steps to create an AEM page that displays the component.
Go to the CQ Websites page at http://localhost:4502/siteadmin#/content.
Select New Page.
Specify the title of the page in the Title field. Enter NestedApp.
Specify the name of the page in the Name field.
Select multifieldTemplate from the template list that appears. This value represents the template that is created in this development article. If you do not see it, then repeat the steps in this development article. For example, if you made a typing mistake when entering in path information, the template will not show up in the New Page dialog box.
Open the page by clicking the NestedApp page.
The component will be under the Adobe heading in the sidekick. Drag this component onto the AEM page.
Open the dialog and enter values into the dialog fields.

Note: If the CQ sidekick is empty, you can populate it by clicking the Design button located at the bottom of the sidekick.


By aem4beginner

April 14, 2020
Estimated Post Reading Time ~

Creating Adobe Experience Manager Components that use Nested Multifields

You can develop a custom Adobe Experience Manager (AEM) component that uses a nested multi-field control located in a dialog. A nested multi-field control is an inner multi-field control within an outer multi-field control and lets an author dynamically enter data. For example, assume the AEM control lists developers and each developer has an unknown number of skills to display. That is, within the inner multi-field, the author enter details such as professional skill set. The outer multi-field determines how may developers to display.

Consider the nested multi-field control located in the following illustration.

This article steps you through how to build this AEM component. 



By aem4beginner

April 9, 2020
Estimated Post Reading Time ~

Using the ACS AEM Commons Nested Multifield

Creating dialogs in Adobe Experience Manager, or AEM is key to granting content authors the ability to create dynamic, fully-featured sites within a CMS framework. With AEM’s move to the Touch UI, authors now have a more modern and robust environment to create content.

Developers are able to tap into the power of the Touch UI to construct more powerful and dynamic functionality in order to enhance the authoring experience. Despite the expanded feature set, AEM’s new UI still lacks a handful of decidedly useful features.

One of the biggest sources of frustration that developers face is the multifield resource type. Out of the box, this Granite resource type is only able to contain a single field. In order to add multiple fields, a developer would need to create multiple multifields, each containing a single field, then write a bunch of logic to keep each of those in sync. Quickly, that will become a development nightmare, and an even bigger nightmare to support or enhance later on.

Thankfully, AEM’s open source community has come to the rescue. One of the additions in ACS AEM Commons provides for a nested multifield that allows developers to create a multifield of a fieldset. The rest of this post will go into how to configure a dialog to utilize the acs-commons-nested property and read in the JSON value saved to the JCR.
This guide was written using AEM 6.1 with Service Pack 1 installed and has ACS-Commons version 2.2.4 installed.

It is Just a Property
Adding this functionality to a dialog couldn’t be simpler. All you need to do is add the property acs-commons-nested to a fieldset within a multifield. Let’s look at the snippet below. 

<example-multifield 
 jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/multifield" fieldLabel="Example Multifield with Long Label"> 
 <field jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/fieldset" acs-commons-nested="" name="./example"> 
 <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns" method="absolute"/> <items jcr:primaryType="nt:unstructured"> 
 <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> 
 <items jcr:primaryType="nt:unstructured"> 
<examplePath jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/pathbrowser" fieldLabel="Example Path" name="./examplePath"/> 
<exampleText jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textfield" fieldLabel="Example Text" name="./exampleText"/> 
 </items> 
 </column> 
 </items> 
 </field> 
</example-multifield>

First, we have the example-multifield node with its sling:resourceTypeset to be the standard Granite multifield. Nothing unusual so far. That node then contains the field node, which has its sling:resourceType set to be Granite’s fieldset. Contained within this node is the property called acs-commons-nested=””, which is what ACS Commons is looking to enable the multi-multifield functionality.
From there, the rest is just your standard container of fields. In this example, I’ve added a path browser and a textfield.

The multifield on the dialog ends up functioning just like the standard AEM version, with the Add field button, along with the reordering and delete controls.

Make it Look Better
While the previous section did result in a functional dialog, the usability of the various form fields is quite low. Thankfully, this can be easily resolved by adding a couple of classes in three different locations in your XML.

The classes we will add are foundation-layout-util-maximized-alt and long-label. They will extend the length of the fieldLabel properties when they render in the dialog, as well as place the fieldLabel above the form fields.
Below is what the nodes look like in the XML with classes added to them: 

<example-multifield 
 class="foundation-layout-util-maximized-alt long-label" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/multifield" fieldLabel="Example Multifield with Long Label"> 

 <examplePath 
 class="foundation-layout-util-maximized-alt long-label" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/pathbrowser" fieldLabel="Example Path" 
 name="./examplePath"/> 

 <exampleText 
 class="foundation-layout-util-maximized-alt long-label" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textfield" fieldLabel="Example Text" 
 name="./exampleText"/>

And this is the much more author-friendly dialog that will now display:

SaveAs JSONArray
When an author fills out the new nested multifield dialog, the data entered ends up being saved into the JCR as a String array, with the value of each array being a JSON object. If you were to view the example.infinity.json page for this node, you would see the following JSON returned: 

{ "example": [ "{\"examplePath\":\"/content/dam/geometrixx\",\"exampleText\":\"Example Text Field Text\"}", "{\"examplePath\":\"/content/geometrixx/en\",\"exampleText\":\"Second Example Text Field\"}" ] }

And if you were to look at it in CRX/DE, the values look like the following:

Read In the JSON
I feel this guide wouldn’t be complete if I didn’t include how to actually read in the JSON from the JCR. This example is going to do this all within the JSP so that you can have a self contained, a fully functional component that you can use in the Geometrixx demo site. In practice, however, I have used custom JSP Tags that utilize reusable utility classes to handle the retrieval of the node’s properties and create JSONObject or JSONArrayobjects.
First, we will need a simple POJO to hold our data. 

class ExamplePojo { 
 private String examplePath; 
 private String exampleText; 
 public ExamplePojo(String examplePath, String exampleText) { 
 this.examplePath = examplePath; 
 this.exampleText = exampleText; 
 } 
 public String getExamplePath() { 
 return this.examplePath; 
 } 
 public void setExamplePath(String examplePath) { 
 this.examplePath = examplePath; 
 } 
 public String getExampleText() { 
 return this.exampleText; 
 } 
 public void setExampleText(String exampleText) { 
 this.exampleText = exampleText; 
 } 
}

Next would be to read in the property from the component’s node and convert that into a JSONObject. From there, we can use the key/value pairs to create an ExamplePojo object, and then add that to an ArrayList. It should be noted that there is not a way to deserialize the JSONObjectdirectly into your POJO, which is why ExamplePojo’s constructor is used to create the object. 

Value[] values = new Value[]{}; 
PropertyIterator propItr = resource.adaptTo(Node.class).getProperties("example"); if (propItr.hasNext()) { 
 Property prop = propItr.nextProperty(); 
 if (prop.isMultiple()) { 
 values = prop.getValues(); 
 } else {
 values = (new Value[]{prop.getValue()}); 
 } 

List<ExamplePojo> exampleList = new ArrayList<ExamplePojo>(); 
for (Value value : values) { 
 JSONObject jsonObj = new JSONObject(
 new JSONTokener(value.getString())); 
 ExamplePojo pojo = new ExamplePojo( 
 jsonObj.getString("examplePath"), 
 jsonObj.getString("exampleText")); 
 exampleList.add(pojo); }

You can drop this component onto any Geometrixx page after it is added to the dropzone via Design Mode.

Source: https://www.bounteous.com/insights/2016/09/13/using-acs-aem-commons-nested-multifield/?ns=h


By aem4beginner

April 7, 2020
Estimated Post Reading Time ~

Creating Adobe Experience Manager Components that use Nested Multifields

You can develop a custom Adobe Experience Manager (AEM) component that uses a nested multi-field control located in a dialog. A nested multi-field control is an inner multi-field control within an outer multi-field control and lets an author dynamically enter data. For example, assume the AEM control lists developers and each developer has an unknown number of skills to display. That is, within the inner multi-field, the author enters details such as a professional skill set. The outer multi-field determines how may developers to display.

adobe community: https://helpx.adobe.com/experience-manager/using/nested_multifield.html


By aem4beginner