durchstarten mit AngularJs & Bootstrap

Angularjs Tutorial Teaser

Ziel ist es, eine Single-page APP zu entwickeln, die auf NodeJS (zum Ausliefern der statischen Dateien), AnuglarJS sowie Boostrap basiert. Dabei habe ich auf die Wartbarkeit und Update-Möglichkeiten viel Wert gelegt. Die App stellt dem Nutzer einen Wochenplaner zur Verfügung, indem er Aufgaben hinterlegen, löschen und als „erledigt“ markieren kann. Das Speichern der Daten erfolgt im Browser, dafür wird die App auf den Localstorage des Browsers zugreifen.
Codebase: Taskmanager
Lizenz: MIT Lizenz
Demo: Taskmanager

Übersicht

  1. Komponenten
  2. NodeJs starten
  3. Ordnerstruktur
  4. Abhänigkeiten
  5. APP-Grundgerüst
  6. Routes
  7. Templates
  8. Controller
  9. Service
  10. App Abhänigkeiten

Komponenten

Node.js

Node.js ist eine Backendumgebung, die auf der V8 Engine von Google aufbaut. Die Umgebung führt JavaScript Code aus, sie ist auf allen gängigen Systemen (Windows, Linux, OSX) lauffähig. Node.js brintg einen Paketmanager mit, der eine umfangreiche Auswahl an Modulen hat. Es steht unter der Expat Lizenz , ähnelt stark MIT.
https://nodejs.org/
https://www.npmjs.com/

AngularJS

AngularJS ist ein Frontendumgebung, die für Single-page Web-Apps konzipiert ist. Es gibt ein initial Request, danach erfolgt die Bearbeitung ausschließlich über Ajax-Requests. Dies bietet den Vorteil, dass bei Inhaltsänderungen der „Rahmen“ nicht neu geladen werden muss, sondern lediglich die Informationen ausgetauscht werden. AngularJS hat eine große Entwicklergemeinschaft und bietet viele Module. Es steht unter der MIT Lizenz .
https://angularjs.org/

AngularJS Route / Animate

Angular Route und Animate sind Module von AngularJS. Das Route Modul wertet die URL aus und hinterlegt den Controller sowie das Template auf die URL. Das Animate Modul, wie der Name schon erahnen lässt, ist ein „Helfer“ für die Animationen in AngularJS. Es bietet eine breite Palette an fertigen CSS Animationen, angefangen von einfachen Ein/Ausblenden-Effekten bis hin zum Verschieben von Elementen.
Route Dokumentation
Animate Dokumentation

AngularUI

AngularUI ist ein Modul für AngularJS. Mit diesem Modul kann man die interaktiven Elemente von Bootstrap leichter mit AngularJS ansteuern. Es beinhaltet mehr Elemente als das Original Bootstrap Framework, unter anderem ein Kalender und ein Timepicker. MIT Lizenz .
https://angular-ui.github.io/bootstrap

Bootstrap

Bootstrap ist ein CSS/HTML/JS Framework mit dem Fokus auf Responsivität und bringt eine umfangreiche Komponenten Sammlung mit. Es wird von Twitter entwickelt und hat eine sehr große Entwicklergemeinschaft. Des weiteren findet man im Web zahlreiche Anleitungen und Tipps wie man mit dem Framework umzugehen hat. MIT Lizenz .
http://www.getbootstrap.com/

NodeJS starten

Ich werde nicht weiter auf die Installation eingehen, da es diesbezüglich schon sehr gute und umfangreiche Anleitungen im Web gibt Installationsanleitung.
Nachdem NodeJs und NPM installiert ist, wird noch ein einfacher Fileserver benötigt, um die statischen Dateien auszuliefern. Um den Server systemweit zu installieren, müssen in der Konsole folgende Befehle ausgeführt werden.

sudo npm install http-server -g
cd /mein/Projekt/Pfad
http-server
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8082

Um sich mit dem System Vertraut zu machen, empfiehlt sich ein kleiner Test mit einer Hallo Welt Datei.

