Benutzer-Werkzeuge

Webseiten-Werkzeuge


de.bkp:intern:ipa:bl2017:ipa_bl2017

Inhaltsverzeichnis

Einen Webshop für einen Internetauftritt





Teil 1

1Aufgabenstellung

1.1Ausgangslage

Der Kunde möchte einen Webauftritt mit dem er Merchandising-Artikel verkaufen kann. In der ersten Phase wird ein einfacher Webshop ohne Designelemente programmiert. Nachdem der Webshop fertiggestellt wurde wird der eigentliche Webauftritt erstellt.



Die Bedienung soll möglichst einfach sein.



Seitens der Rafisa möchten wir, dass die Software modular aufgebaut ist.



Als Vorarbeit wird eine Test-Datenbank mit Artikel-Daten aufgebaut die dann, bei der IPA, in die produktive Datenbank importiert werden. Diese dient um den Umgang mit Bilddaten (BLOB) aus der SQL-Datenbank zu testen.



Die IPA soll eine folgende Funktionalität liefern:

• Einen Warenkorb bei dem man eine Bestellung abgeben kann.

• Einen Warenkorb bei dem man Artikel aufnehmen, löschen und deren Bestellungsanzahl ändern kann.

• Eine Artikelliste die maximal 10 Artikel pro Blatt anzeigt.

• Eine Artikelliste die man Blättern kann.

• Eine Artikelliste bei der man einen Artikel in einer Detailansicht einsehen kann.

• Speicherung einer Bestellung.



Folgende Arbeiten gehören nicht zur IPA und werden bei dessen Abschluss weiterentwickelt:

• Online-Bezahlungsmöglichkeit (Paypal).

• Artikelverwaltung (neue Artikel erfassen, Statusänderung, Löschung, etc…).

• Bestellungsverwaltung (Bestellungen stornieren, Rechnungsadresse/Lieferadresse ändern, Artikelpositionen löschen, ändern, etc…).

• Design des Webauftrittes nach Kundenvorgabe.

• Seitenaufbau des Webauftrittes (Home, Geschichte, Webshop, etc…)

• Bestellungsbestätigung per E-Mail



1.2Detaillierte Aufgabenstellung

Der erste Schritt ist das Sicherstellen, dass Sie die notwendige Software und Hardware installiert und konfiguriert haben, damit Sie professionell damit entwickeln können. Stellen Sie sicher, dass das Endprodukt dem objektorientierten Paradigma entspricht.



Source Control:

Damit eine Kontrolle und Nachverfolgung möglich ist, erstellen Sie ein Mercurial Repository auf dem externen Rafisa Root Server. Die Commits sollen im Umfang möglichst klein sein und themenverwandt. Die Commits müssen in guter Qualität kommentiert werden. Jeden Abend soll ein Push auf den Server erfolgen.

• Initialisierung des „Repository“ auf dem Mercurial-Server.

• Kleine Commits, z.B. beim Arbeitsjournal wurde einen Tageseintrag fertiggestellt.

• Themenverwandte Commits, z.B. Business-Klassen wurden erweitert.

• Täglicher Push auf den Server.



Dokumentation:

Um den Aufbau für den Auftraggeber möglichst transparent zu machen, erstellen Sie die notwendigen Use-Cases, ERM-Diagramme, aus welche folgende Angaben ableitbar sind:

• Systemaufbau

• Schnittstellen

• Bestellungsabwicklung durch den Benutzer

• Warenkorb-Funktionalität/System

• Komplettes ERM-Diagramm der Persistenzklassen



Technologien:

Folgende Technologien werden vom Auftraggeber vorgegeben:

• PHP OOP

• JavaScript

• jQuery

• HTML5

CSS

• MySQL





Warenkorb:

Der Internet-Benutzer soll Artikel auswählen können und diese in den Warenkorb legen. Der Warenkorb soll durch den Benutzer editierbar sein. Der Warenkorb soll von der Artikelliste auswählbar sein und folgende Funktionalität enthalten:

• Anzahl der bestellten Artikel änderbar, per Artikel.

• Einzelne Artikel löschbar.

• Der Warenkorb nach 15 Minuten Nichtbenutzung ungültig werden.

• Anzeige der folgenden Felder: Beschreibung, Anzahl, Preis, Subtotal.

• Die letzte Zeile der Artikel-Positionen/Zeilen soll das Gesamttotal der Ausgewählten Artikel ausgeben.

• Die Subtotal- und Total-Werte werden bei jeder Aktion (Entfernen/Anzahl ändern) neu berechnet.

• Korrekte Validierung der Daten.



Zur Veranschaulichung, (die geschweiften Klammern bedeuten dass der Wert in einem Input-Feld dargestellt werden und die eckigen Klammern bedeuten einen Knopf):



Beschreibung Anzahl Preis Subtotal

'Gloria' T-Schirt {2} 50.00 100.00 [entfernen]

'Gloria' Sonnenbrille {1} 95.00 95.00 [entfernen]



Total 195.00



Artikel:

Die Artikel werden als Vorarbeit als Testdaten in einer Datenbank und Artikel-Tabelle erfasst. Die Testdaten sollen in die neue Datenbank in dessen Artikel-Tabelle importiert werden. Dabei ist darauf zu achten, dass die korrekte Kollation gesetzt wurde, UTF-8.

• Import in die neue Datenbank der als Vorarbeit erfassten Artikel-Testdaten.

• UTF-8 als Kollation.

• Beachten Sie dabei, dass die Felddefinitionen (VARCHAR, INT, TEXT, etc…) beider Tabellen und Datenbanken (Vorarbeit und Produktive) übereinstimmen.

• Artikel bestehen aus folgenden Felder: Code/ID (VARCHAR 255), Titel (VARCHAR 255), Beschreibung (LONGTEXT), Preis (INT 11), Status (VARCHAR 255), Bild (BLOB).

• Der Status eines Artikels kann folgende Zustände haben: Lieferbar, Nicht an Lager, Inaktiv.

• Inaktive Artikel werden nicht angezeigt.



Artikelliste:

Die Anzeige der Artikel soll maximal 10 Artikel gleichzeitig anzeigen. Sollten mehr als 10 Artikel vorhanden sein, soll dem Benutzer eine Navigation angeboten werden, mit der der Benutzer weitere Artikel ansehen kann. Die Artikelanzeige in der Listenanzeige soll aus einem Titel, ein Foto, die Beschreibung auf maximal 200 Zeichen beschränkt und einen Preis. Bei jedem Artikel in der Liste soll es einen „in den Warenkorb“-Knopf geben und einen Knopf mit der man die Einzelansicht anzeigen kann.

• Maximal 10 Artikel auf der Liste.

• Seitenweise Navigation der Liste, wenn mehr als 10 Artikel vorhanden.

• Listenansicht der Artikel mit Bild, Kurzbeschreibung (maximal 200 Zeichen) und Preis.

• „In den Warenkorb“ Knopf bei jedem Artikel der auf der Liste erscheint.



Artikel-Detailansicht:

Ein Artikel soll in einer Einzelansicht detaillierter beschrieben werden. Diese soll erscheinen wenn ein Benutzer einen Knopf in der Artikelliste anklickt. Pro Artikel, in der Artikelliste, sollen einen Knopf um diesen im Warenkorb aufzunehmen und einer um die Detailansicht anzuzeigen, vorhanden sein. Die Artikel-Einzelansicht bietet ein Bild, einen Titel, eine Beschreibung, einen Preis und einen „in den Warenkorb“-Knopf. Die Einzelansicht soll als Fenster angezeigt werden.

• Artikel-Detailansicht mit Bild, Titel, Beschreibung und Preis.

• „In den Warenkorb“ Knopf.



Bestellung:

Die Artikelliste soll eine Möglichkeit bieten eine Bestellung Abzusenden. Zuerst wird der Warenkorb angezeigt. Sobald der Benutzer diesen Bestätigt muss ein Formular erscheinen wo der Benutzer seine Personalien eingeben kann. Die ersten zwei Punkte sind zwingend und müssen so kennzeichnet werden, dass ein Benutzer diese als Pflichtfelder wahrnimmt:

• Formular mit Anrede, Name, Vorname, E-Mail-Adresse.

• Formular mit Rechnungsadresse.

• Formular mit Lieferadresse.

• Formular mit Telefonnummer.

• Korrekte Validierung der Daten.



Wurde eine Bestellung erfolgreich abgeschlossen ist diese in der Datenbank zu speichern. Folgende Daten sind zu speichern: Bestellungsnummer, Bestellungsdatum, Name, E-Mail-Adresse, Telefonnummer (wenn vorhanden) und Adresse der Person die bestellt, Lieferadresse (wenn vorhanden) und die Artikel (in einem Textfeld) die die Person bestellt hat. Die Datumsfelder sind mit Linux-Timestamp Werte zu füllen.

• Bestellungen bestehen aus folgenden Felder: Bestellungsnummer (VARCHAR 255), Bestellungsdatum (INT 11), Name (VARCHAR 255), Email (VARCHAR 255), Telefonnummer (VARCHAR 255), Adresse (TEXT), Lieferadresse (TEXT), Artikel (LONGTEXT).

• Die Adressfelder sind folgend zu formatieren: die erste Zeile besteht aus einem Vornamen und Namen, die Zweite Zeile aus der Strasse und Hausnummer, die dritte Zeile aus der Postleitzahl und die Ortschaft, die letzte Zeile aus dem Land. Die Zeilen sind durch die '\r\n' Escape-Sequenz zu trennen.

• Die Artikelliste im Artikel-Feld ist folgend zu formatieren: Artikelnummer, Beschreibung, Anzahl und Preis. Die einzelne Werte werden durch ein Semikolon ';' separiert. Jeder Artikel-Eintrag wird durch die '\r\n' Escape-Sequenz getrennt.

• Bestellung wird gespeichert mit all den erforderlichen Daten.



Zur Veranschaulichung des Aufbaus der Daten der Adressfelder:

Max Muster\r\n

Musterstrasse 1\r\n

8000 Zürich\r\n

Schweiz\r\n



Zur Veranschaulichung des Aufbaus der Daten des Artikelfeldes, die Werte sind nur als Beispiel zu betrachten:

01;'Gloria' T-Schirt;2;50.00\r\n

02;'Gloria' Sonnenbrille;1;95.00'\r\n'



Fehlerbehandlung:

Mögliche Fehler müssen abgefangen werden, z.B. wenn der MySQL Server nicht mehr funktioniert, soll nicht eine PHP interne Fehlermeldung erscheinen welche Rückschlüsse auf die Daten- und Projekt-Struktur zulässt. Die Fehler müssen durch eigene Meldungen/Fehlercodes ersetzt werden, z.B. 'Datenbankfehler D001' bei einem Fehlversuch sich beim MySQL-Server anzumelden, oder 'Datenbankfehler D002' bei einem misslungenem 'INSERT' Versuch. Diese Fehlercodes müssen dokumentiert werden.

• Kritische Datenbank-Fehler werden abfangen.

• Eigene Fehlercodes erzeugen.

• Anzeige des Fehlercodes/Meldungen bei abgefangenen Fehler.



Validation:

Um eine korrekte Bestellung aufnehmen zu können müssen die Benutzereingaben überprüft werden. Pflichtfelder müssen überprüft werden. Bei fehlerhaften Eingaben muss der Benutzer darauf aufmerksam gemacht werden.

• Beim Warenkorb muss überprüft werden ob die Anzahl eine Ganzzahl ist.

• Bei der Angabe der Personalien des Benutzers sind die Pflichtfelder auf sinnvolle Eingaben zu überprüfen.

• Der Benutzer soll auf nicht ausgefüllte Pflichtfelder aufmerksam gemacht werden.

• Der Benutzer soll auf nicht korrekt ausgefüllte Felder aufmerksam gemacht werden.



Diverses:

• Die Benutzungsmasken sollen selbsterklärend, einfach und klar strukturiert sein.

• Die Sicherheit soll durch den Einsatz der PHP-PDO-Bibliothek erhöht werden.

• Es dürfen keine Daten physikalisch gelöscht werden, sondern nur als ‚gelöscht‘ markiert werden.

• Jede Aktion soll in einer Logdatei festgehalten werden. Aus dieser Datei soll ersichtlich sein was, wann getätigt wurde.

• Die Software muss objekt-orientiert und modularisiert programmiert werden.





Tests:

Die Aufgabe umfasst die Installation eines Webauftrittes der funktionell ist, jedoch ohne definitive Designelemente. Das Ganze soll ausgiebig getestet werden.

• Dazu ist ein Testkonzept zu erstellen.

• Die Tests sind gemäss Konzept durchführen.



Mindestens folgende Testszenarien müssen durchgeführt werden:

• Webshop anzeigen.

• Warenkorb Artikelanzahl ändern, Artikelposition einfügen und entfernen.





Code-Konventionen:

• Namenskonvention: analog Java für selbstdefinierte Programmteile. Klassen sind ‚upper camel case‘, Methoden sind ‚lower camel case‘, Variablen sind ‚lower camel case‘ und Konstanten müssen ‚upper case underscore separated‘ sein.

• Formatierung des Codes: Netbeans PHP Standard Formatierer.

• Quellcode Dokumentation, Klassen Definition, ausführliche Kommentare inklusive Instanzierungs-Beispiele. Methoden ausführlich inklusive Parameter, Rückgabewerte und etwaige Throws. Konstanten und deren Bedeutung werden erklärt.

• Variablen/Felder durch das ‚protected‘ Schlüsselwort geschützt, Zugriff auf diese nur durch Methoden.

• Stringliterale wo möglich und sinnvoll als Konstanten.



2Projektaufbauorganisation

Tabelle 1: Projektaufbauorganisation

Person Rolle Aufgaben Verantwortung
Herr Lade Basil Kandidat IPA durchführen und abschliessen Die IPA vollständig und korrekt abschliessen
Herr Windmeisser Jorge Fachvor-gesetzter Beurteilt die fachliche Richtigkeit der Arbeit Steht bei Fachfragen zur Verfügung und bewertet die Fachkompetenzen des Kandidaten
Herr Peter Tobias Experte Stellt die Qualität der fachlichen Beurteilung sicher Studiert den Bericht und leitet das Fachgespräch Legt die Termine (Besuch kurz nach Start der Arbeit, zweiter Besuch gegen Ende der Arbeit sowie Präsentation und Fachgespräch) fest Entscheidet bei auftretenden Problemen über Massnahmen und meldet diese unverzüglich dem Chefexperten Liefert die Dokumente termingerecht an den Chefexperten
Herr Achermann Martin Zweit-Experte Schreibt ausführliche und lesbare Notizen während der Präsentation, dem Fachgespräch und der Bewertung auf
Unterstützt den Experten beim Bewerten der Arbeit.





3Vorkenntnisse

• OO Programmieren.

HTML, PHP, CSS, SQL, JavaScript/jQuery

• Projektabwicklung.



4Vorarbeiten

• Installation von MAMP/XAMPP-Software.

• Installation und Konfiguration der Netbeans-IDE.

• Erfassung von Testdaten (Artikel).



5Firmenstandards

Code-Konventionen:

• Namenskonvention: analog Java für selbstdefinierte Programmteile. Klassen sind ‚upper camel case‘, Methoden sind ‚lower camel case‘, Variablen sind ‚lower camel case‘ und Konstanten müssen ‚upper case underscore separated‘ sein.

• Formatierung des Codes: Netbeans PHP Standard Formatierer.

• Quellcode Dokumentation, Klassen Definition, ausführliche Kommentare inklusive Instanzierungs-Beispiele. Methoden ausführlich inklusive Parameter, Rückgabewerte und etwaige Throws. Konstanten und deren Bedeutung werden erklärt.

• Variablen/Felder durch das ‚protected‘ Schlüsselwort geschützt, Zugriff auf diese nur durch Methoden.

• Stringliterale wo möglich und sinnvoll als Konstanten.



6Zeitplan




7Arbeitsprotokolle

7.1Arbeitsprotokoll vom 13.04.2017

7.1.1Zeitübersicht:

Std. Heute1 Std. Verg. 2 Std. Verbl. 3 Ausgeführte Arbeit Bemerkungen
00:00h 00:00h 80:00h - Start der IPA
04:00h 04:00h 76:00h Erstellung des Zeitplans Unterstützung von Fachvorgesetzten und Berufsbildner in Form von Beratung Zeitplanvorlage von vergangenem Projekt und für die IPA entsprechend modifiziert
02:00h 06:00h 74:00h Verfassung von Teil1 des IPA Berichtes Vorlage des Berichts von vergangenem Projekt und für die IPA entsprechend modifiziert
00:25h 06:25h 73:35h Expertenbesuch 1 (inklusive Aufgabenstellung analysieren und wesentliche Infos) Beteiligte: Kandidat, Fachvorgesetzter und Experte
00:53 07:18h 72:42h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 00:29h


07:18h 07:18h 72:42h



7.1.2Reflektion

Der erste Tag der IPA verlief bis auf weiteres reibungslos.

Alle vorgenommenen Tätigkeiten konnten erfüllt werden, jedoch nahm der Zeitplan mehr Zeit in Beanspruchung als geplant. Die Wartezeit wurde durch die Überarbeitung der Darstellung (z.B. der Legende) überbrückt.

