I need to centralize event registration, deregistration, and cleanup for my form elements while traversing the tree and managing dependencies. The challenge is ensuring that when an element depends on another by name, it registers and listens to the closest one within its scope—especially when there are multiple elements with the same name, taking Scoped Dependency lookup into account.

I’m thinking about two approaches, but I’m not sure which is best:

  1. Path-based resolution: Elements in my form come with a path, but expressions only reference names (not paths). I could resolve the path for each dependency upfront, during serialization or storage, ensuring that each element registers based on a unique path rather than just a name. This way, notifications would be sent specifically to listeners associated with that path.
  2. Hierarchical dispatching: Another idea is to have the dispatcher send notifications hierarchically based on the path. Elements would register dependencies by name and provide their current path, and the dispatcher would resolve the closest dependency during the notification process.

I’m looking for ideas on how to best implement this while decoupling these mechanisms, Example:

{
    "form": "inventoryForm",
    "name": "inventoryForm",
    "fields": {
        "name": "transactionInfo",
        "type": "Section",
        "fields": [
            {
                "name": "transaction",
                "type": "SelectOne",
                "listName": "transactionTypes"
            },
            {
                "name": "supplier",
                "type": "Text",
                "rules": [
                    {
                        "expression": "#{transaction} == 'supply'",
                        "action": "Show"
                    }
                ]
            }
        ]
    }
}

My current approach involves extracting dependencies at runtime using an extension on the FieldTemplate and the Rule:

extension FieldTemplateDependencies on FieldTemplate {
  List<String> get dependencies {
    List<String> dependencySet = [];
    for (final rule in rules) {
      final ruleDependencies = rule.dependencies;
      dependencySet.addAll(ruleDependencies);
    }
    return dependencySet.toSet().toList();
  }

  /// from the choiceFilter expression
  List<String> get filterDependencies {
    List<String> dependencyList = [];
    final fieldPattern = RegExp(r'#\{(.*?)\}');

    if (type.isSelectType) {
      if (choiceFilter != null) {
        final filterDependencies = fieldPattern
            .allMatches(choiceFilter!)
            .map((match) => match.group(1)!)
            .toList();
        dependencyList.addAll(filterDependencies);
      }

      return dependencyList.toSet().toList();
    }

    return [];
  }

  String? get evalChoiceFilterExpression =>
      choiceFilter?.replaceAll("#{", "").replaceAll("}", "");
}

extension RuleDependencies on Rule {
  List<String> get dependencies {
    if (expression != null) {
      final fieldPattern = RegExp(r'#\{(.*?)\}');

      return fieldPattern
          .allMatches(expression!)
          .map((match) => match.group(1)!)
          .toSet()
          .toList();
    }
    return [];
  }

  String? get evalExpression =>
      expression.replaceAll("#{", "").replaceAll("}", "");
}

Ideas about this page from notion’s AI pot 😃 😋:

October 13, 2024

Materialized Paths, for abstracting some of the complexities, if not all: