Andreas Bruns

Softwareentwicklung für Oldenburg und Bremen

Web-Applikationen erstellen mit jQuery Mobile

Wenn man mit einer Applikation unterschiedliche Geräte unterstützen möchte, wie Laptops, Tablets und Handys, dann kann eine sogenannte Web-App die passende Lösung sein. Dabei erstellt man mit den üblichen Web-Technologien (HTML, CSS, JavaScript) eine Webseite, die innerhalb des Browsers ausgeführt wird. Die im Browser dargestellte Webseite verhält sich auf einem Handy dann ähnlich wie eine für das Betriebssystem entwickelte App.

Eines der bekanntesten Frameworks für die Erstellung von Web-Apps ist jQuery Mobile, das auf Tablets und Handys auch die übliche Gestensteuerung unterstützt (Streifen, Tippen, Drehen, Gedrückthalten usw.). Auf der jQuery Mobile Webseite wird das Framework mit den Worten beschrieben:

A Touch-Optimized Web Framework

jQuery Mobile is a HTML5-based user interface system designed to make responsive web sites and apps that are accessible on all smartphone, tablet and desktop devices.

Das Beispiel in diesem Artikel zeigt eine kleine Web-App, die mit den Bibliotheken Leaflet, jQuery und jQuery Mobile folgende Seiten bietet:

  • Übersichtsseite mit den größten Türmen der Welt
  • Detailseite mit den Informationen zu einem Turm
  • Leaflet-Karte auf Basis von OpenStreetMap mit dem Standort des Turms
  • Einstellungen-Seite

HTML-Code für jQuery Mobile

Zunächst einmal werden für die Wep-App die benötigten Seiten und deren Elemente definiert und konfiguriert. Dann in das Navigieren zwischen den einzelnen Seiten bereits möglich. Dazu brauchen wir kein CSS oder JavaScript anzupassen, sondern wir müssen lediglich in den HTML-Tags die passenden Data-Attribute verwenden:

  • Seite: data-role="page"
  • Header: data-role="header"
  • Inhalt-Bereich: role="main" class="ui-content"
  • Listen: data-role="listview"
  • Footer: data-role="footer" data-position="fixed"
  • Navigation: data-role="navbar" data-iconpos="bottom"
  • Icons: data-icon="gear"

Der gesamte HTML-Quellcode und die angezeigte Einstiegsseite sehen so aus:

 
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example JQuery Mobile</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="libs/jquery-mobile/jquery.mobile-1.4.5.css" />
  <link rel="stylesheet" href="libs/leaflet/leaflet-0.7.3.css" />
  <script src="libs/jquery/jquery-2.1.4.js"></script>
  <script src="libs/jquery-mobile/jquery.mobile-1.4.5.min.js"></script>
  <script src="libs/leaflet/leaflet-0.7.3.js"></script>

  <style>
    .ui-content.map-content {
      position: absolute;
      top: 40px;
      right: 0;
      bottom: 56px;
      left: 0;
      padding: 0 !important;
    }
    #mapElement {
      height: 100%;
    }
    dt {
      float: left;
      clear: left;
      width: 70px;
      text-align: right;
      font-weight: bold;
      color: rgb(51, 136, 204)
    }
    dd {
      margin: 0 0 0 80px;
      padding: 0 0 1em 0;
    }
  </style>
</head>

<body>

<div data-role="page" id="towers">
  <div data-role="header">
    <h1>T&uuml;rme</h1>
  </div>
  <div role="main" class="ui-content">
    <ul id="towerList" data-role="listview" data-inset="true" data-filter="true">
      <li>
        <a href="#details" class="detailLink">
          <img src="https://upload.wikimedia.org/wikipedia/de/thumb/9/93/Burj_Khalifa.jpg/245px-Burj_Khalifa.jpg">
          <h2>Eiffelturm</h2>
          <p>Paris</p>
          <span class="ui-li-count">324 m</span>
        </a>
        <a href="#image" class="imageLink" data-rel="popup" data-position-to="window" data-transition="pop">Tower</a>
      </li>
      <li>
        <a href="#details" class="detailLink">
          <img src="https://upload.wikimedia.org/wikipedia/de/thumb/9/93/Burj_Khalifa.jpg/245px-Burj_Khalifa.jpg">
          <h2>Fernsehturm Berlin</h2>
          <p>Berlin</p>
          <span class="ui-li-count">368 m</span>
        </a>
        <a href="#image" class="imageLink" data-rel="popup" data-position-to="window" data-transition="pop">Tower</a>
      </li>
     </ul>
  </div>
  <div data-role="footer" data-position="fixed">
    <div data-role="navbar" data-iconpos="bottom">
      <ul>
        <li><a href="#towers" data-icon="bullets" class="ui-btn-active ui-state-persist">T&uuml;rme</a></li>
        <li><a href="#settings" data-icon="gear">Einstellungen</a></li>
      </ul>
    </div>
  </div>
</div>

