Teil 2 - Basis-Layout implementieren

Dieses Lab ist Teil der Amenity Finder Serie, einer umfassenden Anleitung, die Schritt für Schritt zeigt, wie man eine Web-Applikation mithilfe von Web-Components entwickelt. In jedem Teil der Serie werden spezifische Aspekte der Entwicklung behandelt und es werden bewährte Methoden und Techniken vermittelt, um eine moderne und skalierbare Webanwendung aufzubauen.

Im zweiten Teil der Serie konzentrieren wir uns auf die Implementierung des Basis-Layouts unserer Amenity Finder Applikation. Wir erstellen die grundlegende Struktur und das Design, um eine solide Grundlage für die weiteren Funktionen zu schaffen.

VorschauAnimation von Basic-Layout der Amenity-Finder Applikation

Wenn du dieses Lab bereits abgeschlossen hast, geht es im dritten Teil der Amenity Finder Serie weiter. Dort lernst du, wie du die Amenity Finder Applikation in einzelne Seiten unterteilen und das Routing implementieren kannst.

Vorbereitung

Ziele

Dieses Lab konzentriert sich auf die Erreichung der folgenden Ziele:

  • Überprüfung und Optimierung der Verzeichnisstruktur: Wir gehen die Verzeichnisstruktur des Projekts durch und optimieren sie, um eine bessere Organisation und Strukturierung unserer Amenity Finder Applikation zu ermöglichen. Dies umfasst die Aufteilung der Dateien und Ordner in sinnvolle Kategorien und das Entfernen unnötiger oder überflüssiger Elemente.
  • Implementierung des grundlegenden Applikationslayouts mit Material Design Web-Components (MWC): Wir nutzen die Material Design Web-Components (MWC), um das grundlegende Layout unserer Amenity Finder Applikation zu gestalten. MWC bietet eine Sammlung von vordefinierten, reaktionsfähigen und ansprechenden Web-Components, die auf den Designprinzipien des Material Designs basieren. Durch die Implementierung des Layouts mit MWC können wir eine konsistente und moderne Benutzeroberfläche schaffen.
  • Implementierung einer Seitenleiste mit Auf- und Zuklapp-Mechanismus: Eine Seitenleiste ist ein nützliches Element, um zusätzliche Navigationsoptionen oder Informationen in unserer Applikation anzuzeigen. Wir werden eine Seitenleiste implementieren, die die Möglichkeit bietet, sie auf- und zuzuklappen. Dadurch können Benutzer den verfügbaren Bildschirmplatz optimal nutzen und bei Bedarf die Seitenleiste ein- oder ausblenden.

Schritte

Das Basis Layout für die Applikation wird in 6 Schritten erstellt:

  1. Verzeichnisstruktur durchgehen
  2. Verzeichnisstruktur anpassen
  3. Layout mit Material Design
  4. Layout Anpassungen und Theming
  5. Navigation in der Sidebar einbauen
  6. Sidebar ein- und ausblenden

Branch

Der zweite Teil des Amenity Finder Labs ist auf GitHub verfügbar.

Für weitere Informationen zu den behandelten Themen findest du nützliche Ressourcen unter den folgenden Links:


Los geht’s!

Verzeichnisstruktur durchgehen

In der vorherigen Lektion haben wir mittels des open-wc Generators unser Projekt aufgesetzt und die folgende Verzeichnisstruktur erhalten:

amenity-finder/
├── assets/
│   └── open-wc-logo.svg
├── src/
│   ├── amenity-finder.js
│   └── AmenityFinder.js
├── .editorconfig
├── .gitignore
├── custom-elements.json
├── index.html
├── LICENSE
├── package.json
├── README.md
├── rollup.config.js
└── web-dev-server.config.mjs

