Neues Material

Womit erstell ein Java Entwickler seine Webseiten? So traurig es im ersten Augenblick klingen mag, er verwendet Javascript. Denn egal welche Technologie verwendet werden soll, damit die Website dem Benutzer gefällt, wird der  Einsatz von Javascript benötigt.

Warum also nicht gleich aus der Not eine Tugend machen und die Javascript Bibliothek React verwenden. Die Bibliothek  beweißt ihre Tauglichkeit im täglichen Einsatz bei Facebook, Instagram und Co. Eine React Applikation besteht aus einer hierarchischen Anordnung von abgeschlossenen Komponenten.  Der State der Applikation wird zentral gehalten und die Informationen von dort aus in die Komponenten hinuntergereicht. Alle Änderungen am aktuellen Zustand der Applikation führen augenblicklich zu Änderungen in den einzelnen Komponenten. Damit die Oberfläche sich dabei schnell anpasst, werden nur die tatsächlichen Änderungen am DOM Modell berechnet und ausgeführt.

Damit unsere kleine Beispielapplikation auch ein wenig glänzen kann, verwenden wir zusätzlich die Bibliothek  Material-UI. Diese Bibliothek liefert React Komponenten, die Googles Material Design bereitstellen.

Der zentraler Code unserer Applikation findet sich in den folgenden Zeilen

render() {
  const { classes, people } = this.state
  return (
    <Paper>
      <NavBar onClickReload={this.handleReload} />
      <div style={{ flexGrow: 1, marginTop: 64, padding: 8 }}>
        <Grid container spacing={8} alignItems='stretch'>
          {people.map((person) =>
            <Grid item xs={12} sm={6} md={4} lg={2} key={person.id}>
    	      <PeopleCard value={person}/>
	    </Grid>
          )}
        </Grid>
      </div>
      <Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'left', }}
        open={this.state.open} autoHideDuration={6000} onClose={this.handleClose} message={''}
        action={[
          <IconButton key="close" aria-label="Close" color="inherit" onClick={this.handleClose}>
            <CloseIcon />
          </IconButton>,
        ]}
      />
    </Paper>
  )
}

Die render Methode liefert die Darstellung unserer Applikation, die auch nur eine React Komponente ist. In der ersten Zeile holen wir uns Informationen aus dem state Member der Komponente und speichern sie unter classes und people.  In der Variable people befindet sich eine Liste von Personen aus meinem Familienstammbaum, die dargestellt werden soll.  Danach folgt schon gleich das return Statement, das eine Struktur zurückliefert. Was sofort auffällt, es handelt sich augenscheinlich um HTML Code mitten in den Javascript Sourcen. Es ist nicht wirklich HTML Code, stattdessen wird mit Hilfe von JSX, unhandlicher Javascript Code durch eine Domain Specific Language (DSL) ersetzt.

Unsere Applikation besteht aus einer Paper Komponente, auf der eine eigene Menu Komponente NavBar und darunter eine Grid Komponente, die für alle Personen eine spezielle Card Komponente PersonCard enthält.  Darunter ist noch eine Snackbar Komponente definiert, die im Fehlerfall eine Meldung anzeigt.

Der Zustand der Applikation ist recht einfach definiert, sie besteht anfänglich aus einer leeren Liste von Personen, einer Status-Variablen für unsere Rest-Schnittstelle und einen Flag, ob unsere Snackbar angezeigt wird oder nicht.

state = {
  people: [],
  etag: 'ETAG',
  open: false 
}

Die Liste der Personen wird direkt in der render Methode verwendet, in der für jedes Element  ein Grid-Item mit einer PersonCard erzeugt wird. Die Daten zur Anzeige werden dafür über das Attribute value an die Card übergeben und dort mit Hilfe anderer Komponenten dargestellt.

{people.map((person) => 
  <Grid item xs={12} md={4} lg={2} key={person.id}>
    <PeopleCard value={person}/>
  </Grid> 
)}

Die optionalen Attribute xs (extra-small),  md (medium) und lg (large) dienen zur Anpassung des Darstellung bei unterschiedlichen Endgeräten. In unseren Beispiel definieren sie, dass auf meinem Handy (extra-small) die Cards untereinander angeordnet sind und auf meinem Notebook (large) sechs Spalten mit Cards zu sehen sind.

