WASM

A Multilevel Responsive Blazor-menu

When you generate a Blazor-server app or Webassembly the standard template generates a menu docked to the left which is responsive, but has some drawbacks :

  • It doesn’t support multiple levels
  • It depends on bootstrap which introduces additional complexity
  • Menu-items are hardcoded

Webapplications need a flexible way enabling multilevel-, responsive menus, the standard template is not flexible enough for this purpose, this post describes a solution to this problem. The approach taken consists of the following parts :

  1. A Component ‘ResponsiveMenuComponent’
  2. A stylesheet ‘menu.css’
  3. JQuery functions to toggle menus

Suppose our requirement is to generate a menu whos structure comes from an external source and simulates a webshop, the menustructure looks like this :

Home
Jeans -> Wide leg jeans
Jeans -> Straight jeans
Jeans -> Loose jeans
Shorts -> Sweet jersey shorts -> Jersey1
Shorts -> Sweet jersey shorts -> Jersey2
Shorts -> Denim shorts
Skirts
Blazors

The ResponsiveMenu-component has a property ‘MenuEntryCollection’ which is used to render the menu. With the ResponsiveMenu-component in place, the following creates the required menu-structure :

MainLayout.razor :

<ResponsiveMenuComponent Items=@Items />

@code {
public MenuEntryCollection	Items = new MenuEntryCollection( )
	{
	new MenuEntry( ) { Text = "Home",		Url = "/" }
,	new MenuEntry( ) { Text = "Jeans",		Url = "/jeans" 
	, SubItems = new MenuEntryCollection( )
		{
		new MenuEntry( ) { Text = "Wide leg jeans",	Url = "/wide_leg_jeans" }
	,	new MenuEntry( ) { Text = "Straight jeans",	Url = "/straight_jeans" }
	,	new MenuEntry( ) { Text = "Loose jeans",	Url = "/loose_jeans" }
}
}
,	new MenuEntry( ) { Text = "Shorts",		Url = "/shorts" 
	, SubItems = new MenuEntryCollection( )
		{
		new MenuEntry( ) { Text = "Sweet jersey shorts",	Url = "/sweet_jersey" 
		, SubItems = new MenuEntryCollection( )
			{
			new MenuEntry( ) { Text = "Jersey1",	Url = "/Jersey1" }
		,	new MenuEntry( ) { Text = "Jersey2",	Url = "/Jersey2" }
	}
	}
	,	new MenuEntry( ) { Text = "Denim shorts",			Url = "/demin_jersey" 	}
}
}
,	new MenuEntry( ) { Text = "Skirts",		Url = "/skirts" }
,	new MenuEntry( ) { Text = "Blazers",	Url = "/blazers" }
};
}

The ‘MenuEntryCollection’ and ‘MenuEntry’-classes:

public class MenuEntryCollection : System.Collections.Generic.List<MenuEntry>{ }

public class MenuEntry
{
	internal string Text { get; set; }
	internal string Url  { get; set; }

	internal MenuEntryCollection SubItems{ get; set; }
}

When the website is rendered for non-mobile devices, multilevel-items are expanded by css included in ‘menu.css’. Before blazor, if a user would select an item in the menu a postback would occur rebuilding the menu, in Blazor this is not the case causing the selected menu-item to be expanded. In a regular blazor app, it is collapsed using css switched when a user clicks a div, the ‘@onclick=”ToggleNavMenu’ construct. Doing this for a menu whos structure is unknown in advance would be very convenient, therefore this is done using jQuery declared inside _Host.cshtml together with some other needed functions:

<script type="text/javascript">
window.onBlazorReady = function() {
	$('.top-menu').not(".mobile").click(function () {
		$(this).find('ul > li').toggle();
	});

	$('.top-menu').not(".mobile").hover(function () {
		$(this).find('ul > li').show();
	}, function() {});

	$('.menu-toggle').click(function () {
		$(this).siblings('.top-menu.mobile').slideToggle('slow');
	});

	$('.top-menu.mobile .sublist-toggle').click(function () {
		$(this).siblings('.sublist').slideToggle('slow');
	});

	$('.top-menu.mobile .sf-with-ul').click(function () {
		$(this).siblings('.sublist').slideToggle('slow');
	});

	$('.top-menu.mobile li:not(:has(ul))').click(function () {
		$(this).closest('.top-menu.mobile').toggle();
	});
};
</script>

The ‘onBlazorReady’-function is called inside the ‘OnAfterRenderAsync’ inside ‘ResponsiveMenuComponent.razor’:

protected override Task OnAfterRenderAsync( bool firstRender )
{
  if( firstRender)
  {
    ValueTask valueTask = JsRuntime.InvokeVoidAsync("onBlazorReady");
  }

  return base.OnAfterRenderAsync( firstRender );
}

A running website using this component can be seen here, sources can be downloaded from github.

Notes:

  • The menu in component ResponsiveMenuComponent is generated in codebehind which disables css-isolation
  • The example given is Blazor-server but can also be used for Blazor-WASM
  • Menu items for the desktop are collapsed on hover using css, but there is no such thing as ‘hover’ on mobile devices. Because of this, we need two separate menus mobile and desktop