Dynamic expanding multi-level menus

Blog Index
Author: Warren Belz

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.

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. 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
   }
)


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
   }
)

Visible
Again controlled by what is required

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
First put a label in the gallery (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

I will give you a bit more flexibility as I found significant differences in smaller and larger entry widths. 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  (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.

Comments

Popular posts from this blog

With() Statement managing Delegation

Practical Power Apps - Blog Index