Die einzelnen Cards werden durch folgende Funktion definiert

function PersomCard(props) {
  const { classes, value } = props;
  return (
    <Card>
      <CardHeader 
        avatar={
          <Avatar aria-label="Recipe" className={value.gender == 'MALE' ? classes.blueAvatar : (value.gender == 'FEMALE' ? classes.redAvatar : classes.grayAvatar)}>{value.sex}</Avatar>
        }
        action={
          <div>
            <IconButton><EditIcon /></IconButton>
            <IconButton><ToggleIcon on={value.mark} onIcon={<BookmarkOnIcon />} offIcon={<BookmarkOffIcon />} /></IconButton>
          </div>
        }
        title={value.name}
        subheader={value.birth}
      />
      <CardContent className={classes.card}></CardContent>
    </Card>
  );
}

Die hier verwendeten Komponenten sind alle Bestandteil des Material-UI Bibliothek. Sie stellen je nach Geschlecht der Person, einen roten, bzw. einen blauen Avatar mit dem Geschlechts Symbol dar, sowie den Namen und das Geburtsdatum der Person und darunter zwei, momentan funktionslose Button mit Standard Material Icons. Je nachdem, ob in unserem Person-Datensatz ein Bookmark gesetzt ist oder nicht, verwenden wir unterschiedliche Icons.

Bislang haben wir noch keine Personen Daten für unsere kleine Web-Anwendung, aber die können wir von der Rest-Schnittstelle aus REST in Peace beziehen. Dafür definieren wir die beiden folgenden Funktionen.

handleReload = () => {
  axios.get("http://<SERVER>/ancestors", { 
    auth: { username: <USER>, password: <PASSWORD> }, 
    headers: {'If-None-Match': this.state.etag }})
    .then(response => {
      const newPeople = response.data._embedded.personResourceList.map(c => {
        return {
          id: c.person.urn, sex: c.person.sex, gender: c.person.gender,
          name: c.person.name,
          birth: c.person.birth == null || c.person.birth.date == null ? '' : '*' + c.person.birth.date,
      };
    });
    const newState = Object.assign({}, this.state, {
      people: newPeople, etag: response.headers.etag
     });
     newPeople.sort(function (l,u) {
       return l.name.toLowerCase().localeCompare(u.name.toLowerCase());
     });

     this.setState(newState);
  })
  .catch(error => { 
    if (error == 'Error: Request failed with status code 304') {
      return;
    }
    this.setState(Object.assign({}, this.state, { message: '' + error, open: true })) 
  });
}

componentDidMount() {
  this.handleReload();
}

Wir verwenden den Javascript Rest Client axios in der Funktion handleReload um die Liste aller Ahnen zu ziehen. War die Anfrage erfolgreich, dann werden die Personen Daten in eine Liste eingefügt und nach Namen sortiert. Dann wird der Status der Applikation neu gesetzt. Bei React wird dafür immer ein neuer Status erzeugt und nicht der aktuelle Status verändert.  In unserem Fall mit der neuen Liste von Personen und dem ETag Flag aus der Rest Antwort. Diesen Wert nutzen wir, um nicht unnötig Daten vom Server anzufordern, wenn es keine Änderung in der Ahnenliste gab. Die Funktion componentDidMount initialisiert unsere Komponente beim Start der Applikation. Zusätzlich definieren wir in unserem Menu einen Reload Button, mit dem wir die Ahnenliste auf unserem Endegerät aktualisieren können.

<IconButton className={classes.menuButton} onClick={this.props.onClickReload} color="inherit" aria-label="Reload">
  <ReloadIcon />
</IconButton>

Die Funktion this.props.onClickReload ist unsere handleReload Funktion die wir mit <NavBar onClickReload={this.handleReload} /> an unser Menu übergeben. Damit wird, bei jedem Druck auf den Button, eine Rest-Anfrage an unseren Server übermittelt und die Ansicht ggf. aktualisiert.

Damit ist unsere erste React Applikation auch schon fertig, wir können sie aber leicht durch weitere Komponenten zum Erstellen neuer Ahnen Einträge oder zur Korrektur bestehender Ahnen Einträge ergänzen.