Introduction

In version 4.1, a new feature has been introduced in the priint:comet ID desktop plug-ins, which dramatically simplifies layouting Mutliframe placeholders:
A new panel was implemented called Area build, which is described in the print:comet plug-ins online documentation. Combined with another feature named Function Variables resp. Generic Placeholders, working with repeating elements has become a very attractive option to arrange sub entities of any data in a docment layout.
To join the loop, support for the Element data type, which is used for Repeating Elements, has been added to the priint:publishing server and CometBridge, also new datatypes for elements have been introduced in cscript, so that Element data can easily be retrieved from Java methods just like any other type of data.

This documentation adresses Comet project configurators and Java developers as well. The concept is described by two common use cases in section Examples, implementation of these use cases is explained in section Example Implementation.

We focus on priint:publishing server Server / Java development aspects. Usage of the Area Build panel is described in detail in the priint:comet ID plug-ins online documentation.

Requirements

Generic Placeholders are supported from priint:publishing server and priint:comet plug-in version 4.1. Please use the latest available release; while basic functionality should work with any 4.1 version, the full functionality as described in this documentation requires

Examples 

The following examples use standard DataMapping and DataProcessing methods delivered with priint:publishing server.
We assume, that an entity model and Comet project already exist, also some example data would be nice. In that regard, the output on your system probably will differ from the result shown in the examples.
The configuration of all other resources (DataProvider, placeholder, templates, Java methods) is described in the section Example implementation, so at least it should be possible to achieve equivalent results in your environment.

Repeating Elements based on DataProvider 

In this example, we use a Multiframe placeholder to build sub elements. We add a DataProcessing method to the placeholder configuration, which allows to choose the templates for sub elements via a function variable. This DataProcessing method is included in the standard delivery, see Repeating Elements based on DataProvider Implementation for details.

The placeholder defines two function variable:

Usage of Repeating Elements based on DataProvider

We create a frame and tag it as a Multiframe placeholder (configuration of this Multiframe placeholder see Repeating Elements based on DataProvider Implementation).
In the Place Holder Values panel, we can see the list of Function Variables, which can be configured for this placeholder (the frame must be selected). By clicking the lock item at the top of the list, values can be edited - either by selecting an entry of the DropDown menu in the Value column or by typing a value in the Custom value column (when clicking in the cell, a text input field should appear).

Load result with template=Product Label Green

We set template to Product Label Green, link the frame with a product and execute Repeating elements > Create sub elements of selected products of the Product Pool panel menu while the frame is selected.
Note: we leave resultEntityId empty, thus we can use this frame for any entity type. The DataProvider of the example configuration just loads all direct child buckets of a given bucket.

Load result with template=Product Label Yellow

We change template to Product Label Yellow, clear the sub elements created before and run Repeating elements > Create sub elements of selected products of the Product Pool panel menu again:

Repeating Elements based on CScript 

Repeating elements data can also be assembled in cscript. This allows to access document properties (e.g. the current page indes, is there enough space available on the current page, how many products have already been inserted etc.) or to add special data processing to the element list, which is not available in Java.

In addition to the example above, we need

Usage in InDesign is virtualy the same like in the previous example. After changing the placeholder definition in ISON (relevant code in the Implementation section below), we reset the script cache (use button / action in Placeholder Values panel) or relogin to the priint:publishing server.

We should make the target frame somewhat bigger, because we expect more results.

After linking the frame with the some node (in our example the "Hammer, axes ..." category), we see the following result:

Example Implementation 

Repeating Elements based on DataProvider Implementation 

This placeholders requires

Repeating Elements based on DataProvider for Project Configurators

  1. In the ISON DataProvider Explorer, create a new DataProvider
  2. Use Label="Child Buckets As Elements", Identifier="childbucketsaselements" and Entity class="Bucket".
  3. In the DataProvider editor, click the "Choose" button to choose a DataQuery method. Select "getEntityChildBuckets" from the EntityManager Plugin.
  4. Change to the DataMapping tab and click the "Choose" button to choose a DataMapping method. Select "bucketsToElements" from the DataMappingToElement plug-in.
  5. Save and checkin the DataProvider
  6. In the ISON CometExplorer, create a new Placeholder.
  7. Set Name="Child Buckets" and Type=MULTIFRAMES (also choose an appropriate description and category):
  8. In the Placeholder editor, click the "Choose" button next to the DataProvider field and select the "Child Buckets As Elements" DataProvider we just created. As you like, you can also set the Result Entity Id, if you want to load buckets of a certain entity only.
  9. Change to the DataProcessing Load tab, right-click in the function list and select "Add" to add a DataProcessing method.
  10. In the dialog, select the "setTemplate" method from the CometElementFunctions plug-in and click "Ok".
  11. Save and checkin the placeholder.

