Dynamic

Dynamic expanding multi-level menus

As users have become accustomed in many applications, top menus with context-sensitive sub-menus are the norm, but are not part of the standard Power Apps “suite”, so creating this type of function is a useful enhancement to user experience using a “look and feel” familiar to them.
Firstly, the App and the Excel file (which is already imported) can be downloaded here. 
I will start this a bit backwards, showing the end result of a three-level menu based on a list as below and then discuss what needs to be done to accomplish this.

These are galleries with one field in each: –

  • galManDevice shows the PC manufacturer list.
  • galTypeDevice is for all device types belonging to the manufacturer.
  • galModelDevice shows all models of the type from the manufacturer.

To the right of these, there is a picture of the chosen device and the specifications.

So what is happening here? There are some things that need to be done to the galleries and panels to control their size, position and visibility. This greatly enhances the user experience an provides something similar to regular “expanding” menu dropdown trees. The main elements: –

  1. “Line up” the top of the second and third galleries and panels with the item chosen.
  2. Control the visibility of all elements giving the user the ability to hide them in a logical way.
  3. Size the height of all galleries to fit the number of items.
  4. Size the width of the galleries to fit the largest item contained in them

The data you may recognize as list of computer manufacturers and relevant models as it is part of a few Power Apps courses. The List name is Devices.  So what is needed to get this working? 
For performance reasons, I have done a Collection (as you normally would with a remote data source) at App OnStart. The Collection is colDevices. Looking at the various elements


Main Menu

The top “Computer” is a label lbHeader with the OnSelect

UpdateContext( 
   {
      varViewMan: !varViewMan, 
      varViewType: false, 
      varViewDevice: false, 
      varViewPhoto: false
   }
)


galManDevice

Items 
A sequential number needs to be added to the gallery for positioning the following panels. It also uses Distinct values from the list

With(
   {
      _Man: 
      Sort(
         Distinct( 
            colDevices, 
            ManufacturerName
         ),
         Result
      )
   }, 
   ForAll(
      Sequence(
         CountRows(_Man)
      ),
      Patch(
         Index( 
            _Man,
            Value
         ), 
         {RowNo: Value}
      )
   )
)

OnSelect  
Controls the visibility of the following elements

UpdateContext(
   {
      varViewType: true, 
      varViewDevice: false, 
      varViewPhoto: false
   }
)

Visible
Again controlled by what is required

varViewMan

Height
To allow for the number of items in the panel

CountRows(Self.AllItems) * (Self.TemplateHeight + Self.TemplatePadding) + Self.TemplatePadding 

X

To align with the header label

lbHeader.X

Y
To sit below the header

lbHeader.Y + lbHeader.Height

Width
First put a label in the gallery (lbSizeMan) with this as the Text and hide it

Len(ThisItem.Value)

Then the Width of the gallery depends a bit on the font size and to some degree how many capital letters are involved. I have used this, but you can adjust to suit.

Max(
   galManDevice.AllItems,
   lbSizeMan
) * 9 + 10

galTypeDevice

Items

Same logic as previous

With(
   {
      _Type: 
      Sort( 
         Distinct( 
            Filter( 
               colDevices, 
               ManufacturerName = galManDevice.Selected.Result
            ),
            DeviceType
         ),
         Value
      )
   }, 
   ForAll(
      Sequence(
         CountRows(_Type)
      ), 
      Patch( 
         Index( 
            _Type, 
            Value
         ), 
         {RowNo: Value} 
      ) 
   )
)

OnSelect

Same logic as previous

UpdateContext( 
   { 
      varViewDevice: true, 
      varSelDevice: false, 
      varViewPhoto: false
   }
)

Visible

 varViewType

Height

(CountRows(Self.AllItems) * (Self.TemplateHeight + Self.TemplatePadding)) + Self.TemplatePadding

X
To align with the edge of the last gallery

galManDevice.X + galManDevice.Width

Y
To align with the item selected in galManDevice

(CountRows(Self.AllItems) * (Self.TemplateHeight + Self.TemplatePadding)) + Self.TemplatePadding

Width
As per the hidden label

Max(
   galTypeDevice.AllItems,
   lbSizeType
) * 9 + 10

galModelDevice

Items

All uncommented properties below are as per previous logic

With(
   { 
      _Model: 
      Filter( 
         colDevices, 
         ManufacturerName = galManDevice.Selected.Result && 
         DeviceType = galTypeDevice.Selected.Result 
      ) 
   }, 
   ForAll(
      Sequence(
         CountRows(_Model)
      ),
      Patch( 
         Index( 
            _Model, 
            Value
         ), 
         {RowNo: Value} 
      )
   )
)

OnSelect
This also sets a Variable varItem to the record selected.

UpdateContext(
   {
      varViewPhoto: true, 
      varItem: 
      LookUp( 
         colDevices, 
         Title1 = ThisItem.Title1
      )
   }
)

Visible

 varViewDevice

Height

(CountRows(Self.AllItems) * (Self.TemplateHeight + Self.TemplatePadding)) + Self.TemplatePadding

X

galTypeDevice.X + galTypeDevice.Width

Y

(CountRows(Self.AllItems) * (Self.TemplateHeight + Self.TemplatePadding)) + Self.TemplatePadding

Width

I will give you a bit more flexibility as I found significant differences in smaller and larger entry widths. I ended up with

With(
   {
      _Wide: 
      Max(
         galModelDevice.AllItems,
         lbSizeModel
      )
   },
   If(
      _Wide < 6,
      _Wide * 11 + 15,
      _Wide <15,
      _Wide * 9 + 10,
      _Wide * 8 + 10
   )
)


Picture

The Image control imDevice is standalone with the following

Image

varItem.Photo

Visible

varViewPhoto

X

galModelDevice.X + galModelDevice.Width

Y

galModelDevice.Selected.RowNo * (galModelDevice.TemplateHeight + galModelDevice.TemplatePadding) + galModelDevice.Y - galModelDevice.TemplateHeight - galModelDevice.TemplatePadding

Specifications

The Rectangle Icon at the right has the same Visibility and Y, with the X based on the Photo.

It has five labels in it, with their Y based on a distance below the Rectangle  (example for the first one)

icDetails.Y + 50

and their X based on the distance from the left  side of the rectangle

icDetails.X +20

Their Text is based on varItem (varItem.Price/Memory/Storage/Processor/Screen 

I hope this is useful for you  – bear in mind you will need to adjust everything for your field names and what needs to match.

6 Comments

  • Kerim

    Hi Warren,

    Thank you for sharing the app and this technic.

    quick question, can we use a similar technic but with a multi-select value/column in SharePoint? I’m no expert and I tried to use a multi-select column but couldn’t find the right formula.

    Thank you,

  • Kerim

    Hi Warren,

    Yes, it is a bit complex but I got it working with my SharePoint list if I use single-choice columns.

    however I’d like to use it with choice multiple selection, I tried to start with the multi for the first gallery but it didn’t work then tried to keep the single choice column and match it to the multi in the second gallery but will not allow me.

    should we still use distinct?

Leave a Reply

Your email address will not be published. Required fields are marked *