cancel
Showing results for 
Search instead for 
Did you mean: 

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 is attached (just use the Import canvas app button at the top to install it).
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.

Menu3Levels.gif

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.

ClearCollect(colDevices,Devices)

 

Looking at the various elements


Main Menu

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

UpdateContext( 
   {
      vViewMan: !vViewMan, 
      vViewType: false, 
      vViewDevice: false, 
      vViewPhoto: false
   }
)

 

the position of this can be anywhere required, but note that all other elements adjust to this.

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(
   {
      wMan: 
      Sort(
         Distinct( 
            colDevices, 
            ManufacturerName
         ),
         Result
      )
   }, 
   ForAll(
      Sequence(
         CountRows(wMan)
      ),
      Patch(
         Last(
            FirstN( 
               wMan,
               Value
            )
         ), 
         {RowNo: Value}
      )
   )
)

OnSelect  
Controls the visibility of the following elements

UpdateContext(
   {
      vViewType: true, 
      vViewDevice: false, 
      vViewPhoto: false
   }
)

 

Visibility
Toggled on the selection of the head label

 

vViewMan

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
IMPORTANT STEP - First put a label in the gallery and name it lbSizeMan with this as the Text and hide it

Len(ThisItem.Result)

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(
   {
      wType: 
      Sort( 
         Distinct( 
            Filter( 
               colDevices, 
               ManufacturerName = galManDevice.Selected.Result
            ),
            DeviceType
         ),
         Result
      )
   }, 
   ForAll(
      Sequence(
         CountRows(wType)
      ), 
      Patch( 
         Last( 
            FirstN( 
               wType, 
               Value
            )
         ), 
         {RowNo: Value} 
      ) 
   )
)

 

OnSelect

Same logic as previous

UpdateContext( 
   { 
      vViewDevice: true, 
      vSelDevice: false, 
      vViewPhoto: false
   }
)

Visible

 vViewType

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(
   { 
      wModel: 
      Filter( 
         colDevices, 
         ManufacturerName = galManDevice.Selected.Result && 
         DeviceType = galTypeDevice.Selected.Result 
      ) 
   }, 
   ForAll(
      Sequence(
         CountRows(wModel)
      ),
      Patch( 
         Last( 
            FirstN( 
               wModel, 
               Value
            )
         ), 
         {RowNo: Value} 
      )
   )
)

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

UpdateContext(
   {
      vViewPhoto: true, 
      vItem: 
      LookUp( 
         colDevices, 
         Title1 = ThisItem.Title1
      )
   }
)

Visible

 vViewDevice

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  may require a bit more flexibility as I found significant differences in smaller and larger entry widths, particularly is a lot of capital letters are used. I ended up with

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


Picture
The Image control imDevice is standalone with the following Image

vItem.Photo

Visible

vViewPhoto

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 Y  (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 vItem (vItem.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.

Meet Our Blog Authors
  • PowerApps Community Mentor 2018, Practicing surgeon and former Professor of Surgery, University of Illinois, Chicago.
  • I am the Owner/Principal Architect at Don't Pa..Panic Consulting. I've been working in the information technology industry for over 30 years, and have played key roles in several enterprise SharePoint architectural design review, Intranet deployment, application development, and migration projects. I've been a Microsoft Most Valuable Professional (MVP) 12 consecutive years and am also a Microsoft Certified SharePoint Masters (MCSM) since 2013.
  • Systems developer
  • My name is Timothy Shaw and I create digital solutions using the Power Platform, Office 365, and Azure SQL and handle the IT for a small company in the energy sector. When not building the future :), I enjoy playing guitar, good (or really bad!) sci-fi, Xbox therapy, and hanging with my wife and son. Twitter: @ShortForTim