There is no more configuration required on the ISON side. Once you relogin from InDesign (or click the "Search" button in the InDesign Placeholder panel while pressing down the ALT key), you should find Child Buckets in the list of placeholders.
Before you can use this placeholder, you should also create appropriate templates for the sub elements in InDesign. After creating these templates, you have to reload the Placeholder panel again (click "Search" button while pressing down the ALT key); after that you can use this placeholder like shown in Repeating Elements based on DataProvider example above.

Repeating Elements based on DataProvider for Java Developers

The Java implementation of the Template selector example consists of two parts:

  1. DataProcessing method with appropriate parameter annotations
  2. ValueList method, which provides the list of templates
(both methods are part of the standard delivery, but nevertheless described below).

DataProcessing method setTemplate

Remarks:

  1. by convention, the result list of previous methods in the processing chain is the first parameter.
  2. optional name attribute, this has no further effect on the Function Variable behaviour.
  3. this value will be used as default when adding the method to a configuration (DataProvider or Placeholder). If the parameter should be exposed as a Function Variable, the default value must start with the <Variable. prefix. The identifier of the Function Variable is derived from this value, see PubServerMethodParameter Annotation for details.
  4. source for the value list is a Plugin method. In this case, the attributes pluginMappedName and methodName must also be provided.
package com.priint.pubserver.comet.bridge.dataprocessing;

import java.util.List;
import javax.ejb.Stateless;
import com.priint.pubserver.comet.bridge.entitydata.Element;
import com.priint.pubserver.comet.bridge.value.CometValueDefinitionsLocal;
import com.priint.pubserver.parameterparser.ParameterTags;
import com.priint.pubserver.plugin.PluginControlDefault;
import com.priint.pubserver.plugin.PluginMethod;
import com.priint.pubserver.plugin.annotation.PubServerMethod;
import com.priint.pubserver.plugin.annotation.PubServerMethodParameter;
import com.priint.pubserver.plugin.annotation.PubServerPlugin;
import com.priint.pubserver.plugin.annotation.Value;
import com.priint.pubserver.tracing.Tracer;
import com.priint.pubserver.tracing.TracerFactory;

@Stateless
@PubServerPlugin
public class CometElementFunctions extends PluginControlDefault {

  private static final Tracer TRACER = TracerFactory.getTracer(CometElementFunctions.class);

  @PubServerMethod(
      type = PluginMethod.MethodType.DATA_PROCESSING, 
      description="Applies a template Id to a list of elements.")
  public List<Element> setTemplate(
      @PubServerMethodParameter(
          name="inputList", 
          defaultValue=ParameterTags.Tag.ENTITY_RESULTLIST, 
          description="List of elements") List<Element> inputList,        // Remark (1)
      @PubServerMethodParameter(
          name="templateId",                                              // Remark (2)
          defaultValue="<Variable.TemplateId>",                           // Remark (3)
          description="Id of the template",
          value=@Value(
            type=Value.Type.PLUGIN,                                       // Remark (4)
            label="Template",
            pluginMappedName=CometValueDefinitionsLocal.MAPPED_NAME,
            methodName=CometValueDefinitionsLocal.getTemplates
              )) int templateId) {
    TRACER.info(this.getClass().getSimpleName(), "", 
                     "inputList={}, templateId={}", inputList, templateId);
    for (Element e : inputList) {
      e.setTemplateId(templateId);
    }
    return inputList;
  }
}
    

ValueList provider method getTemplates

Remarks:

  1. type for ValueList provider methods must be PluginMethod.MethodType.VALUE_LIST
  2. by convention, the signature of a ValueList provider method must be
    public List<ValueItem> methodName(Map<String,Object> parameters)
    The method is allowed to throw any PubServerException (which will be handled when processing this method).