Wir gehen jetzt auf die Struktur und einige ausgewählte Dateien ein.

  • index.html

    Das ist der Einstiegspunkt in unsere Applikation. Hier definieren wir das Basis-HTML und binden unsere Applikation ein:

    <amenity-finder></amenity-finder>
    <script type="module" src="./src/amenity-finder.js"></script>
  • src/AmenityFinder.js

    Hier ist unsere Einstiegskomponente als JavaScript-Klasse und Erweiterung von Lit definiert. Sie ist das oberste Element in unserem Komponenten-Baum und fungiert als «Orchestrator» unserer Applikation. In Angular ist das Pendant dazu das AppModule, in der VueJS-Welt wäre es z. B. die App.vue und bei React (erstellt mit Create React App) das App.js.

  • src/amenity-finder.js

    Dieses File ist ein simpler Wrapper rund um unsere AmenityFinder Klasse. Es verbindet die Funktionalität unserer Applikation mit dem HTML (DOM) mittels Custom Elements API:

    customElements.define('amenity-finder', AmenityFinder);

    Hier teilen wir dem Browser mit: jedes Mal, wenn du den Tag <amenity-finder> siehst, kannst du die Klasse AmenityFinder instanziieren.

    Die Trennung der Klasse vom Custom-Tag macht beim Publizieren von Komponenten durchaus Sinn. So kann nämlich die Klasse AmenityFinder importiert und verwendet werden, ohne dass dabei auch das Custom Element definiert wird.

  • custom-elements.json

    custom-elements-json ist ein Dateiformat zur Beschreibung von Custom Elements. Es soll die Struktur einer Webkomponente einfacher maschinenlesbar machen und so zu besserem Editor-Support, Dokumentation oder Linting verhelfen. Die Spezifikation ist im Juli 2021 in der Version 1 released worden.

  • rollup.config.js

    Für die Entwicklung verwenden wir den @web/dev-server. Der Server kann «nackte Imports» (Imports ohne Angabe eines Pfads) on-the-fly umschreiben, sodass der Browser damit umgehen kann. Zum Beispiel wird im AmenityFinder.js die Zeile:

    import { LitElement, html, css } from 'lit';

    zu

    import { LitElement, html, css } from '../node_modules/lit-element/index.js';

    Weil der Server standardmässig von unserem Repository-Root aus alle Dateien ausliefert, kann der Browser diese umgeschriebenen Pfade direkt selber auflösen.

    Beispiel Requests es-dev-server

    Für den Produktions-Build verwendet open-wc rollup.js. Beim Build werden, ausgehend vom index.html, alle Dependencies aufgelöst und landen schlussendlich in nur einem JavaScript-File (auch als «Bundle» bezeichnet), welches wiederum in einem transformierten index.html referenziert wird.

package.json

Der Vollständigkeit halber gehen wir noch auf die wichtigsten npm-Skripte im package.json ein:

  • start

    Startet den @web/dev-server Server mit index.html als Applikation-Index-File. Der server wird mit der Datei web-dev-server.config.mjs konfiguriert. Hier ist unter anderem das appIndex property relevant. Damit wird das Auto-Rewrite (von Requests) auf eine Index-Datei (z.B. index.html) aktiviert, sodass ein Single-Page-Applikation (SPA) Routing (im Client) möglich ist.

    WICHTIG: Während der Entwicklung arbeiten wir lokal mit diesem Befehl ($ npm start). Beim Installieren neuer Abhängigkeiten ($ npm install) muss der Befehl neu gestartet werden.

  • build

    Erstellt einen Produktions-Build der Applikation mithilfe von Rollup. Die Konfiguration dazu ist im rollup.config.js definiert. Standardmässig landet der Build unserer Applikation im dist Verzeichnis.

  • start:build

    Erstellt zuerst einen Produktions-Build der Applikation mit build und stellt diesen danach unter einer localhost URL über @web/dev-server zur Verfügung. Damit kann der Produktions-Build lokal getestet werden.

  • lint

    Überprüft den Code auf Lint-Fehler mithilfe von ESLint und Prettier.

  • format

    Formatiert den Code mithilfe von Prettier und korrigiert alle ESLint-Fehler, welche automatisch behoben werden können.


Verzeichnisstruktur anpassen