<html>
  <head>
    <title>Hallo Welt!</title>
  </head>
  <body>Hallo Welt!</body>
</html>

Den HTML-Code als index.html in das Projektverzeichnis speichern und im Browser die lokale Adresse eingeben. Bei meinem Beispiel wäre das http://127.0.0.1:8082.

Ordnerstruktur

Erläuterung wie ich meine Ordnerstruktur gewählt habe und welche Dokumente später in den Ordnern liegen.

Ordnerübersicht

Ordnerstruktur der App

app

Hier wird die Logik für die App angelegt sein. Im Controller Ordner werden die Logikbausteine für die einzelnen Seiten/Elemente liegen. Im Direktiven-Ordner kann Logik hinterlegt werden, der seitenübergreifend, an DOM-Elemente gebunden werden kann. Die im Service-Ordner enthaltene Logik ist für die Kommunikation zwischen Server und Client zuständig und bereitet die Daten für die Controller vor. Im Template Ordner werden die einzelnen Parcials für die jeweiligen Seiten hinterlegt.

css

Hier werden alle selbst erstellten CSS/LESS Dateien abgelegt. Im Theme-Ordner werden die custom Designs von den jeweiligen Librarys abgelegt, z.B. für Bootstrap ein Flat-Design.

font

Den Font-Ordner habe ich inhaltlich aufgeteilt, damit Zeichen und Text voneinander getrennt sind.

image

Den Image-Ordner halte ich lieber etwas flexibler, da er sehr Content abhängig ist und deswegen von Projekt zu Projekt stark variieren kann. In meinem Beispiel werde ich dort nur kleine Systembilder speichern, dazu gehören Logo und das FavIcon.

library

In diesem Ordner werden die externen Ressourcen abgelegt. Dabei spielt es keine Rolle, welches Dateiformat sie haben. Für jede Ressource, in diesem Fall AngularJs und Bootstrap, habe ich einen eigenen Ordner angelegt.

Abhänigkeiten

Zuerst müssen die jeweiligen Librarys über NPM runter geladen werden, danach werden die Dateien in die jeweiligen Ordner kopiert. Der node_modules Ordner wird von NPM automatisch erstellt sobald man ein Paket nur für das Projekt installiert.

npm install angular
npm install angular-route
npm install angular-animate
npm install bootstrap
npm install angular-ui-bootstrap

Nach dem installieren, hat das Projekt einen neuen Ordner namens node_modules im Projektverzeichnis erhalten. In diesen werden alle Abhängigkeiten, die über NPM installiert werden, zwischengespeichert.

Bootstrap compilieren

Damit eigene Pfadangaben in Bootstrap hinterlegen werden können, müssen die LESS Dateien compiliert werden. Um alle Abhängigkeiten von Bootstrap zu installieren, muss in den boostrap Ordner gewechselt und die Installationsdatei ausgeführt werden.

cd /node_modules/boostrap/
npm install
sudo npm install -g grunt-cli

In der Datei „/node_modules/boostrap/less/variables.less“ kann jetzt die Pfadangabe bearbeitet werden.

//** Load fonts from this directory.
@icon-font-path:          "../fonts/";
// zu
@icon-font-path:          "../../font/icon/";

Um die LESS Dateien zu compilieren, muss die Grunt Datei im Bootstrap Ordner ausgeführt werden – Im Terminal die Befehl grunt ausführen und kurz warten bis der Prozess abgeschlossen ist.

grunt

Nun können die geladenen Dokumente aus dem node_modules Ordner in den Projekt-Ordner kopiert werden.

