Defining choicelist initializers

  Written by The Jahia Team
   Estimated reading time:

ChoiceList initializers allow you to extend the way a dropdown list (or combobox) is populated for end users when they edit or create content. In your definitions.cnd file, you specify that a property must use a choicelist (rendered as a dropdown list) to specify its value.

This simple example uses choicelist for the selector and lists the allowed values in the constraint of the definition.

- contract (string, choicelist) < contract1, contract2, contract3, contract4

This will render a dropdown list like this.

simple-content-list-initializer.png

This is nice, but maybe your end users want more help with choosing values. This example shows you how to use a resource bundle to modify the rendering of your dropdown list.

- contract (string, choicelist[resourceBundle]) < contract1, contract2, contract3, contract4

This will render a dropdown list like this.

choicelist-advanced-initializer.png

How choicelist initializers work

ChoiceList Initializers are implementations of the ChoiceListInitializer interface. All implementations are managed by the ChoiceListInitializerService. This service is defined in the applicationcontext-jcr.xml file.

<bean id="rbInitializer" class="org.jahia.services.content.nodetypes.initializers.ResourceBundleChoiceListInitializerImpl"/>
<bean id="choiceListInitializers" class="org.jahia.services.content.nodetypes.initializers.ChoiceListInitializerService" factory-method="getInstance">
    <property name="initializers">
        <map>
            <entry key="resourceBundle" value-ref="rbInitializer"/>
        </map>
    </property>
</bean>

When initializing the ChoiceListInitializerService, Jahia passes a map of initializers to use in the system. Each initializer is associated with a keyword. Here the ResourceBundleChoiceListInitializerImpl is attached to the resourceBundle  keyword. When writing your definition, you can chain your initializers to make complex stuff. Each initializer will receive the list of values from its predecessors. This way you can have one initializer that fills the values, and others in the pipe that change some of those values or add properties to them. Here an example for the Templates choicelist in the layout panel.

[jmix:renderable] > jmix:layout, jmix:contentMixin mixin
- j:view (string, choicelist[templates,resourceBundle,image])

This means that to display this dropdown list, Jahia first calls the templates initializer responsible for filling the available values. Then, this list of values is passed to the resourceBundle initializer which will try to replace the labels displayed in the dropdown list with the ones found in the resource bundles, if available. Then, the updated values go to the image initializer that adds an image to be associated with each value.

Developing your own choicelist initializers

You can develop your own initializers. Custom initializers must implement the ModuleChoiceListInitializer interface.