Wir können nun die Verzeichnisstruktur etwas entschlacken und vereinfachen.

  • AmenityFinder.js und amenity-finder.js kombinieren

    Wir können die Zeile customElements.define('amenity-finder', AmenityFinder); aus amenity-finder.js entfernen und am Schluss der Datei AmenityFinder.js einfügen. amenity-finder.js werden wir später löschen.

    --- src/AmenityFinder.js
    +++ src/AmenityFinder.js
        `;
      }
    }
    +
    +customElements.define('amenity-finder', AmenityFinder);
    
  • index.html anpassen

    Hier müssen wir nun die AmenityFinder.js statt amenity-finder.js referenzieren

    --- index.html
    +++ index.html
     <body>
       <amenity-finder></amenity-finder>
    
    -  <script type="module" src="./src/amenity-finder.js"></script>
    +  <script type="module" src="./src/AmenityFinder.js"></script>
     </body>
    
  • Nicht benötigte Dateien löschen

    Mit $ rm src/amenity-finder.js assets/open-wc-logo.svg löschen wir Dateien, die wir nicht mehr benötigen. Die Referenz zum open-wc-Logo müssen wir im AmenityFinder.js noch entfernen:

    --- src/AmenityFinder.js
    +++ src/AmenityFinder.js
    @@ -1,7 +1,5 @@
     import { LitElement, html, css } from 'lit';
    
    -const logo = new URL('../assets/open-wc-logo.svg', import.meta.url).href;
    -
     export class AmenityFinder extends LitElement {
       static get properties() {
         return {
    @@ -62,7 +60,6 @@
       render() {
         return html`
           <main>
    -        <div class="logo"><img alt="open-wc logo" src=${logo} /></div>
             <h1>${this.title}</h1>
    
             <p>Edit <code>src/AmenityFinder.js</code> and save to reload.</p>
    
  • Custom Elements Manifest + Analyzer entfernen (optional)

    Weil wir keine Webkomponente als Library publizieren, sondern eine Applikation bauen wollen, können wir zusätzlich auch noch auf das Custom Elements Manifest und die damit verbundenen Dateien und abhängigkeiten verzichten. Der Build-Prozess wird dadurch etwas einfacher und schneller.

    Wir können die Datei custom-elements.json löschen sowie die dazugehörigen Skripte und Dependencies aus dem package.json entfernen:

    --- package.json
    +++ package.json
    @@ -7,9 +7,8 @@
       "scripts": {
         "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
         "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
    -    "build": "rimraf dist && rollup -c rollup.config.js && npm run analyze -- --exclude dist",
    +    "build": "rimraf dist && rollup -c rollup.config.js",
         "start:build": "web-dev-server --root-dir dist --app-index index.html --open",
    -    "analyze": "cem analyze --Lit",
         "start": "web-dev-server",
         "deploy": "npm run build && surge --domain itchy-frog.surge.sh dist"
       },
    @@ -18,7 +17,6 @@
       },
       "devDependencies": {
         "@babel/preset-env": "^7.15.4",
    -    "@custom-elements-manifest/analyzer": "^0.4.17",
         "@open-wc/building-rollup": "^1.10.0",
         "@open-wc/eslint-config": "^4.3.0",
         "@rollup/plugin-babel": "^5.3.0",
    

    Danach führen wir noch $ npm install aus, damit unser package-lock.json File auf die neuen Vorgaben aus dem package.json angepasst wird.

Die neue Verzeichnisstruktur sollte nun wie folgt aussehen:

amenity-finder/
├── assets/
├── src/
│   └── AmenityFinder.js
├── .editorconfig
├── .gitignore
├── index.html
├── LICENSE
├── package.json
├── package-lock.json
├── README.md
├── rollup.config.js
└── web-dev-server.config.mjs

Die App sieht im Browser nun wie folgt aus:

Screenshot der Amenity Finder Applikation nach Cleanup

Layout mit Material Design

Damit wir uns auf die eigentliche Funktionalität konzentrieren können, verwenden wir für das Layout unserer Applikation Material Design. Material Design ist das Design System von Google mit Implementationen von Android, iOS, Flutter und für das Web.

Die Web-Version der Material Design Components ist in Plain-JavaScript1 implementiert. Neben diversen Third-Party Framework Wrappern gibt es auch eine offizielle Implementation mit Web-Components (material-components/material-components-web-components).

Basis-Layout Definition

Unser Applikation-Layout soll grundsätzlich aus drei Teilen bestehen:

  • eine globale Titel-Leiste, die jeweils anzeigt, in welchem Bereich der Applikation wir uns befinden
  • eine Seitenleiste (Sidebar), in der wir unsere Navigation unterbringen können
  • einem Bereich für den eigentlichen Inhalt jeder Seite unserer Applikation
](./amenity-finder-teil-02/assets/app-basic-layout.png)Basis Layout (generell)

