How to detect the item that was expanded/collapsed in a Tree component?

I don’t know if I’m wasting my time.. but I’m attempting to create a historical tag tree browser with filtering ability. I’ve got a few things I want to include though, like:

  • include the tooltip of tags in the tree nodes (e.g. “Tag name - This is the tooltip text”)
  • include the description parameter of Udt instances, if available (e.g. “ABC-PMP01 - Tank Outlet Pump”)
  • ability to filter on tagpath as well as the extra info (tooltip or udt instance description)
  • [expand]/collapse all nodes (might have to think about this when expanding for large numbers of tags)

Perhaps obviously, when I set the Tree’s props.items to include a moderate number of tags, the web socket has a hissy fit and the client goes in and out of being connected.

So I wanted to implement something “smarter” and update the items with only the branches shown. Upon expanding/collapsing the nodes, I want to trigger the items to be regenerated again to show or remove the nodes. But there’s no “onNodeExpand/Collapse” event handler on the tree :confused:

I have some javascript mutation observer that can fire whenever an item is expanded/collapsed, however I have no idea how to get the node path so I can do something useful with it… (I can provide it but it’s a dodgy AI-written monstrosity that has some issues at the moment)

I don’t know if @victordcq is still on here :slight_smile: or @bmusson perhaps can help (sorry for the pings)

Or maybe I should just leave this beast alone

We do this in Vision but haven’t been able to use the treeview in Perpective the same way.

We switched over to using a table view with a single column visible (toolTip/TagName) and the other values in hidden columns. It is fully filterable and has all of the needed information.

I haven’t been on here for months yet here i am within a workday reading the first ping mentioning me (with a question) :magic_wand:

I dont have ignition installed on my laptop anymore though… And im not really going to do that.
But i can guide you with the script if you give me the relative html bit of a tree component.

i remember it was possible to find the component throught he _client.views

basicly you need to use the compannt path to navigate trhough the childeren of the view to the correct component and do something liek this

view.root.childComponents[0].childComponents[0].props.read(‘items’)

