Develop a Collector Plugin - BloomReach Experience - Open Source CMS

This article covers a Hippo CMS version 12. There's an updated version available that covers our most recent release.

07-07-2017

Develop a Collector Plugin

Introduction

Goal

Develop a collector plugin to override collector data using the channel manager's Alter Ego feature. 

Background

The Alter Ego functionality allows a CMS user to impersonate a visitor with certain characteristics. The 'As viewed by' menu in the channel manager always contains the option 'Alter Ego'. When the 'Alter Ego' option is selected, targeting data will be collected while previewing the channel, and targeted content will be shown. The 'Edit Alter Ego' button opens a window in which collected targeting data can be overridden with a specific value. For example, it is possible to select a specific location instead of location collected by the Relevance Module.

To be able to override collector data, a collector plugin must be provided that can edit the (JSON representation) of the targeting data. Such a plugin is similar to a characteristic plugin and provides the UI components shown in the 'Edit Alter Ego' window in the Channel Editor.

This page explains how to implement a collector plugin.

Configuration

Collector plugins are configured in the repository at:

/hippo:configuration/hippo:frontend/cms/hippo-targeting

Each collector plugin is configured in one child node of type frontend:pluginconfig. As a best practice, name the node collector-<ID of your collector>. Each collector plugin node can have the following properties:

  • collector (String, mandatory) The ID of the collector.

  • plugin.class (String, mandatory) The Java class name of the collector plugin

A collector plugin can define more configuration properties to customize the plugin.

Example: GroupsCollectorPlugin

The groups collector plugin allows you to alter the groups a user is a member of. The targeting data of the GroupsCollector simply returns the groups as a comma-separated string. The groups collector plugin consists of a checkbox group in which one or more groups can be selected.

The plugin consist of three files:

The code shown below is a slightly simplified version of the GroupsCollectorPlugin in the Relevance Module.

Java Class

The Java class contains an @ExtClass annotation that specifies the associated Javascript class of the plugin.

GroupsCollectorPlugin.java:

package com.onehippo.cms7.targeting.frontend.plugin.groups;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;

import com.onehippo.cms7.targeting.frontend.plugin.CollectorPlugin;

import org.hippoecm.frontend.plugin.IPluginContext;
import org.hippoecm.frontend.plugin.config.IPluginConfig;
import org.hippoecm.frontend.session.UserSession;
import org.json.JSONException;
import org.json.JSONObject;
import org.wicketstuff.js.ext.util.ExtClass;

/**
 * Plugin for the groups collector. Available plugin properties:
 * <ul>
 * <li>groups: multi-value String property, each string specifies
 *     a selectable group</li>
 * </ul>
 */
@ExtClass("Hippo.Targeting.GroupsCollectorPlugin")
@SuppressWarnings("unused")
public class GroupsCollectorPlugin extends CollectorPlugin {

    private List<Pattern> excludes;

    public GroupsCollectorPlugin(final IPluginContext context,
                                 final IPluginConfig config) {
        super(context, config);
        final String[] excludesConfig = config.getStringArray("excludes");
        excludes = new ArrayList<Pattern>();
        if (excludesConfig != null) {
            for (String exclude : excludesConfig) {
                excludes.add(Pattern.compile(exclude));
            }
        }
    }

    @Override
    protected void onRenderProperties(final JSONObject properties)
                                                    throws JSONException {
        super.onRenderProperties(properties);
        try {
            properties.put("groups", listGroups());
        } catch (RepositoryException e) {
            throw new JSONException(e);
        }
    }

    private List<String> listGroups() throws RepositoryException {
        final Session session = UserSession.get().getJcrSession();

        final StringBuilder statement = new StringBuilder();
        statement.append("//element");
        statement.append("(*, ").append("hipposys:group").append(")");
        statement.append(" order by @jcr:name");

        final Query q = session.getWorkspace().getQueryManager()
                           .createQuery(statement.toString(), Query.XPATH);

        final List<String> groups = new ArrayList<String>();
        final NodeIterator nodes = q.execute().getNodes();
        while (nodes.hasNext()) {
            final String group = nodes.nextNode().getName();
            if (!isExcluded(group)) {
                groups.add(group);
            }
        }

        return groups;
    }