Die verlorenere Zeit konnte jedoch wieder ausgeglichen werden, da die Vorlage des IPA Berichts eine Menge Zeit einsparte. Ausserdem war heute der erste Expertenbesuch, welcher positiv verlief.

Als nächstes steht unteranderem die Erstellung der notwendigen Diagramme an.



7.2Arbeitsprotokoll vom 18.04.2017

7.2.1Zeitübersicht:

Std. Heute4 Std. Verg. 5 Std. Verbl. 6 Ausgeführte Arbeit Bemerkungen
05:00h 12:18h 67:42h Erstellung des Use Case Diagramms User stories schreiben Use Case mit draw.io erstellt Endprodukt mit Fachvorgesetzten angeschaut
00:50h 13:08h 66:52h Erstellung des Flowcharts Flowchart mit draw.io erstellt
00:14h 13:22h 66:38 Erstellung des ERM Diagramms ERM Diagramm mit MySQL Workbench erstellt
00:50h 14:12h 65:48h Erstellung des Klassendiagramms (Fortsetzung morgen) Klassendiagramm mit draw.io begonnen
00:43 14:55h 65:05h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 00:43h


07:37h 14:55h 65:05h



7.2.2Reflektion

Nach einer Osterpause ging es mit dem zweiten Tag der IPA weiter.

Alle heutigen Tätigkeiten konnten erledigt werden, wobei das Use Case Diagramm viel mehr Zeit beansprucht hat als geplant. Der Grund dafür ist, dass ich bei der Erstellung des Diagramms zu weit gedacht habe und somit die Darstellung zu detailliert wurde. Ausserdem habe ich zusätzlich user stories („Anwendererzählung“) verfasst, damit ich eine bessere Vorstellung davon habe welche Use Cases schlussendlich ins Diagramm kommen. Zu guter Letzt besprach ich das Use Case Diagramm mit meinem Fachvorgesetzten. Die weiteren Diagramme konnte ich ohne grössere Probleme erstellen. Die durch das Use Case Diagramm verlorene Zeit konnte ich gut aufholen.

Aus dem heutigen Tag ziehe ich die Lehre Sachen nicht unnötig zu komplizieren.

Für den morgigen Tag muss ich gemäss Plan das Klassendiagramm fertigstellen, das Testkonzept verfassen, Libraries vergleichen und auswerten (jquery) und zu guter Letzt das Mercurial Repository initialisieren.



7.3Arbeitsprotokoll vom 19.04.2017

7.3.1Zeitübersicht:

Std. Heute7 Std. Verg. 8 Std. Verbl. 9 Ausgeführte Arbeit Bemerkungen
03:44h 17:39h 61:21h Fertigstellung des Klassendiagramms Besprechung mit Fachvorgesetzten
03:19h 20:58h 58:02h Erstellung des Testkonzepts Besprechung mit Fachvorgesetzten
00:05h 21:03h 57:53h Mercurial Repository initialisieren Initialisierung durch Fachvorgesetzten
00:27h 21:30h 57:26h Grundlegende Ordnerstruktur + erster commit -
00:25h 21:55h 57:01h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 01:32h


07:20h 21:55h 57:01h



7.3.2Reflektion

Nach dem dritten Tag kann ich sagen, dass ich mir immer noch zu viele Sorgen und Gedanken mache. Ich komme zwar stetig voran, jedoch brauche ich manchmal ein bisschen mehr Zeit als eingeplant, was mich heute das erste Mal ein wenig heruntergezogen hat. Im Zeitplan stehe ich momentan ein wenig im Hintertreffen. Das heisst, dass ich heute die Libraries nicht evaluieren konnte. Stattdessen habe ich die Initialisierung des Mercurial Repository, durch den Fachvorgesetzten, die grundlegende Ordnerstruktur und den ersten Commit vorgezogen, da diese Tätigkeiten im Vergleich zur Library Evaluation weniger Zeitaufwand benötigt.

Jedoch hatte mein Arbeitsrechner in den letzten Minuten ein Performanceproblem, weshalb ich ihn neustarten musste. Das kostete mir unnötig Zeit.

Für morgen nehme ich mir vor mit einem kühleren Kopf an die Sache ran zu gehen. Des Weiteren muss ich morgen die Library Evaluation nachholen und mit der Programmierung beginnen.



7.4Arbeitsprotokoll vom 20.04.2017

7.4.1Zeitübersicht:

Std. Heute10 Std. Verg. 11 Std. Verbl. 12 Ausgeführte Arbeit Bemerkungen
01:45h 22:40h 56:20h Libraries evaluieren -
00:55h 23:40h 55:25h Libraries auswerten entscheiden -
01:45h 24:30h 54:40h Datenbank aufbereiten Testdaten als Vorarbeit
02:20h 26:50h 52:20h Persistence Layer (Basisklassen) implementieren Klasse aus einem vorherigen Projekt übernommen und modifiziert bzw. angepasst
00:22h 27:12h 51:58h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 01:09h


06:27h 27:12h 51:58h



7.4.2Reflektion

Am heutigen Tag konnte ich alle Tätigkeiten erfüllen und die unerledigten von gestern nachholen. Im Gegensatz zu gestern konnte ich mich viel besser konzentrieren und bin weniger erschöpft als am Vortag. Die Verbesserte Konzentration resultiert daraus, dass ich ganz viele fünf Minuten Pausen gemacht habe. Im Chrome Browser habe ich eine Erweiterung (Pomodoro timer) installiert, die mir alle 25 Minuten sagt, dass ich fünf Minuten Pause machen soll.

Für Morgen stehen die Implementierung der Basisklassen des Business und Presentation Layers an.



7.5Arbeitsprotokoll vom 21.04.2017

7.5.1Zeitübersicht:

Std. Heute13 Std. Verg. 14 Std. Verbl. 15 Ausgeführte Arbeit Bemerkungen
03:25h 30:37h 48:21h Business Layer (Basisklassen) implementieren Klasse aus einem vorherigen Projekt übernommen und modifiziert bzw. angepasst
03:10h 33:57h 45:15h Presentation Layer (Basisklassen) implementieren Klasse aus einem vorherigen Projekt übernommen und modifiziert bzw. angepasst
00:20h 34:17h 44:55h Zweites Expertengespräch Vorhandene Personen: Experte, Fachvorgesetzter, Kandidat
00:30h 34:47h 44:25h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 01:30h


07:25h 34:47h 34:47h



7.5.2Reflektion

Heute habe ich die Hälfte der IPA erreicht und bin einigermassen erleichtert. Alle gesetzten Tätigkeiten konnten erreicht werden, obwohl zwischendurch noch das zweite Expertengespräch stattfand, welches positiv verlaufen ist. Heute konnte ich mich weitgehend gut konzentrieren. Zwischendurch gab es ein Stimmungstief, aus dem ich schnell wieder raus kam.

Nächste Woche werde ich die Action Basisklassen implementieren und mit der eigentlichen Programmierung des Webshops anfangen.



7.6Arbeitsprotokoll vom 25.04.2017

7.6.1Zeitübersicht:

Std. Heute16 Std. Verg. 17 Std. Verbl. 18 Ausgeführte Arbeit Bemerkungen
06:20h 41:07h 28:27h Action Layer (Basisklassen) implementieren Klasse aus einem vorherigen Projekt übernommen und modifiziert bzw. angepasst
00:20h 41:27h 28:05h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 01:37h


06:40h 41:27h 28:05h



7.6.2Reflektion

Heute konnte ich leider nicht alle Tagesziele erreichen. Die Produktliste wollte ich heute schon implementieren, jedoch hat die Action Layer Implementation länger gedauert als ich gedacht hatte. Zum einen liegt es daran, dass ich sorgfältige Kommentare schreibe und zum anderen habe ich diverse Methoden angepasst bzw. in kleinere Methoden aufgeteilt. Das klingt nicht nach viel Arbeit, aber damit es einfach aussieht muss man viel Zeit investieren.

Zwischendurch, am späten Nachmittag, hatte ich eine Schreibblockade. Deshalb musste ich eine längere Pause einlegen. Danach war mein Kopf wieder frei genug und ich konnte mit der Arbeit fortfahren.

Für Morgen muss ich den Bericht um die Beschreibung der Aktion Layer Basisklassen erweitern und natürlich die Implementation der Artikelliste nachholen.



7.7Arbeitsprotokoll vom 26.04.2017

7.7.1Zeitübersicht:

Std. Heute19 Std. Verg. 20 Std. Verbl. 21 Ausgeführte Arbeit Bemerkungen
01:42h 41:27h 28:05h Action Layer (Basisklassen) dokumentieren -
04:10h 45:37h 23:55h Artikelliste implementiert -
00:50h 46:13 23:05h Produktdetailansicht implementiert -
00:28h 46:41h 22:37h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 01:30h


07:10h 46:41h 22:37h



7.7.2Reflektion

Heute habe ich die Dokumentation des Action Layer und die Implementation der Artikelliste nachgeholt. Weiterhin gelang es mir die Produktdetailansicht zu implementieren, jedoch reichte die Zeit nicht mehr für den Warenkorb.

Im Verlauf des Tages habe ich beim Programmieren hin und wieder Probleme gehabt. Diese konnte ich jedoch relativ schnell lösen. Meistens waren diese Probleme auf Flüchtigkeitsfehler zurückzuführen. Ausserdem hat mich die Firewall aussergewöhnlich oft rausgeworfen. Damit ich Zugang zum Internet habe, muss ich bei der Firewall angemeldet sein. Das war ziemlich ärgerlich. Jedoch hat mir das sehr wenig Zeit gekostet.

Morgen muss ich die Implementierung des Warenkorbs nachholen und die Persistierung der Bestellung implementieren. Ich muss mich beeilen, denn die IPA ist schon fast vorüber.



7.8Arbeitsprotokoll vom 27.04.2017

7.8.1Zeitübersicht:

Std. Heute22 Std. Verg. 23 Std. Verbl. 24 Ausgeführte Arbeit Bemerkungen
06:52h 53:33h 15:45h Warenkorb implementieren -
00:17h 53:50h 15:28h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 01:13h


07:09h 53:50h 15:28h



7.8.2Reflektion

Heute ist der dritt-letzte Tag der IPA. Für den heutigen Tag habe ich mir vorgenommen, fertig zu werden mit dem Programmieren. Doch das muss ich auf morgen verschieben.

Im Grossen und Ganzen ging es mir heute wieder besser, doch gegen Abend verliere ich an Konzentration und kämpfte mich durch.

Für morgen muss ich die Persistierung der Bestellung implementieren.



7.9Arbeitsprotokoll vom 28.04.2017

7.9.1Zeitübersicht:

Std. Heute25 Std. Verg. 26 Std. Verbl. 27 Ausgeführte Arbeit Bemerkungen
06:15h 60:05h 09:03h Persistierung einer Bestellung implementiert -
00:10h 60:15h 08:53h Arbeitsprotokoll Erfassung Zeitdauerberechnung der einzelnen Tätigkeiten Reflektion aufschreiben
TOTAL
Überstunden: 00:05h


06:25h 53:50h 15:28h



7.9.2Reflektion

Heute konnte ich die Persistierung einer Bestellung implementieren. Momentan wird nur überprüft, ob alle Pflichtfelder ausgefüllt sind. Ein weiterer Schritt ist es die Benutzereingaben auf Falschheit zu überprüfen und ungewollte Character zu entfernen bzw. unschädlich zu machen.

Heute hatte ich sehr viel weniger Stress als gestern.

Die nächsten Schritte sind, Benutzereingaben validieren und „säubern“ und die Loggingfunktionalität implementieren.



Teil 2

8IPA Kurzfassung

Der IPA Bericht besteht aus mehreren Teilen:

  • Teil 1
  • Teil2
  • Anhang

Im Teil 1 befinden sich grösstenteils die Informationen, welche auch auf PkOrg auffindbar sind. Sprich beteiligte Personen, Aufgabenstellung, an welchen Tagen an der IPA gearbeitet wird usw. Des Weiteren sind auch die Tagesprotokolle und der Zeitplan im Teil 1 untergebracht.

Teil 2 handelt sich dann um die Realisierung des Projekts bzw. deren Beschreibung. Die Struktur der Kapitel und Unterkapitel mitsamt deren Titelbezeichnungen versucht sich so gut wie möglich an den Tätigkeiten des Zeitplans zu orientieren.

Im Anhang wird der selbstgeschriebene Quellcode aufgelistet und bekommt ein eigenes Inhaltsverzeichnis. Die Struktur des Inhaltsverzeichniss orientiert sich an die Ordnerstruktur des Projekts.

Aufgabenstellung:

Laut Aufgabenstellung, soll ein Webshop für einen Internetauftritt, welcher nach der IPA realisiert wird, programmiert werden. Der Nutzer sieht, nachdem er den webshop aufgerufen hat, als erstes die Artikelliste. Von dort aus kann er über einen Artikel mehr Informationen erfahren oder zum Warenkorb hinzufügen. Ausserdem kann die Artikelliste geblättert werden, um sich weitere Artikel anschauen zu können.

Der Nutzer kann von der Artikelliste aus auf den Warenkorb zugreifen und deren Positionen bearbeiten (Quantität ändern) oder entfernen. Weiterhin kann der Nutzer seine Bestellung speichern, nachdem dieser ein Bestellformular ausfüllt.



9Planen

9.1User Stories

Normalerweise sind user stories (deutsch „Anwendererzählung“) im Bereich der agilen Softwareentwicklung in Verwendung und dienen unteranderem dazu einzelne zusammengehörende Arbeitsschritte in einer Arbeitseinheit zusammenzufassen, welche dann in einem Sprint erledigt werden können soll. Bei dieser IPA wird allerdings nicht die agile Softwareentwicklung (z.B. scrum) eingesetzt, sondern IPERKA. Die user stories werden hier lediglich dazu verwendet, um eine bessere Vorstellung davon zu bekommen, welche use cases es abzudecken gilt.

Eine user story wird in der Regel in einem Satz formuliert und besitzt keinen festen Standard bezüglich der Formulierung. Es gibt jedoch eine Vorlage welche häufig genutzt wird:
„Als <Rolle> möchte ich <Ziel/Wunsch>, um <Nutzen>“. Dementsprechend gibt es auch eine kürzere Variante, sprich: „Als <Rolle> möchte ich <Ziel/Wunsch>.

  • Als Webshopbesucher möchte ich eine Artikelliste einsehen können, um einen Überblick des Artikelangebotes zu bekommen.
  • Als Webshopbesucher möchte ich die Artikelliste blättern können, um weniger scrollen zu müssen.
  • Als Webshopbesucher möchte ich eine Artikeldetailansicht einsehen können, um weitere Details über den Artikel zu erfahren.
  • Als Webshopbesucher möchte ich ein Artikel dem Warenkorb hinzufügen, um später diesen oder mehrere Artikel kaufen bzw. bestellen zu können.
  • Als Webshopbesucher möchte ich die Artikelanzahl in einer Warenkorbposition erhöhen / senken, um den gleichen Artikel in einer höheren Quantität bestellen zu können.
  • Als Webshopbesucher möchte ich eine Warenkorbposition entfernen können.
  • Als Webshopbesucher möchte ich eine Bestellung aufgeben (im Bereich der IPA nur speichern) können.



9.2Diagramme

9.2.1Verwendetes Tool

Für die Erstellung der Diagramme wurde die kostenlose Webapplikation draw.io verwendet. Draw.io bietet diverse Speicheroptionen an. Die Diagramme können entweder lokal auf der Festplatte oder in der Cloud gespeichert werden. Dabei bietet draw.io folgende Cloud-Dienste an: Google Drive, Dropbox, OneDrive. Zusätzlich werden umfangreiche Exportoptionen angeboten. Wie bei Excel kann eine Datei mehrere Blätter beinhalten. Weiterhin können mehrere Mitarbeiter das gleiche Dokument gleichzeitig bearbeiten.

9.2.2Use-Cases

Abbildung 1: Use-Cases


Das Use-Case Diagramm visualisiert die verschiedenen Aktionen, die ein Nutzer im Webshop ausführen kann.

9.2.3Flussdiagramm

Abbildung 2: Flussdiagramm


Das Flussdiagramm visualisiert den kompletten webshop Ablauf, der vom Anschauen der Artikelansicht bis hin zum Persistieren der Bestellung reicht.



9.2.4ERM-Diagramm

Abbildung 3: ERM Diagramm


Die Beststellungstabelle hat keine Referenz zur Artikeltabelle, da die ID des Artikels im Adresse-Tabellenfeld der Bestellungstabelle gespeichert ist.

Ausserdem ist der Datentyp der ID ein varchar und nicht ein int, da die ID sich durch den Tabellenname und einem GUID V4 String zusammensetzt.

Beispiel: Artikel_cf741e89-321b-4564-abc8-a82e6d0a77a6 oder

Bestellung_33519ebf-e98f-4cc7-88d5-9bb41e118990



9.2.5Klassendiagramme

Abbildung 4: Klassendiagramm


