Using rule schema to look up a value

Hi Folks,

In my schema I have two arrays, “things” and “profiles”

Each profile has a name and a kind (enum of “Type1” or “Type2”)

Each thing has a “profile” property which is the name value of one of the profiles (an enum generated from the profile list), and two other properties (“key1” and “key2”)

I want add a rule to key2 so that it is hidden when the profile selected by its profile property has a kind of “Type2”.

I can see from the examples how I can add a rule based on the profile name, but in this case I need a level of indirection into the profile data values, and I can;t find any pointers on how to express that within the rule schema.

Here is my schema, uischema, and some initial data

The uischema has a rule for “key2” based on the name of the profile, what I need to do is replace that with something that uses the value of the “kind” property of the selected profile. (so something like Hide if profiles[#profile].kind == “Type2”

schema

{
  "type": "object",
  "properties": {
    "things": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "dead": {
            "type": "boolean"
          },
          "profile": {
            "type": "string",
            "enum": ["Profile#1", "Profile#2", "Profile#3"]
          },
          "key1": {
            "type": "string"
          },
          "key2": {
            "type": "string"
          }
        }
      }
    },
    "profiles": {
      "type": "array",
      "items": {    
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": ["Type1", "Type2"]
          }
        }
      }
    }
  }
}

uischema

"type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "scope": "#/properties/things",
      "options": {
        "detail": {
          "type": "HorizontalLayout",
          "elements": [
            {
              "type": "Control",
              "scope": "#/properties/name"
            },
            {
              "type": "Control",
              "scope": "#/properties/profile"
            },
            {
              "type": "Control",
              "scope": "#/properties/key1"
            },
            {
              "type": "Control",
              "scope": "#/properties/key2",
              "rule": {
                "effect": "HIDE",
                "condition": {
                  "scope": "#/properties/profile",
                  "schema": {
                    "const": "Profile#2"
                  }
                }
              }
            }
          ]
        }
      }
    },
    {
      "type": "Control",
      "scope": "#/properties/profiles"
    }
  ]
}

initialData

const initialData = {
  things: [
    {name: "Thing1", dead: true,  profile: "Profile#1", key1: "xxxx", key2: "yyyy"},
    {name: "Thing2", dead: false, profile: "Profile#2", key1: "xxxx", key2: "yyyy"}
  ],
  profiles: [
    {name: "Profile#1", kind: "Type1"},
    {name: "Profile#2", kind: "Type2"},
    {name: "Profile#3", kind: "Type1"},
  ]
};

Hi @PhilDay,

The schema in the detail is scoped against the object or array it’s a detail for. So the schema / uischema combination you posted works perfectly fine for me.

HideKey

Is there anything I’m missing? Would you like to see a different behavior?

Hi Stefan,

Sorry for the confusion, I included that rule as a place holder to show what I wanted to change :wink:

Yes it works fine when I use a rule based on the value of the Profile - but what I want to change that to is to use the value (e.g. “Profile#1”) to look up the value of “kind” in the profile array. Its not the profile name as such that should decide whether or not to hide key2, but some property of the selected profile

So the logic I want (in normal JS) is

if (profiles[thing.profile].kind = “Type2”)

I do have a somewhat hacky way of making this work which is to add a hidden “kind” property to each thing, and then use the onChange() callback to set that by looking up the value in the profile:

const setData = (data: any, setter: any) => {
  let update = false;
  for (let t of data.things) {
    for (let p of data.profiles ) {
      if (p.name == t.profile) {
        if (!('kind' in t) || (t.kind != p.kind)) {
          t['kind'] = p.kind;
          update = true;
        }
        break
      }
    }
  }
  if (update)
    setter({...data})
  else
    setter(data)
}

Hi @PhilDay,

Ah I see, thanks for the explanation. The problem here is that JSON Schema is not powerful enough to refer to instance values, which is why you can’t express this in schema.

You could check AJV’s JSON Schema extensions, especially $data. This can resolve to instance values, maybe you could solve your use case with this. Note that for this to work you then need to hand over a customized AJV instance to JSON Forms which has this feature enabled.

Obviously your workaround also works but it’s not that nice. I would rather suggest implementing a custom renderer for key. For this you can basically reuse the existing renderer and just additionally hand over visible:false in your use cases.

Thanks @sdirix

I suspected from some of the other questions in here that a customer renderer might be the way to go, but being a backend systems programming guy who’s looking for an easy way to create a UI, without any knowledge of react even with the tutorial that’s kind of daunting :wink:

But thanks for confirming that I can’t just do it via some more complex schema rule - that saves me a bunch of head banging against the wall. :slight_smile:

Don’t worry in this case it should be very straightforward. Following the second part of the tutorial should do the trick. What you need is the MaterialTextControl.

Do access data outside of your renderer you can use

const ctx = useJsonForms();
const data = ctx.core.data;

There you can look up whatever you like and then adapt the visible prop, i.e.

<MaterialTextControl {...props} visible={myVisibleDetermination} />

Thank you - I’ll give that a try.

So if the modified text control is representing one element in array, how would it find the value of the other elements from that same array entry ?

in the context of the example I posted, each “key 2” control will need to find the value of the corresponding “Profile” control

Via the path prop you can determine which array element you are a part of and then resolve accordingly.

1 Like