package com.priint.pubserver.comet.bridge.value;

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

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

import com.priint.pubserver.comet3.entity.PageItem;
import com.priint.pubserver.exception.PubServerException;
import com.priint.pubserver.plugin.PluginControlDefault;
import com.priint.pubserver.plugin.PluginMethod;
import com.priint.pubserver.plugin.ValueItem;
import com.priint.pubserver.plugin.annotation.PubServerMethod;
import com.priint.pubserver.plugin.annotation.PubServerMethodParameter;
import com.priint.pubserver.plugin.annotation.PubServerPlugin;
import com.priint.pubserver.util.ValueUtils;

@Stateless
@PubServerPlugin
public class CometValueDefinitions extends PluginControlDefault {

  @PubServerMethod(type=PluginMethod.MethodType.VALUE_LIST)                        // Remark (1)
  public List<ValueItem> getTemplates(
      @PubServerMethodParameter(name="parameters") Map<String,Object> parameters)  // Remark (2)
      throws PubServerException {
    List<ValueItem> result = new ArrayList<ValueItem>();
    Map<String,PageItem> pageitems = this.getPageItemHash();
    
    for (PageItem it : pageitems.values()) {
      result.add(new ValueItem(it.getId() + "", it.getName()));
    }
    ValueUtils.sortAscending(result);
    return result;
  }

}

Repeating Elements based on CScript Implementation 

In addition to the configuration above, we have to

Java Method Implementation

The Java method implementation is pretty straightforward, the listing below shows the full priint:publishing server plug-in class.

The relevant method is collectSubBuckets, to expose this method to cscript, we add the @CometCScriptFunction annotation.

package com.priint.pubserver.comet.cscript;

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

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.priint.pubserver.comet.bridge.CometCScriptFunction;
import com.priint.pubserver.comet.bridge.datamapping.DataMappingToElementLocal;
import com.priint.pubserver.comet.bridge.dataprovider.ProductDataProviderLocal;
import com.priint.pubserver.comet.bridge.entitydata.Element;
import com.priint.pubserver.comet.bridge.util.MappingUtils;
import com.priint.pubserver.comet.config.CometConfigurationLocal;
import com.priint.pubserver.comet.entity.CometProject;
import com.priint.pubserver.comet.exception.CometException;
import com.priint.pubserver.exception.InvalidSessionException;
import com.priint.pubserver.plugin.PluginControlDefault;
import com.priint.pubserver.plugin.PluginMethod;
import com.priint.pubserver.plugin.PluginUtils;
import com.priint.pubserver.plugin.annotation.PubServerMethod;
import com.priint.pubserver.plugin.annotation.PubServerMethodParameter;
import com.priint.pubserver.plugin.annotation.PubServerPlugin;
import com.priint.pubserver.plugin.entitydata.Bucket;
import com.priint.pubserver.plugin.entitydata.Context;
import com.priint.pubserver.plugin.exception.EntityManagerException;
import com.priint.pubserver.plugin.interfaces.EntityManagerLocal;
import com.priint.pubserver.util.Constants;

@Stateless(mappedName=Elements.MAPPED_NAME)
@LocalBean
@PubServerPlugin
public class Elements extends PluginControlDefault {
  
  public static final String MAPPED_NAME = "com.priint.pubserver.comet.cscript.Elements";
  
  private static final Logger LOGGER = LoggerFactory.getLogger(Elements.class);
  
  private InitialContext initialContext = null;
  
  private EntityManagerLocal getEntityManager (String sessionId) throws CometException {
    return PluginUtils.getPlugin(Constants.MANAGER_ENTITY, sessionId, EntityManagerLocal.class);
  }
  
  private DataMappingToElementLocal getElementDataMapping() {
    try {
      if (initialContext == null) {
        initialContext = new InitialContext();
      }
      return (DataMappingToElementLocal)initialContext.lookup(DataMappingToElementLocal.JNDINAME);
    } catch (NamingException e) {
      LOGGER.error(
         "FATAL ERROR: Failed to lookup Product Data Mapping, reason='" + e.getMessage() + "'", e);
      initialContext = null;
    }
    return null;    
  }
    
