Controling bindings evaluation order

Hello folks,

I've been struggling to find a good way to do something.
Here's some context:
I have 3 dropdowns, let's call them dropA, dropB and dropC.
dropC depends on dropB, and dropB depends on dropA.
I want to reset the value prop of drop B and drop C when the dropdown they depend on change.
There are many ways to do this: property change scripts, script transforms...

BUT, I also need to pre-load selections when the view is opened, based on parameters.
I couldn't find a way to reliably set the prop values of the 3 dropdowns.
If they're not set in order (dropA -> dropB -> dropC), the values gets reset because of the reset requirement described above.

I can think of convoluted ways to achieve this, but I'd like to pick your brains before I make a mess.
How would YOU do it ?

Can you script an onStart to set dropA.options to null followed by a line to set the correct options? Maybe you'd need to initialise dropA, B and C to null initially.

They're null initially, it's their default.

I think I tried an onStartup event, uncessfully.
I may have to try again, as I was so tangled up in my own mess that I might have boinked up a few things.

edit: I was actually hoping for a way of reseting the values that would allow me to just bind them to the parameters. But I guess such a way, if it exists, would be even messier...

Describe these dependencies a bit more please.

{I would try to do this with bindings, each with the relevant dependencies. My new SimAids asList() function is handy for staging a group of dependencies for a transform, even if one or more is unused.}

I have 3 database tables, let's call them 'families', 'categories' and 'items'.
categories have a foreign key to families' id, and items have a FK to categories' id.
dropA allows the selection of a family, its options prop is bound to a parameterless query that fetches all families.
dropB allows the selection of a category, options named query uses dropA's value as its family_id parameter
dropC allows the selection of an item, options named query uses dropB's value as its 'category_id' parameter.

I have a boolean custom prop that's false if no item is selected, and true otherwise. There's a bunch of things that are only displayed if this prop is true.
I want to clear those things when families or categories change, which is why I want to reset dropC's value.

Sounds like you need to reset DropC's value in property change events on DropA's and dropB's options property. Hmm. Values, too.

The problem with that is when I pre-load values from the view's parameter.

If dropA or dropB's values are set after dropC, its value is reset.
I tried doing this in a script (startup script and property change script on a parameter), thinking it would set things in order. It didn't.

Perhaps it would be useful to re-evaluate how you are attacking the problem?

From a high level conceptual plan of attack, I have generally found when I have a bunch of items that need to interact (or evaluate in a certain order) a single function can alleviate problems.

So basically:

  1. Feed data to the function/script (from all of the sources)
  2. Have the function determine your order of operation and or interactions
  3. Get results from the function (and distribute to all necessary items)

Your parameters supply starting values for DropA, DropB, and DropC? But are stymied by the options arriving slightly later?

Yep, exactly.

@j.israelsen: I tried. Uncessfully. If you can find a different angle to tackle the problem, I'm all ears. Or, well, eyes.

Hmm. Do you have some sample data I can play with?

I can put together a minimum working view if that's what you're asking.
I'll replace the db tables and query bindings with custom props I guess.

Sure, but I'll load them in my DB playground. Named query timing (async behavior, more than absolute time) is certainly significant for this case.

1 Like

BUT, I also need to pre-load selections when the view is opened, based on parameters.
I couldn't find a way to reliably set the prop values of the 3 dropdowns.
If they're not set in order (dropA -> dropB -> dropC), the values gets reset because of the reset requirement described above.

From your initial description, it sounds like your method works except for pre-loading (because of the value change script or something like that)? That sounds like an edge-case. If that's the only issue, could you raise a flag on initialization such that you somehow disable your change script during initialization?

1 Like

I tried the semaphore way, but what will turn it off ?
If I can't control initialization order, then the flag might get turned off before initialization is finished.

So, I'd need multiple flags, and it's one of the messes I'm talking about in the first post that I'm trying to avoid.

will all 3 preinitial view params always be filled in (if any of them is)?

They're either all provided, or none of them is.

Here's a minimal working page.
Well, working is not exactly the proper word.
But the 'things' are there.