cp node_modules/angular/angular.min.js library/angular/angular.min.js
cp node_modules/angular-route/angular-route.min.js library/angular/angular-route.min.js
cp node_modules/angular-animate/angular-animate.min.js library/angular/angular-animate.min.js
cp node_modules/bootstrap/dist/css/bootstrap.min.css library/bootstrap/bootstrap.min.css
cp node_modules/bootstrap/dist/js/bootstrap.min.js library/bootstrap/bootstrap.min.js
cp node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.eot font/icon/glyphicons-halflings-regular.eot
cp node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.svg font/icon/glyphicons-halflings-regular.svg
cp node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf font/icon/glyphicons-halflings-regular.ttf
cp node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff font/icon/glyphicons-halflings-regular.woff
cp node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 font/icon/glyphicons-halflings-regular.woff2
cp node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js library/angularUI/ui-bootstrap.js
cp node_modules/angular-ui-bootstrap/dist/ui-bootstrap-csp.css library/angularUI/ui-bootstrap-csp.css
cp node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js library/angularUI/ui-bootstrap-tpls.js

Dadurch sind alle Ressourcen auf den neusten Stand, wenn ein neues Projekt erstellt wird.

APP-Grundgerüst

In die index.html müssen wir nun die Ressourcen einbetten, die soeben runter geladen wurden. Die CSS Dateien sind am besten im Header einzubauen und die Javascript Dateien als letztes im Body einzubinden. In den Body Tag muss noch der Name der App vermerkt werden, damit Angular korrekt initialisiert wird. Des Weiteren habe ich eine Navigation hinzugefügt. Als Basis hatte ich dieses Beispiel verwendet http://www.getbootstrap.com/examples/navbar/ und ein wenig modifiziert. Unter der Navigation soll der Inhalt platziert werden, dafür benötigen wir noch ein Platzhalter damit auch AngularJS weiß, wo der Inhalt zugeordnet wird.

<!DOCTYPE html>
  <head>
    <title>Taskmanager</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <base href="/">

    <!-- externe CSS Resourcen laden -->
    <link rel="stylesheet" href="/library/bootstrap/bootstrap.min.css">
    <link rel="stylesheet" href="/library/angularUI/ui-bootstrap-csp.css">
  </head>
  <body ng-app="Taskmanager">

    <!-- standart Bootstrap Navigationsleiste -->
    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarWeek" aria-expanded="false">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Taskmanager</a>
        </div>
        <div id="navbarWeek" class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
            <li><a href="#/day/0">Montag</a></li>
            <li><a href="#/day/1">Dienstag</a></li>
            <li><a href="#/day/2">Mittwoch</a></li>
            <li><a href="#/day/3">Donnerstag</a></li>
            <li><a href="#/day/4">Freitag</a></li>
            <li><a href="#/day/5">Sammstag</a></li>
            <li><a href="#/day/6">Sonntag</a></li>
          </ul>
        </div>
      </div>
    </nav>

    <!-- An dieser stelle wird Angular die Templates einfügen -->
    <div class="container">
      <div ng-view></div>
    </div>

    <!-- externe Resourcen laden -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="/library/bootstrap/bootstrap.min.js"></script>
    <script src="/library/angular/angular.min.js"></script>
    <script src="/library/angular/angular-route.min.js"></script>
    <script src="/library/angular/angular-animate.min.js"></script>
    <script src="/library/angularUI/ui-bootstrap.js"></script>
    <script src="/library/angularUI/ui-bootstrap-tpls.js"></script>

    <!-- App Resourcen laden -->
    <script src="/app/main.js"></script>
  </body>
</html>

Nun wird noch die app.js benötigt. In der wird AngularJS initialisiert und die Routes für das Projekt setzen.

var app = angular.module('Taskmanager', ['ngRoute', 'ui.bootstrap']);

Die main.js muss nun in den App-Ordner gespeichert werden.
Um zu prüfen ob alles bis zu dieser Stelle funktioniert, kann man den Server starten und das Zwischenergebnis betrachten.

http-server

Browseransicht Zwischenergebnis

Routes

Um zwischen den Tagen wechseln zu können, müssen die Route-Angaben in der main.js definiert werden. Der erste Eintrag definiert die Startseite und der zweite die einzelnen Tage.