<div data-role="page" id="details">

  <div data-role="header" data-position="fixed">
    <a href="#towers" data-icon="arrow-l">T&uuml;rme</a>
    <h1>Eiffelturm</h1>
    <a href="#map" data-icon="location">Karte</a>
  </div>
  <div role="main" class="ui-content">
    <dl id="towerData">
      <dt>Name</dt>
      <dd id="towerName">Eiffelturm</dd>
      <dt>Platz</dt>
      <dd id="towerScore">76</dd>
      <dt>H&ouml;he</dt>
      <dd id="towerHeight">324</dd>
      <dt>Stadt</dt>
      <dd id="towerCity">Paris</dd>
      <dt>Staat</dt>
      <dd id="towerState">Frankreich</dd>
      <dt>Baujahr</dt>
      <dd id="towerYear">1889</dd>
    </dl>
    <a href="#popupImageContainer" data-rel="popup" data-position-to="window" data-transition="pop">
      <img id="towerImageTag" src="https://upload.wikimedia.org/wikipedia/de/thumb/9/93/Burj_Khalifa.jpg/245px-Burj_Khalifa.jpg">
    </a>
  </div>
  <div data-role="popup" id="popupImageContainer" data-overlay-theme="b" data-theme="b">
    <a href="#" data-rel="back" class="ui-btn ui-corner-all ui-shadow ui-btn-a ui-icon-delete ui-btn-icon-notext ui-btn-right">Ok</a>
    <img id="popupImage" src="https://upload.wikimedia.org/wikipedia/de/thumb/9/93/Burj_Khalifa.jpg/245px-Burj_Khalifa.jpg">
  </div>

  <div data-role="footer" data-position="fixed">
    <div data-role="navbar" data-iconpos="bottom">
      <ul>
        <li><a href="#towers" data-icon="bullets" class="ui-btn-active ui-state-persist">T&uuml;rme</a></li>
        <li><a href="#settings" data-icon="gear">Einstellungen</a></li>
      </ul>
    </div>
  </div>
</div>

<div data-role="page" id="map">
  <div data-role="header" data-position="fixed">
    <a href="#details" data-icon="arrow-l">Turm</a>
    <h1>Karte</h1>
  </div>

  <div role="main" class="ui-content map-content">
    <div id="mapElement"></div>
  </div>

  <div data-role="footer" data-position="fixed">
    <div data-role="navbar" data-iconpos="bottom">
      <ul>
        <li><a href="#towers" data-icon="bullets" class="ui-btn-active ui-state-persist">T&uuml;rme</a></li>
        <li><a href="#settings" data-icon="gear">Einstellungen</a></li>
      </ul>
    </div>
  </div>
</div>

<div data-role="page" id="settings">
  <div data-role="header">
    <h1>Einstellungen</h1>
  </div>
  <div role="main" class="ui-content">
    <fieldset data-role="controlgroup">
      <legend>Popup des Markers auf Karte anzeigen:</legend>
      <input name="popup" id="popup-no" value="no" type="radio">
      <label for="popup-no">Popup nicht anzeigen</label>
      <input name="popup" id="popup-yes" value="yes" type="radio" checked="checked">
      <label for="popup-yes">Popup anzeigen</label>
    </fieldset>
  </div>
  <div data-role="footer" data-position="fixed">
    <div data-role="navbar" data-iconpos="bottom">
      <ul>
        <li><a href="#towers" data-icon="bullets">T&uuml;rme</a></li>
        <li><a href="#settings" data-icon="gear" class="ui-btn-active ui-state-persist">Einstellungen</a></li>
      </ul>
    </div>
  </div>
</div>

</body>
</html>
jQuery Mobile: Übersicht nur HTML

jQuery Mobile: Übersicht nur HTML

JavaScript-Code für jQuery Mobile

Das Navigieren zwischen den Seiten funktioniert also schon ganz gut, allerdings werden noch die falschen Daten angezeigt und die Leaflet-Karte funktioniert nicht. Das bedeutet, wir benötigen noch einiges an JavaScript, das wir am Ende des Body-Elements einfügen:

  • Turm-Daten aus einer JSON-Datei lesen
  • Initialisierung von Seite anpassen (Page-Event ‚pageinit‘)
  • Anzeigen von Seiten anpassen (Page-Event ‚pageshow‘)
  • Klick-Events auswählen (Event ‚vclick‘)
  • Leaflet-Karte korrekt befüllen und aktualisieren