Das Klassendiagramm visualisiert die Beziehung der einzelnen Aktionen zur Basisklasse und deren Interface. Die SaveOrderAction ist die einzige Aktion, welche Daten in die Datenbank persistiert da der Warenkorb sich in einer temporären Session befindet und nicht in die Datenbank persistiert wird.



9.3Testkonzept

Ziel eines Testkonzepts ist, einen Überblick davon zu bekommen was auf welche Art und Weise getestet wird.

9.3.1Modultest / Unittest

Hierbei werden kleinst-mögliche, voneinander isolierte und testbare Funktionalitäten getestet. Bezüglich dieser IPA beschränken sich die Modultests auf die Aktionen bzw. Actions wie zum Beispiel „UpdateCartPosition“ oder „SaveOrderAction“.

Bei der „SaveOrderAction“ kann zum Beispiel getestet werden, ob die Benutzereingaben des Kontaktformulars valide sind oder ob die Bestellung (Order) erfolgreich gespeichert wurde.

Tabelle 2: Modultestplan

Test Nr. Testbeschreibung Erwartetes Resultat Effektives Resultat Bestanden








Hierbei wird die Funktionalität der Zusammenarbeit von unabhängigen Komponenten in einem System getestet. Der Integrationstest wird erst durchgeführt, sobald alle Modultests bzw. Unittests erfolgreich durchgelaufen sind.



Der Integrationstest beschränkt sich in diesem Fall auf das Subsystem Webshop und ob dieser, nachdem der Nutzer ihn von der Homepage aus aufgerufen hat, erfolgreich angezeigt wird.

Tabelle 3: Integrationstestplan

Test Nr. Testbeschreibung Erwartetes Resultat Effektives Resultat Bestanden
Der Webshop wird korrekt angezeigt, nachdem der Nutzer ihn aufgerufen hat. Webshop korrekt angezeigt



9.3.3GUI Testing

Hierbei wird auf das korrekte Verhalten der Benutzeroberfläche bzw. GUI getestet. Das heisst, ist z.B. der Zurück- oder Vorwärtsknopf deaktiviert, wenn sich die Artikelliste auf der ersten bzw. letzen Seite befindet. Oder wird die Artikeldetailansicht mit den korrekten Artikeldaten angezeigt.



Tabelle 4: GUI Testplan

Test Nr. Testbeschreibung Erwartetes Resultat Effektives Resultat Bestanden
Artikelansicht
Der Nutzer klickt in der Artikelliste auf den Vorwärtsknopf, damit die nächsten zehn Artikel angezeigt werden. Die nächsten zehn Artikel werden angezeigt

Der Nutzer klickt in der Artikelliste auf den Zurückknopf, damit die vorherigen zehn Artikel angezeigt werden. Die vorherigen zehn Artikel werden angezeigt

Der Nutzer klickt auf den „Details ansehen“ Knopf, welcher dafür sorgt, dass sich die Detailansicht öffnet. Die Detailansicht wird geöffnet

Der Nutzer klickt auf den Schliessknopf, um die Detailansicht zu schliessen. Die Detailansicht wird geschlossen

Der Nutzer klickt auf den „Zum Warenkorb hinzufügen“ Knopf, um den Artikel dem Warenkorb hinzuzufügen. Der Artikel wird dem Warenkorb hinzugefügt

Der Nutzer klickt auf den „Warenkorb anzeigen“ Knopf, um den Warenkorb anzuzeigen. Der Warenkorb wird angezeigt

Warenkorb
Der Nutzer erhöht bzw. senkt die Artikelanzahl, somit wird das Subtotal und Total neu berechnet. Das Subtotal und Total wird neu berechnet

Der Nutzer klickt auf den „Entfernen“ Knopf, damit die Warenkorbposition entfernt wird. Die Artikelposition wird entfernt

Der Nutzer klickt auf den „Bestellen“ Knopf, um das Kontaktformular anzuzeigen. Das Kontaktformular wird angezeigt

Kontaktformular
Der Benutzer wird allenfalls auf Falscheingaben hingewiesen, nachdem er auf den „Bestellung speichern“ Knopf geklickt hat. Der Nutzer wird auf Falscheingaben hingewiesen

Der Nutzer wird darauf hingewiesen, nachdem alle Eingaben korrekt getätigt und der „Bestellung speichern“ Knopf geklickt wurden, dass seine Bestellung erfolgreich gespeichert wurde. Der Nutzer wird auf die erfolgreiche Speicherung der Bestellung hingewiesen





10Entscheiden

10.1Library Auswahl

Im Rahmen der IPA wird ausschliesslich eine Library verwendet namentlich jquery. Nun was gibt es da zu entscheiden? Von jquery gibt es drei verschiedene Versionen, welche unterschiedliche Browser unterstützen. Das heisst z.B. jquery in der Version 1 unterstützt den Internet Explorer von 6-8 und jquery Version zwei und drei nicht mehr. Nun besteht die Entscheidung daraus, ob es wichtig ist den Internet Explorer 6 zu unterstützten und allenfalls Sicherheitslücken in Kauf zu nehmen oder die altern Internet Explorer einfach links liegen zu lassen.

10.2Evaluation der jquery Versionen

Um die Evaluation so effizient wie möglich zu gestalten, ist die Entscheidungsmatrix ein passendes Werkzeug. Die Entscheidungsmatrix visualisiert die zur Verfügung stehenden Optionen, die einzelnen Kriterien und deren Gewichtung und wie die Punkteverteilung in den jeweiligen Kriterien pro Produkt ausfällt. All diese Informationen werden in einer übersichtlichen Tabelle dargestellt.

Tabelle 5: jquery Evaluation

Optionen (Alternativen)
Kriterien Gewichtung jquery 1 jquery 2 jquery 3


Bewertung Total Bewertung Total Bewertung Total
Browser Support (bis 2012) 1 4 ** Fehlerhafter Ausdruck ** 2 ** Fehlerhafter Ausdruck ** 2 ** Fehlerhafter Ausdruck **
Browser Support (ab 2013) 4 2 ** Fehlerhafter Ausdruck ** 4 ** Fehlerhafter Ausdruck ** 4 ** Fehlerhafter Ausdruck **
Sicherheit 5 2 ** Fehlerhafter Ausdruck ** 3 ** Fehlerhafter Ausdruck ** 4 ** Fehlerhafter Ausdruck **
Community Support 5 2 ** Fehlerhafter Ausdruck ** 3 ** Fehlerhafter Ausdruck ** 4 ** Fehlerhafter Ausdruck **
Verbreitung 3 4 ** Fehlerhafter Ausdruck ** 3 ** Fehlerhafter Ausdruck ** 2 ** Fehlerhafter Ausdruck **
Total - - 0 - 0 - 0

10.3 Auswertung und Entscheidung

10.3.1Auswertung

Beim Kriterium „Nutzung“ erzielt jquery 1 überraschenderweise die höchste Punktzahl und jquery 3 die kleinste. Dies geht aus einem Blog Post 1(Stand: 29.03.2017) von Snyk hervor.

Snyk ist ein kostenpflichtiger Dienst und testet die Dependencies(deutsch Abhängigkeiten), wie z.B. jquery, in Webapplikationen auf Sicherheitslücken und behebt diese. Daraus ist zu schliessen, dass viele Webapplikationen bzw. Webseiten ihre jquery Version nicht auf die aktuellste Version upgraden, da dies, gerade bei umfangreicheren Projekte, ein grösseres Unterfangen darstellt.

Hingegen ist das Ergebnis bezüglich des Kriteriums „Sicherheit“ weniger überraschend, da alte Internet Explorer Versionen Sicherheitslücken mit sich bringen.

10.3.2Entscheidung

Im Rahmen der IPA würde der Support von sehr alten Browsern höchstwahrscheinlich den Rahmen sprengen und ist ergo auch kein Bestandteil der IPA.

Die Entscheidung fällt auf jquery 3, obwohl jquery 1 bei der Nutzung am besten Abgeschnitten hat. Nur weil jquery 1 eine grosse Nutzerschaft geniesst, bedeutet das nicht, dass dies unterstützt werden soll, vor allem bezüglich der Sicherheit. Weiterhin zeigen die Benutzerstatistiken der weltweiten Browsernutzung auf, dass der Internetexplorer so gut wie gar nicht mehr genutzt wird, beziehungsweise nicht mehr der Marktführer ist.



11Realisierung

11.1Repository initialisieren

Der Fachvorgesetze hat das Mercurial Repository initialisiert, da er dazu die nötigen Adminrechte besitzt.

11.1.1Erster Commit

Der erste Commit beinhaltet die grundlegende Projektstruktur und allenfalls die .hgignore Datei, in der festgelegt werden kann, welche Dateien nicht versioniert werden sollen wie z.B. temporäre Microsoft Office Dateien. Netbeans graut die ignorierten Dateien in der Projektstrukturansicht aus.

11.2Datenbank Aufbereitung

11.2.1Testdaten

Als Vorarbeit wurden für die Datenbank Testdaten erfasst, damit dies nicht während der IPA gemacht werden muss. Die Testdaten spiegeln die Artikel, welche schlussendlich nach der IPA erfasst werden, nicht wieder. Da neben dem Programmieren auch die Interesse in PC-Hardware besteht, handeln sich die Testdaten um diverse Hardware Komponenten.

11.2.2Export der Testdaten

Teil der Vorgabe ist, dass die Artikelbilder in der Datenbank als BLOB (binary large object) persistiert werden, anstatt auf dem Server. Die binären Bilddaten werden in der hexadezimalen Notation im Tabellenfeld hinterlegt. Und hierbei trat das erste Problem auf. Die Datenbankverwaltung wird mit MySQL Workbench gemacht, was nicht das eigentliche Problem ist. Beim Exportieren einer Datenbank wird der Inhalt der BLOB Tabellenfelder standardmässig nicht in der hexadezimalen Notation sondern als normaler Text exportiert.

11.2.3Import der Testdaten

Nach erfolgreichem Import des MySQL Dumps, stürzt MySQL Workbench beim Versuch den Inhalt des BLOB Tabellenfelds darzustellen ab. Das liegt daran, weil der Inhalt des BLOB Tabellenfelds normaler Text ist und keine binäre Zeichenfolge. Nach einer schnellen Recherche im Internet, lieferte Google keine zufriedenstellende Lösung für das spezifische Problem. Die Lösung besteht darin, die Exporteinstellungen in MySQL Workbench zu ändern. Jedoch ist diese eine Einstellung ein wenig versteckt, denn sie befindet sich in den erweiterten Einstellungen. Die Sache ist, dass die MySQL Workbench GUI nicht klar kommuniziert, dass es überhaupt erweiterte Exporteinstellungen gibt.

In den erweiterten Einstellungen gibt es dann die Einstellungsmöglichkeit, binäre Tabellenfelder in der hexadezimalen Notation zu exportieren. Sobald diese Einstellungsoption ausgewählt wurde, merkt sich das MySQL Workbench. Nach erneutem Exportieren und Importieren war es nun möglich den BLOB Inhalt anzuzeigen, ohne dass dabei Workbench abstürzt.



11.3Persistence Layer Basisklassen

Im Persistence Layer befinden sich Klassen, welche dafür zuständig sind Informationen an einem Ort zu persistieren, z.B. in eine Datenbank oder in eine Sessionvariable.

11.3.1Database

Die Datenbankklasse ist dafür zuständig Daten aus der Datenbank herauszulesen und zu persistieren. Dafür bietet sie entsprechende Variablen und Methoden, welche im Klassendiagramm aufgeführt sind.

11.3.1.1Fehlercodes

Die Datenbankklasse besitzt Fehlercodes welche dann im Browser nach einem aufgetretenen Fehler angezeigt werden. Dies wird gemacht, damit nach aussen nicht sichtbar ist wie das Programm aufgebaut ist.

  • Datenbankfehler D001: Fehler beim Aufbau der Datenbankverbindung
  • Datenbankfehler D002: Fehler beim Persistieren der Daten in die Datenbank
  • Datenbankfehler D003: Fehler beim Zählen der Tabellenreihen
  • Datenbankfehler D004: Fehler beim Laden eines Datensatzes
  • Datenbankfehler D005: Fehler beim Laden der Datensätze

11.3.2SessionHelper

Der SessionHelper bietet Helper Methoden an, um auf bestehende Sessionvariablen zugreifen zu können oder neue zu erstellen. Ausserdem kann ein Sessiontimeout gestartet werden, der laut der Projektbeschreibung nach 15 Minuten abläuft. Diese Helper Methoden sind im Klassendiagramm aufgelistet.

11.3.3Logging

Jede PHP Aktion geht durch den ActionHypervisor, deshalb wird dort der Aktivitätslog geschrieben. Ein Aktivitätslog beinhaltet Datum und Uhrzeit, die ausgeführte Aktion und zusätzliche Postparameter, falls vorhanden. Datenbankfehler werden direkt von der entsprechenden Methode aus in die Logdatei geschrieben. Diese Logeinträge beinhalten neben Datum und Uhrzeit, den Stacktrace und das ausgeführte sql Statment bzw. den Connectionstring, falls bei der Herstellung der Datenbankverbindung ein Fehler aufgetreten ist.



11.4Business Layer Basisklassen

Der Business Layer ist für Berechnungen zuständig. Weiterhin beinhaltet der Business Layer Entities. Ein Entity ist eine Repräsentation einer Tabellenreihe in Form eines PHP Objekts. Die Tabellendaten werden zusammen mit den entsprechenden Feldnamen als assoziatives Array im Entity Objekt gespeichert.

Beispiel: $this→data[Entity::ID] = „GUID v4 string“

11.4.1EntityInterface

Das Interface beinhaltet Methoden welche die Entity Klasse implementieren muss. In anderen Worten ausgedrückt: Das Interface ist ein Vertrag, bei dem alle Klassen, die ihn unterschreiben, Folge leisten müssen. Ausserdem erinnert die IDE (Integrierte Entwicklungsumgebung) den Programmierer daran, die Methoden, welche im Interface deklariert sind, in der jeweiligen Klasse einzufügen.

Wissenswert ist auch, dass PHP mehrere Interface Implementierungen bei einer Klasse zulässt, im Gegensatz zur Vererbung.

Methodendefinitionen von EntityInterface:

  • set()
  • setData()
  • get()
  • getData()

11.4.2Entity Klasse

Die Entity Klasse implementiert die vordefinierten Methoden und fügt weitere hinzu.

Zusätzliche Methoden:

  • getGUID()
  • getFields()
  • getClass()
  • __toString()



11.5Presentation Layer Basisklassen

Der Presentation Layer sorgt für die Darstellung des Webshops. Jedes Element auf der Webseite wird durch ein Widget repräsentiert. Das Widget ist ein für sich isoliertes PHP Objekt das den entsprechenden HTML Code enthält. Widgets können jedoch miteinander kombiniert werden, so dass ein „grösseres“ Widget entsteht. Die JavaScript Library „React“ z.B. verfolgt ein vergleichbares Konzept, nur dass es dort keine Widgets sondern „Components“ sind.

Ein Widget kann von einem einfachen HTML Inputfeld bis zu einem komplett funktionierenden Warenkorb reichen.

11.5.1WidgetInterface

Das WidgetInterface definiert alle für die Widget Klasse wichtigen Methoden.

Methodendefinitionen von WidgetInterface:

  • prepare()
  • setLabel()
  • getLabel()
  • setValue()
  • getValue()
  • setActionName()
  • setTooltip()
  • addCssClass()

11.5.2Widget Klasse

Die Widget Klasse implementiert die Methoden aus dem Interface, jedoch mit einer Ausnahme.

Die prepare() Methode wird erst im jeweiligen Widget selbst implementiert, da sich der HTML Code von Widget zu Widget unterscheidet. Ausserdem ist die Widget Klasse abstrakt, da sie nie direkt aufgerufen bzw. instanziiert wird.

Ein spezifisches Widget kann je nach Bedarf zusätzliche Funktionalitäten implementieren. Jedoch muss sich diese Funktionalität eindeutig von anderen Widgets unterscheiden, dabei spricht man von generalisieren und spezialisieren.



11.6Action Basisklassen

Eine Aktion bzw. Action setzt eine Kette von Arbeitsschritten in Bewegung. Eine Action kann zum Beispiel dafür sorgen, dass ein Artikel im Warenkorb hinterlegt wird. Dabei werden alle notwendigen Schritte ausgeführt. Wie bei einem Yin Yang hat eine Action zwei Hälften, eine JavaScript und eine PHP.

  1. Yin Yang - JavaScript

Ein JavaScript Event, namens ClickListener, wartet darauf dass irgendein HTML Element geklickt wird. Hat das geklickte Element ein data-action Attribut, dessen Wert der Action Name ist z.B. addToCartAction, wird eine externe JavaScript Datei geladen. Der Dateiname entspricht dem Wert des data-action Attributs. Auf diese Art ist es dem ClickListener möglich auf die perform Methode der jeweilig geladenen JavaScript Datei zuzugreifen und auszuführen. Dabei übergibt der „ClickListener“ das zuvor geklickte Element der perform Methode als Parameter.

  1. Yin Yang - PHP