{
  "custom": {},
  "params": {
    "category_id": "",
    "family_id": "",
    "item_id": ""
  },
  "propConfig": {
    "params.category_id": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.family_id": {
      "paramDirection": "input",
      "persistent": true
    },
    "params.item_id": {
      "onChange": {
        "enabled": null,
        "script": "\tself.getChild(\"root\").getChild(\"families\").getChild(\"Dropdown\").props.value \u003d self.params.family_id\n\tself.getChild(\"root\").getChild(\"categories\").getChild(\"Dropdown\").props.value \u003d self.params.category_id\n\tself.getChild(\"root\").getChild(\"items\").getChild(\"Dropdown\").props.value \u003d self.params.item_id"
      },
      "paramDirection": "input",
      "persistent": true
    }
  },
  "props": {},
  "root": {
    "children": [
      {
        "children": [
          {
            "meta": {
              "name": "Label"
            },
            "position": {
              "basis": "150px"
            },
            "props": {
              "text": "families"
            },
            "type": "ia.display.label"
          },
          {
            "meta": {
              "name": "Dropdown"
            },
            "position": {
              "basis": "256px"
            },
            "propConfig": {
              "props.options": {
                "binding": {
                  "config": {
                    "queryPath": "sandbox/list_families"
                  },
                  "type": "query"
                }
              },
              "props.value": {
                "onChange": {
                  "enabled": null,
                  "script": "\tself.parent.parent.getChild(\"categories\").getChild(\"Dropdown\").props.value \u003d None"
                }
              }
            },
            "props": {
              "showClearIcon": true
            },
            "type": "ia.input.dropdown"
          },
          {
            "meta": {
              "name": "value"
            },
            "position": {
              "basis": "150px"
            },
            "propConfig": {
              "props.text": {
                "binding": {
                  "config": {
                    "path": "../Dropdown.props.value"
                  },
                  "type": "property"
                }
              }
            },
            "type": "ia.display.label"
          }
        ],
        "meta": {
          "name": "families"
        },
        "position": {
          "basis": "34px"
        },
        "props": {
          "style": {
            "gap": "3px"
          }
        },
        "type": "ia.container.flex"
      },
      {
        "children": [
          {
            "meta": {
              "name": "Label"
            },
            "position": {
              "basis": "150px"
            },
            "props": {
              "text": "categories"
            },
            "type": "ia.display.label"
          },
          {
            "meta": {
              "name": "Dropdown"
            },
            "position": {
              "basis": "256px"
            },
            "propConfig": {
              "props.options": {
                "binding": {
                  "config": {
                    "parameters": {
                      "family_id": "{.../families/Dropdown.props.value}"
                    },
                    "queryPath": "sandbox/list_categories"
                  },
                  "type": "query"
                }
              },
              "props.value": {
                "onChange": {
                  "enabled": null,
                  "script": "\tself.parent.parent.getChild(\"items\").getChild(\"Dropdown\").props.value \u003d None"
                }
              }
            },
            "props": {
              "showClearIcon": true
            },
            "type": "ia.input.dropdown"
          },
          {
            "meta": {
              "name": "value"
            },
            "position": {
              "basis": "150px"
            },
            "propConfig": {
              "props.text": {
                "binding": {
                  "config": {
                    "path": "../Dropdown.props.value"
                  },
                  "type": "property"
                }
              }
            },
            "type": "ia.display.label"
          }
        ],
        "meta": {
          "name": "categories"
        },
        "position": {
          "basis": "34px"
        },
        "props": {
          "style": {
            "gap": "3px"
          }
        },
        "type": "ia.container.flex"
      },
      {
        "children": [
          {
            "meta": {
              "name": "Label"
            },
            "position": {
              "basis": "150px"
            },
            "props": {
              "text": "items"
            },
            "type": "ia.display.label"
          },
          {
            "meta": {
              "name": "Dropdown"
            },
            "position": {
              "basis": "256px"
            },
            "propConfig": {
              "props.options": {
                "binding": {
                  "config": {
                    "parameters": {
                      "category_id": "{.../categories/Dropdown.props.value}"
                    },
                    "queryPath": "sandbox/list_items"
                  },
                  "type": "query"
                }
              }
            },
            "props": {
              "showClearIcon": true
            },
            "type": "ia.input.dropdown"
          },
          {
            "meta": {
              "name": "value"
            },
            "position": {
              "basis": "150px"
            },
            "propConfig": {
              "props.text": {
                "binding": {
                  "config": {
                    "path": "../Dropdown.props.value"
                  },
                  "type": "property"
                }
              }
            },
            "type": "ia.display.label"
          }
        ],
        "meta": {
          "name": "items"
        },
        "position": {
          "basis": "34px"
        },
        "props": {
          "style": {
            "gap": "3px"
          }
        },
        "type": "ia.container.flex"
      }
    ],
    "meta": {
      "name": "root"
    },
    "props": {
      "direction": "column",
      "style": {
        "gap": "10px",
        "padding": "15px"
      }
    },
    "type": "ia.container.flex"
  }
}

