Dynamic Data Filtering for Relation Fields via Metadata Dependencies

Dynamic Data Filtering for Relation Fields via Metadata Dependencies

Overview

In response of : https://crossmedia.atlassian.net/browse/WXM-19474

This feature provides the ability to filter data within Relation fields in the Front Office based on the values of other fields. By establishing these relationships, the selection in a dependency field (the source) dynamically restricts the available options in its dependent fields (the targets).

These are the cursors which names prefixed by “~edit/”. The are used for all the list metadatas in the forms for the front-end application to retreive their instances. They can be found in the state of the application.

The mechanism relies on an event-driven update of the “~edit” cursor configuration, ensuring that the next fetch call for a relation field is automatically filtered by the backend logic.


Configuration

Dependencies are declared in the JSON configuration files. To make a field react to changes in another, add the dependsOn array to its definition.

Configuration Examples

  1. keywords filtering by brand (for “asset” library)

Path: src/wedia-portal-apps-configs/starter-kit/edit/asset/fields/names/keywords.json

{ "default": { "dependsOn": ["brand"] } }
  1. coverage filtering by brand (for an other specific library)

Path: src/wedia-portal-apps-configs/starter-kit/edit/vvasset/fields/names/coverage.json

{ "default": { "dependsOn": ["brand"] } }
  1. organization filtering by brand (global default = applied to all the libraries)

Path: src/wedia-portal-apps-configs/starter-kit/edit/default/fields/names/organization.json

{ "default": { "dependsOn": ["brand"] } }

Technical Mechanism

1. Front Office: The Cursor Update

The interface uses specialized cursors to manage data fetching. The dependency flow works as follows:

  1. Change Event: A user selects a value in a dependency field (e.g., brand).

  2. Cursor Injection: The interface identifies all dependent fields (e.g., keywords). It injects a customParameter (prefixed with x_dependency_) specifically into the “~edit” cursors associated with those fields.

  3. Automatic Fetch: Updating the cursor automatically triggers a background fetch.

  4. Ready State: When the user opens the "select" input for the dependent field, the filtered data is already loaded and ready to go.

The customParameter Payload:

The parameter (e.g., x_dependency_brand) contains a JSON string including:

  • id: The technical ID of the selected dependency.

  • context: The editing context (edit, massimport, etc.).

  • damobjects: The IDs of the assets currently being modified.

2. Backend Logic (Java Reference)

To interpret these parameters, you must implement a custom service. While the class naming is flexible, it must extend APIBaseWhereBusinessServiceAdapter. The process follows three main steps:

  • Interception: The service detects parameters starting with x_dependency_.

  • Parsing: It extracts the metadata IDs, context, and related assets from the JSON payload.

  • Query Modification: It builds a PreparedWhere clause to restrict the results (e.g., adding an AND condition to ensure only values linked to the selected dependency are returned).

Detailed documentation about APIBaseWhereBusinessServiceAdapter => https://crossmedia.atlassian.net/wiki/x/CIBPow

Implementation Hint: While you can tailor the filtering logic to your needs, the extractParams method provided in the example below is highly recommended. It is specifically designed to correctly parse the complex JSON payload (IDs, context, and asset arrays) sent by the Front Office.

