Defining choicelist initializers

  Written by The Jahia Team
   Estimated reading time:

ChoiceList initializers

ChoiceList initializers allow to extends the way  a dropdown list (or combobox) is populated for end users when editing or creating content.

Jahia allows you to define in your definitions, that a property must use a choicelist (will be rendered by a dropdown list) to specify its value.

The simplest example is to use choicelist for the selector and to list the allowed values in the constraint of the definition :

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

This will give you a dropdown list like that :

simple-content-list-initializer.png

This is nice but maybe our end users will want to have something more explanatory for choosing the right values.
We will now show you how to use resource bundle to modify the rendering of your dropdown list :

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

This will give you a dropdown list like that :

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 we pass a map of initializers we want to use in the system. Each Initializer will be associated to a keyword. Here the ResourceBundleChoiceListInitializerImpl will be attached to the keyword resourceBundle. 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 fill the values, and other 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 for displaying this dropdown list we first call the templates initializer that is responsible for filling the available values.
Then this list of values is passed to the resourceBundle initializer which will try to replace the label displayed in the dropdown list by one found in the resource bundles if available. Then all this updated values will go to the image initializer that will add an image to be associated with each value.

HOW TO DEVELOP YOUR OWN CHOICELIST INITIALIZERS?

You have also the possibility to develop your own initializers. Those custom initializers have to 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 will be 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 OF CHOICELIST IN CONTENT EDITOR

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

For now only displayName property of ChoiceListValue is supported by choicelists. We do not support the ChoiceListValue property called image that was previoulsy used by GWT engines to display an image if front of the option name.

Countries Initializer example

A full example for creating a complex initializer that will allow user to choose a country from a dropdown list and display a flag if available in front of the country name.

This example will use two classes, one for filling the countries in the list of values available for the dropdown list. And another class 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 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 if placed at the start of the pipe will be of no use 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 to you two very flexible initializer :

  • Node Initializer
  • Script Initializer

Node initializer

The node initializer allow 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 folder /categories.

- 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 script language JSR-223 compatible to write your initializer.

How to declare a scripted initializer ?

This is as simple as that :

- 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 type of script is defined by the extension of the script, for example if your script has the name type.groovy, the script engine manager will try to find a declared script engine for the groovy extension .

What are the things my script must absolutely do ?

The script must return a List of ChoiceListValue.

Whaou, this is crazily powerful, but any suggestion to how to use that in the real world will be greatly appreciated. The first usage that came in mind is to define script that interacts with external system to fill the values, like XML files, databases, REST service etc.

Example of Groovy script parsing 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.