Die perform Methode führt ein ajax Request aus und spricht index.php an. Dabei wird der Aktionsname und im Beispiel von addToCartAction die Artikel ID per POST Methode übertragen. Von index.php aus wird der ActionHypervisor aufgerufen. Der ActionHypervisor führt dann die entsprechende PHP Aktion aus. So gesehen ist ActionHypervisor das Äquivalent zum ClickListener, da beide darüber entscheiden welche Aktion ausgeführt werden soll.

Wenn seitens PHP die Aktion erfolgreich ausgeführt wurde, gibt PHP per echo Statement eine entsprechende Meldung oder ein neues Widget, wenn die Aktion vorsieht ein bestehendes Widget zu aktualisieren, aus. Natürlich wird bei einem Fehler auch ein echo Statement gemacht.

11.6.3Viele Wege führen zum JavaScript

Das vorhin erwähnte echo Statement ist essentiell, da dessen Inhalt an das JavaScript Gegenstück der Aktion weitergereicht wird. Schlussendlich wird diese Information im DOM (document object model) eingespeist.

11.6.4Beteiligte Akteure

Der Übersicht halber werden nochmal alle „Akteure“ aufgelistet, die dafür zuständig sind eine Aktion auszuführen.

  • ClickListener: JavaScript
  • Auszuführende Aktion: JavaScript
  • ActionHypervisor: PHP
  • Auszuführende Aktion: PHP



11.7Artikelliste

Abbildung 5: Artikelliste

Die Artikelliste wir angezeigt, nachdem auf der Homepage den webshop Knopf gedrückt wurde. Die Artikelliste zeigt nur zehn Artikel auf einmal an. Damit die nächsten zehn Artikel angezeigt werden können, befinden sich am Ender der Liste zwei Knöpfe, mit denen die Artikelliste vor und zurück geblättert werden kann.

Pro Artikel in der Liste befinden sich zwei Knöpfe (siehe Abbildung oben). Der eine öffnet die Detailansicht und der andere fügt den Artikel dem Warenkorb hinzu.



11.8Detailansicht

Abbildung 6: Detailansicht

Die Detailansicht öffnet sich nachdem in der Artikelliste auf dem entsprechenden Knopf gedrückt wurde. Hierbei wird die vollständige Beschreibung angezeigt im Gegensatz zur Artikelliste. Von der Detailansicht aus kann der entsprechende Artikel zum Warenkorb hinzugefügt werden.

Es kann erst wieder mit der Artikelliste interagiert werden, nachdem die Detailansicht geschlossen wurde. Die Detailansicht ist also ein Modaldialog. Zum Schliessen der Detailansicht kann entweder auf das X, im rechten oberen Ecken, oder auf die Fläche ausserhalb der Detailansicht geklickt werden.



11.9Warenkorb

Abbildung 7: Leerer Warenkorb

Abbildung 8: Warenkorb mit Artikelpositionen

In der Artikelliste befindet sich ganz oben ein Knopf, durch den der Warenkorb angezeigt wird. Der Warenkorb kann sich in zwei Zuständen befinden. Entweder ist er leer (siehe Abbildung oben) und der Bestellen-Knopf bleibt deaktiviert oder er enthält Artikel und der Bestellen-Knopf ist dementsprechend aktiviert.

Vom Warenkorb aus kann eine Warenkorbposition durch den Nutzer entfernt werden oder die Artikelanzahl verändert werden. Ausserdem wird der Warenkorb durch ein 15 Minütiges Sessiontimeout geleert.



11.10Bestellformular

Abbildung 9: Bestellformular

Wenn der Nutzer auf den Bestellen-Knopf klickt, erscheint ein Bestellformular, welches Pflichtfelder und Optionalfelder beinhaltet. Die Pflichtfelder sind jeweils mit einem * markiert.

Der Nutzer wird auf leere Pflichtfelder und allgemeine Fehleingaben hingewiesen. Sind alle Felder korrekt ausgefüllt wird die Bestellung in die Datenbank persisitert.





12Testphase



Tabelle 6: Tests

Test Nr. Bereich Test Beschreibung Erwartetes Resultat Effektives Resultat Bestanden
1 Einstieg Einstieg Webshop Aufrufen der Webshop-URL
http://192.168.1.250/webshop
Der „Webshop“ Knopf wird angezeigt. wie erwartet ja
2 Artikelansicht Artikelliste anzeigen Der Nutzer klickt auf den „Webshop“ Knopf Die Artikelliste wird angezeigt. wie erwartet ja
3
Artikelliste blättern Der Nutzer klickt in der Artikelliste auf den Vorwärtsknopf, damit die nächsten zehn Artikel angezeigt werden. Die nächsten zehn Artikel werden angezeigt. Auf der letzten Seite ist der Vorwärtsknopf disabled. wie erwartet ja
4

Der Nutzer klickt in der Artikelliste auf den Zurückknopf, damit die vorherigen zehn Artikel angezeigt werden. Die vorherigen zehn Artikel werden angezeigt. Auf der ersten Seite ist der Zurückknopf disabled. wie erwartet ja
5
Detailansicht öffnen Der Nutzer klickt auf den „Details ansehen“ Knopf, welcher dafür sorgt, dass sich die Detailansicht öffnet. Die Detailansicht wird in einem modalen Dialog geöffnet wie erwartet ja
6
Detailansicht schliessen Der Nutzer klickt in der Detailansicht auf den Schliessknopf, um die Detailansicht zu schliessen. Die Detailansicht wird geschlossen, der Nutzer befindet sich wieder auf der Artikelliste wie erwartet ja
7

Der Nutzer klickt in der Detailansicht auf einen Bereich ausserhalb der Detailansicht in die Artikelliste, um die Detailansicht zu schliessen. Die Detailansicht wird geschlossen, der Nutzer befindet sich wieder auf der Artikelliste wie erwartet ja
8
Zum Warenkorb hinzufügen Der Nutzer klickt in der Detailansicht auf den „Zum Warenkorb hinzufügen“ Knopf, um den Artikel dem Warenkorb hinzuzufügen. Der Artikel wird dem Warenkorb hinzugefügt. Wenn der Artikel schon im Warenkorb ist, wird derselbe Artikel mit einer neuen Position hinzugefügt. wie erwartet ja
9

Der Nutzer klickt in der Artikelliste auf den „Zum Warenkorb hinzufügen“ Knopf, um den Artikel dem Warenkorb hinzuzufügen. Der Artikel wird dem Warenkorb hinzugefügt. Wenn der Artikel schon im Warenkorb ist, wird derselbe Artikel mit einer neuen Position hinzugefügt. wie erwartet ja
10
Warenkorb anzeigen Der Nutzer klickt in der Artikelliste auf den „Warenkorb anzeigen“ Knopf, um den Warenkorb anzuzeigen. Der Warenkorb wird angezeigt
Warenkorb
wie erwartet ja
11 Warenkorb Artikelanzahl ändern Der Nutzer erhöht bzw. senkt die Artikelanzahl, somit wird das Subtotal und Total neu berechnet. Das Subtotal und Total werden neu berechnet. Bei der Eingabe von 0 oder einem negativen Wert wird der vorherige Wert wiederhergestellt. Nachkommastellen bei der Eingabe werden abgeschnitten. wie erwartet ja
12
Position löschen Der Nutzer klickt auf den „Position entfernen“ Knopf, damit die Warenkorbposition entfernt wird. Die Artikelposition wird entfernt, das Total wird neu berechnet. Wenn der Warenkorb leer ist, ist der „Bestellen“ Knopf disabled. wie erwartet ja
13
Bestellen Der Nutzer klickt auf den „Bestellen“ Knopf, um das Bestellformular anzuzeigen. Das Bestellformular wird angezeigt. wie erwartet ja
14 Bestellformular Bestellformular ausfüllen Der Nutzer füllt das Bestellformular falsch aus und klickt auf den „Bestellung speichern“ Knopf.
Mögliche Falscheingaben:
- leere Pflichtfelder
- unplausible Namensangaben
- ungültige Telefonnummer
- ungültige E-Mail-Adresse
- unplausible Adressangaben
Die Bestellung wird nicht gespeichert und der Nutzer wird auf Falscheingaben hingewiesen. wie erwartet ja
15
Bestellung speichern Der Nutzer füllt das Bestellformular korrekt aus und klickt auf den „Bestellung speichern“ Knopf. Die Bestellung wird gespeichert und der Nutzer wird darauf hingewiesen, dass seine Bestellung erfolgreich gespeichert wurde. wie erwartet ja
16
Bestellung kontrollieren Nach Speichern einer Bestellung muss diese auf der Datenbank geprüft werden Bestellungs-Record auf der Datenbank enthält die Daten der Bestellung wie erwartet ja
17 Integration Webshop anzeigen Nach dem auf der Homepage der „webshop“ Knopf gedrückt wurde, soll als erstes die Artikelliste angezeigt Die Artikelliste wird dem Nutzer präsentiert wie erwartet ja
18 Sonstiges Timeout Der Nutzer führt nach mehr als 15 Minuten eine beliebige Aktion aus. Dem Nutzer wird die Artikelliste angezeigt. Warenkorb und Bestellformular sind leer. - - Falls der Nutzer sich beim Timeout im Warenkorb befindet, wird die Artikelliste unterhalb des Warenkorbs angezeigt.

-„Bestellung speichern“ nach Ablauf des Timeouts wird nicht verhindert, es wird ein leerer „artikel“ gespeichert
nein





13Persönliche Auswertung

13.1.1Reflexion

Im Verlaufe der IPA habe ich hin und wieder Fragen gehabt oder bin nicht weitergekommen. Anfangs fragte ich noch nach oder habe mir eine Zweit- und Drittmeinung eingeholt. Sobald es darum ging das geplante umzusetzen bzw. die Realisierungsphase startete, wollte ich alles alleine schaffen. Zugegebenermassen waren die Basisklassen und Interfaces aus einem vorherigen Projekt kopiert wurden und vom Fachvorgesetzen geschrieben, das habe ich aber in den Kommentaren des Quellcodes angemerkt. Trotzdem lag noch viel Arbeit vor mir, die ich wie schon gesagt alleine Stämmen wollte.

Bei einem Problem habe ich die Lösung selbst gefunden oder im Internet nach einem Lösungsansatz gesucht. Die Sache ist die. Obwohl ich die Lösung selbst gefunden habe, wäre es doch oft zeitlich gesehen effizienter gewesen, den Fachvorgesetzen nach einem Ratschlag zu fragen, um das Problem von einem anderen Blickwinkel sehen zu können.

Die meisten Probleme jedoch lagen einem Flüchtigkeits- oder Gedankenfehler zu Grunde.

Im Realisierungsteil, war meine Vorgehensweise zuerst den Quellcode zu schreiben, diesen dann zu kommentieren und zu guter Letzt den Bericht zu erweitern. Für die Modultests hatte ich keinen Raum mehr, was nicht heisst dass ich gar nie überprüft habe, ob das Programmierte funktioniert oder nicht. Ich habe einfach kein Testframework verwendet oder versucht eins zu emulieren.



13.1.2Erkenntnisse

Rückblickend kann ich sagen, dass ich viel zu viel Arbeit auf mich genommen habe und zu perfektionistisch war. Ich habe mich zu fest darauf fokussiert alles ohne Fremdhilfe zu schaffen und habe dabei Fremdhilfe mit Nachfragen und Ratschläge einholen gleichgesetzt.

Das Resultat ist, dass ich mit weniger Punktzahlen bestehen würde, als ich hätte eigentlich erreichen können. Das legitimiere ich mir damit, dass es nie mein Ziel war eine sehr gute Note zu bekommen. Teilweise stimmt das auch.

Ausserdem haben mich die zehn Tage der IPA sehr viel stärker mitgenommen als ich im Vorhinein gedacht habe.

Doch das allerwichtigste was ich für mich aus diesen zehn Tage ziehen kann ist, und das mag sehr banal und trivial klingen, dass es keine Schande ist nachzufragen, dass es keine Schande ist manchmal festzustecken und dass ich meine Intuition weniger durch mein Ego streitig machen soll.



14Quellenverzeichnis

14.1Internetquellen

14.1.1Allgemeine Quellen

14.1.2Spezifische Quellen



15Glossar

Abkürzung Begriff Erklärung
- Warenkorb Der Warenkorb beinhaltet Warenkorbpositionen.
- Warenkorbposition Eine Warenkorbposition besteht aus der Anzahl, den Artikelnamen, Stückpreis und Subtotal





Anhang

Projektroot 1

config 1

Action 2

Js 2

AddToCartAction 2

CloseWidgetAction 5

EditCartPosition 7

LoadArticleListAction 10

LoadNextAction 13

LoadPreviousAction 16

RemoveCartPositionAction 18

SaveOrderAction 21

ShowArticleDetailsAction 24

ShowCartTableAction 27

ShowContactFormAction 30

PHP 33

AddToCartAction 33

CloseWidgetAction 34

EditCartPosition 35

LoadArticleListAction 36

LoadNextAction 37

LoadPreviousAction 38

RemoveCartPositionAction 39

SaveOrderAction 40

ShowArticleDetailsAction 51

ShowCartTableAction 52

ShowContactFormAction 52

Business 53

Entity 53

Artikel 53

Bestellung 54

CartPosition 55

Decorator 57

Main 57

Persistence 60

Database 60

Logging 69

Session 72

Presentation 80

Widget 80

ArticleDetailsWidget 80

ArticleListWidget 82

ButtonWidget 85

CartTableWidget 86

ContactFormWidget 88

DropdownWidget 94

ImageWidget 100

InputWidget 101

PaginationWidget 103





Projektroot

config

<?php



/**

* —————————————————————————–

* Konfigurationsdatei

* —————————————————————————–

*

* Hier werden diverse Pfadkonstanten erzeugt.

*

* @author b.lade

*/

define('DOCUMENT_ROOT', $_SERVER['DOCUMENT_ROOT']);

define('LIB_PATH', DOCUMENT_ROOT.'/_lib/');



define('BUSINESS_PATH', DOCUMENT_ROOT.'/business/');

define('HELPER_PATH', BUSINESS_PATH.'helper/');

define('DATA_PATH', BUSINESS_PATH.'data/');



define('ACTION_PATH', DOCUMENT_ROOT.'/action/');



define('CLASS_MAP_PATH', DOCUMENT_ROOT.'/classMap.php');



define('LOG_PATH', DOCUMENT_ROOT.'/_log/');

define('LOG_FILE_PATH', LOG_PATH.'webshop.log');



Action

Js

AddToCartAction

/**

* —————————————————————————–

* AddToCartAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {AddToCartAction}

*

* @author b.lade

*/

var AddToCartAction = function AddToCartAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