and another page used to navigate to it with parameters:

{
  "custom": {},
  "params": {},
  "props": {},
  "root": {
    "children": [
      {
        "children": [
          {
            "meta": {
              "name": "Label"
            },
            "position": {
              "basis": "150px"
            },
            "props": {
              "text": "families"
            },
            "type": "ia.display.label"
          },
          {
            "meta": {
              "name": "Dropdown"
            },
            "position": {
              "basis": "256px"
            },
            "propConfig": {
              "props.options": {
                "binding": {
                  "config": {
                    "queryPath": "sandbox/list_families"
                  },
                  "type": "query"
                }
              }
            },
            "props": {
              "showClearIcon": true,
              "value": null
            },
            "type": "ia.input.dropdown"
          }
        ],
        "meta": {
          "name": "families"
        },
        "position": {
          "basis": "34px"
        },
        "type": "ia.container.flex"
      },
      {
        "children": [
          {
            "meta": {
              "name": "Label"
            },
            "position": {
              "basis": "150px"
            },
            "props": {
              "text": "categories"
            },
            "type": "ia.display.label"
          },
          {
            "meta": {
              "name": "Dropdown"
            },
            "position": {
              "basis": "256px"
            },
            "propConfig": {
              "props.options": {
                "binding": {
                  "config": {
                    "parameters": {
                      "family_id": "{.../families/Dropdown.props.value}"
                    },
                    "queryPath": "sandbox/list_categories"
                  },
                  "type": "query"
                }
              }
            },
            "props": {
              "showClearIcon": true,
              "value": 9
            },
            "type": "ia.input.dropdown"
          }
        ],
        "meta": {
          "name": "categories"
        },
        "position": {
          "basis": "34px"
        },
        "type": "ia.container.flex"
      },
      {
        "children": [
          {
            "meta": {
              "name": "Label"
            },
            "position": {
              "basis": "150px"
            },
            "props": {
              "text": "items"
            },
            "type": "ia.display.label"
          },
          {
            "meta": {
              "name": "Dropdown"
            },
            "position": {
              "basis": "256px"
            },
            "propConfig": {
              "props.options": {
                "binding": {
                  "config": {
                    "parameters": {
                      "category_id": "{.../categories/Dropdown.props.value}"
                    },
                    "queryPath": "sandbox/list_items"
                  },
                  "type": "query"
                }
              }
            },
            "props": {
              "showClearIcon": true,
              "value": null
            },
            "type": "ia.input.dropdown"
          }
        ],
        "meta": {
          "name": "items"
        },
        "position": {
          "basis": "34px"
        },
        "type": "ia.container.flex"
      },
      {
        "events": {
          "component": {
            "onActionPerformed": {
              "config": {
                "params": {
                  "category_id": "{/root/categories/Dropdown.props.value}",
                  "family_id": "{/root/families/Dropdown.props.value}",
                  "item_id": "{/root/items/Dropdown.props.value}"
                },
                "view": "VUES/SUIVI_OUTILLAGE/sandbox/mainpage"
              },
              "scope": "C",
              "type": "nav"
            }
          }
        },
        "meta": {
          "name": "Button"
        },
        "position": {
          "basis": "34px"
        },
        "type": "ia.input.button"
      }
    ],
    "meta": {
      "name": "root"
    },
    "props": {
      "direction": "column",
      "style": {
        "gap": "10px",
        "padding": "15px"
      }
    },
    "type": "ia.container.flex"
  }
}