(todo replace childComponents[0] with something smart using the 'data-component-path'
like i always use to get the view

where

be sure to use the read function as the static props you could read out here are not dynamic through bindings

(edit im not sure i know what you want xD)

util_trend.txt (14.4 KB)

I built something similar that maybe you can use or take parts from? I built it for a table rather than a tag tree browser though but similar functionality to what you’re trying to do. It pulls all the tags into a table, with columns for pad (area), tag name, description, datatype, and path. Then users select a tag from the table and hit add to push it into a powerchart’s pens binding. The textfield does a filter search on everything so they can just type partials of area, tag, desc and filter to that.

  1. It’s also mostly AI-supported coding but it works perfectly in my application.
  2. It works for ~thousands of tags. It struggles with > ten thousand tags. A pre-filter by area would certainly improve that.
  3. I put types of UDTs to include, tag keywords to include as well as tag keywords to exclude to give control over what I do/don’t want in the list.
  4. Around line 150 there are also overrides for certain tags that have different structures to point them to their correct values, for example in our dual PID UDT there’s two different tag names

Usage wise:

On view startup -> self.view.custom.taglist = util_trend.getTagDataset()

Within the view -> filteredTagList binded to textfield with script transform:
	dataset = self.custom.taglist
	return util_trend.filterTagDataset(dataset, value)



@Nol and @MMaynard

A table was what I was originally using, and it’s ok but it’s not hierarchical which is why I wanted to move to a Tree instead.

My first thought was “Is your table set to virtualised?”, but then I considered that a Tree is essentially virualised as well (only the tree nodes that are visible are actually in the DOM), yet I’m still having issues due to the size of the items structure, in a similar way to how a Table would presumably still have issues with large data structure despite being set to virtualised.

@victordcq sorry to wake you from your sleep from Ignition! (if my memory serves me correct for once in my life, you moved onto coding for games?? - You know there was the Ignition Arcade at ICC the last 2 years right…? :grinning_face_with_smiling_eyes:)

But here is the entire HTML element for an example Tree:

<div data-component="ia.display.tree" data-component-path="D.0:2" style="border: 1px solid rgb(43, 43, 43); margin: 0.3rem; flex: 1 1 200px;">
	<div class="tree">
		<div class="node-wrapper">
			<div class="parent-node" draggable="false" data-item-path="0" data-label="Testing">
				<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
					<div class="tree-item" data-label="Testing" style="padding-left: 0px; height: 24px;">
						<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
							<g>
								<g class="icon" id="arrow_drop_down">
									<path d="M7 10l5 5 5-5z"/>
								</g>
							</g>
						</svg>
						<div class="tree-item-label">
							<div class="label-wrapper label-wrapper-icon">
								<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-folder">
									<g>
										<path id="path1" d="m2.5074 5.5v8h10.95v-8z" stroke="#5c7080"/>
										<path id="path3" d="m2.0074 4.4509h11.993v-1.0882h-7s0-1-2.5018-1c-2.4982 0-2.4982 1-2.4982 1z"/>
									</g>
								</svg>
							</div>
							<div class="label-wrapper label-wrapper-text">
								<div class="text-scroll">Testing</div>
							</div>
						</div>
					</div>
				</div>
				<div class="parent-node" draggable="false" data-item-path="0/0" data-label="Test">
					<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
						<div class="tree-item" data-label="Test" style="padding-left: 20px; height: 24px;">
							<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
								<g>
									<g class="icon" id="arrow_drop_down">
										<path d="M7 10l5 5 5-5z"/>
									</g>
								</g>
							</svg>
							<div class="tree-item-label">
								<div class="label-wrapper label-wrapper-icon">
									<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-folder">
										<g>
											<path id="path1" d="m2.5074 5.5v8h10.95v-8z" stroke="#5c7080"/>
											<path id="path3" d="m2.0074 4.4509h11.993v-1.0882h-7s0-1-2.5018-1c-2.4982 0-2.4982 1-2.4982 1z"/>
										</g>
									</svg>
								</div>
								<div class="label-wrapper label-wrapper-text">
									<div class="text-scroll">Test</div>
								</div>
							</div>
						</div>
					</div>
					<div class="parent-node" draggable="false" data-item-path="0/0/0" data-label="WT111">
						<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
							<div class="tree-item" data-label="WT111" style="padding-left: 40px; height: 24px;">
								<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
									<g>
										<g class="icon" id="arrow_drop_down">
											<path d="M7 10l5 5 5-5z"/>
										</g>
									</g>
								</svg>
								<div class="tree-item-label">
									<div class="label-wrapper label-wrapper-icon">
										<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-udtinstance">
											<g>
												<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#fff" stroke="#5c7080"/>
												<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#5c7080"/>
											</g>
										</svg>
									</div>
									<div class="label-wrapper label-wrapper-text">
										<div class="text-scroll">WT111</div>
									</div>
								</div>
							</div>
						</div>
						<div class="parent-node" draggable="false" data-item-path="0/0/0/0" data-label="Displacement Sensor 1 -  Sensor 1">
							<div class="tree-alignment vert-alignment ia_treeComponent__alignmentGuide" style="left: 72px; top: 24px; height: calc(100% - 24px);"/>
							<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
								<div class="tree-item" data-label="Displacement Sensor 1 -  Sensor 1" style="padding-left: 60px; height: 24px;">
									<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
										<g>
											<g class="icon" id="arrow_drop_down">
												<path d="M7 10l5 5 5-5z"/>
											</g>
										</g>
									</svg>
									<div class="tree-item-label">
										<div class="label-wrapper label-wrapper-icon">
											<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-udtinstance">
												<g>
													<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#fff" stroke="#5c7080"/>
													<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#5c7080"/>
												</g>
											</svg>
										</div>
										<div class="label-wrapper label-wrapper-text">
											<div class="text-scroll">Displacement Sensor 1 -  Sensor 1</div>
										</div>
									</div>
								</div>
							</div>
							<div class="parent-node" draggable="false" data-item-path="0/0/0/0/0" data-label="Sts">
								<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
									<div class="tree-item" data-label="Sts" style="padding-left: 80px; height: 24px;">
										<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
											<g>
												<g class="icon" id="arrow_drop_down">
													<path d="M7 10l5 5 5-5z"/>
												</g>
											</g>
										</svg>
										<div class="tree-item-label">
											<div class="label-wrapper label-wrapper-icon">
												<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-folder">
													<g>
														<path id="path1" d="m2.5074 5.5v8h10.95v-8z" stroke="#5c7080"/>
														<path id="path3" d="m2.0074 4.4509h11.993v-1.0882h-7s0-1-2.5018-1c-2.4982 0-2.4982 1-2.4982 1z"/>
													</g>
												</svg>
											</div>
											<div class="label-wrapper label-wrapper-text">
												<div class="text-scroll">Sts</div>
											</div>
										</div>
									</div>
								</div>
								<div class="terminal-node" data-item-path="0/0/0/0/0/0" data-label="Filtered - Filtered and zero-offset position value">
									<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--collapsed">
										<div class="tree-alignment terminal-alignment ia_treeComponent__alignmentGuide" style="left: 112px;">
											<div class="cross-alignment ia_treeComponent__alignmentGuide"/>
										</div>
										<div class="tree-item" data-label="Filtered - Filtered and zero-offset position value" style="padding-left: 100px; height: 24px;">
											<svg viewBox="0 0 24 24" class="terminal-expand-icon ia_treeComponent__terminalExpandIcon" data-icon=""/>
											<div class="tree-item-label">
												<div class="label-wrapper label-wrapper-icon">
													<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--collapsed" data-icon="icons/tag-notype">
														<g>
															<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#829db3" stroke="#829db3"/>
															<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#fff"/>
														</g>
													</svg>
												</div>
												<div class="label-wrapper label-wrapper-text">
													<div class="text-scroll">Filtered - Filtered and zero-offset position value</div>
												</div>
											</div>
										</div>
									</div>
								</div>
								<div class="terminal-node" data-item-path="0/0/0/0/0/1" data-label="Raw - The raw position value from the instrument">
									<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--collapsed">
										<div class="tree-alignment terminal-alignment ia_treeComponent__alignmentGuide last-child" style="left: 112px;">
											<div class="cross-alignment ia_treeComponent__alignmentGuide"/>
										</div>
										<div class="tree-item" data-label="Raw - The raw position value from the instrument" style="padding-left: 100px; height: 24px;">
											<svg viewBox="0 0 24 24" class="terminal-expand-icon ia_treeComponent__terminalExpandIcon" data-icon=""/>
											<div class="tree-item-label">
												<div class="label-wrapper label-wrapper-icon">
													<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--collapsed" data-icon="icons/tag-notype">
														<g>
															<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#829db3" stroke="#829db3"/>
															<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#fff"/>
														</g>
													</svg>
												</div>
												<div class="label-wrapper label-wrapper-text">
													<div class="text-scroll">Raw - The raw position value from the instrument</div>
												</div>
											</div>
										</div>
									</div>
								</div>
							</div>
						</div>
						<div class="parent-node" draggable="false" data-item-path="0/0/0/1" data-label="Displacement Sensor 2 -  Sensor 2">
							<div class="tree-alignment vert-alignment ia_treeComponent__alignmentGuide" style="left: 72px; top: 24px; height: calc(100% - 24px);"/>
							<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
								<div class="tree-item" data-label="Displacement Sensor 2 -  Sensor 2" style="padding-left: 60px; height: 24px;">
									<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
										<g>
											<g class="icon" id="arrow_drop_down">
												<path d="M7 10l5 5 5-5z"/>
											</g>
										</g>
									</svg>
									<div class="tree-item-label">
										<div class="label-wrapper label-wrapper-icon">
											<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-udtinstance">
												<g>
													<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#fff" stroke="#5c7080"/>
													<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#5c7080"/>
												</g>
											</svg>
										</div>
										<div class="label-wrapper label-wrapper-text">
											<div class="text-scroll">Displacement Sensor 2 -  Sensor 2</div>
										</div>
									</div>
								</div>
							</div>
							<div class="parent-node" draggable="false" data-item-path="0/0/0/1/0" data-label="Sts">
								<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
									<div class="tree-item" data-label="Sts" style="padding-left: 80px; height: 24px;">
										<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
											<g>
												<g class="icon" id="arrow_drop_down">
													<path d="M7 10l5 5 5-5z"/>
												</g>
											</g>
										</svg>
										<div class="tree-item-label">
											<div class="label-wrapper label-wrapper-icon">
												<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-folder">
													<g>
														<path id="path1" d="m2.5074 5.5v8h10.95v-8z" stroke="#5c7080"/>
														<path id="path3" d="m2.0074 4.4509h11.993v-1.0882h-7s0-1-2.5018-1c-2.4982 0-2.4982 1-2.4982 1z"/>
													</g>
												</svg>
											</div>
											<div class="label-wrapper label-wrapper-text">
												<div class="text-scroll">Sts</div>
											</div>
										</div>
									</div>
								</div>
								<div class="terminal-node" data-item-path="0/0/0/1/0/0" data-label="Filtered - Filtered and zero-offset position value">
									<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--collapsed">
										<div class="tree-alignment terminal-alignment ia_treeComponent__alignmentGuide" style="left: 112px;">
											<div class="cross-alignment ia_treeComponent__alignmentGuide"/>
										</div>
										<div class="tree-item" data-label="Filtered - Filtered and zero-offset position value" style="padding-left: 100px; height: 24px;">
											<svg viewBox="0 0 24 24" class="terminal-expand-icon ia_treeComponent__terminalExpandIcon" data-icon=""/>
											<div class="tree-item-label">
												<div class="label-wrapper label-wrapper-icon">
													<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--collapsed" data-icon="icons/tag-notype">
														<g>
															<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#829db3" stroke="#829db3"/>
															<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#fff"/>
														</g>
													</svg>
												</div>
												<div class="label-wrapper label-wrapper-text">
													<div class="text-scroll">Filtered - Filtered and zero-offset position value</div>
												</div>
											</div>
										</div>
									</div>
								</div>
								<div class="terminal-node" data-item-path="0/0/0/1/0/1" data-label="Raw - The raw position value from the instrument">
									<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--collapsed">
										<div class="tree-alignment terminal-alignment ia_treeComponent__alignmentGuide last-child" style="left: 112px;">
											<div class="cross-alignment ia_treeComponent__alignmentGuide"/>
										</div>
										<div class="tree-item" data-label="Raw - The raw position value from the instrument" style="padding-left: 100px; height: 24px;">
											<svg viewBox="0 0 24 24" class="terminal-expand-icon ia_treeComponent__terminalExpandIcon" data-icon=""/>
											<div class="tree-item-label">
												<div class="label-wrapper label-wrapper-icon">
													<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--collapsed" data-icon="icons/tag-notype">
														<g>
															<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#829db3" stroke="#829db3"/>
															<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#fff"/>
														</g>
													</svg>
												</div>
												<div class="label-wrapper label-wrapper-text">
													<div class="text-scroll">Raw - The raw position value from the instrument</div>
												</div>
											</div>
										</div>
									</div>
								</div>
							</div>
						</div>
						<div class="parent-node" draggable="false" data-item-path="0/0/0/2" data-label="Displacement Sensor 3 -  Sensor 3">
							<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
								<div class="tree-item" data-label="Displacement Sensor 3 -  Sensor 3" style="padding-left: 60px; height: 24px;">
									<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
										<g>
											<g class="icon" id="arrow_drop_down">
												<path d="M7 10l5 5 5-5z"/>
											</g>
										</g>
									</svg>
									<div class="tree-item-label">
										<div class="label-wrapper label-wrapper-icon">
											<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-udtinstance">
												<g>
													<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#fff" stroke="#5c7080"/>
													<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#5c7080"/>
												</g>
											</svg>
										</div>
										<div class="label-wrapper label-wrapper-text">
											<div class="text-scroll">Displacement Sensor 3 -  Sensor 3</div>
										</div>
									</div>
								</div>
							</div>
							<div class="parent-node" draggable="false" data-item-path="0/0/0/2/0" data-label="Sts">
								<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--expanded">
									<div class="tree-item" data-label="Sts" style="padding-left: 80px; height: 24px;">
										<svg viewBox="0 0 24 24" class="expand-icon ia_treeComponent__expandIcon ia_treeComponent__expandIcon--expanded" data-icon="material/arrow_drop_down">
											<g>
												<g class="icon" id="arrow_drop_down">
													<path d="M7 10l5 5 5-5z"/>
												</g>
											</g>
										</svg>
										<div class="tree-item-label">
											<div class="label-wrapper label-wrapper-icon">
												<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--expanded" data-icon="icons/tag-folder">
													<g>
														<path id="path1" d="m2.5074 5.5v8h10.95v-8z" stroke="#5c7080"/>
														<path id="path3" d="m2.0074 4.4509h11.993v-1.0882h-7s0-1-2.5018-1c-2.4982 0-2.4982 1-2.4982 1z"/>
													</g>
												</svg>
											</div>
											<div class="label-wrapper label-wrapper-text">
												<div class="text-scroll">Sts</div>
											</div>
										</div>
									</div>
								</div>
								<div class="terminal-node" data-item-path="0/0/0/2/0/0" data-label="Filtered - Filtered and zero-offset position value">
									<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--collapsed">
										<div class="tree-alignment terminal-alignment ia_treeComponent__alignmentGuide" style="left: 112px;">
											<div class="cross-alignment ia_treeComponent__alignmentGuide"/>
										</div>
										<div class="tree-item" data-label="Filtered - Filtered and zero-offset position value" style="padding-left: 100px; height: 24px;">
											<svg viewBox="0 0 24 24" class="terminal-expand-icon ia_treeComponent__terminalExpandIcon" data-icon=""/>
											<div class="tree-item-label">
												<div class="label-wrapper label-wrapper-icon">
													<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--collapsed" data-icon="icons/tag-notype">
														<g>
															<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#829db3" stroke="#829db3"/>
															<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#fff"/>
														</g>
													</svg>
												</div>
												<div class="label-wrapper label-wrapper-text">
													<div class="text-scroll">Filtered - Filtered and zero-offset position value</div>
												</div>
											</div>
										</div>
									</div>
								</div>
								<div class="terminal-node" data-item-path="0/0/0/2/0/1" data-label="Raw - The raw position value from the instrument">
									<div class="tree-row ia_treeComponent__node  ia_treeComponent__node--collapsed">
										<div class="tree-alignment terminal-alignment ia_treeComponent__alignmentGuide last-child" style="left: 112px;">
											<div class="cross-alignment ia_treeComponent__alignmentGuide"/>
										</div>
										<div class="tree-item" data-label="Raw - The raw position value from the instrument" style="padding-left: 100px; height: 24px;">
											<svg viewBox="0 0 24 24" class="terminal-expand-icon ia_treeComponent__terminalExpandIcon" data-icon=""/>
											<div class="tree-item-label">
												<div class="label-wrapper label-wrapper-icon">
													<svg viewBox="0 0 16 16" class="node-icon ia_treeComponent__node__icon ia_treeComponent__node__icon--collapsed" data-icon="icons/tag-notype">
														<g>
															<path id="path1" d="m2.5074 2.5063v10.987h8.9505l2.9835-5.4937-2.9835-5.4937z" fill="#829db3" stroke="#829db3"/>
															<circle id="circle2" cx="11.334" cy="8" r="1.2618" fill="#fff"/>
														</g>
													</svg>
												</div>
												<div class="label-wrapper label-wrapper-text">
													<div class="text-scroll">Raw - The raw position value from the instrument</div>
												</div>
											</div>
										</div>
									</div>
								</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
</div>

And this is where I copied it from and the Tree itself:

I want to be able to expand or collapse a tree node and know which node was modified

You can tell if a node is expanded or collapsed looking at its classes:

  • ia_treeComponent__node--expanded
  • ia_treeComponent__node--collapsed

So I was using a mutation observer looking for these changes… :grimacing:

I know that I can use this below to write to a view custom prop, so I just need to find the node that I modified:

const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path').value.split('.')[0]).value;
view.custom.write('TEST', 'Helloooo');

I got it :open_mouth: (well.. Claude Grok got it, with Victor’s magic to write to the view’s custom prop)

Markdown component (sorry Ben… I need it to be useable for projects that can’t include 3rd party modules) below:

[
  {
    "type": "ia.display.markdown",
    "version": 0,
    "props": {
      "markdown": {
        "escapeHtml": false
      }
    },
    "meta": {
      "name": "JSInjector"
    },
    "position": {
      "x": 600,
      "y": -0.375,
      "height": 200,
      "width": 200
    },
    "custom": {
      "description": "Write to a view custom prop called \"TEST\" => \"bobby\"",
      "jsCode": "const tree = document.querySelector('.tree');\nif (tree) {\n  const observer = new MutationObserver((mutations) => {\n    mutations.forEach((mutation) => {\n      // Only process if the target is an expand icon\n      if (mutation.target.classList.contains('expand-icon')) {\n        const node = mutation.target.closest('[data-item-path]');\n        if (!node) return;\n        const path = node.dataset.itemPath;\n        const isExpanded = mutation.target.dataset.icon === 'material/arrow_drop_down';\n        const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path').value.split('.')[0]).value;\n        view.custom.write('nodePath', path);\n        view.custom.write('nodePathChange', isExpanded ? 'expanded' : 'collapsed');\n      }\n    });\n  });\n  observer.observe(tree, {\n    attributes: true,\n    subtree: true,\n    attributeFilter: ['data-icon']\n  });\n}",
      "notes": "Don't use \" in the jsCode. New lines can be funny.."
    },
    "propConfig": {
      "props.source": {
        "binding": {
          "type": "expr",
          "config": {
            "expression": "{this.custom.jsCode}"
          },
          "transforms": [
            {
              "code": "\tcode =  '''<img style=\"display:none\" src=\"/favicon.ico\" onload=\"\n''' + value + '\"/>'\n\treturn code",
              "type": "script"
            }
          ]
        }
      }
    }
  }
]

Here’s a test view:

{
  "custom": {
    "nodePath": "0/0/0",
    "nodePathChange": "collapsed"
  },
  "params": {},
  "propConfig": {
    "custom.nodePath": {
      "persistent": true
    },
    "custom.nodePathChange": {
      "persistent": true
    }
  },
  "props": {},
  "root": {
    "children": [
      {
        "meta": {
          "name": "Tree_0"
        },
        "position": {
          "height": 690,
          "width": 530,
          "x": 20,
          "y": 66
        },
        "props": {
          "appearance": {
            "defaultNodeIcons": {
              "collapsed": {
                "path": "material/folder"
              },
              "empty": {
                "path": "material/stop"
              },
              "expanded": {
                "path": "material/folder_open"
              }
            },
            "expandIcons": {
              "collapsed": {
                "path": "material/arrow_right"
              },
              "expanded": {
                "path": "material/arrow_drop_down"
              }
            }
          },
          "items": [
            {
              "data": {
                "tagPath": "Testing",
                "tagType": "Folder"
              },
              "expanded": true,
              "icon": {
                "path": "icons/tag-folder"
              },
              "items": [
                {
                  "data": {
                    "tagPath": "Testing/Test",
                    "tagType": "Folder"
                  },
                  "expanded": true,
                  "icon": {
                    "path": "icons/tag-folder"
                  },
                  "items": [
                    {
                      "data": {
                        "Parameters": {
                          "Description": "",
                          "Sensor1IPAddress": "",
                          "Sensor2IPAddress": "",
                          "Sensor3IPAddress": ""
                        },
                        "tagPath": "Testing/Test/WT111",
                        "tagType": "UdtInstance",
                        "type": "UdtInstance"
                      },
                      "expanded": false,
                      "icon": {
                        "path": "icons/tag-udtinstance"
                      },
                      "items": [
                        {
                          "data": {
                            "Parameters": {
                              "Alarm_Area": "",
                              "Alarm_ParentDevice": "",
                              "Description": " Sensor 1",
                              "DeviceName": "",
                              "Global.": "",
                              "PLCName": "",
                              "SMSActivePipeline": ""
                            },
                            "tagPath": "Testing/Test/WT111/Displacement Sensor 1",
                            "tagType": "UdtInstance",
                            "type": "UdtInstance"
                          },
                          "expanded": true,
                          "icon": {
                            "path": "icons/tag-udtinstance"
                          },
                          "items": [
                            {
                              "data": {
                                "tagPath": "Testing/Test/WT111/Displacement Sensor 1/Sts",
                                "tagType": "Folder"
                              },
                              "expanded": false,
                              "icon": {
                                "path": "icons/tag-folder"
                              },
                              "items": [
                                {
                                  "data": {
                                    "dataType": "Float8",
                                    "tagPath": "Testing/Test/WT111/Displacement Sensor 1/Sts/Filtered",
                                    "tagType": "AtomicTag",
                                    "tooltip": "Filtered and zero-offset position value",
                                    "type": "memory",
                                    "valueSource": "memory"
                                  },
                                  "expanded": false,
                                  "icon": {
                                    "path": "icons/tag-notype"
                                  },
                                  "items": [],
                                  "label": "Filtered - Filtered and zero-offset position value"
                                },
                                {
                                  "data": {
                                    "dataType": "Float8",
                                    "tagPath": "Testing/Test/WT111/Displacement Sensor 1/Sts/Raw",
                                    "tagType": "AtomicTag",
                                    "tooltip": "The raw position value from the instrument",
                                    "type": "memory",
                                    "valueSource": "memory"
                                  },
                                  "expanded": false,
                                  "icon": {
                                    "path": "icons/tag-notype"
                                  },
                                  "items": [],
                                  "label": "Raw - The raw position value from the instrument"
                                }
                              ],
                              "label": "Sts"
                            }
                          ],
                          "label": "Displacement Sensor 1 -  Sensor 1"
                        },
                        {
                          "data": {
                            "Parameters": {
                              "Alarm_Area": "",
                              "Alarm_ParentDevice": "",
                              "Description": " Sensor 2",
                              "DeviceName": "",
                              "Global.": "",
                              "PLCName": "",
                              "SMSActivePipeline": ""
                            },
                            "tagPath": "Testing/Test/WT111/Displacement Sensor 2",
                            "tagType": "UdtInstance",
                            "type": "UdtInstance"
                          },
                          "expanded": true,
                          "icon": {
                            "path": "icons/tag-udtinstance"
                          },
                          "items": [
                            {
                              "data": {
                                "tagPath": "Testing/Test/WT111/Displacement Sensor 2/Sts",
                                "tagType": "Folder"
                              },
                              "expanded": false,
                              "icon": {
                                "path": "icons/tag-folder"
                              },
                              "items": [
                                {
                                  "data": {
                                    "dataType": "Float8",
                                    "tagPath": "Testing/Test/WT111/Displacement Sensor 2/Sts/Filtered",
                                    "tagType": "AtomicTag",
                                    "tooltip": "Filtered and zero-offset position value",
                                    "type": "memory",
                                    "valueSource": "memory"
                                  },
                                  "expanded": false,
                                  "icon": {
                                    "path": "icons/tag-notype"
                                  },
                                  "items": [],
                                  "label": "Filtered - Filtered and zero-offset position value"
                                },
                                {
                                  "data": {
                                    "dataType": "Float8",
                                    "tagPath": "Testing/Test/WT111/Displacement Sensor 2/Sts/Raw",
                                    "tagType": "AtomicTag",
                                    "tooltip": "The raw position value from the instrument",
                                    "type": "memory",
                                    "valueSource": "memory"
                                  },
                                  "expanded": false,
                                  "icon": {
                                    "path": "icons/tag-notype"
                                  },
                                  "items": [],
                                  "label": "Raw - The raw position value from the instrument"
                                }
                              ],
                              "label": "Sts"
                            }
                          ],
                          "label": "Displacement Sensor 2 -  Sensor 2"
                        },
                        {
                          "data": {
                            "Parameters": {
                              "Alarm_Area": "",
                              "Alarm_ParentDevice": "",
                              "Description": " Sensor 3",
                              "DeviceName": "",
                              "Global.": "",
                              "PLCName": "",
                              "SMSActivePipeline": ""
                            },
                            "tagPath": "Testing/Test/WT111/Displacement Sensor 3",
                            "tagType": "UdtInstance",
                            "type": "UdtInstance"
                          },
                          "expanded": true,
                          "icon": {
                            "path": "icons/tag-udtinstance"
                          },
                          "items": [
                            {
                              "data": {
                                "tagPath": "Testing/Test/WT111/Displacement Sensor 3/Sts",
                                "tagType": "Folder"
                              },
                              "expanded": false,
                              "icon": {
                                "path": "icons/tag-folder"
                              },
                              "items": [
                                {
                                  "data": {
                                    "dataType": "Float8",
                                    "tagPath": "Testing/Test/WT111/Displacement Sensor 3/Sts/Filtered",
                                    "tagType": "AtomicTag",
                                    "tooltip": "Filtered and zero-offset position value",
                                    "type": "memory",
                                    "valueSource": "memory"
                                  },
                                  "expanded": false,
                                  "icon": {
                                    "path": "icons/tag-notype"
                                  },
                                  "items": [],
                                  "label": "Filtered - Filtered and zero-offset position value"
                                },
                                {
                                  "data": {
                                    "dataType": "Float8",
                                    "tagPath": "Testing/Test/WT111/Displacement Sensor 3/Sts/Raw",
                                    "tagType": "AtomicTag",
                                    "tooltip": "The raw position value from the instrument",
                                    "type": "memory",
                                    "valueSource": "memory"
                                  },
                                  "expanded": false,
                                  "icon": {
                                    "path": "icons/tag-notype"
                                  },
                                  "items": [],
                                  "label": "Raw - The raw position value from the instrument"
                                }
                              ],
                              "label": "Sts"
                            }
                          ],
                          "label": "Displacement Sensor 3 -  Sensor 3"
                        }
                      ],
                      "label": "WT111"
                    }
                  ],
                  "label": "Test"
                }
              ],
              "label": "Testing"
            }
          ],
          "selection": [
            "0/0/0/2/0/1"
          ],
          "selectionData": [
            {
              "itemPath": "0/0/0/2/0/1",
              "value": {
                "dataType": "Float8",
                "tagPath": "Testing/Test/WT111/Displacement Sensor 3/Sts/Raw",
                "tagType": "AtomicTag",
                "tooltip": "The raw position value from the instrument",
                "type": "memory",
                "valueSource": "memory"
              }
            }
          ],
          "style": {
            "border": "1px solid #2b2b2b",
            "margin": "0.3rem"
          }
        },
        "type": "ia.display.tree"
      },
      {
        "custom": {
          "description": "Write to a view custom prop called \"TEST\" \u003d\u003e \"bobby\"",
          "jsCode": "const tree \u003d document.querySelector(\u0027.tree\u0027);\nif (tree) {\n  const observer \u003d new MutationObserver((mutations) \u003d\u003e {\n    mutations.forEach((mutation) \u003d\u003e {\n      // Only process if the target is an expand icon\n      if (mutation.target.classList.contains(\u0027expand-icon\u0027)) {\n        const node \u003d mutation.target.closest(\u0027[data-item-path]\u0027);\n        if (!node) return;\n        const path \u003d node.dataset.itemPath;\n        const isExpanded \u003d mutation.target.dataset.icon \u003d\u003d\u003d \u0027material/arrow_drop_down\u0027;\n        const view \u003d [...window.__client.page.views._data.values()].find(view \u003d\u003e view.value.mountPath \u003d\u003d this.parentNode.parentNode.parentNode.getAttributeNode(\u0027data-component-path\u0027).value.split(\u0027.\u0027)[0]).value;\n        view.custom.write(\u0027nodePath\u0027, path);\n        view.custom.write(\u0027nodePathChange\u0027, isExpanded ? \u0027expanded\u0027 : \u0027collapsed\u0027);\n      }\n    });\n  });\n  observer.observe(tree, {\n    attributes: true,\n    subtree: true,\n    attributeFilter: [\u0027data-icon\u0027]\n  });\n}",
          "notes": "Don\u0027t use \" in the jsCode. New lines can be funny.."
        },
        "meta": {
          "name": "JSInjector"
        },
        "position": {
          "height": 200,
          "width": 200,
          "x": 600,
          "y": -0.375
        },
        "propConfig": {
          "props.source": {
            "binding": {
              "config": {
                "expression": "{this.custom.jsCode}"
              },
              "transforms": [
                {
                  "code": "\tcode \u003d  \u0027\u0027\u0027\u003cimg style\u003d\"display:none\" src\u003d\"/favicon.ico\" onload\u003d\"\n\u0027\u0027\u0027 + value + \u0027\"/\u003e\u0027\n\treturn code",
                  "type": "script"
                }
              ],
              "type": "expr"
            }
          }
        },
        "props": {
          "markdown": {
            "escapeHtml": false
          }
        },
        "type": "ia.display.markdown"
      }
    ],
    "custom": {
      "TEST": "value"
    },
    "meta": {
      "name": "root"
    },
    "type": "ia.container.coord"
  }
}

The JS:

const tree = document.querySelector('.tree');
if (tree) {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      // Only process if the target is an expand icon
      if (mutation.target.classList.contains('expand-icon')) {
        const node = mutation.target.closest('[data-item-path]');
        if (!node) return;
        const path = node.dataset.itemPath;
        const isExpanded = mutation.target.dataset.icon === 'material/arrow_drop_down';
        const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path').value.split('.')[0]).value;
        view.custom.write('nodePath', path);
        view.custom.write('nodePathChange', isExpanded ? 'expanded' : 'collapsed');
      }
    });
  });
  observer.observe(tree, {
    attributes: true,
    subtree: true,
    attributeFilter: ['data-icon']
  });
}

