Vertical menu tree in perspective

Hi guys,

Any idea how to set a vertical menu tree like when I click Menu Item then display the sub-menu in the same view when I click this Menu Item again and then collapse this sub-menu in perspective?

Same as that dropdown.

image

Thanks,
Priyanka

I don't think this component can do what you want, but it's easy enough to make yourself.
The simplest way would be to use flex containers to host your navigation buttons, and make those containers basis depend on a custom property on the menu button.
You can even add a transition: flex-basis 500ms style prop to the container to make it animate nicely.

Here's the json of an example based on what I use:

{
  "custom": {
    "current_category": "debug"
  },
  "params": {},
  "propConfig": {
    "custom.current_category": {
      "binding": {
        "config": {
          "path": "page.props.primaryView"
        },
        "transforms": [
          {
            "code": "\ttry:\n\t\treturn value.split(\u0027/\u0027)[-2]\n\texcept:\n\t\treturn \"debug\"",
            "type": "script"
          }
        ],
        "type": "property"
      },
      "persistent": true
    }
  },
  "props": {},
  "root": {
    "children": [
      {
        "children": [
          {
            "custom": {
              "view": "some_category/mainpage"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "main_button"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({view.custom.current_category} \u003d {parent.custom.category},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "some category"
            },
            "type": "ia.input.button"
          },
          {
            "custom": {
              "view": "some_category/page_1"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "page1"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "\u003e page 1"
            },
            "type": "ia.input.button"
          },
          {
            "custom": {
              "view": "some_category/page_2"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "page2"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "\u003e page 2"
            },
            "type": "ia.input.button"
          },
          {
            "custom": {
              "view": "some_category/page_3"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "page3"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "\u003e page 3"
            },
            "type": "ia.input.button"
          }
        ],
        "custom": {
          "category": "some_category"
        },
        "meta": {
          "name": "some_category"
        },
        "position": {
          "shrink": 0
        },
        "propConfig": {
          "position.basis": {
            "binding": {
              "config": {
                "expression": "if ({view.custom.current_category} \u003d {this.custom.category},\r\n\t\u0027148px\u0027,\r\n\t\u002734px\u0027\r\n)"
              },
              "type": "expr"
            }
          }
        },
        "props": {
          "direction": "column",
          "style": {
            "gap": "4px",
            "margin": "6px",
            "overflow": "hidden",
            "transition": "flex-basis 500ms"
          }
        },
        "type": "ia.container.flex"
      },
      {
        "children": [
          {
            "custom": {
              "view": "other_category/mainpage"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "main_button"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({view.custom.current_category} \u003d {parent.custom.category},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "other category"
            },
            "type": "ia.input.button"
          },
          {
            "custom": {
              "view": "other_category/page_1"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "page1"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "\u003e page 1"
            },
            "type": "ia.input.button"
          },
          {
            "custom": {
              "view": "other_category/page_2"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "page2"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "\u003e page 2"
            },
            "type": "ia.input.button"
          },
          {
            "custom": {
              "view": "other_category/page_3"
            },
            "events": {
              "component": {
                "onActionPerformed": {
                  "config": {
                    "params": {},
                    "view": "{this.custom.view}"
                  },
                  "scope": "C",
                  "type": "nav"
                }
              }
            },
            "meta": {
              "name": "page3"
            },
            "position": {
              "basis": "34px",
              "shrink": 0
            },
            "propConfig": {
              "props.style.classes": {
                "binding": {
                  "config": {
                    "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
                  },
                  "type": "expr"
                }
              }
            },
            "props": {
              "justify": "start",
              "style": {
                "borderColor": "#808080",
                "fontWeight": "normal"
              },
              "text": "\u003e page 3"
            },
            "type": "ia.input.button"
          }
        ],
        "custom": {
          "category": "other_category"
        },
        "meta": {
          "name": "other_category"
        },
        "position": {
          "shrink": 0
        },
        "propConfig": {
          "position.basis": {
            "binding": {
              "config": {
                "expression": "if ({view.custom.current_category} \u003d {this.custom.category},\r\n\t\u0027148px\u0027,\r\n\t\u002734px\u0027\r\n)"
              },
              "type": "expr"
            }
          }
        },
        "props": {
          "direction": "column",
          "style": {
            "gap": "4px",
            "margin": "6px",
            "overflow": "hidden",
            "transition": "flex-basis 500ms"
          }
        },
        "type": "ia.container.flex"
      }
    ],
    "meta": {
      "name": "root"
    },
    "props": {
      "direction": "column"
    },
    "type": "ia.container.flex"
  }
}

This assumes folders some_category and other_category, each containing the views mainpage, page_1, page_2 and page_3.
And also the styles button and button_selected, because I like highlighting the button that corresponds to the category and the page I'm in.

How it works:
The view itself has a custom property current_category that uses the page.primaryView property to determine what category the user is currently in.
Each category container has a category custom prop.
Those 2 properties are compared in a binding on the mainpage button's style classes, to selected the proper class.
The category containers have a binding on the basis property to make it grow or shrink based on whether the currently displayed page matches the category. And as a bonus there's a transition style prop to make it look nice.
Each button has a view custom property that is the path it navigates to when clicked.
That property is also used in a binding to select a style class.
And finally each button has an onActionPerformed navigation event to navigate to the view specified in the custom property.

This will need customization to match your needs but it's a start.

edit: forgot the json ;p

1 Like

image

Can use this tree component for navigation in perspective and any idea how to set view?

There are a LOT of issues with the built in menu component. My team started using the tree component for navigation a few months back and it has never given us any issues.

Any idea how to use this?

Menu_2023-06-29_1126.zip (11.3 KB)

This is what we are using. There might be some stuff in there that you don't need.

@ToMakPo Thank you so much I will try.

Here's how I did it.
Page configuration
Figure 1. Do the Page Configuration.


Figure 2. Create the menu structure in text. I do this in the actual application. Note the use of the '|' pipe character to separate the menu and URL.

Bind the tree's items property to the text and apply a script transform on it:

The 'items' binding script transform.
def transform(self, value, quality, timestamp):
	# Update the test tree with the structure.
	#	Anchors and path URLs to be provided in form shown below using '|' (vertical bar) as separator.
	#	A001            |/Home
	#	A001/B001       |/Area/1/Bath/1
	#	A001/B001/C001  |/Area/1/Bath/1/Clamp1
	# 2023-06-29 Transistor
	
	items = []
	treeExpand = self.view.params.treeExpand
	
	for line in value.splitlines():
		if line.strip():						# Ignore empty lines.
			if line[0] != "#":				# Lines can be commented out with #.
				current = items
				branch, url = line.split('|')
	
				for part in branch.strip().split("/"):		# Strip off leading and trailing spaces.
					folderExists = False					# Check if the current folder exists in our items list.
					for itemsPointerItem in current:
						if part == itemsPointerItem['label']:
							folderExists = True
							current = itemsPointerItem['items']
			
					if not folderExists:
						item = {
							"label": part,
							"expanded": treeExpand,
							"items": [],
							"data": {"url": url.strip()}
						}
						current.append(item)
						current = item['items']
	
	return items

The selected item is available in selectionData.0.value.url

[
  {
    "itemPath": "3/0",
    "value": {
      "url": "/machine/Sealer03"
    }
  }
]

On the menu tree add an onItemClicked event, Script:

def runAction(self, event):
	pagePath = self.props.selectionData[0].value.url
	system.perspective.navigate(pagePath)

Note that I've used a script to do the navigation. If found that if I used a navigation event that the navigation happened before selectionData[0].value.url had been updated and so a second click was required each time.

Have fun!