Ein universelles Menü für Webanwendungen
ulrich
2019-12-15 ddb505bbff64a1e1c187b9c9e89e43d24ab3f973
jQuery ausgebaut
3 files modified
282 ■■■■ changed files
README.md 18 ●●●● patch | view | raw | blame | history
app-menu.css 6 ●●●● patch | view | raw | blame | history
app-menu.js 258 ●●●● patch | view | raw | blame | history
README.md
@@ -15,7 +15,7 @@
Das app-menu wird wie folgt in eine Webanwendung eingebaut.
1. Hinzufügen des Ordners `app-menu`
1. Einbinden der Abhängigkeiten (jQuery und Mustache)
1. Einbinden von Mustache
1. Einbinden des Stylesheets
1. Herstellen der Menübeschreibungen
1. Aufruf der Funktion `app_menu_init`
@@ -39,13 +39,12 @@
### Einbinden der Abhängigkeiten
Das app-menu verwendet die JavaScript-Bibliotheken [jQuery](https://jquery.com) und [Mustache](https://github.com/janl/mustache.js). Sie werden einer Webanwendung hinzugefügt, indem ihre Skripte wie folgt eingebunden werden.
Das app-menu verwendet die JavaScript-Bibliothek [Mustache](https://github.com/janl/mustache.js). Sie wird einer Webanwendung hinzugefügt, indem ihr Skript wie folgt eingebunden wird.
```
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
```
Das obige Beispiel bindet jQuery und Mustache aus dem Content Delivery Network [cdnjs](https://cdnjs.com/) ein. Stattdessen können die beiden JavaScript-Komponenten auch lokal ausgeliefert werden.
Das obige Beispiel bindet Mustache aus dem Content Delivery Network [cdnjs](https://cdnjs.com/) ein. Stattdessen kann die JavaScript-Komponente auch lokal ausgeliefert werden.
### Einbinden des Stylesheets
@@ -101,11 +100,11 @@
Der Schalter `wurzel` legt fest, ob noch ein übergeordnetes Menü dargestellt wird. Wenn ja (`wurzel=false`) wird in `vorgaenger.vverweis` der Name der Menübeschreibung und in `vorgaenger.vtitel` der Name, unter dem der Eintrag im Menü erscheinen soll angegeben. Der Schalter `umenue` legt fest, ob ein Menüeintrag auf ein Untermenü verweist. Ist `umenue=true` muss in `verweis` der Name der Menübeschreibung angegeben sein.
Im Element `funktion` eines Menüeintrages wird ein Funktionsname notiert. Die so benannte Funktion wird aufgerufen, wenn das Menü gewählt wird. Eine solche Funktion darf keine Parameter erwarten.
Im Element `funktion` eines Menüeintrages wird ein Funktionsname notiert. Die so benannte Funktion wird aufgerufen, wenn das Menü gewählt wird. Eine solche Funktion darf keine Parameter erwarten und muss im Javascript existieren, das die betreffende Web-App begleitet.
Für jedes Menü, das in der Webanwendung gezeigt werden soll, muss eine Beschreibung im oben angegebenen Format existieren. Menübeschreibungen können beispielsweise in einem Unterordner `menu` abgelegt und der Webanwendung beigegeben werden, wie es weiter oben geschildert ist.
Beispiele für Menübeschreibungen finden sich im [Code der Demo-Anwendung](/gitblit/tree/web!app-vorlage.git/master/menu).
Beispiele für Menübeschreibungen finden sich im [Code der Demo-Anwendung](/gitblit/tree/web!app-vorlage.git/master).
## Aufruf des Menüs
@@ -115,12 +114,13 @@
app_menu_init("menu/", "hauptmenue.json", "jslib/app-menu/app-menu.tpl", ".west", "5em");
```
Die Funktion zur Initialisierung bewirkt, dass eine Menüschaltfläche zum Ein- und Ausblenden von Menüs an der Stelle des Elements mit der ID `nav-toggle` erscheint. Beim Betätigen der Schaltfläche wird das Menü eingeblendet, das in der Beschreibung `hauptmenue.json` im Ordner `menu` beschrieben ist. Das Menü wird an der Stelle des Elements mit dem Klassennamen `west` dargestellt. Nachfolgend die Parameter der Funktion `app_menu_init` in der Übersicht.
Der Aufruf der Funktion `app_menu_init` bewirkt, dass die Vorlage `jslib/app-menu/app-menu.tpl` zur Darstellung
des Menüs geladen und das Menü gezeigt wird, das mit der JSON-Struktur `hauptmenue.json` beschrieben ist. Das Menü wird an der Stelle des Elements mit dem Klassennamen `west` dargestellt. Nachfolgend die Parameter der Funktion `app_menu_init` in der Übersicht.
1. Name des Unterverzeichnisses mit Menübeschreibungen
1. Name der Menübeschreibung, die zuerst angezeigt werden soll
1. Unterverzeichnis und Name der Vorlage für Mustache
1. Name des Selektors, der das Menü aufnehmen soll
1. Name des HTML-Elements, das das Menü aufnehmen soll
1. Breite des Menüs
Der dritte Parameter bezeichnet eine Vorlage, die mit Hilfe von Mustache während der Ausführung der Webanwendung Menübeschreibungen in JSON nach HTML umwandelt. Die so bezeichnete Vorlage kann genutzt werden, um das HTML, aus dem ein Menü besteht, zu verändern.
Der dritte Parameter bezeichnet eine Vorlage, die mit Hilfe von Mustache während der Ausführung der Webanwendung Menübeschreibungen in JSON nach HTML umwandelt. Die so bezeichnete Vorlage fungiert als Bindung zwischen den Menübeschreibungen im JSON-Format und dem HTML, das aus den JSON-Beschreibungen entstehen soll. Die Vorlage kann genutzt werden, um das HTML, aus dem ein Menü bestehen soll, nach eigenen Wünschen zu verändern.
app-menu.css
@@ -27,6 +27,10 @@
  cursor: pointer;
}
/*
  Das div-Element, das das Menue aufnimmt erhaelt
  die Klasse app-menu-content
*/
.app-menu-content {
  overflow: hidden;
}
}
app-menu.js
@@ -1,141 +1,137 @@
/*
 * die nachfolgenden Funktionen steuern das ein- und
 * ausblenden des menues
 */
function app_menu_init(url_prefix, mdesc, mtpl, mselector, mbreite) {
  _app_menu_selector = mselector;
  _app_menu_mbreite = mbreite;
  /*document.querySelector( "#nav-toggle" ).addEventListener("click", function() {
    app_menu_toggle();
  });*/
  $(_app_menu_selector).css("flex-basis", "0em");
  _app_menu_url_prefix = url_prefix;
  $.get(mtpl, function(template) {
    _app_menu_template = template;
    Mustache.parse(_app_menu_template);   // optional, speeds up future uses
    var menuDiv = $("<div/>");
    $(menuDiv).addClass('app-menu-content');
    $(menuDiv).css('position', "relative");
    $(menuDiv).css('left', '-300px');
    $(menuDiv).css('right', '0px');
    $(_app_menu_selector).append(menuDiv);
function AppMenu() {
  var self = this;
  var _app_menu_selector;
  var _app_menu_mbreite;
  var _app_menu_url_prefix = "";
  var _app_menu_template;
    app_menu_laden(mdesc);
  });
}
  /*
   * die nachfolgenden Funktionen steuern das ein- und
   * ausblenden des menues
   */
  this.init = function (url_prefix, mdesc, mtpl, mselector, mbreite) {
    self._app_menu_selector = mselector;
    self._app_menu_mbreite = mbreite;
    var menu = document.querySelector(self._app_menu_selector);
    menu.style.flexBasis = '0em';
    self._app_menu_url_prefix = url_prefix;
    /*
      Die Menue-Vorlage wird einmal zu Beginn geladen und
      waehrend dem Programmlauf immer wieder neu zum Rendern
      einer dynamisch gelandenen Menuebeschreibung verwendet
    */
    var request = new XMLHttpRequest();
    request.open("GET", mtpl);
    request.addEventListener('load', function(event) {
       if (request.status >= 200 && request.status < 300) {
          self._app_menu_template = request.responseText;
          Mustache.parse(self._app_menu_template);   // optional, speeds up future uses
          self.app_menu_laden(mdesc);
       } else {
          console.warn(request.statusText, request.responseText);
       }
    });
    request.send();
  };
function app_menu_do_toggle(elem) {
  /*document.querySelector( "#nav-toggle" ).classList.toggle( "active" );
  document.querySelector( "#nav-toggle" ).blur();*/
  app_menu_toggle();
}
  this.app_menu_do_toggle = function(elem) {
    self.toggle();
  };
function app_menu_toggle() {
  /*document.querySelector( "#nav-toggle" ).classList.toggle( "active" );
  document.querySelector( "#nav-toggle" ).blur();*/
  var westDiv = $(_app_menu_selector);
  if($(westDiv).hasClass('app-menu-open')) {
    $(westDiv).removeClass('app-menu-open');
    $(westDiv).css("flex-basis", "0em");
  } else {
    $(westDiv).addClass('app-menu-open');
    $(westDiv).css("flex-basis", _app_menu_mbreite); // z.B. "16em"
  }
}
/*
 * ab hier Steuerung des Menueinhalts
 */
var _app_menu_url_prefix = "";
var _app_menu_template;
var _app_menu_selector;
var _app_menu_mbreite;
/*
 * mdesc: der URL einer JSON-Datei mit einer Menuebeschreibung
 */
function app_menu_laden(mdesc, richtung) {
  $.ajax({
    url: _app_menu_url_prefix + mdesc,
    type: "GET",
    dataType : "json"
  }).done(function( json ) {
    app_menu_bauen(json, richtung);
  });
}
function app_menu_bauen(menuejs, richtung) {
  // Endposition des bestehenden Menues beim Wechsel herauf
  var linksEndeAlt = "500px";
  var rechtsEndeAlt = "600px";
  // Anfangsposition des neuen Menues beim Wechsel herauf
  var linksAnfangNeu = "-300px";
  var rechtsAnfangNeu = "-1px";
  // Endposition des neuen Menues beim Wechsel herauf
  var linksEndeNeu = "0px";
  var rechtsEndeNeu = "300px";
  if(richtung === 'herunter') {
    // Endposition des bestehenden Menues beim Wechsel herunter
    linksEndeAlt = "-300px";
    rechtsEndeAlt = "-1px";
    // Anfangsposition des neuen Menues beim Wechsel herunter
    linksAnfangNeu = "500px";
    rechtsAnfangNeu = "600px";
    // Endposition des neuen Menues beim Wechsel herunter
    linksEndeNeu = "0px";
    rechtsEndeNeu = "300px";
  }
  var menuDiv = $("<div/>");
  $(menuDiv).addClass('app-menu-content');
  $(menuDiv).css('position', "relative");
  $(menuDiv).css('left', linksAnfangNeu);
  $(menuDiv).css('right', rechtsAnfangNeu);
  $(menuDiv).html(Mustache.render(_app_menu_template, menuejs));
  $('.app-menu-content').delay(100).animate(
    {left: linksEndeAlt },
    100,
    function() {
      $('.smenu').attr('onclick','').unbind('click');
      $('.bitem').attr('onclick','').unbind('click');
      $('.mitem').attr('onclick','').unbind('click');
      $(_app_menu_selector).empty();
      $(_app_menu_selector).append(menuDiv);
      $('.smenu').on('click', app_menu_klick_herunter);
      $('.bitem').on('click', app_menu_klick_herauf);
      $('.mitem').on('click', app_menu_ausfuehren);
      $('.app-menu-content').delay(100).animate({left: linksEndeNeu });
      $('.app-menu-content').animate({right: rechtsEndeNeu });
  this.toggle = function() {
    var menuDiv = document.querySelector(self._app_menu_selector);
    if(menuDiv.classList.contains('app-menu-open')) {
      menuDiv.classList.remove('app-menu-open');
      menuDiv.style.flexBasis = '0em';
    } else {
      menuDiv.classList.add('app-menu-open');
      menuDiv.style.flexBasis = self._app_menu_mbreite;
    }
  );
  $('.app-menu-content').animate({right: rechtsEndeAlt });
}
  };
function app_menu_klick_herunter() {
  app_menu_laden($(this).attr('data-verweis'), 'herunter');
}
  /*
   * ab hier Steuerung des Menueinhalts
   */
function app_menu_klick_herauf() {
  app_menu_laden($(this).attr('data-verweis'), 'herauf');
}
function app_menu_ausfuehren() {
  var functionName = $(this).attr('data-verweis');
  eval(functionName + "(this)");
}
  /*
   * Menuebeschreibung als JSON-Datei laden
   * mdesc: der URL einer JSON-Datei mit einer Menuebeschreibung
   * richtung: z.Zt. unbenutzt: Animationsrichtung
   */
  this.app_menu_laden = function(mdesc, richtung) {
    var xmlhttp = new XMLHttpRequest();
    var url = self._app_menu_url_prefix + mdesc;
    xmlhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        self.app_menu_bauen(JSON.parse(this.responseText), richtung);
      }
    };
    xmlhttp.open("GET", url, true);
    xmlhttp.send();
  };
function app_menu_test() {
  alert("Test");
}
  /*
    Aus einer Menuebeschreibung im JSON-Format mit Hilfe
    von Mustache und der zu Beginn geladenen HTML-Vorlage
    ein div-Element zusammenbauen, das als Menue eingeblendet
    werden kann und dem Element _app_menu_selector hinzufuegen
  */
  this.app_menu_bauen = function(menuejs, richtung) {
function app_menu_test_2() {
  alert("Test 2");
    // neues Menue als div-Element zusammensetzen
    var menuDiv = document.createElement("div");
    menuDiv.classList.add('app-menu-content');
    menuDiv.style.position = 'relative';
    menuDiv.innerHTML = Mustache.render(self._app_menu_template, menuejs);
    // altes Menue loeschen
    self.app_menu_remove_event_listener_multi('.smenu', 'click', self.app_menu_klick_herunter);
    self.app_menu_remove_event_listener_multi('.bitem', 'click', self.app_menu_klick_herauf);
    self.app_menu_remove_event_listener_multi('.mitem', 'click', self.app_menu_ausfuehren);
    var menu = document.querySelector(self._app_menu_selector);
    menu.innerHTML = '';
    // neues Menue hinzufuegen
    menu.append(menuDiv);
    self.app_menu_add_event_listener_multi('.smenu', 'click', self.app_menu_klick_herunter);
    self.app_menu_add_event_listener_multi('.bitem', 'click', self.app_menu_klick_herauf);
    self.app_menu_add_event_listener_multi('.mitem', 'click', self.app_menu_ausfuehren);
    menuDiv = document.querySelector('.app-menu-content');
    menuDiv.classList.add('slidein-from-right');
  };
  this.app_menu_klick_herunter = function() {
    self.app_menu_laden(this.getAttribute('data-verweis'), 'herunter');
  };
  this.app_menu_klick_herauf = function() {
    self.app_menu_laden(this.getAttribute('data-verweis'), 'herauf');
  };
  this.app_menu_ausfuehren = function() {
    var functionName = this.getAttribute('data-verweis');
    eval(functionName + "(this)");
  };
  /* --- Helferlein ---*/
  /*
    sel - '.smenu'
    evt - 'click' fuer onclick
    func - der verweis auf die funktion
  */
  this.app_menu_remove_event_listener_multi = function(sel, evt, func) {
    var elem = document.querySelectorAll(sel);
    for (var index = 0; index < elem.length; index++) {
      elem[index].removeEventListener(evt, func);
    }
  };
  this.app_menu_add_event_listener_multi = function(sel, evt, func) {
    var elem = document.querySelectorAll(sel);
    for (var index = 0; index < elem.length; index++) {
      elem[index].addEventListener(evt, func);
    }
  };
}