Für das Basis-Layout können wir die folgenden zwei MWC verwenden

  • <mwc-drawer> für das generelle Layout mit Sidebar und Hauptinhalt
    • Diese Komponente hat einen default «Slot», diesen verwenden wir für die Sidebar.
    • Weiter hat die Komponente einen appContent Slot. Dieser wird unseren Seiteninhalt beherbergen.
  • <mwc-top-app-bar> für den Titelbereich beim Hauptinhalt

Grafisch sieht die Zusammensetzung der Komponenten wie folgt aus:

](./amenity-finder-teil-02/assets/app-basic-layout-drawer.png)Basis Layout (mwc-drawer)

MWC einbinden

Wir installieren als Erstes zwei neue Web-Components wie folgt (ACHTUNG: Workaround):

Wir entfernen zuerst das dependencies Keyword aus package.json

--- package.json
+++ package.json
@@ -12,9 +12,6 @@
     "start": "web-dev-server",
     "deploy": "npm run build && surge --domain itchy-frog.surge.sh dist"
   },
-  "dependencies": {
-    "lit": "^2.0.0-rc.4"
-  },
   "devDependencies": {
     "@babel/preset-env": "^7.15.4",
     "@open-wc/building-rollup": "^1.10.0",

und entfernen das package-lock.json sowie das gesamte node_modules Verzeichnis

$ rm -rf package-lock.json node_modules

und führen danach $ npm install aus. Anschliessend führen wir die folgenden Befehle im Terminal aus:

$ npm install lit-html@^1.4.1
$ npm install lit@^2.0.0-rc.4
$ npm install @material/mwc-drawer@^0.22.1 @material/mwc-top-app-bar@^0.22.1
Technischer Hintergrund für diesen Workaround
Eigentlich wäre die Installation einfacher, nämlich $ npm install @material/mwc-drawer @material/mwc-top-app-bar. Das @open-wc-Template arbeitet allerdings mit einer neueren Version von Lit (2.x) als derjenigen, die bei MWC verwendet wird (1.x). Wegen der Art und Weise, wie NPM mit verschachtelten Abhängigkeiten umgeht und einer ungünstigen Implementation eines Features in lit 1.x, müssen wir zuerst die Version 1.x installieren und erst danach alle Abhängigkeiten mit 2.x.

Wir können jetzt diese beiden Web-Components im AmenityFinder.js importieren und einbinden:

import '@material/mwc-drawer';
import '@material/mwc-top-app-bar';

Die render-Methode im AmenityFinder.js können wir mit dem folgenden Inhalt überschreiben:

<mwc-drawer>
  <div>
    <p>Drawer Content!</p>
  </div>
  <div slot="appContent">
    <mwc-top-app-bar>
      <div slot="title">Title</div>
    </mwc-top-app-bar>
    <div>
      <p>Main Content!</p>
    </div>
  </div>
</mwc-drawer>

und entfernen mal alles, was wir in dieser Klasse nicht benötigen. Die AmenityFinder-Klasse sieht danach wie folgt aus:

src/AmenityFinder.js
import { LitElement, html, css } from 'lit';

import '@material/mwc-drawer';
import '@material/mwc-top-app-bar';

export class AmenityFinder extends LitElement {
  static get styles() {
    return css`
      :host {
        min-height: 100vh;
      }
    `;
  }

  render() {
    return html`
      <mwc-drawer>
        <div>
          <p>Drawer Content!</p>
        </div>
        <div slot="appContent">
          <mwc-top-app-bar>
            <div slot="title">Title</div>
          </mwc-top-app-bar>
          <div>
            <p>Main Content!</p>
          </div>
        </div>
      </mwc-drawer>
    `;
  }
}

customElements.define('amenity-finder', AmenityFinder);

Unsere Applikation sollte nun in etwa wie folgt aussehen:

Basis Layout #1

Einen Schönheitspreis gewinnen wir damit (noch) nicht, es geht allerdings schon in die richtige Richtung und die Einbindung der beiden Komponenten hat funktioniert.


Layout Anpassungen und Theming

Die MWC sind eingebunden und das grundsätzliche Layout funktioniert. In diesem Kapitel machen wir nun aber noch den Feinschliff und passen das Layout und Inhalte so an, dass wir eine gute Basis für die Weiterentwicklung unserer Applikation haben.

Der <mwc-drawer> wird noch nicht über die gesamte Höhe des Browserfensters gestreckt. In den Entwicklertools können wir die Situation untersuchen und stellen fest, dass das <html>-Element eine noch zu geringe Höhe hat:

Seitenhöhe ist zu gering

Dies können wir mit einem Einzeiler im index.html korrigieren:

--- index.html
+++ index.html
       margin: 0;
       padding: 0;
       font-family: sans-serif;
       background-color: #ededed;
+      height: 100%;
     }
   </style>
   <title>amenity-finder</title>