public class MyCustomInitializer implements ModuleChoiceListInitializer {

Then, in your module's spring configuration file, declare a bean for this initializer:

<bean id="dogChoiceList" class="com.mycompany.initializers.MyCustomInitializer" >
    <property name="key" value="customChoiceList"/>
</bean>

As a ModuleChoiceListInitializer, this initializer is automatically detected by the system, and registered in the ChoiceListInitializerService, with the key specified in the spring file. You can then use this new initializer in your definitions.cnd file.

Rendering choicelists in Content Editor

When a user creates or updates a content using the Jahia UI, they will see a popup with data to enter for each field of the current definition. If a field is associated with a choicelist selector, Jahia will go through the pipe of initializers, if defined. For the rendering Jahia uses the displayName property of the ChoiceListValue object that is received from the pipe. Each option value is associated with the javax.jcr.Value of the same object. 

Currently, only the displayName property of ChoiceListValue is supported by choicelists. The ChoiceListValue property called image is not supported, which was previoulsy used by GWT engines to display an image in front of the option name.

Countries initializer example

This example shows you how to create a complex initializer to allow users to choose a country from a dropdown list and display a flag, if available, in front of the country name. The example uses two classes, one for filling the countries in the list of values available for the dropdown list, and another to add the flag if found for each country.

The code of the country initializer :

public class CountryChoiceListInitializerImpl implements ChoiceListInitializer {
    public List<ChoiceListValue> getChoiceListValues(ProcessingContext jParams, ExtendedPropertyDefinition declaringPropertyDefinition,
                                                     String param, String realNodeType, List<ChoiceListValue> values) {
        String[] iso = Locale.getISOCountries();
        List<ChoiceListValue> l = new ArrayList<ChoiceListValue>(iso.length);
        for (String anIso : iso) {
            l.add(new ChoiceListValue(new Locale("en", anIso).getDisplayCountry(jParams.getLocale()), null,
                    new ValueImpl(anIso, PropertyType.STRING, false)));
        }
        Collections.sort(l, new Comparator<ChoiceListValue>() {
            public int compare(ChoiceListValue o1, ChoiceListValue o2) {
                return o1.getDisplayName().compareTo(o2.getDisplayName());
            }
        });
        return l;
    }
}

You see that in this example when we create the ChoiceListValue we do not associated any properties, but only a display name in current locale of the user, and a value that is not sensitive to the locale the ISO code of the country. We sort the list based on the displayName (this could totally be done in another initializer in the pipe, for example a sort initializer at the end of the pipe).

This initializer is of no use if placed at the start of the pipe as it will return an empty list.

This initializer need to be declared in the ChoiceListInitializerService .

<bean id="rbInitializer" class="org.jahia.services.content.nodetypes.initializers.ResourceBundleChoiceListInitializerImpl"/>
<bean id="countryInitializerRenderer" class="org.jahia.services.content.nodetypes.initializers.CountryChoiceListInitializerImpl"/>
<bean id="choiceListInitializers" class="org.jahia.services.content.nodetypes.initializers.ChoiceListInitializerService" factory-method="getInstance">
    <property name="initializers">
        <map>
            <entry key="resourceBundle" value-ref="rbInitializer"/>
            <entry key="country" value-ref="countryInitializerRenderer"/>
        </map>
    </property>
</bean>

Other Initializers

You can define as many initializers as you want. By default, Jahia provides you with very flexible initializers:

  • Node initializer
  • Script initializer

Node initializer

The node initializer allows you to bind a dropdown list to the content of a node in the JCR, by defining the root path of this dropdown list and the type of child nodes you want to list.

- firstLevelCategory (weakreference,choicelist[nodes='/sites/systemsite/categories;jnt:category'])

This will create a dropdown listing all the jnt:category elements from the /categories folder.

- j:theme (weakreference,choicelist[nodes='$currentSite/files/themes;jnt:folder'])

This will create a dropdown listing all the subfolders elements of the folder themes in the current site.

Script initializer

This is the most versatile initializer provided by Jahia as it allows you to use any JSR-223 compatible script language to write your initializer. Declaring a scripted initializer is as simple as this.

- type (string,choicelist[script=type.groovy])

The initializer will look for the script in the module containing the definition in the scripts folder.

.
|-jnt_lastNews
|---html
|-jnt_news
|---html
|-resources
|-scripts
|-WEB-INF

The script extension defined the script type. For example, if your script has the name type.groovy, the script engine manager tries to find a declared script engine for the Groovy extension .

What the things must your script absolutely do? The script must return a List of ChoiceListValue.

A potential usage is to define script that interacts with external system to fill the values, like XML files, databases, and REST service. This example shows Groovy script parsing of an XML file to fill the values:

class RecordsHandler extends DefaultHandler {
    def values

    RecordsHandler(List values) {
        this.values = values
    }

    void startElement(String ns, String localName, String qName, Attributes atts) {
        switch (qName) {
            case 'books':
                String labelName = atts.getValue("title")
                values.add(new ChoiceListValue(labelName,null,new ValueImpl(labelName,PropertyType.STRING,false))); break

        }
    }
}

def newValues = new ArrayList<ChoiceListValue>();
def handler = new RecordsHandler(values)
def reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader()
reader.setContentHandler(handler)
reader.parse(new InputSource(new FileInputStream(file)))
return newValues

Each script can also access to the list of values from its predecessors as it is bound to the values variable.