    private CometConfigurationLocal getCometConfiguration() {
      try {
        if (initialContext == null) { 
            initialContext = new InitialContext();
        }
        return (CometConfigurationLocal)this.initialContext.lookup(CometConfigurationLocal.JNDINAME);
      } catch (NamingException e) {
        LOGGER.error(
             "FATAL ERROR: Failed to lookup CometConfiguration, reason='" + e.getMessage() + "'", e);
        initialContext = null;
      }
      return null;
    }
    
  private List<Bucket> getSubBuckets(
                     String parentStringId,
                     String sessionId, 
                     String entityModelName, 
                     EntityManagerLocal em) throws CometException {
    List<Bucket> result = new ArrayList<Bucket>();
    MappingUtils.PubServerId parentId = MappingUtils.getPubServerId(parentStringId);
    
    String searchString = "";
    Context context = new Context();
    
    List<Bucket> children = null;
    try {
      children = em.getEntityChildBuckets(sessionId, entityModelName, 
                                      parentId.getEntityId(), parentId.getRecordId(),
                                      "", context.copy(), searchString);
    } catch (EntityManagerException e) {
      throw new CometException(this, "Failed to load data, exception when calling EntityManager", e);
    }
    
    if (children != null) {
      for (Bucket child : children) {
        result.add(child);
        result.addAll(this.getSubBuckets(
                                MappingUtils.createBucketId(child, null),
                                sessionId, 
                                entityModelName,
                                em)
                     );
      }
    }
    return result;
  }
  
  /**
   * <p>Recursively collects all sub buckets for a given bucket</p>
   * 
   * @param bucketId id of the parent bucket
   * @return all sub buckets (recursively) as flat Element list
   * @throws CometException
   */
  @PubServerMethod(type=PluginMethod.MethodType.GENERIC)
  @CometCScriptFunction
  public List<Element> collectSubBuckets(
      @PubServerMethodParameter(name="parentStringId") String parentStringId) 
       throws CometException {  
    try {
      String sessionId = PluginControlDefault.getSessionId();
      CometConfigurationLocal configuration = this.getCometConfiguration();
      
      CometProject project   = configuration.getSessionProject();
      String entityModelName   = project.getEntityModel();
          
      EntityManagerLocal em = this.getEntityManager(sessionId);
      if (em == null) {
        throw new CometException(
                        this,
                        "Failed to load data, could not get an instance of EntityManager");
      }
      List<Bucket> childBuckets = this.getSubBuckets(parentStringId, sessionId, entityModelName, em);
      return this.getElementDataMapping().bucketsToElements(
                                childBuckets,
                                (Bucket)null, 
                                entityModelName, 
                                new Context());
    }
    catch (CometException e) {
      throw e;
    }
    catch (Exception e) {
      throw new CometException(this, e);
    }
  }
}
    

CScript Load Statement Implementation

In the cscript, we use a kind of forbidden feature: we refer to the function variable templateId, which is defined by a DataProcessing method in the Placeholder processing chain (remember: we use the Placeholder configuration we originally setup for the first example. This configuration includes the setTemplate DataProcessing method).
Though the DataProcessing method is bypassed when the Placeholder is loaded via a custom cscript, the definitions (including function variables) are processed when generating the load cscript as exposed to priint:comet ID plug-ins or priint:renderer.
This includes the #pragma ... directives for the templateId variable, though this is not visible in the ISON cscript editor.

Please refer to the Generic Placeholders HowTo to learn, how cscript variable names are derived from DataProcessing parameter names resp. default values.
In this case, the defaultValue for the templateId parameter is <Variable.TemplateId>, the name of the cscript variable is templateId (just omit the prefix + lowercase first character).

// include the cscript client stub code from the plug-in shown above:
#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.Elements.c"

