Blazor Navbar Top Menü auf Bootstrap Basis

Die Standard Visual Studio Templates für Blazor Projekte, platzieren das Menü links. Dies ist vermutlich in der Programmierpraxis eher die Ausnahme. Viele Websites oder Webapps werden per Drop Down Navigation am oberen Bildschirm Rand gesteuert. Da Blazor auf Bootstrap 5 setzt ohne die zugehörigen JS Libraries, klappt das nicht so einfach.

Wer sich schon ein Stück mit Blazor auseinander gesetzt hat wird wissen, dass ein Gutteil der JavaScript Logik, per C# Code erledigt wird. Eine direkte DOM Manipulation ist dabei unerwünscht. Für Server Side Blazor wird dabei pro Benutzer Aktivität, über Websocket, mit dem Server kommuniziert und häppchenweise HTML Schnipsel gerendert und über das Internet geschickt. Im Gedankenexperiment klappt ein Benutzer das Menü auf und zu, schwupp mehrere Anforderungen an den Server. Ist ja doof, weil das Menü sich zu 99% nicht ändern wird. Also bleibt die klassische Web Browser Client Variante mit JavaScript. Man könnte die Bootstrap JS Datei(en) einbinden. Ist aber auch ein bisschen viel.

Also Challenge: den JavaScript Show Part nachimplementieren

Bootstrap Navbar Grundlagen

Aus der Bootstrap Doku kopiert eine Top Navbar mit Drop Down Möglichkeit

navbar1

Der dafür nötige HTML Code, schon aufgeräumt um die data- und aria- Attribute.

   1:  <nav class="navbar navbar-expand-lg navbar-light bg-light">
   2:    <div class="container-fluid">
   3:      <a class="navbar-brand" href="#">Navbar</a>
   4:      <button class="navbar-toggler" type="button">
   5:        <span class="navbar-toggler-icon"></span>
   6:      </button>
   7:      <div class="collapse navbar-collapse" >
   8:        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
   9:          <li class="nav-item">
  10:            <a class="nav-link active" href="#">Home</a>
  11:          </li>
  12:          <li class="nav-item">
  13:            <a class="nav-link" href="#">Link</a>
  14:          </li>
  15:          <li class="nav-item dropdown">
  16:            <a class="nav-link dropdown-toggle" href="#" role="button" >
  17:              Dropdown
  18:            </a>
  19:            <ul class="dropdown-menu"  >
  20:              <li><a class="dropdown-item" href="#">Action</a></li>
  21:              <li><a class="dropdown-item" href="#">Another action</a></li>
  22:              <li><hr class="dropdown-divider"></li>
  23:              <li><a class="dropdown-item" href="#">Something else here</a></li>
  24:            </ul>
  25:          </li>

Analysiert man die Bootstrap JavaScript Dom Magie im Browser per F12 Browser Tools, lässt sich erkennen, dass zwei Mal die CSS Klasse Show eingefügt wird um den Drop Down Teil eines Menüs anzuzeigen.

navbar2

Der Screenshot wurde aus der Bootstrap Doku Seite erstellt und unterscheidet sich deshalb vom oberen HTML teilweise.

Das JavaScript Experiment

Es werden drei Funktionen benötigt

Öffnen des Popups mittels hinzufügen des Show Class Attributes. Das gleich an zwei Stellen. Einmal eins drüber und einmal auf gleicher Ebene mit dem spezifischen CSS Attribut.

   1:  function openDropDown() {
   2:      cleanDropDown();
   3:      var parent = this.parentNode;
   4:      parent.classList.toggle("show");
   5:      parent.querySelector('.dropdown-menu')
.classList.toggle("show");
   6:      }

 

Die Clean Methode stellt sicher, dass alle anderen Menü Drop Downs auch verschwinden, also zuklappen.

   1:  function cleanDropDown() {
   2:      var dropdowns = document.getElementsByClassName("dropdown-menu");
   3:      var i;
   4:      for (i = 0; i < dropdowns.length; i++) {
   5:          var openDropdown = dropdowns[i];
   6:          if (openDropdown.classList.contains('show')) {
   7:              openDropdown.classList.remove('show');
   8:          }
   9:      }
  10:  }

Übliches Verhalten eines Popup Menüs ist, dass wenn der Benutzer irgendwo anders hinklickt, alles einklappt und zu ist.

   1:  window.onclick = function (event) {
   2:      if (!event.target.matches('.dropdown-toggle')) {
   3:          cleanDropDown();
   4:      }
   5:  }

Nun muss man noch jedem passenden Element mit der CSS Klasse dropdown-toggle das Click Event auf openDropDown hinzufügen. Der folgende JavaScript Code kapselt das aber noch in eine Methode, die man manuell aufrufen muss.

   1:  window.attachHandlers = () => {
   2:      var elements = document.getElementsByClassName('dropdown-toggle');
   3:      for (var i = 0; i < elements.length; i++) {
   4:          elements[i].addEventListener("click", openDropDown, false);
   5:      }
   6:  }

Blazor Events und JavaScript

Ein Teil der Blazor Magie liegt darin, dass die DOM Events, wie das onclick, zu C# Logik gemappt wird. Während Blazor das tut, überschreibt er aber alle vorigen EventListener. Aus diesem Grund müssen wir sehr spät, wenn der HTML Renderer durch ist, unsere Events wieder anmelden. Das muss in der Blazor Component geschehen per JavaScript Bridge.

Da Microsoft in der Datei Navmenu.razor das Menü angelegt hat belassen wir es dabei. Der HTML Teil von davor kommt da rein und der Code um das JavaScript von vorhin auszuführen.

   1:  @code {
   2:      protected override void OnAfterRender(bool firstRender)
   3:      {
   4:   if(firstRender)
   5:         {
   6:          JSRuntime.InvokeVoidAsync("attachHandlers");
   7:       }
   8:      }
   9:  }

Vergessen Sie nicht am Component Beginn den Inject für IJSRuntime!

Wenn Sie den Inhalt von Attachhandlers anders ausführen lassen dann bekommen sie in Blazor keine Onclick Events ausgelöst!

Final und Komplett

Die JavaScript Schnippsel kommen in eine Datei unterhalb des wwwroot Verzeichnisses- Hier Scripts und Navhelper.js.

In der Datei _layout.cshtml wird dann diese referenziert.

   1:   <script src="_framework/blazor.server.js"></script>
   2:      <script src="/scripts/NavHelper.js"></script>
   3:  </body>
   4:  </html>

In Mainlayout.razor ändern sie die HTML Struktur so, das das Seitenmenü verschwindet und als Topbar erscheint. Zum Beispiel mit folgenden HTML Code

   1:  @inherits LayoutComponentBase
   2:  <PageTitle>Paula</PageTitle>
   3:  <NavMenu />
   4:  <div class="page">
   5:      <main>
   6:          <div class="top-row px-4 auth">
   7:              <LoginDisplay />
Kommentare sind geschlossen