It doesn’t work for multiple trees which I thought was odd considering the query selector… but I don’t need it for multiple so.. :man_shrugging:

1 Like

Haha no problem, i still check in from time to time on the forum to see what i’ve missed :stuck_out_tongue:
And yes i’m studying for indie game dev :smiley:
I did not know, i never been able to go to icc :frowning:

oh hehe thats all you rly needed eh, seems i already went in to deep (and on the wrong component) for my quick test xD

for the const view, you should replace this nested parentNode with closest like you (grok) did for selecting the node. i did nest that in the very few exmaples i posted, but in the newer onces i should been using closest i think. not that it matters much:p But for the ai its probably easier to understand if you let it make changes with closest xd

this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path')

this.closest('[data-component-path]').getAttributeNode('data-component-path')

3 Likes

Ah cool, I got it, cheers!

const tree = document.querySelector('.tree');
if (tree) {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      // Only process if the target is an expand icon
      if (mutation.target.classList.contains('expand-icon')) {
        const node = mutation.target.closest('[data-item-path]');
        if (!node) return;
        const path = node.dataset.itemPath;
        const isExpanded = mutation.target.dataset.icon === 'material/arrow_drop_down';
        const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.closest('[data-component-path]').getAttributeNode('data-component-path').value.split('.')[0]).value;
        view.custom.write('nodePath', path);
        view.custom.write('nodePathChange', isExpanded ? 'expanded' : 'collapsed');
      }
    });
  });
  observer.observe(tree, {
    attributes: true,
    subtree: true,
    attributeFilter: ['data-icon']
  });
}
1 Like

It works!

4 Likes