# Dependent Selector Configurations

The last article explained how to make a dynamic configuration, a setting where the options are fetched from an API or database so they change from tenant to tenant

This article will explain how to make a dependent selector configuration, a setting where the options are fetched from an API or data base *and* vary based on the user's selection of an earlier config (which can also be dynamic)

### Here is an example of a dependent selector configuration:

A tenant settings page that allows the the user select their favorite food.

The options for the favorite food will be fetched from an API or database, so they will be different from tenant to tenant.

There will also be a food type configuration.

* The user's selection for food type will also affect what options are presented to the user when they are selecting their favorite food.
* The food type config is also dynamic, so the food type options will vary from tenant to tenant.

#### Here are what the options could look like to different users:

* Tenant A: food type options will be **Sweet** and **Savory**.

  <figure><img src="/files/QTRmoygp8BXzFAUOkgWE" alt=""><figcaption><p>The only food type option for Tenant A are "Sweet" and "Savory"</p></figcaption></figure>

  * If the user chooses food type **Sweet** then the favorite food options will be: **Chocolate**, **Caramel**, **Jello**

  <figure><img src="/files/nDXZncINqACLj58txEHV" alt=""><figcaption><p>When the user selects the "Sweet: food type, the options for favorite food are only sweet foods.</p></figcaption></figure>

  * If the user chooses food type **Savory** then the favorite food options will be: **Chips**, **Pizza**, **Bread**

  <figure><img src="/files/uPROwDl6CIyKMowyBu10" alt=""><figcaption><p>When the user selects the "Savory" food type, the options for favorite food are only savory foods.</p></figcaption></figure>
* Tenant B: food type options will be **Fruits** and **Veggies**.

  <div align="center" data-full-width="false"><figure><img src="/files/Xqi5ipjyFXH0t64FHEBt" alt=""><figcaption><p>The only food type option for Tenant B are "Fruits" and "Veggies"</p></figcaption></figure></div>
* If the user chooses food type **Fruits** then the favorite food options will be: **Strawberry**, **Banana**, **Kiwi**

  <figure><img src="/files/LRvA7P4qC0NCLD11NeI9" alt=""><figcaption><p>When the user selects the "Fruits" food type, the options for favorite food are only fruits.</p></figcaption></figure>
* If the user chooses food type **Veggies** then the favorite food options will be: **Carrot**, **Broccoli**, **Pepper**

  <figure><img src="/files/7Xm3Yc6XcNvuRdS4CmpP" alt=""><figcaption><p>When the user selects the "Veggies" food type, the options for favorite food are only vegetables.</p></figcaption></figure>

#### A dependent dynamic configuration is implemented by creating a parent selector and a dependent selector:

1. Create the **parent selector:** This is the dynamic configuration whose selected value determines the options available for the **dependent selector**.
   * In this example the **parent selector** is the `food_type`.
   * Here is a quick review on how to set up a dynamic configuration:
     1. Like other dynamic configurations, the `schema` property for this must have a reference to a list of options in the `schema`'s `definitions`.
        * In this example the `food_type` schema property must have a reference to `food_type_options`.
     2. Like other dynamic configurations, the schema needs to have a definitions section with an entry that has the options for the **parent selector**.
        * In this example the `schema` `definitions` must include a list of `food_type_options`.
     3. Like a normal configuration, the `uischema` `elements` section must have an entry for the **parent selector**.
        * In this example there must be a uischema element with `type: control` and `scope: '#/properties/food_type'`
     4. Like other dynamic configurations, the integration's code must have an init sync which fetches the tenant specific options for the **parent selector** and prints them to the standard out.
        * Its value must be an array of objects, each of which has a `const` and `title` property. The `const` will be the ID of that option, and the `title` will be the name of the option.
        * In this example the standard out of an init sync must include `food_type_options`
          * It could look like this: `food_type_options = [`

            `{'const': 1, 'title': 'Fruits'},`

            `{'const': 2, 'title': 'Veggies'},`

            `]`
          * It could look like this: `food_type_options = [`

            `{'const': 3, 'title': 'Sweet'},`

            `{'const': 4, 'title': 'Savory'},`

            `]`