Damit wir noch den Vorgaben bezüglich Schriftart und Icons von Material Design entsprechen, müssen wir die Roboto und Material Icons Schriftarten wie folgt einbinden:

--- index.html
+++ index.html
   <meta name="Description" content="Put your description here.">
   <base href="/">

+  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
+  <link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">
+
   <style>
     html,
     body {

Nun werden wir noch die Default Material Design Farbe los. Material Design kommt mir einem Theming System daher. Damit können viele Aspekte der MWC angepasst und überschrieben werden. MWC unterstützt das Theming mittels CSS Custom Properties.

Die Details dazu sind im MWC Theming Guide beschrieben. In einem ersten Schritt passen wir die primäre Farbe an, damit wir das (subjektiv) nicht so schöne Violett los sind und nehmen dafür das Inventage Blau. Ihr könnt selbstverständlich eine eigene Farbe wählen.

--- index.html
+++ index.html
       font-family: sans-serif;
       background-color: #ededed;
       height: 100%;
+
+      --mdc-theme-primary: #004996;
     }
   </style>
   <title>amenity-finder</title>

Unsere Applikation sollte nun in etwa wie folgt aussehen:

Applikation mit angepasstem Layout und Farbe

In diesem Schritt bereiten wir die Navigation unserer Applikation vor. Diese werden wir in die folgenden Bereiche einteilen:

  • Home (Startseite)
  • Search (Seite für die Suche)
  • Results (Seite für Suchresultate)

<mwc-list> für Navigationselemente

Für die Navigationspunkte können wir uns der <mwc-list> Komponente bedienen. Zuerst installieren wir diese mit:

$ npm install @material/mwc-list@^0.22.1

Importieren diese entsprechend der Dokumentation im AmenityFinder.js:

import '@material/mwc-list/mwc-list.js';
import '@material/mwc-list/mwc-list-item.js';

und binden den dazugehören HTML-Code als Inhalt vom <mwc-drawer> ein:

<mwc-list>
  <mwc-list-item>Home</mwc-list-item>
  <mwc-list-item>Search</mwc-list-item>
  <mwc-list-item>Results</mwc-list-item>
</mwc-list>

Unsere AmenityFinder-Klasse sieht nun so aus:

src/AmenityFinder.js
import { LitElement, html, css } from 'lit';

import '@material/mwc-drawer';
import '@material/mwc-top-app-bar';
import '@material/mwc-list/mwc-list.js';
import '@material/mwc-list/mwc-list-item.js';

export class AmenityFinder extends LitElement {
  static get styles() {
    return css`
      :host {
        min-height: 100vh;
      }
    `;
  }

  render() {
    return html`<mwc-drawer>
      <mwc-list>
        <mwc-list-item>Home</mwc-list-item>
        <mwc-list-item>Search</mwc-list-item>
        <mwc-list-item>Results</mwc-list-item>
      </mwc-list>
      <div slot="appContent">
        <mwc-top-app-bar> <div slot="title">Title</div> </mwc-top-app-bar>
        <div><p>Main Content!</p></div>
      </div>
    </mwc-drawer>`;
  }
}

customElements.define('amenity-finder', AmenityFinder);