Bei der dynamischen Erweiterung des HTML-DOMs durch Leaflet und jQuery Mobile kommen sich die beiden Bibliotheken in den verwendeten Versionen leider in die Quere. Entweder werden Karten-Kacheln im Desktop-Browser oder in den mobilen Browsern nicht korrekt angezeigt. Auf dieser Seite wird das Problem geschildert. Mit dem Aufruf meiner Funktion recalculateMapSize() (Zeilen 57 und 69-75) beim Anzeigen der Karte werden die Darstellungsprobleme behoben.

 
[
  {
    "id": "1",
    "name": "Burj Khalifa",
    "height": "830",
    "city": "Dubai",
    "state": "Vereinigte Arabische Emirate",
    "year": "2010",
    "location": [25.197222, 55.274167],
    "image": "https://upload.wikimedia.org/wikipedia/de/thumb/9/93/Burj_Khalifa.jpg/245px-Burj_Khalifa.jpg"
  },
  {
    "id": "2",
    "name": "Tōkyō Sky Tree",
    "height": "634",
    "city": "Tokio",
    "state": "Japan",
    "year": "2012",
    "location": [35.709947, 139.810853],
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Tokyo_Sky_Tree_2012.JPG/200px-Tokyo_Sky_Tree_2012.JPG"
  }
]
 
<script>
  var selectedTower = null;
  var allTowers = null;

  // Initialisierung der Übersicht-Seite
  $(document).on('pageinit', '#towers', function () {
    $.getJSON("towers.json", function(jsonTowers) {
      allTowers = jsonTowers;
      var towerItems = [];
      for (var i=0; i<jsonTowers.length; i++) {
        var tower = jsonTowers[i];
        towerItems.push( '<li><a href="#" data-id="' + tower.id + '" class="detailLink"><img src="' + tower.image + '"><h2>' + tower.name + '</h2><p>' + tower.city + ' / ' + tower.state + '</p><span class="ui-li-count">' + tower.height + ' m</span></a></li>' );
      }
      $('#towerList').empty();
      $('#towerList').append( towerItems.join( "" ) );
      $('#towerList').listview('refresh');
    });
  });

  // Transition zur Detail-Seite
  $(document).on('vclick', '#towerList li a.detailLink', function () {
    var selectedTowerIdAsString = $(this).attr('data-id');
    selectedTower = getTowerById(allTowers, selectedTowerIdAsString);
    $(":mobile-pagecontainer").pagecontainer( "change", "#details", { transition: "slide" } );
  });

  // Anzeigen der Detail-Seite
  $(document).on('pageshow', '#details', function (e, data) {
    $('#details h1').text(selectedTower.name);
    $('#details #towerData #towerName').text(selectedTower.name);
    $('#details #towerData #towerScore').text(selectedTower.id);
    $('#details #towerData #towerHeight').text(selectedTower.height);
    $('#details #towerData #towerCity').text(selectedTower.city);
    $('#details #towerData #towerState').text(selectedTower.state);
    $('#details #towerData #towerYear').text(selectedTower.year);
    $('#details #towerImageTag').animate({width: "20%"}, {duration: 0}).attr("src",selectedTower.image);
    $('#details #popupImage').attr("src",selectedTower.image);
  });

  // Initialisierung der Karten-Seite
  var map = null;
  var marker = null;
  var popup = null;
  $(document).on('pageinit', '#map', function(){
    map = L.map('mapElement');
    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
      minZoom: 2,
      maxZoom: 18,
      attribution: 'Beispiel JQuery Mobile von <a href="http://andreas-bruns.com">Andreas Bruns</a>'
    }).addTo(map);
    marker = L.marker([0,0]).addTo(map);
    marker.bindPopup();
  });

  // Anzeigen der Karten-Seite
  $(document).on('pageshow', '#map', function (e, data) {
    recalculateMapSize();
    map.setView(selectedTower.location, 14);
    marker.setLatLng(selectedTower.location);
    marker.getPopup().setContent("<b>" + selectedTower.name + "</b><br>H&ouml;he: " + selectedTower.height + " m");
    var showPopup = $("#settings #popup-yes").prop('checked');
    if (showPopup) {
      marker.openPopup();
    } else {
      marker.closePopup();
    }
  });

  function recalculateMapSize() {
    // Use full height in desktop browser: checked on MacOS with Firefox, Chrome and Safari
    $(window).resize();
    // Avoid grey tiles in mobile browser: checked on iOS with Chrome and Safari
    // https://groups.google.com/forum/#!topic/leaflet-js/Br-gY0aJ5Dc
    map.invalidateSize(false);
  };

  function getTowerById(allTowers, id) {
    for (var i=0; i<allTowers.length; i++) {
      if (allTowers[i].id === id) {
        return allTowers[i];
      }
    }
    return;
  };
</script>

Eine schöne Web-App dank jQuery Mobile

Damit haben wir eine kleine Web-App erstellt, die auf allen Geräten ein einheitliches Design aufweist und auf mobilen Geräten auch die Gestensteuerung unterstützt. Das Beispiel kann bei Github (https://github.com/me4bruno/blog-examples -> example-example-jquery-mobile) heruntergeladen werden. Die fertige Anwendung sieht übrigens so aus:

jQuery Mobile: Übersicht

jQuery Mobile: Übersicht

jQuery Mobile: Details

jQuery Mobile: Details

jQuery Mobile: OpenStreetMap-Karte mit Leaflet

jQuery Mobile: OpenStreetMap-Karte mit Leaflet

jQuery Mobile: Einstellungen

jQuery Mobile: Einstellungen

Kommentare sind geschlossen.