and some queries to create/fill the tables used (MSSQL):

create table families (
	id int identity primary key,
	name nvarchar(50)
)

create table categories (
	id int identity primary key,
	family_id int references families (id),
	name nvarchar(50)
)

create table items (
	id int identity primary key,
	category_id int references categories (id),
	name nvarchar(50)
)

insert into families (name)
values
	('familyA'),
	('familyB'),
	('familyC'),
	('familyD')

insert into categories (name, family_id)
values
	('category1A', 1),
	('category1B', 1),
	('category1C', 1),
	('category1D', 1),
	('category2A', 2),
	('category2B', 2),
	('category2C', 2),
	('category2D', 2),
	('category3A', 3),
	('category3B', 3),
	('category3C', 3),
	('category3D', 3),
	('category4A', 4),
	('category4B', 4),
	('category4C', 4),
	('category4D', 4)


insert into items (name, category_id)
values
	('1AA', 1),
	('1AB', 1),
	('1AC', 1),
	('1AD', 1),
	('1BA', 2),
	('1BB', 2),
	('1BC', 2),
	('1BD', 2),
	('1CA', 3),
	('1CB', 3),
	('1CC', 3),
	('1CD', 3),
	('1DA', 4),
	('1DB', 4),
	('1DC', 4),
	('1DD', 4),
	('2AA', 5),
	('2AB', 5),
	('2AC', 5),
	('2AD', 5),
	('2BA', 6),
	('2BB', 6),
	('2BC', 6),
	('2BD', 6),
	('2CA', 7),
	('2CB', 7),
	('2CC', 7),
	('2CD', 7),
	('2DA', 8),
	('2DB', 8),
	('2DC', 8),
	('2DD', 8),
	('3AA', 9),
	('3AB', 9),
	('3AC', 9),
	('3AD', 9),
	('3BA', 10),
	('3BB', 10),
	('3BC', 10),
	('3BD', 10),
	('3CA', 11),
	('3CB', 11),
	('3CC', 11),
	('3CD', 11),
	('3DA', 12),
	('3DB', 12),
	('3DC', 12),
	('3DD', 12),
	('4AA', 13),
	('4AB', 13),
	('4AC', 13),
	('4AD', 13),
	('4BA', 14),
	('4BB', 14),
	('4BC', 14),
	('4BD', 14),
	('4CA', 15),
	('4CB', 15),
	('4CC', 15),
	('4CD', 15),
	('4DA', 16),
	('4DB', 16),
	('4DC', 16),
	('4DD', 16)
1 Like

I haven't had a chance to look at your sample page, but here was a quick thought on enabling or disabling code. If you had a single function or message handler, perhaps could you could enable function items (like startup versus change of value) with basic IF statements, something like this:

def update(self):

#Only call if not already updating
if self.custom.updatingFlag == False:
	
	#Startup
	if self.custom.updatingFlag == False and startup:
		self.custom.updatingFlag = True
		#(CODE)
		
	#Update A (Clear B & C)
	if self.custom.updatingFlag == False and updateA:
		self.custom.updatingFlag = True
		#(CODE)
		
	#Update B (Clear C)
	if self.custom.updatingFlag == False and updateB:
		self.custom.updatingFlag = True
		#(CODE)

	#Clear Updating Flag
	self.custom.updatingFlag = False

*Edit, missed a tab.

1 Like

I'm assuming updateA and updateB are also flags ?
I envisioned something like this, but as I said I'd like to try to find a simpler solution first.

Not that it's particularly complicated, but I feel like there's a 'better' way.