Problem with custom renderer (reaching nested schema)

Hello, I have a problem regarding creating custom renderer.

I have ui schema which includes this:

{
      "type": "Control",
      "scope": "#/properties/TradeSettingsOne",
    },
    {
      "type": "Control",
      "scope": "#/properties/TradeSettingsTwo",
    },

In Schema they are nested like this, first we pass ref:

"TradeSettingsOne": {
      "$ref": "#/definitions/commonTradeSettings"
    },

    "TradeSettingsTwo": {
      "$ref": "#/definitions/commonTradeSettings"
    },

And here we get a full object:

"commonTradeSettings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "platform": {
            "type": "string"
          },
          "regions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "region": {
                  "type": "string"
                },
                "ratioMin": {
                  "type": "number",
                  "description": "Min Ratio",
                  "minimum": 0.5,
                  "maximum": 1.1
                },
                "ratioMax": {
                  "type": "number",
                  "description": "Max Ratio",
                  "minimum": 0.5,
                  "maximum": 1.1
                }
              }
            }
          }
        }
      }
    },

In tester I need somehow to reach ratioMin and ratioMax properties, but I’ve already tried several functions such as schemaMatches or schemaSubPathMatches and even isNumberControl (as these properties have type number). But it doesn’t work. I think there is a lack in docs that I can’t see how I can reach nested schemas’ properties and also using refs. Could you guide me where to start digging? I really can’t understand:(

Hi @Ookami,

A tester looks like this: (uischema, schema, context) => number. The handed over parameters are the current ui schema element, the scoped schema (often just the root schema) and context where for example the root schema is contained. Using these parameters you are definitely able to resolve anything you like as you have all relevant information in your hand.

Helper functions like schemaMatches just do part of the job for you. For example schemaMatches looks at the scope in the uischema element and already resolves that for you. Normally we resolve until we no longer are hitting a $ref, so I would have expected that it works for your described case. It would be interesting why it fails: What does schemaMatches actually resolve to in your case? To reproduce it would also be helpful to paste the full schema, or a reproducible example and not only parts of it.

Hello Stefan,

Thank you for your reply. Apologies for the confusion, let me then describe you what I do in details:

I have uiSchema:

{

  "type": "VerticalLayout",
  "elements": [
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "label": "Example Sellers",
          "type": "Control",
          "scope": "#/properties/exampleSellers",
          "options": {
            "readonly": true
          }
        },
        {
          "type": "Control",
          "scope": "#/properties/exampleSellers2",
          "options": {
            "readonly": true
          }
        }
      ]
    },
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "label": "Keys on Sellers",
          "type": "Control",
          "scope": "#/properties/keysAccountEnabledOnSellers",
          "options": {
            "toggle": true
          }
        },
        {
          "label": "Keys on Sellers2",
          "type": "Control",
          "scope": "#/properties/keysAccountEnabledOnSellers2",
          "options": {
            "toggle": true
          }
        }
      ]
    },
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "label": "Market on Sellers",
          "type": "Control",
          "scope": "#/properties/marketAccountEnabledOnSellers",
          "options": {
            "toggle": true
          }
        },
        {
          "label": "Market on Sellers2",
          "type": "Control",
          "scope": "#/properties/marketAccountEnabledOnSellers2",
          "options": {
            "toggle": true
          }
        }
      ]
    },
    {
      "type": "Control",
      "scope": "#/properties/sellersTradeSettings"
    },
    {
      "type": "Control",
      "scope": "#/properties/sellers2TradeSettings"
    },
    {
      "type": "Group",
      "label": "Sellers2 Commissions",
      "elements": [
        {
          "type": "Group",
          "label": "Market",
          "elements": [
            {
              "type": "HorizontalLayout",
              "elements": [
                {
                  "type": "Control",
                  "scope": "#/properties/seller2Commissions/properties/market/properties/percent"
                },
                {
                  "type": "Control",
                  "scope": "#/properties/seller2Commissions/properties/market/properties/fixed"
                }
              ]
            }
          ]
        },
        {
          "type": "Group",
          "label": "Keys",
          "elements": [
            {
              "type": "HorizontalLayout",
              "elements": [
                {
                  "type": "Control",
                  "scope": "#/properties/seller2Commissions/properties/keys/properties/percent"
                },
                {
                  "type": "Control",
                  "scope": "#/properties/seller2Commissions/properties/keys/properties/fixed"
                }
              ]
            }
          ]
        },
        {
          "type": "Control",
          "scope": "#/properties/seller2Commissions/properties/whenPriceLower"
        },
        {
          "type": "HorizontalLayout",
          "elements": [
            {
              "type": "Control",
              "scope": "#/properties/seller2Commissions/properties/lowValueProduct/properties/percent"
            },
            {
              "type": "Control",
              "scope": "#/properties/seller2Commissions/properties/lowValueProduct/properties/fixed"
            }
          ]
        },
        {
          "type": "Control",
          "scope": "#/properties/seller2Commissions/properties/platformCommission",
          "options": {
            "detail": {
              "type": "VerticalLayout",
              "elements": [
                {
                  "type": "Control",
                  "scope": "#/properties/platformNames",
                  "options": {
                    "readonly": true
                  },
                  "rule": {
                    "effect": "HIDE",
                    "condition": {}
                  }
                },
                {
                  "type": "Control",
                  "scope": "#/properties/platforms",
                  "options": {
                    "readonly": true
                  },
                  "rule": {
                    "effect": "HIDE",
                    "condition": {}
                  }
                },
                {
                  "type": "HorizontalLayout",
                  "elements": [
                    {
                      "type": "Control",
                      "scope": "#/properties/value/properties/percent"
                    },
                    {
                      "type": "Control",
                      "scope": "#/properties/value/properties/fixed"
                    }
                  ]
                }
              ]
            }
          }
        }
      ]
    },
    {
      "type": "Control",
      "scope": "#/properties/sellers3TradeSettings"
    },
    {
      "type": "Control",
      "scope": "#/properties/sellers3ProductTemplates",
      "options": {
        "detail": {
          "type": "VerticalLayout",
          "elements": [
            {
              "type": "Control",
              "scope": "#/properties/region"
            },
            {
              "type": "Control",
              "scope": "#/properties/description/properties/eur",
              "options": {
                "multi": true
              }
            },
            {
              "type": "Control",
              "scope": "#/properties/description/properties/us",
              "options": {
                "multi": true
              }
            },
            {
              "type": "Control",
              "scope": "#/properties/addInfo/properties/eur",
              "options": {
                "multi": true
              }
            },
            {
              "type": "Control",
              "scope": "#/properties/addInfo/properties/us",
              "options": {
                "multi": true
              }
            }
          ]
        }
      }
    }
  ]
}