int main () {
  // call the Java method shown above:
  ElementList elements = collectSubBuckets(gRecordStringID);
  // convert templateId function variable to int
  int templateIdAsInt = val(templateId);
  int size = elementlist::length(elements);
  int c = 0;
  Element tmpElement = 0;
  Element newElement = 0;
  char * buffer = alloc(4096);
  
  for (c = 0; c < size; ++c) {
    tmpElement = elementlist::get(elements, c);
    // assign templateId from function variable to element:
    element::set_templateid(tmpElement, templateIdAsInt);    
    sprintf (buffer, 
             "Element\n\tId: %d\n\tStringId: %s\n\ttemplateId: %d\n\tclassId: %d\n\tformat=%s\n",
             element::id(tmpElement),
             element::stringid(tmpElement),
             element::templateid(tmpElement),
             element::classid(tmpElement),
             element::formatstring(tmpElement));
    wlog("", "%s", buffer);
  }
  elementlist::add_all(gElements, elements, 1); // add all from elements to 
                                                // gElements and remove (-> 1)
                                                // items from source list
  elementlist::release(elements);
  release(buffer);
  return 0;
}

Stripped down to the relevant code (removed logging etc.), the load script looks like this:

#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.Elements.c"

int main () {
  ElementList elements = collectSubBuckets(gRecordStringID);
  int templateIdAsInt = val(templateId);
  int size = elementlist::length(elements);
  int c = 0;
  Element tmpElement = 0;
  
  for (c = 0; c < size; ++c) {
    tmpElement = elementlist::get(elements, c);
    element::set_templateid(tmpElement, templateIdAsInt);    
  }
  elementlist::add_all(gElements, elements, 1);
  elementlist::release(elements);
  return 0;
}

Standard Methods supporting Repeating Elements Variables

DataMapping Methods

Return type for all ...ToElements methods is jav.util.List<Element>, return type for the createElementFrom... methods is Element

For further information about the parameter meanings, refer to the SDK documentation

bucketsToElements(List<Bucket>, String, String, Context)

Maps Buckets to Elements.

Parameters:

bucketsToElements(List<Bucket>, Bucket, String, Context)

Maps Buckets to Elements.

Parameters:

bucketsToElements(List<Bucket>, String, Context)

Maps Buckets to Elements.

Parameters:

cordsToElements(List<Cord>, Bucket, String, Context)

Maps Cords to Elements.

Parameters:

cordsToElements(List<Cord>, String, String, Context)

Maps Cords to Elements.

Parameters:

createElementFromBucket(Bucket, EntityModel, Bucket)

create one Element from Bucket

Parameters:

createElementFromCord(Cord, EntityModel, Bucket)

create one Element from Cord

Parameters:

createElementFromKeyValue(KeyValue, EntityModel, Bucket)

create one Element from KeyValue

Parameters:

createElementFromMediaAsset(MediaAsset, EntityModel, Bucket)

create one Element from MediaAsset

Parameters:

createElementFromPlanning(Planning, Context)

create one Element from Planning

Parameters:

createElementFromPrice(Price, EntityModel, Bucket)

create one Element from Price

Parameters:

createElementFromTableData(TableData, EntityModel, Bucket)

create one Element from TableData

Parameters:

createElementFromText(Text, EntityModel, Bucket)

create one Element from Text

Parameters:

keyValuesToElements(List<KeyValue>, Bucket, String, Context)

Maps KeyValues to Elements.

Parameters:

keyValuesToElements(List<KeyValue>, String, String, Context)

Maps KeyValues to Elements.

Parameters:

mediaAssetsToElements(List<MediaAsset>, Bucket, String, Context)

Maps MediaAssets to Elements.

Parameters:

mediaAssetsToElements(List<MediaAsset>, String, String, Context)

Maps MediaAsset to Elements.

Parameters:

planningsToElements(List<Planning>, Context)

Maps Plannings to Elements.

Parameters:

planningsToElementsGeneric(List<Planning>, Context, String, String, String, String, String, String, String)

Generic mapping method for Plannings to Elements.

Parameters:

pricesToElements(List<Price>, Bucket, String, Context)

Maps Prices to Elements.

Parameters:

pricesToElements(List<Price>, String, String, Context)

Maps Price to Elements.

Parameters:

tableDataToElements(List<TableData>, Bucket, String, Context)

Maps TableData to Elements.

Parameters:

tableDataToElements(List<TableData>, String, String, Context)

Maps TableData to Elements.

Parameters:

textsToElements(List<Text>, Bucket, String, Context)

Maps Texts to Elements.

Parameters:

textsToElements(List<Text>, String, String, Context)

Maps Texts to Elements.

Parameters:

DataProcessing Methods

setTemplate(List<Element>, int)

Assigns a template to a list of Elements

Return type: Element

Parameter