package com.wedia.packaged.portals.extensions; import com.noheto.preparedwhere.PreparedWhereService; import com.noheto.restapi.APIBaseWhereBusinessServiceAdapter; import com.noheto.restapi.Context; import com.wedia.packaged.portals.utils.IPluginConstants; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import wsnoheto.engine.CTObjectStructure; import wsnoheto.engine.CTSurfer; import wsnoheto.engine.PreparedWhere; import wsnoheto.engine.PreparedWhereException; import wsnoheto.log.LoggingService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; import java.util.stream.Collectors; /** * Example service to handle dynamic filtering based on dependency parameters. * Note: Extending APIBaseWhereBusinessServiceAdapter is mandatory. */ public class AssetsFilterDependingFieldValues extends APIBaseWhereBusinessServiceAdapter { private static final String DEP_CUSTOM_PARAM_PREFIX = "x_dependency_"; private List<String> depCustomParamsList = new ArrayList<>(); private static final ObjectMapper mapper = new ObjectMapper(); private static final Logger logger = LoggingService.getLogger(IPluginConstants.LOGGER_NAME); /** * Checks if the request contains parameters starting with x_dependency_ */ private boolean doExecute(Context context) { depCustomParamsList = context.getRequest() .getParameterMap() .keySet() .stream() .filter(key -> key.startsWith(DEP_CUSTOM_PARAM_PREFIX)) .collect(Collectors.toList()); return !depCustomParamsList.isEmpty(); } @Override public PreparedWhere getPreparedWhere(Context context) { CTSurfer surfer = context.getSurfer(); String objectName = context.getObjectName(); if (doExecute(context)) { try { CTObjectStructure struct = CTObjectStructure.load(objectName); // Retrieve the standard list query PreparedWhere baseList = PreparedWhereService.getInstance() .getPreparedWhere("base_list", surfer, objectName); PreparedWhere finalPw = baseList; for (String depCustomParam : depCustomParamsList) { // Extract field name (e.g., x_dependency_brand -> brand) String[] parts = depCustomParam.split(DEP_CUSTOM_PARAM_PREFIX); String depField = parts.length > 1 ? parts[1] : null; if (StringUtils.isNotBlank(depField) && struct.isExistField(depField)) { // USEFUL: Utilizing the extractParams helper to get the JSON data Map<String, Object> customParamValues = extractParams(context.getRequest().getParameterMap(), depCustomParam); if (customParamValues != null && customParamValues.get("id") != null) { String depId = customParamValues.get("id").toString(); // Build the filter: WHERE [depField] = [depId] PreparedWhere depPw = new PreparedWhere("?").addChildEquals(depField, depId); try { finalPw.and(depPw); } catch (PreparedWhereException e) { logger.error("Error adding dependency filter: " + e.getMessage()); } } } } return finalPw; } catch (Throwable e) { logger.error("Error in dependency process: " + e.getMessage(), e); } } return super.getPreparedWhere(context); } /** * Highly useful utility method to parse the JSON payload from the Front Office. * Extracts 'id', 'context', and 'damobjects' for easy use in filtering logic. */ public static Map<String, Object> extractParams(Map<String, String[]> paramMap, String key) { Map<String, Object> result = new HashMap<>(); String rawJson = paramMap.get(key)[0]; if (StringUtils.isBlank(rawJson)) return null; try { JsonNode root = mapper.readTree(rawJson); // Extracts the selected ID (path: value -> first element -> id) String id = root.at("/value/0/id").asText(null); // Extracts the editing context String ctx = root.at("/context").asText(null); // Extracts the list of currently edited assets List<String> damObjects = new ArrayList<>(); root.at("/damobjects").forEach(n -> damObjects.add(n.asText())); result.put("id", id); result.put("context", ctx); result.put("damobjects", damObjects); } catch (Exception e) { logger.error("Failed to parse dependency JSON payload: " + e.getMessage()); } return result; } }

Behavior in Mass Editing

The system handles multiple asset selections by:

  • Triggering Refreshes: If the user modifies which assets are selected in a list, the “~edit” cursors for dependent fields are automatically updated.

  • Contextual Parameters: The damobjects and context parameters allow the backend service to apply different filtering rules (e.g., filtering options based on the specific Libraries of the selected assets).


Important Considerations

Data Consistency (Validation)

This evolution manages the available options for selection. It does not automatically clear or invalidate previously saved values if the dependency field is changed later. Final data coherence should be verified by the user.

  • Field Types: Optimized for Relation fields (child, childmulti).

  • Implementation Requirement: You must provide a Java service extending APIBaseWhereBusinessServiceAdapter to process these parameters.