Die <mwc-drawer> Komponente kennt noch einen zusätzlichen Slot title. Mit dessen Hilfe können wir Inhalt, der als Titel in der Sidebar dargestellt wird, definieren. Damit dieser dargestellt wird, muss beim <mwc-drawer> noch das Attribut hasHeader gesetzt sein:

--- src/AmenityFinder.js
+++ src/AmenityFinder.js
   render() {
     return html`
-      <mwc-drawer>
-        <div>
-          <p>Drawer Content!</p>
-        </div>
+      <mwc-drawer hasHeader>
+        <span slot="title">Navigation</span>
+        <mwc-list>
+          <mwc-list-item>Home</mwc-list-item>
+          <mwc-list-item>Search</mwc-list-item>
+          <mwc-list-item>Results</mwc-list-item>
+        </mwc-list>
         <div slot="appContent">
           <mwc-top-app-bar>
             <div slot="title">Title</div>

Unsere Applikation sollte nun etwa so aussehen:

Applikation mit eingebauter Navigation


Die <mwc-drawer> Komponente kennt ein open Attribut / Property. Damit können wir die Sidebar als ein- oder ausgeblendet darstellen. Den Einblende-Zustand der Sidebar speichern wir in einem Property im AmenityFinder. Diesen Wert können wir dann jeweils an das open Property des <mwc-drawer> «binden». Immer wenn der Einblende-Zustand ändert, wird auch der neue Wert an <mwc-drawer> übergeben.

Zum Schluss brauchen wir noch ein Element, mit dem der User im UI den Einblende-Zustand ändern kann. Heute weit verbreitet für dieses Pattern ist der «Hamburger Button». Auch hier bietet uns MWC eine fertige Komponente, die wir verwenden können: <mwc-icon-button>.

Hamburger Button einbauen

Zuerst installieren wir die notwendige <mwc-icon-button> Komponente als Dependency in unserem Projekt

$ npm install @material/mwc-icon-button@^0.22.1

und importieren diese in unsere AmenityFinder Klasse

import '@material/mwc-icon-button';

Danach können wir das <mwc-icon-button> Custom Element entsprechend der Dokumentation verwenden. Wir möchten den Hamburger Button in der <mwc-top-app-bar> links vom Titel anzeigen. Dafür müssen wir den Button lediglich in den navigationIcon Slot der <mwc-top-app-bar> Komponente einbauen. Welches Icon im Button angezeigt wird, steuern wir mit dem icon Attribut und suchen noch den richtigen Namen aus der Liste aller verfügbarer Material Icons aus:

--- src/AmenityFinder.js
+++ src/AmenityFinder.js
         </mwc-list>
         <div slot="appContent">
           <mwc-top-app-bar>
+            <mwc-icon-button
+              icon="menu"
+              slot="navigationIcon"
+            ></mwc-icon-button>
             <div slot="title">Title</div>
           </mwc-top-app-bar>
           <div>

Der eingebaute Hamburger Button sieht dann etwa so aus:

Applikation mit eingebautem Hamburger Button

Zustand der Sidebar abbilden

Unsere Applikation hat jetzt zwar eine Sidebar und einen Hamburger Button im Layout, diese «machen» allerdings noch nichts. Den auf- oder zugeklappten Zustand der Sidebar können wir in unserer Applikation mit einem Property abbilden. Wir definieren ein showSidebar property wie folgt:

static get properties() {
  return {
    showSidebar: {
      type: Boolean
    }
  };
}

Default-Werte für Properties werden in Lit im Konstruktor definiert:

constructor() {
  super();
  this.showSidebar = false;
}

Den Zustand (State) für die Sidebar haben wir nun definiert, jetzt müssen wir ihn noch verwenden. Unsere Sidebar wird als Inhalt von <mwc-drawer> dargestellt. Diese MWC Komponente kennt ein open Property. Das hört sich vielversprechend an und scheint genau das zu sein, was wir brauchen.

Properties können wir in Lit mit der Syntax .value="${...}" binden. In unserem Fall binden wir also das open Property von <mwc-drawer> auf unser Property showSidebar wie folgt:

<mwc-drawer hasHeader .open="${this.showSidebar}">

Nun stellen wir fest, dass die Sidebar nach wie vor sichtbar ist. Die <mwc-drawer> kann ohne zusätzliche Konfiguration die Sidebar nicht verstecken. Entsprechend müssen wir dies über das type Attribut konfigurieren. Hier haben wir zwei Möglichkeiten: entweder dismissible oder modal. Ihr könnt beide ausprobieren und euch für eine Variante entscheiden:

<mwc-drawer hasHeader type="modal" .open="${this.showSidebar}">

Damit haben wir unsere Sidebar versteckt:

Versteckte Sidebar

Zustand der Sidebar durch Interaktion anpassen

Setzen wir nun im Code unser showSidebar Property auf true, sollte die Sidebar wieder Sichtbar sein. Nun brauchen wir noch einen Mechanismus, durch welchen wir den Wert des showSidebar Property über eine Interaktion im UI ändern können. Dafür bedienen wir uns eines weiteren nativen Browser-Mechanismus: DOM Events.

Wir wollen, dass bei jedem Klick der Zustand der Sidebar sich umkehrt. Der eingebaute Hamburger Button <mwc-icon-button> listet in der Dokumentation zwar keine Events auf, allerdings ist die MWC Komponente für den Browser einfach ein natives DOM-Element und wir können ganz normal auf einen Klick-Event einen Listener binden.

In Lit geschieht dies durch die Syntax @event="${...}". So können wir unseren Klick-Handler auf dem <mwc-icon-button> wie folgt registrieren:

--- src/AmenityFinder.js
+++ src/AmenityFinder.js
             <mwc-icon-button
               icon="menu"
               slot="navigationIcon"
+              @click="${() => {
+                this.showSidebar = !this.showSidebar;
+              }}"
             ></mwc-icon-button>
             <div slot="title">Title</div>
           </mwc-top-app-bar>

Et voilà! Wir sollten nun unsere Sidebar durch den Hamburger Button ein- und ausblenden können.

type="modal" vs type="dismissible"

Die <mwc-drawer> Komponente unterscheidet zwischen zwei Typen: modal und dismissible. Falls ihr euch für den Typ modal entschieden habt, wird euch vermutlich noch ein Fehler im UI auffallen. Bei diesem Typ wird die Sidebar über den Inhalt gelegt, statt diesen auf die Seite zu schieben. In diesem Fall kann die modale Sidebar aber auch durch einen Klick ausserhalb der Sidebar wieder geschlossen werden. Der Zustand in unserer Applikation (über das showSidebar Property) wird so allerdings nicht mehr mit dem Zustand im Browser synchronisiert.

Glücklicherweise teilt uns <mwc-drawer> diese Zustandsänderung über den MDCDrawer:closed Event mit. Wir müssen nun also noch für diesen Fall den Zustand der Sidebar mit dem Wert unseres showSidebar Property synchronisieren. Wir binden uns auf den MDCDrawer:closed Event und setzen das showSidebar Property dabei jeweils auf false:

--- src/AmenityFinder.js
+++ src/AmenityFinder.js
   render() {
     return html`
-      <mwc-drawer hasHeader type="modal" .open="${this.showSidebar}">
+      <mwc-drawer
+        hasHeader
+        type="modal"
+        .open="${this.showSidebar}"
+        @MDCDrawer:closed="${() => {
+          this.showSidebar = false;
+        }}"
+      >
         <span slot="title">Navigation</span>
         <mwc-list>
           <mwc-list-item>Home</mwc-list-item>

Gut gemacht!

Du hast den zweiten Teil der Amenity Finder Serie erfolgreich gemeistert. In diesem Teil haben wir das Basis-Layout für die Amenity Finder Applikation implementiert. Du hast HTML- und CSS-Elemente verwendet, um das Layout zu strukturieren und das Aussehen der App zu gestalten. Gleichzeitig haben wir erste Erfahrungen mit Webkomponenten von Material Design gesammelt. Jetzt haben wir eine solide Grundlage, um mit der Umsetzung der funktionalen Anforderungen fortzufahren. Im dritten Teil werden wir die Applikation in einzelne Seiten unterteilen und das Routing implementieren.


Footnotes

1 http://vanilla-js.com