2. Create a **dependent selector:** This is the configuration whose options will depend on the **parent selector** and the values fetched from a database/API.
   * In this example the **dependent selector** is `favorite_food`.
   * Here is how a **dependent selector** is set up:
     1. Like a normal configuration, the `schema` property for this must have a `type`. The `type` for a dependent selector should be `string`.
     2. It must have two additional properties:
        1. optionsMapPath: This must reference the schema definition that has the options for the **dependent selector**.
           * In this example it will be `'#/definitions/food_options'`.
        2. `parentField`: This must reference the schema property for the **parent selector** which controls the options that will be present.
           * In this example it will be `'#/properties/food_type'`
     3. Like other dynamic configurations, the `schema` needs to have a `definitions` section with an entry that has the options for the **dependent selector**. It's type should be `object`.
        * In this example the `schema` `definitions` must include a list of `food_options`.
     4. Like a normal configuration, the `uischema` `elements` section must have an entry for the **dependent selector**, with the type Control.
     5. Like other dynamic configurations, the integration's code must have an init sync which fetches the tenant specific options for the **dependent selector** and prints them to the standard out.
        1. It's value must be a map of the option type IDs (from the **parent selector**) to a list of the options for that type.
        2. All the options for every list must be either entirely `string` or entirely `oneOf`(an objects with a `title` and `const`)
        3. In this example each option for every list will be a `string`.
        4. In this example the standard out of an init sync must include `food_options`
           * It could look like this `food_options = {`

             `1: ['Strawberry', 'Banana', 'Kiwi'],`

             `2: ['Carrot', 'Broccoli', 'Pepper']`

             `}`

             * It could look like this `food_options = {`

               `3: ['Chocolate', 'Caramel', 'Jello'],`

               `4: ['Chips', 'Pizza', 'Bread'],`

               `}`

### Putting it all together

The configs section of the PANDIUM.yaml would include this:

<pre class="language-yaml"><code class="lang-yaml">configs:

<strong>  schema:
</strong>    definitions:
      food_type_options:
        oneOf:
          - const: Placeholder
            title: Placeholder
      food_options:
        type: object
    properties:
      food_type:
        $ref: "#/definitions/food_type_options"
      favorite_food:
        type: string
        optionsMapPath: '#/definitions/food_options'
        parentField: '#/properties/food_type'

  uischema:
    type: VerticalLayout
    elements:
      # Parent
      - label: Food Type
        scope: '#/properties/food_type'
        type: Control
      # Dependent
      - label: Favorite Food
        scope: '#/properties/favorite_food'
        type: Control

</code></pre>

A TypeScript implementation of the init sync of the integration in could look like this:

```javascript
if (pandium.context.runMode === 'init') {
    const foods = await foodClient.getMany('food');
    /* foods is a list that could look like this: 
    [
     { type: 'Sweet', name: 'Chocolate' },
     { type: 'Sweet', name: 'Caramel' },
     { type: 'Sweet', name: 'Jello' },
     { type: 'Savory', name: 'Chips' },
     { type: 'Savory', name: 'Pizza' },
     { type: 'Savory', name: 'Bread' },
    ]
    */
    
    // Loop over the foods to identify the food types.
    const uniqueFoodTypes = foods.reduce((foodTypesList, currentFood) => {
      if(!foodTypesList.includes(currentFood.type)){
        foodTypesList.push(currentFood.type)
      }
      return foodTypesList
    }, [] )
    
    // Map each food type to the oneOf format
    const foodTypes = uniqueFoodTypes.map((type, index) => {
      return {
        const: index + 1,
        title: type
      }
    })
    /* foodTypes could look like this:
    [
     { const: '1', title: 'Sweet' },
     { const: '2', title: 'Savory' },
    ]
    */
    
    // Create a map from food type to ID, which can be used to organize foods into lists by type.
    const foodTypeToIdMap = foodTypes.reduce((typeToIdMap, foodType) => {
      typeToIdMap[foodType.title] = foodType.const
      return typeToIdMap
    }, {})
    /* foodTypeToIdMap could look like this:
    {
      'Sweet': '1',
      'Savory': '2'
    }
    */
    
    // Organize the foods into lists of their types
    foodListByTypeId = foods.reduce((foodListByType,currentFood) => {
      const typeId = foodTypeToIdMap[currentFood.type]
      if(!foodListByType[typeId]) {
        foodListByType[typeId] = [] 
      }
      foodListByType[typeId].push(currentFood.name)
      return foodListByType
    }, {} )
    /* foodListByTypeId could look like this:
    [
      '1': ['Chocolate', 'Caramel', 'Jello' ],
      '2': ['Chips', 'Pizza', 'Bread']
    ]
    */

    const stdout = {
      food_type_options: foodTypes,
      food_options: foodListByTypeId
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
```

### Another example

This example has a static **parent selector** and the options for the **dependent selector** will be `oneOf` rather than `string`.

The configs section of the PANDIUM.yaml would include this:

```yaml
configs:
  schema:
    type: object
    definitions:
      pokemon_types:
        oneOf:
          - const: Fire
            title: Fire
          - const: Water
            title: Water
          - const: Rock
            title: Rock
          - const: Flying
            title: Flying
      pokemon_options:
        type: object
    properties:
      pokemon_type:
        $ref: "#/definitions/pokemon_types"
        type: string
      pokemon_1:
        type: string
        optionsMapPath: "#/definitions/pokemon_options"
        parentField: "#/properties/pokemon_type"
      pokemon_2:
        type: string
        optionsMapPath: "#/definitions/pokemon_options"
        parentField: "#/properties/pokemon_type"

  uischema:
    type: VerticalLayout
    elements:
      - label: Pokemon Type
        scope: "#/properties/pokemon_type"
        type: Control
      - type: Section
        label: Pokemon Selection
        hintText: Choose your Pokemon
        elements:
          - label: First Pokemon
            scope: "#/properties/pokemon_1"
            type: Control
          - label: Second Pokemon
            scope: "#/properties/pokemon_2"
            type: Control
```

A TypeScript implementation of the init sync of this integration in could look like this:

```javascript
if (pandium.context.runMode === 'init') {
    const pokemon = await pokeClient.getMany('pokemon');
    /* pokemon is a list that could look like this: 
    [
     { type: 'Fire', name: 'Charmander', id: 1 },
     { type: 'Fire', name: 'Vulpix', id: 2  },
     { type: 'Water', name: 'Squirtle', id: 3  },
     { type: 'Water', name: 'Poliwag', id: 4  },
     { type: 'Rock', name: 'Geodude', id: 5  },
     { type: 'Rock', name: 'Onix', id: 6  },
     { type: 'Flying', name: 'Butterfree', id: 7 },
     { type: 'Flying', name: 'Pidgeot', id: 8  },
    ]
    */

    // The parent control, pokemon_type, has a static list of options: pokemon_types. 
    // so the unique options for pokemon_types do not need to be identified.
    const pokemonOptions = {
      Fire:[],
      Water:[],
      Rock: [],
      Flying: [],
    }

    // Add each pokemon to the list for the appropriate type.
    for (const currentPokemon of pokemon) {
      pokemonOptions[currentPokemon.type].push({
        title: currentPokemon.name,
        const: currentPokemon.id
      })
    }
 
    /* pokemonOptions could now look like this:
    {
      Fire: [ 
          { title: 'Charmander', const: '1'},  
          { title: 'Vulpix', const: '2' },
      ],
      Water: [ 
          { title: 'Squirtle', const: '3'},  
          { title: 'Poliwag', const: '4' },
      ],
      Rock: [ 
          { title: 'Geodude', const: '5'},  
          { title: 'Onix', const: '6' },
      ],
      Flying: [ 
          { title: 'Butterfree', const: '7'},  
          { title: 'Pidgeot', const: '8' },
      ]
    }
    */
    

    const stdout = {
      pokemon_options: pokemonOptions
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
```

### Dependent Selectors in Arrays

Dependent selectors can be used in arrays. The selector will look first for its parent in its own object, and if it doesn't find it there it will look up the next level in the schema. Consider the following setup:

```
configs:
  schema:
    type: object
    definitions:
      food_options:
        type: object
      pokemon_types:
        enum:
          - Fire
          - Water
          - Rock
          - Flying
      pokemon_options:
        type: object
    properties:
      food:
        type: string
        parentField: '#/properties/food_type'
        optionsMapPath: '#/definitions/food_options'
      food_type:
        enum:
          - fruit
          - Vegetable
      pokemon_list:
        type: array
        items:
          type: object
          properties:
            p_type:
              $ref: '#/definitions/pokemon_types'
              type: string
              title: Pokemon Type
            pokemon_name:
              type: string
              title: Pokemon
              parentField: '#/properties/p_type'
              optionsMapPath: '#/definitions/pokemon_options'
            food_for_pokemon:
              type: string
              title: Food
              parentField: '#/properties/food_type'
              optionsMapPath: '#/definitions/food_options'
  uischema:
    type: Section
    label: Fun with Dependent Configs
    subtitle: This is a test of dependent configs
    elements:
      - type: Control
        label: Food Type
        scope: '#/properties/food_type'
      - type: Control
        label: Pokemon List
        scope: '#/properties/pokemon_list'
    
```

Given dynamic configs as described above, this will render the following form:

<figure><img src="/files/MfK1Sltk2RuU46uETHCc" alt=""><figcaption></figcaption></figure>

Note that the second column in the table depends on the first, but the third column depends on the "food type" selector above.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dependent-selector-configurations.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