app.config(function ($routeProvider, $locationProvider) {
  $routeProvider
      .when('/',
          {
              controller: 'Tasklist',
              templateUrl: '/app/template/Tasklist.html'
          })
      .when('/day/:day',
          {
              controller: 'Tasklist',
              templateUrl: '/app/template/Tasklist.html'
          })
      .otherwise({ redirectTo: '/' });
});

Mit einer Switch/Case Logik kann man den Controllern die jeweiligen Adressen zuweisen.
Parameter werden über :PARAMETERNAME gesetzt, diese werden dann in der URL dynamisch ausgelesen.
/day/:day Beispiel:
http://127.0.0.1:8082/day/0
http://127.0.0.1:8082/day/Montag
/day/:day/detail Beispiel:
http://127.0.0.1:8082/day/0/detail
http://127.0.0.1:8082/day/Montag/detail

Template

Es wird nur ein Template benötigt, da es nur eine Tagesansicht gibt.

<h2>{{day}}</h2>
<div class="panel panel-default">
  <div class="panel-heading">
    <input type="text" class="form-control" placeholder="Aufgabe" ng-model="taskName">
  </div>
  <div class="panel-body">
    <div class="form-group">
      <input type="text" class="form-control" placeholder="Text" ng-model="taskText">
    </div>
    <button type="button" class="btn btn-success pull-right" ng-click="addTask()">erstellen</button>
  </div>
</div>
<div ng-repeat="task in tasks track by $index">
  <div class="panel panel-default">
    <div class="panel-heading">{{task.name}}</div>
    <div class="panel-body">
      {{task.text}}
      <form class="pull-right" data-example-id="btn-tags">
        <button type="button" class="btn btn-danger" ng-click="deleteTask($index)">löschen</button>
        <button type="button" class="btn btn-primary" ng-show="task.status" ng-click="changeStatusTask($index)">abgeschlossen</button>
        <button type="button" class="btn btn-default" ng-show="!task.status" ng-click="changeStatusTask($index)">offen</button>
      </form>
    </div>
  </div>
</div>

Der HTML-Code muss wie im Route angegeben und unter „/app/template/Tasklist.html“ gespeichert werden.

<h2>{{day}}</h2>

Day gibt die Variable aus, die im Controller definiert wird.

<input ... ng-model="taskName">

Mit „ng-model“ wird eine Variable an ein Element gebunden. Es muss aber nicht immer zwangsmäßig ein Formelement sien.

<button ... ng-click="addTask()">erstellen</button>

Mit „ng-click“ wird eine Methode an das klick Event gebunden. In diesem Fall wird beim druck auf den Button, die Methode addTask ausgeführt.

<div ng-repeat="task in tasks track by $index">

Mit „ng-repeat“ kann eine Schleife im Template definiert werden. „tasks“ ist ein Array und die einzelnen Einträge sind in der Schleife mit task abrufbar. Zusätzlich wird noch ein Index erstellt um die Wiederholungen mitzuzählen.

<button ... ng-show="task.status" ... >abgeschlossen</button>

Mit „ng-show“ kann man Elemente ein und ausblenden. Es wird ein boolischer Wert erwartet.

Controller

Ich Controller werden die Methoden für die Templates hinterlegt. Sie dienen auch als „Mittelsmann“ um mit dem Service zu kommunizieren, bei dem die Daten hinterlegt werden.

app.controller('Tasklist', function ($scope, $routeParams, Task) {
  var days = ["Montag","Dienstag","Mitwoch","Donnerstag","Freitag","Sammstag","Sonntag"];
  var currentDay = $routeParams.day || 0;
  $scope.day = days[currentDay];

  Task.loadTasks(currentDay, function(tasks){
    $scope.tasks = tasks || [];
  });

  $scope.addTask = function(){
    $scope.tasks.push({"name": $scope.taskName, "text": $scope.taskText, "status": false});   

    Task.updateTasks(currentDay, $scope.tasks, function(tasks){
      $scope.tasks = tasks;
      $scope.taskName = "";
      $scope.taskText = "";
    });
  };

  $scope.deleteTask = function(index){
    $scope.tasks.splice(index, 1);
    Task.updateTasks(currentDay, $scope.tasks, function(tasks){
      $scope.tasks = tasks;
    });
  };

  $scope.changeStatusTask = function(index){
    $scope.tasks[index].status = !$scope.tasks[index].status;
    Task.updateTasks(currentDay, $scope.tasks, function(tasks){
      $scope.tasks = tasks;
    });
  };
});