AddToCartAction.prototype.perform = function perform(event) {



var productInformation = this.clickedJQueryElement

.closest('.ProductInformation');

var productId = productInformation.data('entity');



var jqxhr = jQuery.post('index.php', {

action: AddToCartAction.name,

id: productId

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

AddToCartAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

console.log('Add To Cart:',textStatus);

console.log(data);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

AddToCartAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};





CloseWidgetAction

/**

* —————————————————————————–

* CloseWidgetAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {CloseWidgetAction}

*/

var CloseWidgetAction = function CloseWidgetAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

CloseWidgetAction.prototype.perform = function perform(event) {



var jqxhr = jQuery.post('index.php', {

action: CloseWidgetAction.name

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

CloseWidgetAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var destroyable = this.clickedJQueryElement.closest('.destroyable');

destroyable.remove();

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

CloseWidgetAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

EditCartPosition

/**

* —————————————————————————–

* EditCartPositionAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {EditCartPositionAction}

*

* @author b.lade

*/

var EditCartPositionAction = function EditCartPositionAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

EditCartPositionAction.prototype.perform = function perform(event) {

var positionId = this.clickedJQueryElement

.parents('tr')

.data('position-id');

var amount = this.clickedJQueryElement.val();



var jqxhr = jQuery.post('index.php', {

action: EditCartPositionAction.name,

id: positionId,

amount: amount

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

EditCartPositionAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var tableContainer = this.clickedJQueryElement

.closest('.CartTableWidgetContainer');

tableContainer.replaceWith(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

EditCartPositionAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

LoadArticleListAction

/**

* —————————————————————————–

* LoadArticleListAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {LoadArticleListAction}

*

* @author b.lade

*/

var LoadArticleListAction = function LoadArticleListAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

LoadArticleListAction.prototype.perform = function perform(event) {



var jqxhr = jQuery.post('index.php', {

action: LoadArticleListAction.name

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

LoadArticleListAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var container = $('.container');

(this.clickedJQueryElement).prop('disabled', true);

container.empty();

container.append(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

LoadArticleListAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

LoadNextAction

/**

* —————————————————————————–

* LoadNextAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {LoadNextAction}

*

* @author b.lade

*/

var LoadNextAction = function LoadNextAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

LoadNextAction.prototype.perform = function perform(event) {



var jqxhr = jQuery.post('index.php', {

action: LoadNextAction.name

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

LoadNextAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var container = this.clickedJQueryElement.closest('.ArticleListWidget');

container.replaceWith(data);

console.log('load next:',textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

LoadNextAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

LoadPreviousAction

/**

* —————————————————————————–

* LoadPreviousAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {LoadPreviousAction}

*

* @author b.lade

*/

var LoadPreviousAction = function LoadPreviousAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

LoadPreviousAction.prototype.perform = function perform(event) {



var jqxhr = jQuery.post('index.php', {

action: LoadPreviousAction.name

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

LoadPreviousAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var container = this.clickedJQueryElement.closest('.ArticleListWidget');

container.replaceWith(data);

console.log('load previous:', textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

LoadPreviousAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

RemoveCartPositionAction

/**

* —————————————————————————–

* RemoveCartPositionAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {RemoveCartPositionAction}

*

* @author b.lade

*/

var RemoveCartPositionAction = function RemoveCartPositionAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

RemoveCartPositionAction.prototype.perform = function perform(event) {

var positionId = this.clickedJQueryElement

.parents('tr')

.data('position-id');



var jqxhr = jQuery.post('index.php', {

action: RemoveCartPositionAction.name,

id: positionId

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

RemoveCartPositionAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var tableContainer = this.clickedJQueryElement

.closest('.CartTableWidgetContainer');

tableContainer.replaceWith(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

RemoveCartPositionAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

SaveOrderAction

/**

* —————————————————————————–

* RemoveCartPositionAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {RemoveCartPositionAction}

*

* @author b.lade

*/

var RemoveCartPositionAction = function RemoveCartPositionAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

RemoveCartPositionAction.prototype.perform = function perform(event) {

var positionId = this.clickedJQueryElement

.parents('tr')

.data('position-id');



var jqxhr = jQuery.post('index.php', {

action: RemoveCartPositionAction.name,

id: positionId

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

RemoveCartPositionAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

var tableContainer = this.clickedJQueryElement

.closest('.CartTableWidgetContainer');

tableContainer.replaceWith(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

RemoveCartPositionAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

ShowArticleDetailsAction

/**

* —————————————————————————–

* ShowArticleDetailsAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {ShowArticleDetailsAction}

*

* @author b.lade

*/

var ShowArticleDetailsAction = function ShowArticleDetailsAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.perform = function perform(event) {



var productInformation = this.clickedJQueryElement

.closest('.ProductInformation');

var productId = productInformation.data('entity');



var jqxhr = jQuery.post('index.php', {

action: ShowArticleDetailsAction.name,

id: productId

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

$('body').append(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

ShowCartTableAction

/**

* —————————————————————————–

* ShowArticleDetailsAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {ShowArticleDetailsAction}

*

* @author b.lade

*/

var ShowArticleDetailsAction = function ShowArticleDetailsAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.perform = function perform(event) {



var productInformation = this.clickedJQueryElement

.closest('.ProductInformation');

var productId = productInformation.data('entity');



var jqxhr = jQuery.post('index.php', {

action: ShowArticleDetailsAction.name,

id: productId

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

$('body').append(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

ShowContactFormAction

/**

* —————————————————————————–

* ShowArticleDetailsAction Konstruktor

* —————————————————————————–

*

*

*

* @param {JQuery} clickedJQueryElement Das HTML Element, jenes geklickt wurde,

* in einem JQuery Objekt verpackt.

*

* @returns {ShowArticleDetailsAction}

*

* @author b.lade

*/

var ShowArticleDetailsAction = function ShowArticleDetailsAction(clickedJQueryElement) {

this.clickedJQueryElement = clickedJQueryElement;

this.perform();

};





/**

* —————————————————————————–

* JavaScript Aktion ausführen

* —————————————————————————–

*

* Im Grunde genommen wird hier ein JQuery ajax <i>POST</i> request ausgeführt.

* Bei Erfolg wird die <i>handleSuccess()</i> Methode aufgerufen. Bei Miss-

* erfolg die <i>handleFailure()</i> Methode. Von Aktion zu Aktion wird

* sich <i>perform()</i> nicht grossartig ändern.

*

* Da bei der Methode <i>perform()</i> sowieso immer ein <i>POST</i> request ge-

* tätigt wird, wird statdessen der JQuery <i>ajax()</i> Funktion die Jquery

* <i>post()</i> Funktion verwendet. Per PHP kann dann auf die <i>POST</>

* Parameter zugegriffen werden. PHP verarbeitet die Daten und gibt

* HTML Code in string Form zurück, damit JQuery, wenn alles gut

* gelaufen ist, das DOM entsprechend manipulieren kann.

*

* @param {Object} event click event Objekt

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.perform = function perform(event) {



var productInformation = this.clickedJQueryElement

.closest('.ProductInformation');

var productId = productInformation.data('entity');



var jqxhr = jQuery.post('index.php', {

action: ShowArticleDetailsAction.name,

id: productId

});



jqxhr.done(this.handleSuccess.bind(this));



jqxhr.fail(this.handleFailure.bind(this));



};





/**

* —————————————————————————–

* Erfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolgreichem JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleSuccess()</i> aufgerufen. Die Implementation der Logik wird

* von Aktion zu Aktion unterschiedlich ausfallen.

*

* @param {*} data

* @param {string} textStatus

* @param {jqXHR} jqXHR

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.handleSuccess = function handleSuccess(data, textStatus, jqXHR) {

$('body').append(data);

console.log(textStatus);

};





/**

* —————————————————————————–

* Misserfolg verarbeiten

* —————————————————————————–

*

* Bei einem erfolglosen JQuery ajax post request wird dementsprechend die Me-

* thode <i>handleFailure()</i> aufgerufen.

*

* @param {jqXHR} jqXHR

* @param {string} textStatus

* @param {string} errorThrown

*

* @returns {undefined}

*/

ShowArticleDetailsAction.prototype.handleFailure = function handleFailure(jqXHR, textStatus, errorThrown) {

console.log(errorThrown);

};

PHP

AddToCartAction

<?php



/**

* —————————————————————————–

* AddToCartAction

* —————————————————————————–

*

* Artikel als Warenkorbposition an den Warenkorb anfügen

*

* @author b.lade

*/

class AddToCartAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$id = filter_input(INPUT_POST, self::ID, FILTER_SANITIZE_STRING);

$entity = Database::getInstance()→load($id);



$cartPosition = [

CartPosition::ARTICLE_NUMBER ⇒ $entity→get(Entity::ID),

CartPosition::AMOUNT ⇒ 1,

CartPosition::PRICE ⇒ $entity→get(Artikel::PRICE)

];

CartPosition::setCartPosition($cartPosition);

}



}

CloseWidgetAction

<?php



/**

* —————————————————————————–

* CloseWidgetAction

* —————————————————————————–

*

* Aktion für das Schliessen eines Widgets

*

* @author b.lade

*/

class CloseWidgetAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

echo 'performed';

}



}

EditCartPosition

<?php



/**

* —————————————————————————–

* EditCartPositionAction

* —————————————————————————–

*

* Artikelanzahl einer Position im gespeicherten Warenkorb ändern

* und den geänderten Warenkorb anzeigen.

*

* @author b.lade

*/

class EditCartPositionAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {



$id = filter_input(INPUT_POST, self::ID, FILTER_SANITIZE_STRING);

$amount = filter_input(INPUT_POST, CartPosition::AMOUNT, FILTER_UNSAFE_RAW);



$cartTable = new CartTableWidget(CartPosition::getAllCartPositions());

$cartTable→hideColumn(Entity::DELETED);

$cartTable→hideColumn(Entity::ID);

$cartTable→hideColumn(CartPosition::CART_POSITION);



if (!is_numeric($amount)) {

echo $cartTable;

return;

}



if ((int) $amount < 1) {

echo $cartTable;

return;

}



$currentPosition = CartPosition::getCartPosition($id);



$currentPosition[CartPosition::AMOUNT] = (int) $amount;



CartPosition::setCartPosition($currentPosition, (int) $id);



$newCartTable = new CartTableWidget(CartPosition::getAllCartPositions());

$newCartTable→hideColumn(Entity::DELETED);

$newCartTable→hideColumn(Entity::ID);

$newCartTable→hideColumn(CartPosition::CART_POSITION);

echo $newCartTable;

}



}

LoadArticleListAction

<?php



/**

* —————————————————————————–

* LoadArticleListAction

* —————————————————————————–

*

* Zeigt die Artikelliste an.

*

* @author b.lade

*/

class LoadArticleListAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

echo new ArticleListWidget(10);

}



}

LoadNextAction

<?php



/**

* —————————————————————————–

* LoadNextAction

* —————————————————————————–

*

* Zeigt die nächste Seite der Artikelliste an, falls vorhanden.

* Die Klasse leitet sich von der Artikelliste ab.

*

* @author b.lade

*/

class LoadNextAction extends LoadArticleListAction {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$rowCount = SessionHelper::get(Database::ROW_COUNT);

$currentOffset = SessionHelper::get(Database::CURRENT_OFFSET);

$nextOffset = SessionHelper::get(Database::NEXT_OFFSET);

$filter = [Entity::DELETED ⇒ 'false'];

$className = Artikel::class;

$recordCount = Database::getInstance()→count($className, $filter);

if($nextOffset >= $recordCount) {

parent::perform();

exit();

}

SessionHelper::set(Database::NEXT_OFFSET, $nextOffset + $rowCount);

SessionHelper::set(Database::CURRENT_OFFSET, $currentOffset + $rowCount);

parent::perform();

}



}

LoadPreviousAction

<?php



/**

* —————————————————————————–

* LoadPreviousAction

* —————————————————————————–

*

* Zeigt die vorherige Seite der Artikelliste an, falls vorhanden.

* Die Klasse leitet sich von der Artikelliste ab.

*

* @author b.lade

*/

class LoadPreviousAction extends LoadArticleListAction {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$rowCount = SessionHelper::get(Database::ROW_COUNT);

$initialRowCount = SessionHelper::get(Database::INITIAL_ROW_COUNT);

$currentOffset = SessionHelper::get(Database::CURRENT_OFFSET);

$nextOffset = SessionHelper::get(Database::NEXT_OFFSET);



if ($currentOffset ⇐ $initialRowCount) {

parent::perform();

exit();

}

SessionHelper::set(Database::NEXT_OFFSET, $nextOffset - $rowCount);

SessionHelper::set(Database::CURRENT_OFFSET, $currentOffset - $rowCount);

parent::perform();

}



}

RemoveCartPositionAction

<?php



/**

* —————————————————————————–

* RemoveCartPositionAction

* —————————————————————————–

*

* Entferne eine Warenkorbposition.

*

* @author b.lade

*/

class RemoveCartPositionAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$id = filter_input(INPUT_POST, self::ID, FILTER_SANITIZE_STRING);

CartPosition::removeCartPosition((int) $id);

$cartTable = new CartTableWidget(CartPosition::getAllCartPositions());

$cartTable→hideColumn(Entity::DELETED);

$cartTable→hideColumn(Entity::ID);

$cartTable→hideColumn(CartPosition::CART_POSITION);

echo $cartTable;

}



}

SaveOrderAction

<?php



/**

* —————————————————————————–

* SaveOrderAction

* —————————————————————————–

*

*

*

* @author b.lade

*/

class SaveOrderAction extends Action {



const REQUIRED_FIELDS = 'requiredFields';

const OPTIONAL_FIELDS = 'optionalFields';



/**

*

* @param array $fields

* @return bool

*/

protected function requiredFieldsEmpty(array $fields): bool {



$emptyFieldsCounter = 0;



foreach ($fields as $field) {

if ($field === '') {

$emptyFieldsCounter++;

}

}



if ($emptyFieldsCounter ⇐ count($fields) && $emptyFieldsCounter != 0) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Ausschliesslich Buchstaben

* ————————————————————————-

*

* Überprüft, ob ein String nur aus Buchstaben besteht.

*

* @param string $string Der zu überprüfende String

*

* @return bool gibt <i>true</i> zurück, falls der String nur aus Buchstaben

* besteht und <i>false</i>, wenn mindestens ein Zeichen kein Buchstabe ist.

*/

protected function hasOnlyLetters(string $string): bool {

if (ctype_alpha($string)) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Ausschliesslich Ziffern

* ————————————————————————-

*

* Überprüft, ob ein String nur aus Ziffern besteht.

*

* @param string $string Der zu überprüfende String

*

* @return bool gibt <i>true</i> zurück, falls der String nur aus Ziffern

* besteht und <i>false</i>, wenn mindestens ein Zeichen keine Ziffer ist.

*/

protected function hasOnlyDigits(string $string): bool {

if (ctype_digit($string)) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Korrekte Strasse

* ————————————————————————-

*

* Überprüft, ob die angegebene Strasse mit dem regex Pattern übereinstimmt.

*

* @param string $string Der zu überprüfende String

* @param bool $optional <i>true</i> wenn das das zu überprüfen Eingabefeld

* optional ist.

*

* @return bool gibt <i>true</i> zurück, falls der String dem regx Pattern

* übereinstimt oder der übergebene String leer ist und <i>$optional</i>

* <i>false</i> ist. Gibt <i>false</i> zurück, wenn der String

* nicht dem regex Pattern entspricht

*/

protected function isValidStreet(string $string, bool $optional = false) {



if ($string == '' && $optional) {

return true;

}



$regex = '/^[A-Za-z][A-Za-z0-9-öäüÖÜÄßàâçéèêëîïôûùüÿñæœ .]{2,64}$/U';



if (preg_match($regex, $string)) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Korrekte Postleitzahl

* ————————————————————————-

*

* Überprüft, ob die angegebene Postleitzahl mit dem regex Pattern überein-

* stimmt.

*

* @param string $string Der zu überprüfende String

* @param bool $optional <i>true</i> wenn das das zu überprüfen Eingabefeld

* optional ist.

*

* @return bool gibt <i>true</i> zurück, falls der String dem regx Pattern

* übereinstimt oder der übergebene String leer ist und <i>$optional</i>

* <i>false</i> ist. Gibt <i>false</i> zurück, wenn der String

* nicht dem regex Pattern entspricht

*/

protected function isValidZipCode(string $string, bool $optional = false) {



if ($string == '' && $optional) {

return true;

}



$regex = '/^[0-9]{4,5}$/U';



if (preg_match($regex, $string)) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Korrekter Name bzw. Vorname

* ————————————————————————-

*

* Überprüft, ob der angegebene Name bzw. Vorname mit dem regex Pattern über-

* einstimmt.

*

* @param string $string Der zu überprüfende String

* @param bool $optional <i>true</i> wenn das das zu überprüfen Eingabefeld

* optional ist.

*

* @return bool gibt <i>true</i> zurück, falls der String dem regx Pattern

* übereinstimt oder der übergebene String leer ist und <i>$optional</i>

* <i>false</i> ist. Gibt <i>false</i> zurück, wenn der String

* nicht dem regex Pattern entspricht

*/

protected function isValidName(string $string) {

$regex = '/^[A-Za-z-öäüÖÜÄßàâçéèêëîïôûùüÿñæœ ]{2,64}$/U';



if (preg_match($regex, $string)) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Korrekter Ort

* ————————————————————————-

*

* Überprüft, ob der angegebene Ort mit dem regex Pattern übereinstimmt.

*

* @param string $string Der zu überprüfende String

* @param bool $optional <i>true</i> wenn das das zu überprüfen Eingabefeld

* optional ist.

*

* @return bool gibt <i>true</i> zurück, falls der String dem regx Pattern

* übereinstimt oder der übergebene String leer ist und <i>$optional</i>

* <i>false</i> ist. Gibt <i>false</i> zurück, wenn der String

* nicht dem regex Pattern entspricht

*/

protected function isValidCity(string $string, bool $optional = false) {



if ($string == '' && $optional) {

return true;

}



$regex = '/^[A-Za-z-öäüÖÜÄßàâçéèêëîïôûùüÿñæœ .]{2,58}$/U';



if (preg_match($regex, $string)) {

return true;

}



return false;

}



/**

* ————————————————————————-

* Korrekte Telefonnummer

* ————————————————————————-

*

* Überprüft, ob die angegebene Telefonnummer mit einer 0 beginnt und voll-

* ständig ist. Vorwahlen werden als nicht korrekt angenommen, da die über-

* prüfung sich momentan auf schweizer Telefonnummenr bezieht

*

* @param string $string Der zu überprüfende String

* @param bool $optional <i>true</i> wenn das das zu überprüfen Eingabefeld

* optional ist.

*

* @return bool gibt <i>true</i> zurück, falls der String einer schweizer

* Telefonnumer ohne Vorwahl entspricht oder der übergebene String leer

* ist und <i>$optional</i> <i>false</i> ist. Gibt <i>false</i> zu-

* rück, wenn der String nicht dem regex Pattern entspricht.

*/

protected function isValidPhoneNumber(string $string, bool $optional = false): bool {



if ($string == '' && $optional) {

return true;

}



$substrResult = substr($string, 0, 1) == 0;



$strippedSpaces = str_replace(„ “, „“, $string);

$strlenResult = strlen($strippedSpaces) === 10;



$onlyDigitsResult = $this→hasOnlyDigits($strippedSpaces);



if ($onlyDigitsResult && $substrResult && $strlenResult) {

return true;

}



return false;

}



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$requiredFields = json_decode(filter_input(INPUT_POST, self::REQUIRED_FIELDS, FILTER_UNSAFE_RAW), true);

$optionalFields = json_decode(filter_input(INPUT_POST, self::OPTIONAL_FIELDS, FILTER_UNSAFE_RAW), true);



$formOfAddress = array_shift($requiredFields);



if ($this→requiredFieldsEmpty($requiredFields)) {

echo 'Pflichtfelder müssen ausgefüllt werden <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidName($requiredFields[ucfirst(Bestellung::LASTNAME)])) {

echo 'Der Nachname ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidName($requiredFields[ucfirst(Bestellung::FIRSTNAME)])) {

echo 'Der Vorname ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!filter_var($requiredFields[ucfirst(Bestellung::EMAIL_ADDRESS)], FILTER_VALIDATE_EMAIL)) {

echo 'Die E-Mail Adresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidStreet($requiredFields[ucfirst(Bestellung::STREET)])) {

echo 'Die Strasse der Rechnungsadresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidZipCode($requiredFields[ucfirst(Bestellung::ZIP_CODE)])) {

echo 'Die Postleitzahl der Rechnungsadresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidCity($requiredFields[ucfirst(Bestellung::CITY)])) {

echo 'Der Ort der Rechnungsadresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidPhoneNumber($optionalFields[ucfirst(Bestellung::PHONENUMBER)], true)) {

echo 'Die Telefonnummer ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidStreet($optionalFields[ucfirst(Bestellung::STREET)], true)) {

echo 'Die Strasse der Lieferadresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidZipCode($optionalFields[ucfirst(Bestellung::ZIP_CODE)], true)) {

echo 'Die Postleitzahl der Lieferadresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



if (!$this→isValidCity($optionalFields[ucfirst(Bestellung::CITY)], true)) {

echo 'Der Ort der Lieferadresse ist nicht gültig. <message/> ';

echo new ContactFormWidget();

return;

}



$order = new Bestellung();



$cartPositions = CartPosition::getAllCartPositions();



$articles = '';



foreach ($cartPositions as $key ⇒ $cartPosition) {

$articles .= $key + 1 . ';';

foreach ($cartPosition as $positionInfoKey ⇒ $positionInfo) {

if ($positionInfoKey === CartPosition::ARTICLE_NUMBER) {

$id = $positionInfo;

$entity = Database::getInstance()→load($id);

$name = $entity→get(Artikel::TITLE);

$articles .= $name . ';';

}

if ($positionInfoKey === CartPosition::AMOUNT) {

$articles .= $positionInfo . ';';

}

if ($positionInfoKey === CartPosition::PRICE) {

$articles .= $positionInfo . '\n\r';

}

}

}



$orderData = [

Bestellung::ORDER_DATE ⇒ time(),

Bestellung::LASTNAME ⇒ $requiredFields[ucfirst(Bestellung::LASTNAME)],

Bestellung::EMAIL ⇒ $requiredFields[ucfirst(Bestellung::EMAIL_ADDRESS)],

Bestellung::PHONENUMBER ⇒ $optionalFields[ucfirst(Bestellung::PHONENUMBER)],

Bestellung::ADDRESS ⇒ $formOfAddress

. ' ' . $requiredFields[ucfirst(Bestellung::LASTNAME)]

. ' ' . $requiredFields[ucfirst(Bestellung::FIRSTNAME)] . '\r\n'

. ' ' . $requiredFields[ucfirst(Bestellung::STREET)] . '\r\n'

. ' ' . $requiredFields[ucfirst(Bestellung::ZIP_CODE)]

. ' ' . $requiredFields[ucfirst(Bestellung::CITY)] . '\r\n'

. 'schweiz \r\n',

Bestellung::SHIPPING_ADDRESS ⇒ $formOfAddress

. ' ' . $requiredFields[ucfirst(Bestellung::LASTNAME)]

. ' ' . $requiredFields[ucfirst(Bestellung::FIRSTNAME)] . '\r\n'

. ' ' . $optionalFields[ucfirst(Bestellung::STREET)] . '\r\n'

. ' ' . $optionalFields[ucfirst(Bestellung::ZIP_CODE)]

. ' ' . $optionalFields[ucfirst(Bestellung::CITY)] . '\r\n'

. 'schweiz \r\n',

Bestellung::ARTICLE ⇒ $articles

];



$order→setData($orderData);



Database::getInstance()→save($order);



echo 'Bestellung gespeichert <message/>';



exit();

}



}

ShowArticleDetailsAction

<?php



/**

* —————————————————————————–

* ShowArticleDetailsAction

* —————————————————————————–

*

* Zeige die Detailansicht eines Artikels an.

*

* @author b.lade

*/

class ShowArticleDetailsAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$productId = filter_input(INPUT_POST, Action::ID, FILTER_SANITIZE_STRING);

$entity = Database::getInstance()→load($productId);

$fields = $entity→getFields(Artikel::class);



echo new ArticleDetailsWidget($entity, $fields);

}



}

ShowCartTableAction

<?php



/**

* —————————————————————————–

* ShowArticleDetailsAction

* —————————————————————————–

*

* Zeige die Detailansicht eines Artikels an.

*

* @author b.lade

*/

class ShowArticleDetailsAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$productId = filter_input(INPUT_POST, Action::ID, FILTER_SANITIZE_STRING);

$entity = Database::getInstance()→load($productId);

$fields = $entity→getFields(Artikel::class);



echo new ArticleDetailsWidget($entity, $fields);

}



}

ShowContactFormAction

<?php



/**

* —————————————————————————–

* ShowArticleDetailsAction

* —————————————————————————–

*

* Zeige die Detailansicht eines Artikels an.

*

* @author b.lade

*/

class ShowArticleDetailsAction extends Action {



/**

* ————————————————————————-

* <i>perform()</i> Implementation

* ————————————————————————-

*

* @see ActionInterface→perform() Interface implementation von <i>perform()</i>

*

*/

public function perform() {

$productId = filter_input(INPUT_POST, Action::ID, FILTER_SANITIZE_STRING);

$entity = Database::getInstance()→load($productId);

$fields = $entity→getFields(Artikel::class);



echo new ArticleDetailsWidget($entity, $fields);

}



}

Business

Entity

Artikel

<?php



/**

* —————————————————————————–

* Artikel Entity

* —————————————————————————–

*

* @author b.lade

*/

class Artikel extends Entity {

const TITLE = 'titel';

const DESCRIPTION = 'beschreibung';

const PRICE = 'preis';

const STATUS = 'status';

const IMAGE = 'bild';

}

Bestellung

<?php



/**

* —————————————————————————–

* BestellungEntity

* —————————————————————————–

*

* @author b.lade

*/

class Bestellung extends Entity {

const ORDER_DATE = 'bestellungsdatum';

const FORM_OF_ADDRESS = 'anrede';

const LASTNAME = 'name';

const FIRSTNAME = 'vorname';

const EMAIL_ADDRESS = 'e-Mail-Adresse';

const EMAIL = 'email';

const ARTICLE = 'artikel';

const ADDRESS = 'adresse';

const STREET = 'strasse';

const ZIP_CODE = 'plz';

const CITY = 'ort';

const BILLING_ADDRESS = 'rechnungsadresse';

const SHIPPING_ADDRESS = 'lieferadresse';

const PHONENUMBER = 'telefonnummer';

const COUNTRY = 'land';

}

CartPosition

<?php



/**

* —————————————————————————–

* Artikel Entity

* —————————————————————————–

*

* @author b.lade

*/

class CartPosition extends Entity {

const CART_POSITION = 'cartPosition';

const AMOUNT = 'amount';

const ARTICLE_NUMBER = 'articleNumber';

const PRICE = 'price';

const SUBTOTAL = 'subtotal';

/**

* ————————————————————————-

* Warenkorbposition hinzufügen

* ————————————————————————-

*

* @param array $cartPosition Description

* @param int $cartPositionIndex Description

*

* @return void

*/

public static function setCartPosition(array $cartPosition, int $cartPositionIndex = null ) {

SessionHelper::setArray(self::CART_POSITION, $cartPosition, $cartPositionIndex);

}

/**

* ————————————————————————-

* Spezifische Warenkorbposition

* ————————————————————————-

*

* @param int $cartPositionIndex

*

* @return array Spezifische Warenkorbposition

*/

public static function getCartPosition(int $cartPositionIndex): array {

$cartArray = SessionHelper::getArray(self::CART_POSITION);

return $cartArray[$cartPositionIndex];

}

/**

* ————————————————————————-

* Alle Warenkorbpositionen

* ————————————————————————-

*

* @return array Alle Warenkorbpositionen

*/

public static function getAllCartPositions(): array {

return SessionHelper::getArray(self::CART_POSITION);

}

/**

* ————————————————————————-

* Warenkorbposition löschen

* ————————————————————————-

*

* @param int $cartPositionIndex

*/

public static function removeCartPosition(int $cartPositionIndex) {

SessionHelper::destroyCartPosition($cartPositionIndex);

}

}

Decorator

Main

/*

Author : b.lade

*/



/*::::::::::::::::::::::::::::::::

:::: __General__ :::::::::::::

::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: Body ::::::::::::::::::::

::::::::::::::::::::::::::::::::*/



body {

position: relative;

}



/*::::::::::::::::::::::::::::::::

:::: Body End ::::::::::::::::

::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: __General__End :::::::::::::

::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: __Helper__ ::::::::::::::

::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: Overlay :::::::::::::::::

::::::::::::::::::::::::::::::::*/



.overlay {

position: fixed;

top: 0;

left: 0;

width: 100%;

height: 100%;

}



.overlay::before {

content: '';

display: block;

position: absolute;

top: 0;

left: 0;

width: 100%;

height: 100%;

background: rgba(0,0,0,0.4);

}



/*::::::::::::::::::::::::::::::::

:::: Overlay End :::::::::::::

::::::::::::::::::::::::::::::::*/



.right {

float: right;

}



.clearfix:after {

content: „“;

display: table;

clear: both;

}



/*::::::::::::::::::::::::::::::::

:::: __Helper__End ::::::::::

::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: __Widgets__ :::::::::::::

::::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: ModalWidget :::::::::::::

::::::::::::::::::::::::::::::::::*/



.Modal {

position: fixed;

top: 30px;

left: 50%;

transform: translateX(-50%);

background: #fff;

width: 90%;

max-width: 850px;

/*height: auto;*/

-webkit-box-shadow: 0 2px 15px rgba(0,0,0,0.30);

box-shadow: 0 2px 15px rgba(0,0,0,0.30);

overflow: auto;

}



.ModalContainer {

max-height: 80%;

overflow-y: auto;

}



.ModalHeader {

padding: 10px 15px;

font-weight: 600;

}



.ModalTitle {

margin: 0;

display: inline;

}



.ModalBody {

padding: 0px 15px;

margin: 5px 0;

/*height: 90%;*/

/*max-height: 200px;*/

/*overflow-y: scroll;*/

}



/*::::::::::::::::::::::::::::::::

:::: ModalWidget End :::::::::

::::::::::::::::::::::::::::::::::*/



/*::::::::::::::::::::::::::::::::

:::: __Widgets__End :::::::::::

::::::::::::::::::::::::::::::::*/



Persistence

Database

<?php



/**

* —————————————————————————–

* Database

* —————————————————————————–

*

* @todo Kommentar vervollständigen

*

* @author j.windmeisser

* @author b.lade

*/

class Database {



/**

* ————————————————————————-

* Database Konstanten

* ————————————————————————-

*

* Konstanten welche Informationen für die Pagination der Artikelliste

* und den Verbindungsaufbau der Datenbank enthalten.

*

*

*/

const CURRENT_OFFSET = 'currentOffset';

const NEXT_OFFSET = 'nextOffset';

const INITIAL_ROW_COUNT = 0;

const ROW_COUNT = 'rowCount';

const ID_SEPERATOR = '_';

const DRIVER = 'mysql';

const HOST = '127.0.0.1';

const SCHEMA = 'webshop';

const USER = 'root';

const PASSWORD = '';



/**

* ————————————————————————-

* PDO Objekt

* ————————————————————————-

*

* Diese Variable wird später eine Instanz der PDO Klasse beinhalten.

*

* @var \PDO

*/

protected $connection = null;



/**

* ————————————————————————-

* Database Objekt

* ————————————————————————-

*

* Diese Variable wird später eine Instanz der Database Klasse beinhalten.

*

*

* @var \Database

*/

protected static $db = null;



/**

* ————————————————————————-

* Datenbankverbindung

* ————————————————————————-

*

* Hier wird die Datenbankverbindung aufgebaut. Die PDO Instanz wird nur ein

* einziges Mal der <i>connection<i> Variable zugewiesen. Falls ein Fehler

* beim Verbinden mit der Datenbank auftritt, wird eine Fehlermeldung im

* Browser ausgegeben.

*

* @return void

*/

protected function connect() {

$dataSourceName = self::DRIVER

. „:dbname=“ . self::SCHEMA

. „;host=“

. self::HOST

. „;charset=utf8“;



if ($this→connection === null) {

try {

$this→connection = new PDO(

$dataSourceName, self::USER, self::PASSWORD

);



$this→connection→setAttribute(

PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION

);

} catch (PDOException $exception) {

echo 'Datenbankfehler D001 <br/>' . $exception;

Log::logError('Datenbankfehler D001', $exception, $dataSourceName);

exit();

}

}

}



/**

* ————————————————————————-

* SQL insert statement

* ————————————————————————-

*

* Hier wird ein insert statement aufgebaut und ausgeführt. Falls ein Fehler

* beim insert statement auftritt, wird eine entsprechende Fehlermeldung

* im Browser ausgegeben.

*

* @param \Entity $entity Enthält alle Informationen welche in die Datenbank

* persistiert werden sollen

* @return void

*/

private function insert(Entity $entity) {



$data = $entity→getData();



$sql = „insert into “ . get_class($entity);

$sqlColumn = „ (“;

$sqlValues = „ (“;

$values = [];



foreach ($data as $fieldName ⇒ $fieldValue) {



$sqlColumn .= $fieldName . „, “;



$sqlValues .= „?, “;



$values[] = $fieldValue;

}



$sqlColumn = rtrim($sqlColumn, ', ');

$sqlValues = rtrim($sqlValues, ', ');



$sql .= $sqlColumn

. „) values “

. $sqlValues

. „)“;



try {

$this→connect();

$statement = $this→connection→prepare($sql);

$statement→execute($values);

} catch (PDOException $e) {

echo 'Datenbankfehler D002';

Log::logError('Datenbankfehler D002', $exception, $statement→queryString);

exit();

}

}



/**

* ————————————————————————-

* Instanzierung des Database Objekts

* ————————————————————————-

*

* Die Database Klasse folgt dem Singeltonpattern. Wärend der Laufzeit wird

* sichergestellt, dass nur eine Instanz der Klasse Database existiert.

*

* @return \Database Gibt eine Instanz der Klasse Database zurück.

*/

public static function getInstance(): Database {

if (self::$db === null) {

self::$db = new Database();

}

return self::$db;

}



/**

* ————————————————————————-

* Anzahl der Tabellenreihen

* ————————————————————————-

*

* Hier wird die Anzahl der Reihen in einer Tabelle zurückgegeben. Bei einem

* Fehler wird im Browser eine entsprechende Fehlermeldung ausgegeben.

*

* @param string $className Entspricht dem Tabellenname in der Datenbank

* @param array $filter Liest nur die Daten aus der Datenbank aus welche im

* Filter angegeben wurden.

* @return int Liefert die anzahl der Tabellenreihe zurück

*/

public function count(string $className, array $filter = null): int {

$filterSql = '';



if ($filter !== null) {

$filterSql = ' where ';

foreach ($filter as $key ⇒ $f) {

$filterSql .= $key;

$filterSql .= '=';

$filterSql .= '\'' . $f . '\' and ';

}

$filterSql = rtrim($filterSql, ' and ');

}



$class = strtolower($className);



$sql = 'select count(*) from ' . $class . ' ' . $filterSql;



try {

$this→connect();

$statement = $this→connection→prepare($sql);

$statement→execute();

$result = $statement→fetch(PDO::FETCH_NUM);

} catch (PDOException $exception) {

echo 'Datenbankfehler D003';

Log::logError('Datenbankfehler D003', $exception, $statement→queryString);

exit();

}



$rowCount = (int) $result[0];



return $rowCount;

}



/**

* ————————————————————————-

* Datensatz aus Datenbank lesen

* ————————————————————————-

*

* Hier wird nur ein Datensatz der der Id entspricht zurückgeliefert

*

* @todo Kommentar anpassen

*

* @param string $id Entspricht der Id aus der Tabelle in der Datenbank

* @return \Entity Liefert ein <i>Entity</i> Objekt zurück

*/

public function load(string $id) {

$dataType = explode(self::ID_SEPERATOR, $id)[0];



$this→connect();

try {

$sql = 'select * from ' . strtolower($dataType) . ' WHERE id=?';



$statement = $this→connection→prepare($sql);



$statement→execute([$id]);



$result = $statement→fetch(PDO::FETCH_ASSOC);



if (!$result) {

return null;

}

$entity = new $dataType();





$entity→setData($result);



return $entity;

} catch (PDOException $exception) {

echo 'Datenbankfehler D004';

Log::logError('Datenbankfehler D004', $exception, $statement→queryString);

exit();

}

}



/**

* ————————————————————————-

* Datensätze aus Datenbank auslesen

* ————————————————————————-

*

* Hier werden mehre bzw. alle Datensätze aus der Datenbank rausgelesen und

* und in einer Liste zurückgegeben.

*

* @todo loadList Parameter + limit validieren und 'säubern'

*

* @param string $className Entspricht dem Tabellenname in der Datenbank

* @param array $filter Liest nur die Daten aus der Datenbank aus welche im

* Filter angegeben wurden.

* @return array Liefert eine Liste mit <i>Entity</i> Objekten zurück

*/

public function loadList(string $className, array $filter = null): array {

$filterSql = '';



if ($filter !== null) {

$filterSql = ' WHERE ';

foreach ($filter as $key ⇒ $f) {

$filterSql .= $key;

$filterSql .= '=';

$filterSql .= '\'' . $f . '\' and ';

}

$filterSql = rtrim($filterSql, ' and ');

}



$class = strtolower($className);



$start = SessionHelper::get(self::CURRENT_OFFSET);

$amount = SessionHelper::get(self::ROW_COUNT);



$limit = ' LIMIT ' . $start . ',' . $amount;



$statement = '';



$result = '';



$this→connect();

try {

$statement = $this→connection→prepare('select * from ' . $class . ' ' . $filterSql . $limit);



$statement→execute();



$result = $statement→fetchAll(PDO::FETCH_ASSOC);

} catch (PDOException $exception) {

echo 'Datenbankfehler D005';

Log::logError('Datenbankfehler D005', $exception, $statement→queryString);

exit();

}



$returnVal = [];



foreach ($result as $data) {



$entity = new $className();



$entity→setData($data);



$returnVal[] = $entity;

}



return $returnVal;

}



/**

* ————————————————————————-

* Daten persistieren

* ————————————————————————-

*

* Momentan ist die <i>save()</i> Methode nur ein Wrapper um die <i>insert()</i>

* Methode. Nach der IPA wird die von <i>save()<i> Funktionalität erweitert.

*

* @see Database::insert()

*

* @param Entity $entity Wird direkt an

*/

public function save(Entity $entity) {

$this→insert($entity);

}



}

Logging

<?php



/**

* —————————————————————————–

* Log

* —————————————————————————–

*

* Klasse zum Loggen

*

* @author b.lade

*/

class Log {

/**

* ————————————————————————-

* Logifle beschreiben

* ————————————————————————-

*

* Schreibt die Message in ein Logfile;

*

* @param string $message Die Message die in das Logfile geschriebenwerden

* soll

*

* @return void

*/

protected static function writeLog(string $message) {



$time = $_SERVER['REQUEST_TIME'];

$ip = $_SERVER['REMOTE_ADDR'];

$url = $_SERVER['REQUEST_URI'];

if ($time == '') {

$time = time();

}

$timestamp = date(„Y-m-d H:i:s“, $time);



if ($ip == '') {

$ip = „REMOTE_ADDR_UNKNOWN“;

}



if ($url == '') {

$url = „REQUEST_URI_UNKNOWN“;

}



$completeMessage = „[ {$timestamp} ] - {$ip} - {$url} - {$message}\n“;

file_put_contents(LOG_FILE_PATH, $completeMessage, FILE_APPEND | LOCK_EX);

}



/**

* ————————————————————————-

* POST Request loggen

* ————————————————————————-

*

* Loggt Inhalt des POST Request.

*

* @return void

*/

public static function logPostRequest() {

$message = '';

$properties = filter_input_array(INPUT_POST);



if ($properties != null) {

foreach ($properties as $propertyName ⇒ $propertyValue) {

$message .= „{$propertyName}={$propertyValue};“;

}

self::writeLog($message);

}

}



// logge eine Fehlermeldung

/**

* ————————————————————————-

* Fehlermeldung loggen

* ————————————————————————-

*

* Loggt eine Fehlermeldung.

*

* @param string $error Fehlermeldung

* @param string $stack Stacktrace

* @param string $optionalInfo optionale Information

*

* @return void

*/

public static function logError(string $error, string $stack, string $optionalInfo = '') {

$message = „#ERROR: {$error}\ninfo1: {$stack}\ninfo2: {$optionalInfo}“;

self::writeLog($message);

}



}

Session

<?php



/**

* —————————————————————————–

* SessionHelper Klasse

* —————————————————————————–

*

*

* @author j.windmeisser

* @author b.lade

*/

class SessionHelper {



/**

* ————————————————————————-

* Konstante für Sessiontimeout

* ————————————————————————-

*/

const TIMEOUT = 'timeout';



/**

* ————————————————————————-

* SessionHelper Instanz

* ————————————————————————-

*

* @var \SessionHelper

*/

private static $sessionHelper = null;

/**

* ————————————————————————-

* Sessiontimeoutlänge

* ————————————————————————-

*

* Die Sessiontimeoutlänge beträgt 15 Minuten.

*

* @var int

*/

private static $timeout = 90 * 10;

/**

* ————————————————————————-

* Sessiontimeout inaktivität

* ————————————————————————-

*

* @var bool

*/

protected static $timeoutInactivity = true;



/**

* ————————————————————————-

* SessionHelper Instanz zurückgeben

* ————————————————————————-

*

* Bei der Klasse <i>SessionHelper</i> wird das Singleton Pattern

* verwendet, damit sichergestellt wird, dass nur eine Instanz dieser

* Klasse existiert.

*

* Ausserdem ist die Methode <i>getInstance</i> <b>protected</b>,

* damit sie nicht von aussen aus aufgerufen werden kann.

*

* @return SessionHelper Gibt eine Instanz der Klasse <i>SessionHelper</i>

* zurück.

*/

protected static function getInstance(): SessionHelper {

if (self::$sessionHelper === null) {

self::$sessionHelper = new SessionHelper();

}



return self::$sessionHelper;

}



/**

* ————————————————————————-

* Session starten

* ————————————————————————-

*

* Die Methode <i>start</i> startet eine Session, falls sie vorher

* noch nicht gestartet wurde.

*

* Ausserdem ist die Methode <i>start</i> <b>protected</b>,

* damit sie nicht von aussen aus aufgerufen werden kann, da sie nur

* <b>innerhalb<b> der Klasse <i>SessionHelper</i> verwendet wird.

*

* @return void

*/

protected static function start() {

if (static::getSessionStatus() !== PHP_SESSION_ACTIVE) {

session_start();

}

}



/**

* ————————————————————————-

* Sessiontimeoutlänge setzen

* ————————————————————————-

*

* @param int $timeout

*

* @return void

*/

public static function setTimeout(int $timeout) {

$this→timeout = $timeout;

$this→timeoutInactivity = false;

}



/**

* ————————————————————————-

* Session immernoch aktiv

* ————————————————————————-

*

* Überprüfen ob die Session immernoch aktiv bzw. noch nicht abgelaufen ist.

* Wenn ja, wird <i>true</i> zurückgegeben. Sonst, wird <i>false</i>

* zurückgegeben.

*

* @return bool

*/

public static function isActive(): bool {

self::getInstance()→start();

if (self::$timeoutInactivity) {



if (!is_int(self::get(self::TIMEOUT))) {

self::set(self::TIMEOUT, time());

}

/*

* Unterschied in Sekunden

*/

$lastActionDelta = self::get(self::TIMEOUT) - time();



if ($lastActionDelta < 0) {

return false;

}



self::newSession();

}



return true;

}



/**

* ————————————————————————-

* Sessionstatus

* ————————————————————————-

*

* @return mixed

*/

public static function getSessionStatus() {

return session_status();

}



/**

* ————————————————————————-

* Sessionvariable array

* ————————————————————————-

*

* Die Methode <i>getArray</i>, gibt den Inhalt einer Sessionvariable

* zurück, welcher ein <i>array</i> ist.

*

* @param string $fieldName Der Feldname, um auf die entsprechenden

* Sessionvariable zugreifen zu können.

* @param int $arrayIndex

*

* @return array Gibt den Inhalt einer Sessionvariable

* zurück, welcher ein <i>array</i> ist. Wenn die Sessionvariable nicht

* existiert, wird ein leerer <i>string</i> zurückgegeben.

*/

public static function getArray(string $fieldName, int $arrayIndex = null): array {

self::getInstance()→start();

if (!isset($_SESSION[$fieldName])) { return []; }

// if ($arrayIndex !== null) { return $_SESSION[$fieldName][$arrayIndex]; }

return $_SESSION[$fieldName];

}



/**

* ————————————————————————-

* Sessionvaribale Inhalt

* ————————————————————————-

*

* Die Methode <i>get</i>, gibt den Inhalt einer Sessionvariable

* zurück.

*

* @param string $fieldName Der Feldname, um auf die entsprechenden

* Sessionvariable zugreifen zu können.

* @return mixed Gibt den Inhalt einer Sessionvariable

* zurück. Wenn die Sessionvariable nicht

* existiert, wird ein leerer <i>string</i> zurückgegeben.

*/

public static function get(string $fieldName) {

self::getInstance()→start();

if (!isset($_SESSION[$fieldName])) { return ''; }

return $_SESSION[$fieldName];

}



/**

* ————————————————————————-

* Sessionvariable array registrieren

* ————————————————————————-

*

* Die Methode <i>setArray</i>, registriert eine Sessionvariable,

* deren Inhalt ein <i>array</i> ist.

*

* @param string $fieldName Der Feldname, um auf die entsprechenden

* Sessionvariable zugreifen zu können.

* @param array $value Das <i>array</i>, welches als Wert der

* Sessionvariable zugewiesen werden soll.

* @param int $arrayIndex

*

* @return void

*/

public static function setArray(string $fieldName, array $value, int $arrayIndex = null) {

self::getInstance()→start();

if ($arrayIndex !== null) {

$_SESSION[$fieldName][$arrayIndex] = $value;

return;

}

else if ($fieldName === CartPosition::CART_POSITION) {

$_SESSION[$fieldName][] = $value;

return;

}

$_SESSION[$fieldName] = $value;

}



/**

* ————————————————————————-

* Sessionvariable Inhalt registrieren

* ————————————————————————-

*

* Die Methode <i>set</i>, registriert eine Sessionvarible.

*

* @param string $fieldName Der Feldname, um auf die entsprechenden

* Sessionvariable zugreifen zu können.

* @param void $value Der <i>string</i>, welcher als Wert der

* Sessionvariable zugewiesen werden soll.

*

* @return void

*/

public static function set(string $fieldName, $value) {

self::getInstance()→start();

$_SESSION[$fieldName] = $value;

}



/**

* ————————————————————————-

* Warenkorbposition entfernen

* ————————————————————————-

*

* Die Warenkorbposition wird entsprechend der Variable <i>cartPositionIndex</i>

* entfernt.

*

* @param int $cartPositionIndex

* @param string $fieldName

*

* @return void

*/

public static function destroyCartPosition(int $cartPositionIndex) {

self::getInstance()→start();

// Warenkorb Position aus der Session Variablen 'cartPosition' entfernen

unset($_SESSION[CartPosition::CART_POSITION][$cartPositionIndex]);

// Session Variable 'cartPosition' neu indexieren

$_SESSION[CartPosition::CART_POSITION] = array_values($_SESSION[CartPosition::CART_POSITION]);

}



/**

* ————————————————————————-

* Session beenden

* ————————————————————————-

*

* Die Methode <code>destroy</code> zerstört bzw. beendet eine Session.

*

* @return void

*/

public static function destroy() {

self::getInstance()→start();

session_unset();

session_destroy();

}



/**

* ————————————————————————-

* Sessiontimeout starten

* ————————————————————————-

*

* @return void

*/

public static function newSession() {

self::getInstance()→start();

$_SESSION[self::TIMEOUT] = time() + self::$timeout;

}



}

Presentation

Widget

ArticleDetailsWidget

<?php



/**

* —————————————————————————–

* ArticleDetailsWidget

* —————————————————————————–

*

* @author b.lade

*/

class ArticleDetailsWidget extends Widget {

protected $entity;

protected $fields;





public function __construct($entity, $fields) {

parent::__construct();

$this→entity = $entity;

$this→fields = $fields;

}

public function prepare() {

echo '<div class=„overlay destroyable“ data-action=„' . CloseWidgetAction::class . '“>';

echo '<div class=„Modal“ role=„dialog“ aria-labelledby=„ModalTitle“>';

echo '<div class=„ModalContainer“>';

echo '<div class=„ModalHeader clearfix“>';

echo '<h3 id=„ModalTitle“ class=„ModalTitle“>Produkt Detailansicht</h3>';

$closeButton = new ButtonWidget('X');

$closeButton→setActionName(CloseWidgetAction::class);

$closeButton→addCssClass('ButtonWidgetSmall');

$closeButton→addCssClass('right');

echo $closeButton;

echo '</div>';

echo '<div class=„ModalBody“>';

echo '<ul class=„ProductInformation“ data-entity=„' . $this→entity . '“>';

echo '<li>';

echo $this→fields['TITLE'] . ': ' . $this→entity→get(Artikel::TITLE);

echo '</li>';

echo '<li>';

$imageContent = $this→entity→get(Artikel::IMAGE);

$encodedImageContent = base64_encode($imageContent);

$imageMimeType = getimagesizefromstring($encodedImageContent)['mime'];

$src = 'data:' . $imageMimeType . ';base64,' . $encodedImageContent;

$alt = $this→entity→get(Artikel::TITLE);

$imageWidget = new ImageWidget($src, $alt);

$imageWidget→addCssClass('product-image');

echo $imageWidget;

echo '</li>';

echo '<li>';

echo $this→fields['DESCRIPTION'] . ': ' . $this→entity→get(Artikel::DESCRIPTION);

echo '</li>';

echo '<li>';

echo $this→fields['PRICE'] . ': ' . $this→entity→get(Artikel::PRICE) . '.-';

echo '</li>';

$addToCartButton = new ButtonWidget('Produkt zum Warenkrob hinzufügen');

$addToCartButton→setActionName(AddToCartAction::class);

echo $addToCartButton;

echo '</ul>';

echo '</div>';

echo '</div>';

echo '</div>';

echo '</div>';

}

}

ArticleListWidget

<?php



/**

* —————————————————————————–

* ArticleListWidget

* —————————————————————————–

*

* @author b.lade

*/

class ArticleListWidget extends Widget {



protected $itemsPerPage;



public function __construct($itemsPerPage) {

parent::__construct();

$this→itemsPerPage = $itemsPerPage;

}



protected function prepareListItems($fieldNames, $entity) {

foreach ($fieldNames as $fieldName) {

echo '<li class=„ProductDetail ' . $fieldName . '“>';

switch ($fieldName) {

case Artikel::DESCRIPTION:

$descriptionLength = strlen($entity→get($fieldName));

if ($descriptionLength > 200) {

$shortDescription = substr($entity→get($fieldName), 0, 200) . '&hellip;';

echo $fieldName . ': ' . $shortDescription;

} else {

echo $fieldName . ': ' . $entity→get($fieldName);

}

break;

case Artikel::PRICE:

echo $fieldName . ': ' . $entity→get($fieldName) . '.-';

break;

case Artikel::IMAGE:

$imageContent = $entity→get($fieldName);

$encodedImageContent = base64_encode($imageContent);

$imageMimeType = getimagesizefromstring($encodedImageContent)['mime'];

$src = 'data:' . $imageMimeType . ';base64,' . $encodedImageContent;

$alt = $entity→get(Artikel::TITLE);

$imageWidget = new ImageWidget($src, $alt);

$imageWidget→addCssClass('product-image');

echo $imageWidget;

break;

default:

echo $fieldName . ': ' . $entity→get($fieldName);

break;

}

echo '</li>';

}

$viewArticleButton = new ButtonWidget('Detailansicht anzeigen');

$viewArticleButton→setActionName(ShowArticleDetailsAction::class);

$addToCartButton = new ButtonWidget('zum Warenkorb hinzufügen');

$addToCartButton→setActionName(AddToCartAction::class);

echo '<li>';

echo $viewArticleButton;

echo $addToCartButton;

echo '</li>';

}



protected function prepareUnorderedList($productList, $fieldNames) {

foreach ($productList as $entity) {

echo '<ul class=„ProductInformation“ data-entity=„' . $entity . '“>';

$this→prepareListItems($fieldNames, $entity);

echo '</ul>';

}

}



public function prepare() {

$productFilter = [

Entity::DELETED ⇒ 'false'

];



$productList = Database::getInstance()→loadList(Artikel::class, $productFilter);

$fieldNames = Entity::getFields(Artikel::class);



echo '<div '

. 'class=„' . self::class . $this→cssClasses . '“ '

. 'id=„' . $this→id . '“ >';

$showCartTableButton = new ButtonWidget('Warenkorb anzeigen');

$showCartTableButton→setActionName(ShowCartTableAction::class);

echo $showCartTableButton;

echo '<h1>Produktliste</h1>';

$this→prepareUnorderedList($productList, $fieldNames);

echo new PaginationWidget($productFilter, $this→itemsPerPage);

echo '</div>';

}



}

ButtonWidget

<?php



/**

* —————————————————————————–

* ButtonWidget Klasse

* —————————————————————————–

*

* @author j.windmeisser

* @author b.lade

*/

class ButtonWidget extends Widget {

protected $isDisabled = false;

public function __construct($label = '') {

parent::__construct();

$this→setLabel($label);

}



public function setDisabled($isDisabled) {

$this→isDisabled = $isDisabled;

}

public function prepare() {

$this→code = '<button '

. 'type=„button“ '

. 'id=„' . $this→id . '“ '

. 'class=„' . self::class . ''. ($this→isDisabled ? ' disabled' : '') . $this→cssClasses .'“ '

. ($this→actionName !== '' ? 'data-action=„' . $this→actionName . '“ ': '')

. ($this→isDisabled ? 'tabindex=„-1“' : '') .' '

. ($this→tooltip !== null ? 'title=„'. $this→tooltip .'“' : '')

. ($this→isDisabled ? 'disabled' : '') .'>';

$this→code .= $this→label;

$this→code .= '</button>';

}

}

CartTableWidget

<?php



/**

* —————————————————————————–

* CartTableWidget

* —————————————————————————–

*

*

* @author b.lade

*/

class CartTableWidget extends Widget {

protected $cartPostions = [];

protected $hiddenColumns = [];



public function __construct(array $cartPositions = []) {

parent::__construct();

$this→cartPostions = $cartPositions;

}



/**

* @todo edit comment

*/

public function hideColumn($columnName) {

$this→hiddenColumns[$columnName] = true;

}



public function prepare() {

echo '<div '

. 'class=„' . self::class . 'Container' . '“>';

$backToProductListButton = new ButtonWidget('Produkliste anzeigen');

$backToProductListButton→setActionName(LoadArticleListAction::class);

echo $backToProductListButton;

echo '<h1>Warenkorb</h1>';

echo '<table '

. 'class=„' . self::class . $this→cssClasses . '“ '

. 'id=„' . $this→id . '“>';

echo '<thead>';

echo '<tr>';

$fieldNames = Entity::getFields(CartPosition::class);

foreach ($fieldNames as $fieldName) {

if (!isset($this→hiddenColumns[$fieldName])) {

echo '<th>' . $fieldName . '</th>';

}

}

echo '</tr>';

echo '</thead>';

echo '<tbody>';

$total = 0;

foreach ($this→cartPostions as $key ⇒ $cartPosition) {

$subTotal = $cartPosition[CartPosition::PRICE] * $cartPosition[CartPosition::AMOUNT];

$total += $subTotal;

echo '<tr '

. 'data-position-id=„' . $key . '“>';

$artikel = Database::getInstance()→load($cartPosition[CartPosition::ARTICLE_NUMBER]);

$amountInputWidget = new InputWidget('', $cartPosition[CartPosition::AMOUNT], InputWidget::NUMBER);

$amountInputWidget→setActionName(EditCartPositionAction::class);

$amountInputWidget→addCssClass(InputWidget::class . 'Amount');

$amountInputWidget→addHtmlAttribute('min=„1“');

$amountInputWidget→addHtmlAttribute('pattern=„[0-9]“');

echo '<td>' . $amountInputWidget . '</td>';

echo '<td>' . $artikel→get(Artikel::TITLE) . '</td>';

echo '<td>' . $cartPosition[CartPosition::PRICE] . '</td>';

echo '<td>' . $subTotal . '</td>';

echo '<td>';

$removeButton = new ButtonWidget('Position entfernen');

$removeButton→setActionName(RemoveCartPositionAction::class);

$removeButton→addCssClass(ButtonWidget::class . 'Small');

echo $removeButton;

echo '</td>';

echo '</tr>';

}

echo '<tr>';

echo '<td colspan=„4“><strong>Total</strong> ' . $total . '</td>';

echo '</tr>';

echo '</tbody>';

echo '</table>';

$orderButton = new ButtonWidget('Bestellen');

$orderButton→setActionName(ShowContactFormAction::class);

if (count($this→cartPostions) === 0 ) {

$orderButton→setDisabled(true);

}

echo $orderButton;

echo '</div>';

}

}

ContactFormWidget

<?php



/**

* —————————————————————————–

* ContactFormWidget

* —————————————————————————–

*

* @author b.lade

*/

class ContactFormWidget extends Widget {



public function prepare() {

/**

* ———————————————————————

* Anrede des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Optionen: 'Herr', 'Frau'

*

* @var \DropdownWidget

*/

$formOfAddress = new DropdownWidget(['Herr', 'Frau'], ucfirst(Bestellung::FORM_OF_ADDRESS));

$formOfAddress→isRequired(true);

/**

* ———————————————————————

* Nachname des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Mustermann

*

* @var \InputWidget

*/

$lastName = new InputWidget(ucfirst(Bestellung::LASTNAME));

$lastName→isRequired(true);

/**

* ———————————————————————

* Vorname des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Hans

*

* @var \InputWidget

*/

$firstName = new InputWidget(ucfirst(Bestellung::FIRSTNAME));

$firstName→isRequired(true);

/**

* ———————————————————————

* E-Mail-Adresse des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: hans.mustermann@email.com

*

* @var \InputWidget

*/

$emailAddress = new InputWidget(ucfirst(Bestellung::EMAIL_ADDRESS), '', InputWidget::EMAIL);

$emailAddress→isRequired(true);

/**

* ———————————————————————

* Telefonnummer des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: 056 222 22 22

*

* @var \InputWidget

*/

$phoneNumber = new InputWidget(ucfirst(Bestellung::PHONENUMBER), '', InputWidget::TEL);

/**

* ———————————————————————

* Strasse der Rechnungsadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Mustermannstrasse 444

*

* @var \InputWidget

*/

$street = new InputWidget(ucfirst(Bestellung::STREET));

$street→isRequired(true);

/**

* ———————————————————————

* Strasse der Lieferadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: Mustermannstrasse 444

*

* @var \InputWidget

*/

$shippingStreet = new InputWidget(ucfirst(Bestellung::STREET));



/**

* ———————————————————————

* PLZ der Rechnungsadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: 8957

*

* @var \InputWidget

*/

$zipCode = new InputWidget(ucfirst(Bestellung::ZIP_CODE));

$zipCode→isRequired(true);

/**

* ———————————————————————

* PLZ der Lieferadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: 8957

*

* @var \InputWidget

*/

$shippingZipCode = new InputWidget(ucfirst(Bestellung::ZIP_CODE));

/**

* ———————————————————————

* Ort der Rechnungsadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Aargau

*

* @var \InputWidget

*/

$city = new InputWidget(ucfirst(Bestellung::CITY));

$city→isRequired(true);

/**

* ———————————————————————

* Ort der Lieferadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: Aargau

*

* @var \InputWidget

*/

$shippingCity = new InputWidget(ucfirst(Bestellung::CITY));

/**

* ———————————————————————

* Vorname Eingabefeld

* ———————————————————————

*

* Dieses Formularsteuerelement setzt den Speichervorgang der Bestellung

* in gang.

*

* @var \ButtonWidget

*/

$confirmOrder = new ButtonWidget('Bestellung speichern');

$confirmOrder→setActionName(SaveOrderAction::class);

echo '<div '

. 'class=„' . self::class . $this→cssClasses . '“ '

. 'id=„' . $this→id . '“>';

echo '<h3>Bestellformular</h3>';

echo '<fieldset>';

echo '<legend>Name</legend>';

echo „{$formOfAddress} {$lastName} <br/> {$firstName}“;

echo '</fieldset>';



echo '<fieldset>';

echo '<legend>Email / Telefon</legend>';

echo „{$emailAddress} <br/> {$phoneNumber}“;

echo '</fieldset>';

echo '<fieldset>';

echo '<legend>Rechnungsadresse</legend>';

echo „{$street} <br/> {$zipCode} {$city}“;

echo '</fieldset>';

echo '<fieldset>';

echo '<legend>Lieferadresse</legend>';

echo „{$shippingStreet} <br/> {$shippingZipCode} {$shippingCity}“;

echo '</fieldset>';

echo '<p><strong>Pflichtfelder</strong> sind mit * markiert. </p>';



echo $confirmOrder;

echo '</div>';

}

}

<?php



/**

* —————————————————————————–

* ContactFormWidget

* —————————————————————————–

*

* @author b.lade

*/

class ContactFormWidget extends Widget {



public function prepare() {

/**

* ———————————————————————

* Anrede des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Optionen: 'Herr', 'Frau'

*

* @var \DropdownWidget

*/

$formOfAddress = new DropdownWidget(['Herr', 'Frau'], ucfirst(Bestellung::FORM_OF_ADDRESS));

$formOfAddress→isRequired(true);

/**

* ———————————————————————

* Nachname des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Mustermann

*

* @var \InputWidget

*/

$lastName = new InputWidget(ucfirst(Bestellung::LASTNAME));

$lastName→isRequired(true);

/**

* ———————————————————————

* Vorname des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Hans

*

* @var \InputWidget

*/

$firstName = new InputWidget(ucfirst(Bestellung::FIRSTNAME));

$firstName→isRequired(true);

/**

* ———————————————————————

* E-Mail-Adresse des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: hans.mustermann@email.com

*

* @var \InputWidget

*/

$emailAddress = new InputWidget(ucfirst(Bestellung::EMAIL_ADDRESS), '', InputWidget::EMAIL);

$emailAddress→isRequired(true);

/**

* ———————————————————————

* Telefonnummer des Bestellers

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: 056 222 22 22

*

* @var \InputWidget

*/

$phoneNumber = new InputWidget(ucfirst(Bestellung::PHONENUMBER), '', InputWidget::TEL);

/**

* ———————————————————————

* Strasse der Rechnungsadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Mustermannstrasse 444

*

* @var \InputWidget

*/

$street = new InputWidget(ucfirst(Bestellung::STREET));

$street→isRequired(true);

/**

* ———————————————————————

* Strasse der Lieferadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: Mustermannstrasse 444

*

* @var \InputWidget

*/

$shippingStreet = new InputWidget(ucfirst(Bestellung::STREET));



/**

* ———————————————————————

* PLZ der Rechnungsadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: 8957

*

* @var \InputWidget

*/

$zipCode = new InputWidget(ucfirst(Bestellung::ZIP_CODE));

$zipCode→isRequired(true);

/**

* ———————————————————————

* PLZ der Lieferadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: 8957

*

* @var \InputWidget

*/

$shippingZipCode = new InputWidget(ucfirst(Bestellung::ZIP_CODE));

/**

* ———————————————————————

* Ort der Rechnungsadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist ein Pflichtfeld.

*

* Beispiel: Aargau

*

* @var \InputWidget

*/

$city = new InputWidget(ucfirst(Bestellung::CITY));

$city→isRequired(true);

/**

* ———————————————————————

* Ort der Lieferadresse

* ———————————————————————

*

* Dieses Formularsteuerelement ist kein Pflichtfeld.

*

* Beispiel: Aargau

*

* @var \InputWidget

*/

$shippingCity = new InputWidget(ucfirst(Bestellung::CITY));

/**

* ———————————————————————

* Vorname Eingabefeld

* ———————————————————————

*

* Dieses Formularsteuerelement setzt den Speichervorgang der Bestellung

* in gang.

*

* @var \ButtonWidget

*/

$confirmOrder = new ButtonWidget('Bestellung speichern');

$confirmOrder→setActionName(SaveOrderAction::class);

echo '<div '

. 'class=„' . self::class . $this→cssClasses . '“ '

. 'id=„' . $this→id . '“>';

echo '<h3>Bestellformular</h3>';

echo '<fieldset>';

echo '<legend>Name</legend>';

echo „{$formOfAddress} {$lastName} <br/> {$firstName}“;

echo '</fieldset>';



echo '<fieldset>';

echo '<legend>Email / Telefon</legend>';

echo „{$emailAddress} <br/> {$phoneNumber}“;

echo '</fieldset>';

echo '<fieldset>';

echo '<legend>Rechnungsadresse</legend>';

echo „{$street} <br/> {$zipCode} {$city}“;

echo '</fieldset>';

echo '<fieldset>';

echo '<legend>Lieferadresse</legend>';

echo „{$shippingStreet} <br/> {$shippingZipCode} {$shippingCity}“;

echo '</fieldset>';

echo '<p><strong>Pflichtfelder</strong> sind mit * markiert. </p>';



echo $confirmOrder;

echo '</div>';

}

}

ImageWidget

<?php



/**

* —————————————————————————–

* ImageWidget Klasse

* —————————————————————————–

*

* @author b.lade

*/

class ImageWidget extends Widget {

protected $src;

protected $alt;



public function __construct(string $src, string $alt = '') {

parent::__construct();

$this→src = $src;

$this→alt = $alt;

}



public function prepare() {

echo '<img '

. 'class=„'. self::class . $this→cssClasses .'“ '

. 'id=„'. $this→id .'“ '

. 'src=„' . $this→src . '“ '

. ($this→alt !== '' ? 'alt=„'. $this→alt .'“' : '') . '/>';

}

}

InputWidget

<?php



/**

* —————————————————————————–

* InputWidget

* —————————————————————————–

*

* @author j.windmeisser

* @author b.lade

*/

class InputWidget extends Widget {

const TEXT = 'text';

const EMAIL = 'email';

const PASSWORD = 'password';

const NUMBER = 'number';

const TEL = 'tel';

const HIDDEN = 'hidden';



protected $type;

protected $isRequired;

protected $htmlAttributes = null;



public function __construct(string $label = '', string $value = '', string $type = self::TEXT) {

parent::__construct();

$this→setLabel($label);

$this→setValue($value);

$this→type = $type;

}

public function isRequired(bool $isRequired) {

$this→isRequired = $isRequired;

}

public function addHtmlAttribute(string $htmlAttribute) {

if ($this→htmlAttributes === null) {

$this→htmlAttributes = ' ' . $htmlAttribute;

return;

}

$this→htmlAttributes .= ' ' . $htmlAttribute;

}



public function prepare() {

$this→code = '<label for=„'. $this→id .'“ class=„' . self::class . 'Label' . '“>';

$this→code .= ($this→isRequired ? '*' : '') . $this→label;

$this→code .= '</label>';

$this→code .= '<input '

. ($this→label !== '' ? 'name=„' . $this→label . '“ ' : '' )

. 'class=„' . self::class . $this→cssClasses . ucfirst($this→label) . '“ '

. 'id=„' . $this→id . '“ '

. 'type=„' . $this→type . '“ '

. $this→htmlAttributes . ' '

. ($this→actionName !== '' ? 'data-action=„' . $this→actionName . '“ ': '')

. ($this→isRequired ? 'required' : '') . ' '

. 'value=„' . $this→value . '“/>';

}

}

PaginationWidget

<?php



/*

* To change this license header, choose License Headers in Project Properties.

* To change this template file, choose Tools | Templates

* and open the template in the editor.

*/



/**

* Description of PaginationWidget

*

* @author b.lade

*/

class PaginationWidget extends Widget {

protected $filter;

protected $itemsPerPage;



public function __construct(array $filter, int $itemsPerPage) {

parent::__construct();

$this→filter = $filter;

$this→itemsPerPage = $itemsPerPage;

}



public function prepare() {

$loadPreviousButton = new ButtonWidget('load previous');

$loadPreviousButton→setActionName(LoadPreviousAction::class);

if (SessionHelper::get(Database::CURRENT_OFFSET) ⇐ Database::INITIAL_ROW_COUNT) {

$loadPreviousButton→setDisabled(true);

}



$loadMoreButton = new ButtonWidget('load next');

$loadMoreButton→setActionName(LoadNextAction::class);

if (SessionHelper::get(Database::NEXT_OFFSET) >= Database::getInstance()→count(Artikel::class, $this→filter)) {

$loadMoreButton→setDisabled(true);

}



echo '<div '

. 'class=„' . self::class . $this→cssClasses . '“ '

. 'id=„' . $this→id . '“ >';

echo $loadPreviousButton;

echo $loadMoreButton;

echo '</div>';

}

}

1 Vergangene Stunden am heutigen Tag

2 Insgesamt vergangene Stunden

3 Im Projekt verbleibende Stunden

4 Vergangene Stunden am heutigen Tag

5 Insgesamt vergangene Stunden

6 Im Projekt verbleibende Stunden

7 Vergangene Stunden am heutigen Tag

8 Insgesamt vergangene Stunden

9 Im Projekt verbleibende Stunden

10 Vergangene Stunden am heutigen Tag

11 Insgesamt vergangene Stunden

12 Im Projekt verbleibende Stunden

13 Vergangene Stunden am heutigen Tag

14 Insgesamt vergangene Stunden

15 Im Projekt verbleibende Stunden

16 Vergangene Stunden am Heutigen Tag

17 Insgesamt vergangene Stunden

18 Im Projekt verbleibende Stunden

19 Vergangene Stunden am heutigen Tag

20 Insgesamt vergangene Stunden

21 Im Projekt verbleibende Stunden

22 Vergangene Stunden am heutigen Tag

23 Insgesamt vergangene Stunden

24 Im Projekt verbleibende Stunden

25 Vergangene Stunden am heutigen Tag

26 Insgesamt vergangene Stunden

27 Im Projekt verbleibende Stunden

Basil Lade Rafisa GmbH

Bernstrasse 88

CH-8953 Dietikon

de.bkp/intern/ipa/bl2017/ipa_bl2017.txt · Zuletzt geändert: 2021/02/09 11:33 von 127.0.0.1