    private boolean isExcluded(final String group) {
        if (group.equals("everybody")) {
            return true;
        }
        for (Pattern exclude : excludes) {
            if (exclude.matcher(group).matches()) {
                return true;
            }
        }
        return false;
    }

Properties File

The .properties file contains all i18n labels. The special key collector-description is shown as the description of the collector in the 'Edit Alter Ego' window.

GroupsCollectorPlugin.properties:

collector-description=is in the user group
groups-empty=No groups available
no-groups=<none>

All properties are automatically available in the Javascript class as via the resources variable. For example, renderGroups method shows <none> when the list of groups is empty.

Javascript Class

(function() {
    "use strict";

    Ext.namespace('Hippo.Targeting');

    Hippo.Targeting.GroupsCollectorPlugin =
                            Ext.extend(Hippo.Targeting.CollectorPlugin, {

        constructor: function(config) {
            var editor;

            if (Ext.isEmpty(config.groups)) {
                editor = {
                    message: config.resources['groups-empty'],
                    xtype: 'Hippo.Targeting.TargetingDataMessage'
                };
            } else {
                editor = {
                    collector: config.collector,
                    groups: config.groups,
                    resources: config.resources,
                    xtype: 'Hippo.Targeting.GroupsTargetingDataEditor'
                };
            }

            Hippo.Targeting.GroupsCollectorPlugin.superclass.constructor
                                           .call(this, Ext.apply(config, {
                editor: editor,
                renderer: this.renderGroups
            }));
        },

        renderGroups: function(value) {
            var groups = value ? value.groups: [];
            if (Ext.isEmpty(groups)) {
                return this.resources['no-groups'];
            }
            return groups.join(', ');
        }

    });

    Hippo.Targeting.GroupsTargetingDataEditor =
                    Ext.extend(Hippo.Targeting.TargetingDataCheckboxGroup, {

        constructor: function(config) {
            var checkboxes = [];
            Ext.each(config.groups, function(group) {
                checkboxes.push({
                    boxLabel: group,
                    name: group
                });
            });
            Hippo.Targeting.GroupsTargetingDataEditor.superclass
                                .constructor.call(this, Ext.apply(config, {
                columns: 2,
                items: checkboxes,
                vertical: true
            }));
        },

        convertDataToCheckedArray: function(data) {
            var checkedArray = this.createBooleanArray(this.checkboxNames
                                                                   .length);

            if (!Ext.isEmpty(data.groups)) {

                Ext.each(data.groups, function(dataItem) {
                    var index = this.checkboxNames.indexOf(dataItem);
                    if (index >= 0) {
                        checkedArray[index] = true;
                    }
                }, this);
            }

            return checkedArray;
        },

        convertCheckedBoxesToData: function(checkedBoxes) {
            var checkedIds = Ext.pluck(checkedBoxes, 'name');
            return {
                collectorId: this.collector,
                groups: checkedIds
            };
        }

    });
    Ext.reg('Hippo.Targeting.GroupsTargetingDataEditor',
             Hippo.Targeting.GroupsTargetingDataEditor);
}());

The Javascript constructor specifies an editor and a renderer. The editor is the component used for editing the targeting data. In this case the editor is a checkbox group, but any Ext.form.Field is possible. The default editor is a textfield. The renderer is a function that converts the data string returned by the collector to a value shown in the 'Edit Alter Ego' window. The groups renderer function simply returns the string as-is, except when it is empty.

Java API

com.onehippo.cms7.targeting.frontend.plugin.CollectorPlugin

Base class for collector plugins.

Plugin configuration properties:

  • collector( String, mandatory) The ID of the collector