And schema:

{
  "type": "object",
  "required": [
    "sellers2",
    "sellers",
    "sellersTradeSettings",
    "sellers2TradeSettings",
    "sellers3TradeSettings",
    "sellers2Commissions",
    "sellers3ProductTemplates"
  ],
  "properties": {
    "sellers2": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "sellers": {
      "description": "Example Sellers",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "keysPriceMultiplier": {
      "type": "number",
      "minimum": 0,
      "maximum": 0.2,
      "default": 0.04
    },
    "keysAccountEnabledOnSellers": {
      "type": "boolean"
    },

    "keysAccountEnabledOnSellers2": {
      "type": "boolean"
    },
    "marketAccountEnabledOnSellers": {
      "type": "boolean"
    },

    "marketAccountEnabledOnSellers2": {
      "type": "boolean"
    },

    "sellersTradeSettings": {
      "$ref": "#/definitions/resellTradeSettings"
    },

    "sellers2TradeSettings": {
      "$ref": "#/definitions/resellTradeSettings"
    },

    "sellers3TradeSettings": {
      "$ref": "#/definitions/resellTradeSettings"
    },

    "sellers2Commissions": {
      "type": "object",
      "required": [
        "market",
        "keys",
        "whenPriceLower",
        "lowValueProduct",
        "platformCommission"
      ],
      "properties": {
        "market": {
          "$ref": "#/definitions/sellers2Commission"
        },
        "keys": {
          "$ref": "#/definitions/sellers2Commission"
        },
        "whenPriceLower": {
          "type": "number",
          "minimum": 0,
          "maximum": 10
        },
        "lowValueProduct": {
          "$ref": "#/definitions/sellers2Commission"
        },
        "platformCommission": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "platforms": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "platformNames": {
                "type": "string"
              },
              "value": {
                "$ref": "#/definitions/sellers2Commission"
              }
            }
          }
        }
      }
    },

    "sellers3ProductTemplates": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "region": {
            "type": "string"
          },
          "description": {
            "$ref": "#/definitions/sellers3ProductEurUsValue"
          },
          "addInfo": {
            "$ref": "#/definitions/sellers3ProductEurUsValue"
          }
        }
      }
    }
  },
  "definitions": {
    "sellers2Commission": {
      "type": "object",
      "required": ["percent", "fixed"],
      "properties": {
        "percent": {
          "type": "number",
          "description": "% Fee",
          "minimum": 0.0,
          "maximum": 40
        },
        "fixed": {
          "type": "number",
          "description": "Fixed Fee",
          "minimum": 0.0,
          "maximum": 0.5
        }
      }
    },
    "sellers3TradeSettings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "platform": {
            "type": "string"
          },
          "regions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "region": {
                  "type": "string"
                },
                "ratioMin": {
                  "type": "number",
                  "description": "Min Ratio",
                  "minimum": 0.5,
                  "maximum": 1.1
                },
                "ratioMax": {
                  "type": "number",
                  "description": "Max Ratio",
                  "minimum": 0.5,
                  "maximum": 1.1
                }
              }
            }
          }
        }
      }
    },

    "sellers3ProductEurUsValue": {
      "type": "object",
      "required": ["eur", "us"],
      "properties": {
        "eur": {
          "type": "string",
          "description": "You can use [name], [platform] and [autodescr] placeholders"
        },
        "us": {
          "type": "string",
          "description": "You can use [name], [platform] and [autodescr] placeholders"
        }
      }
    }
  }
}