Der Controller muss unter „/app/controller/Tasklist.js“ gespeichert werden.

app.controller('Tasklist', function ($scope, $routeParams, Task) {
  var days = ["Montag","Dienstag","Mitwoch","Donnerstag","Freitag","Sammstag","Sonntag"];
  var currentDay = $routeParams.day || 0;
  $scope.day = days[currentDay];

Bei der Controller initialiesierung wird der Name angegeben, danach das Scope Object, mit dem man auf die Variablen im Template zugreifen kann. Des weiteren die Route Parameter, sprich die dynamischen Felder aus der URL. Task ist der Service, der noch erstellt werden muss.

Task.loadTasks(currentDay, function(tasks){
  $scope.tasks = tasks || [];
});

Der Methodenaufruf holt sich vom Service die Daten, falls welche vorhanden sind. Sonst wird ein leeres Array erstellt.

$scope.addTask = function(){
  $scope.tasks.push({"name": $scope.taskName, "text": $scope.taskText, "status": false});   

  Task.updateTasks(currentDay, $scope.tasks, function(tasks){
    $scope.tasks = tasks;
    $scope.taskName = "";
    $scope.taskText = "";
  });
};

Die „addTask“ Methode erstellt ein Array-Eintrag und übergibt das Array an den Service. Danach wird das alte Array mit dem neuen überschrieben und das Formular wird geleert.

$scope.deleteTask = function(index){
  $scope.tasks.splice(index, 1);
  Task.updateTasks(currentDay, $scope.tasks, function(tasks){
    $scope.tasks = tasks;
  });
};

Die „deleteTask“ Methode entfernt aus dem Array den Eintrag mit dem übergenen Indexwert und übergibt das neue Array dem Service.

$scope.changeStatusTask = function(index){
$scope.tasks[index].status = !$scope.tasks[index].status;
  Task.updateTasks(currentDay, $scope.tasks, function(tasks){
    $scope.tasks = tasks;
  });
};

Die „changeStatusTask“ toggled den boolischen Status-Eintarg im Array-Element.

Service

Die Servicemetohden übernehmen die komunikation zwischen Server und App. In diesem Beispiel gibt es kein Server zum speichern der Daten, deswegen übernimmt in diesem Fall der Service die kommunikation zwischen App und Browserspeicher.

app.service('Task', function ($http) {
    return {
      loadTasks: function (day, callback) {
        callback(JSON.parse(localStorage.getItem(day)));
      },
      updateTasks: function (day, tasks, callback) {
        localStorage.setItem(day, JSON.stringify(tasks));
        callback(JSON.parse(localStorage.getItem(day)));
      }
    };
});

Der Code muss unter „/app/service/Task.js“ gespeichert werden.

loadTasks: function (day, callback) {
  callback(JSON.parse(localStorage.getItem(day)));
}

Die „loadTasks“ Methode holt sich die Daten aus dem Speicher und convertiert den String zu ein Objekt.

updateTasks: function (day, tasks, callback) {
  localStorage.setItem(day, JSON.stringify(tasks));
  callback(JSON.parse(localStorage.getItem(day)));
}

Die „updateTasks“ Methode convertiert das Takss Objekt zu ein String und speichert es anschließend im Browserspeicher.

App Abhänigkeiten

Zum Schluss muss noch die Index.html überarbeitet werden, damit die neu erstellten Appdateien geladen werden.

<!-- App Resourcen laden -->
    <script src="/app/main.js"></script>
    <script src="/app/controller/Tasklist.js"></script>
    <script src="/app/service/Task.js"></script>
  </body>
</html>