  • plugin.class( String, mandatory) The Java class name of the characteristic plugin

com.onehippo.cms7.targeting.frontend.plugin.dayofweek.DayOfWeekCollectorPlugin

Plugin to alter the current day of the week.

com.onehippo.cms7.targeting.frontend.plugin.geo.GeoIPCollectorPlugin

Plugin to alter the location of the visitor.

Plugin configuration properties:

  • locations(multiple String) A list of location strings to show as selectable options in the editor. Each location string has the format "city | country | latitude | longitude".

com.onehippo.cms7.targeting.frontend.plugin.groups.GroupsCollectorPlugin

Plugin to alter the groups a visitor is a member of.

Plugin configuration properties:

  • excludes(multiple String) A list of regular expression of patterns of group names to exclude from showing as selectable options in the editor.

com.onehippo.cms7.targeting.frontend.plugin.referrer.ReferrerCollectorPlugin

Plugin to alter the referrer URL.

com.onehippo.cms7.targeting.frontend.plugin.returningvisitor.ReturningVisitorCollectorPlugin

Plugin to alter whether the visitor is new or returning.

Javascript API

Hippo.Targeting.CollectorPlugin

Base class for collector plugins. A collector plugin can define its own renderer and/or editor for targeting data.

Extends: Ext.util.Observable

Properties:

  • renderer (Mixed) Optional interceptor method that transforms the targeting data string to rendered data. See Ext.grid.Column.renderer for details.

  • editor ( Ext.form.Field) Optional form field for editing the targeting data string.

Hippo.Targeting.TargetingDataCheckboxGroup

Checkbox group for editing targeting data. The default implementation iterates over a configurable property in the targeting data and assumes each element is the name of a checkbox in the group. The names of all checked checkboxes are again converted to an array and set in the targeting data. Subclasses can provide their own implementation of the methods convertDataToCheckedArray and convertCheckedBoxesToData to customize this behavior.

Extends: Ext.form.CheckboxGroup

Properties:

  • targetingDataProperty ( String) The property in the targeting data object to iterate over. Must be serialized as a JSON array.

Methods:

  • convertDataToCheckedArrayStringtargetingData\) : Array Converts the targeting data to an array of booleans that indicates which checkboxes should be checked. The default implementation iterates over a configurable property of the targeting data and assumes each element is the name of a checkbox in the group.
    Parameters:
    targetingData(Object): the targeting data object serialized to JSON

    Returns:
    An array of booleans. The Nth boolean indicates whether the Nth checkbox should be checked or not.

  • ( ArraycheckedBoxes) : Array
    Converts an array of Ext.form.Checkbox objects to a targeting data string. The default implementation adds the the name of each checked box to an array and sets that array in the configured targeting data property.

    Parameters:
    checkedBoxes (Array): an array of Ext.form.Checkbox objects that are currently checked.

    Returns:
    A targeting data object

Hippo.Targeting.TargetingDataMessage

'Editor' for targeting data that only displays a string. Useful for only displaying a 'no options available' message instead of the normal editor.

Extends: Ext.form.DisplayField

Properties:

  • message ( String)
    The message to show.

Hippo.Targeting.TargetingDataRadioGroup

Radio group for editing targeting data. The default implementation assumes that targeting data string is the inputValue of the radio button in the group to select. Subclasses can provide their own implementation of the methods convertDataToInputValue and getValue to customize this behavior. Note that each radio button should have the same 'name' property to make them mutually exclusive. Also, commas in the radio button input values lead to incorrect behavior, so avoid those.

Extends: Ext.form.RadioGroup

Methods:

  • convertDataToInputValue( String data) : String
    Converts the targeting data string to the inputValue of the radio button that should be selected. The default implementation returns the data string as-is.

    Parameters:
    data (String): the data string as returned by the targeting data of the collector

    Returns:
    The input value of the radio button to select.

  • getValue(): String

    Returns the targeting data string that reflects the selected radio button. The default implementation returns the inputValue of the selected radio button, or an empty string if no radio button is selected.
Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?