Initially my task is to implement all inputs with type number through custom renderer, which prevents scrolling effect whenever user works with input. I create custom input component in accordance with you docs and implemented it in my code like this:

 const renderers = [
    ...materialRenderers,
    //register custom renderers
    { tester: customInputTester, renderer: CustomRenderers },
  ];

In my tester I did this to ensure that all inputs with type number works in accordance with new custom rules:

import { rankWith, or, isNumberControl } from '@jsonforms/core';

export default rankWith(
  10,
  or(
    isNumberControl,
  )
);

And it works fine. First, I also used function scopeEndsWith on scopes ending with fixed, percent and whenPriceLower. It also worked, because this scopes exist in uiSchema. Though I can’t reach ratioMax and ratioMin properties, because they don’t exist in uiSchema, they refer to sellersTradeSettings and sellers2TradeSettings and then continued in schema through ref as I mentioned previously.

I am sorry for not correctly understanding how it works. I tried multiple functions like this:

schemaSubPathMatches('/properties/sellersTradeSettings/*/properties/ratioMin', () => true),

and like this:

export const isRatioControl = and(
  isControl,
  schemaSubPathMatches('properties/regions/items/properties', (schema) =>
    schemaTypeIs('object')(schema) &&
    schema.hasOwnProperty('ratioMin') &&
    schema.hasOwnProperty('ratioMax')
  )
);

and like this:

schemaSubPathMatches('resellTradeSettings.regions.ratioMin', () => true),

And it didn’t work, but it seems I misunderstand how custom tester works correctly. So I need some hint how can I handle such a problem or maybe what should I pay my attention to. Maybe I use wrong function to handle what I need. Anyway thanks for cooperation.