In English

Das “Contacts” Beispiel

Eine vollständige objektorientierte Datenbank-basierte PHP Web-Anwendung

© 2012- Gerald Zincke, Austria

Wahrscheinlich gibt es keine andere Stelle im WWW, wo Sie eine:

·         Web Applikation finden können, die auf einer

·         leistungsfähigen OO Architektur basiert mit

·         vollständigem kostenlosem PHP Quellcode bereitgestellt wird, über ein

·         GUI in Standard-HTML und CSS verfügt, das

·         Funktioniert ohne auf JavaScript angewiesen zu sein, das

·         In allen gängigen Browsern bis zurück zum IE 6 funktioniert, mit der

·         kostenlosen MySQL Datenbank funktioniert,  auf

·         Standard Web Servern, wie sie von

·         Gratis Web-Hostern und kommerziellen Hostern bereitgestellt werden, läuft. Und

·         natürlich auch lokal auf Ihrem PC.

Hier beschreibe ich, wie Sie so eine Applikation entwerfen, bauen und installieren können.

1         Einführung

Das Web ist voll von Blogs, Scripts und FAQs rund um PHP Software Entwicklung. Aber es ist nicht einfach sich einen Überblick zu verschaffen und eine sinnvolle Anwendungsarchitektur damit aufzubauen. Und es gibt nicht allzu viele Stellen von wo man vollständige Anwendungen herunterladen kann.

Hier werden Sie eine vollständige, dokumentierte, praxisgerechte Web-Anwendung vorfinden mitsamt der gesamten Informationen und allen Downloads die Sie brauchen, damit Sie sie dazu bringen können, dass sie auf Ihrem eigenen  Rechner läuft oder damit Sie sie im Internet auf dem Server ihres Web Hosters verfügbar machen können. Ich werde die Elemente der allgemeinen Architektur beschreiben, die angewendet wurde um die Anwendung zu entwickeln.

1       Einführung.. 1

2       Anforderungen an die Beispiel-Anwendung.. 3

3       Elemente des Entwicklungsprozesses. 3

4       Eine objektorientierte Architektur für Web-Anwendungen.. 4

4.1         Das einfache Bild. 4

4.2         Die grundlegenden Ideen. 4

4.3         Design Richtlinien. 5

4.4         Informationsfluss. 5

4.4.1      Starten des Hauptfensters. 5

4.4.2      Benutzeraktionen in Fenstern. 7

4.4.3      Einen Dialog von einem Fenster aus öffnen. 8

4.4.4      Benutzeraktionen in Dialogen. 10

4.4.5      Einen Dialog schließen und zum Fenster zurückkehren. 10

4.5         Verantwortlichkeit der Klassen. 11

5       Implementierung einer Beispielanwendung.. 13

5.1         Das logische Datenmodell. 13

5.2         Der physische Datenbankentwurf. 14

5.2.1      Die Company-Tabelle. 14

5.2.2      Die Person-Tabelle. 15

5.2.3      Die Beziehung “Firma beschäftigt Person”. 16

5.2.4      Technische Tabellen. 16

5.3         Benutzeroberfläche / GUI 17

5.3.1      Beschreibung. 17

5.3.2      GUI Layouts. 17

5.3.2.1         CTSMainWindow.. 17

5.3.2.2         CTSLoginDialog. 18

5.3.2.3         CTSCompanyBrowserWindow.. 18

5.3.2.4         CTSCompanyInsertDialog. 19

5.3.2.5         CTSCompanyUpdateDialog. 20

5.3.2.6         CTSContactsBrowserWindow.. 22

5.3.2.7         CTSContactInsertDialog. 22

5.3.2.8         CTSContactUpdateDialog. 23

5.4         Code. 24

5.4.1      Vorbereiten des Framework-Verzeichnisses. 25

5.4.2      Einrichten des Framework Icon-Verzeichnisses. 27

5.4.3      Das Basis-Verzeichnis. 27

5.4.4      Einrichten des Anwendungsverzeichnisses. 28

5.4.4.1         Die Definition des Anwendungsmodells. 29

5.4.4.2         Implementierung des Hauptfensters: CTSMainWindow.php. 31

5.4.4.3         Implementierung des Login-Dialogs: CTSLoginDialog.php. 34

5.4.4.4         CTSCompanyBrowserWindow.. 37

5.4.4.5         CTSCompanyInsertDialog. 38

5.4.4.6         CTSCompanyUpdateDialog. 41

5.4.4.7         CTSContactsBrowserWindow.. 45

5.4.4.8         CTSContactInsertDialog. 46

5.4.4.9         CTSContactUpdateDialog. 50

6       Installationsprozeduren.. 54

6.1         Die Contacts Anwendung auf Ihrem PC. 54

6.1.1      Web-Server und Datenbank installieren. 55

6.1.2      Die Anwendung am lokalen Web-Server einrichten. 55

6.2         Die Anwendung auf einem Web-Server im Internet ausführen. 56

6.2.1      Einen Web Server im Internet einrichten. 57

6.2.2      Die Anwendung auf den Web-Server bringen. 58

7       Downloads. 63

2         Anforderungen an die Beispiel-Anwendung

Ich möchte die Architektur anhand der Beispiel-Anwendung beschreiben. Ich gehe dabei von den folgenden Anforderungen aus:

Benutzeranforderungen

1.      Die Anwendung soll Daten zu Kundenkontakten verwalten.

2.      Die gespeicherten Daten müssen Vor- und Zuname, Titel, Firma, Abteilung, Position, Telefonnummer, Mobiltelefonnummer, Straße, Ort, Postleitzahl und Land enthalten.

3.      Die Daten müssen für viele Personen gleichzeitig zugreifbar sein.

4.      Es muss möglich sein durch die gespeicherten Kontakte zu blättern, einzelne Einträge auszuwählen um sie anzusehen und zu bearbeiten.

5.      Es muss möglich sein Daten eines Kontakts auf ein Handy zu kopieren.

6.      Der Zugriff über das Internet muss möglich sein.

7.      Der Zugriff muss auf autorisierte Personen eingeschränkt sein.

Technische Anforderungen

1.      Die Anwendung läuft auf einem Webserver.

2.      Sie wird in PHP programmiert.

3.      Die Daten werden in einer neuen MySQL Datenbank gespeichert.

4.      Alle Datenmodifikationen sind transaktionssicher unter Wahrung der ACID Prinzipien implementiert.

5.      Die Benutzer-Authentifizierung erfolgt über Benutzerkennung und Kennwort.

6.      The Anwendung ist gegen SQL-Injection, XSS-Attacken und Hyperlink Modifikationen geschützt.

7.      Die Datenprüfung erfolgt am Server.

3         Elemente des Entwicklungsprozesses

Wir brauchen die folgenden Elemente um die Anwendung zu entwerfen und zu implementieren.

·         Datenmodell – Beschreibt die wichtigsten Anwendungs-spezifischen Datenstrukturen, die wir brauchen werden.

·         Datenbank – Beschreibt Datenbank Tabellen und Beispieldaten, die benötigt werden um die Datenstrukturen persistent zu machen, um Anwendungsdaten zu speichern und auf sie zuzugreifen.

·         GUI – Entwurf und Implementierung der Benutzeroberfläche der Anwendung.

·         Code – Die Anwendungslogik um alles miteinander zu verbinden.

·         Installationsprozeduren – Beschreibung wie die Anwendung in einem Webserver eingerichtet werden muss – entweder lokal am eigenen Rechner (In Ihrem LAN) oder auf einem Webserver im Internet.

·         Testkonzept – Plan für die Vorbereitung und Durchführung der Tests. 

4         Eine objektorientierte Architektur für Web-Anwendungen

4.1      Das einfache Bild

Das Internet wurde ursprünglich dafür entwickelt um Dokumente zu zugänglich zu machen und nicht Anwendungen. Aus diesem Grund ist die Struktur von Interaktion im Internet im Grunde recht einfach:

·         Der Client sendet eine URL (einen Request) an den Server

·         Der Server antwortet mit einem HTML Dokument

Gängige Web-Browser bieten folgende Möglichkeiten einen URL-Request an den Server zu senden:

·         Indem man den URL manuell über die Adresszeile des Browsers eingibt oder durch Auswahl einer Verknüpfung bzw. eines Shortcuts.

·         Durch Klick auf einen Hyperlink in einem HTML Dokument das bereits im Browser angezeigt wird.

·         Durch Klick auf einen Submit-Button. In diesem Fall werden die Eingabedaten eines HTML Formulars Teil des  Client-Requests und werden zum Server entweder als Teil des URL oder als versteckte Daten übertragen.

·         Indirekt, indem ein Client-Request String von einem Script im Browser (JavaScript, Flash, MS-Silverlight …) erzeugt und abgesendet wird.

Eine Web-Anwendung ist dann ein Stück Code, das die URLs interpretiert und (dynamisch) HTML Dokumente (aus variablem Input) erzeugt. Viele PHP Anwendungen sind sehr einfach strukturiert. Für jede Seite der Anwendung gibt es zwei oder mehr Skripte. Das erste Skript erzeugt die Seite und sendet sie zum Client und das zweite Script oder weitere verarbeiten den eintreffenden URL-Request. Das Problem mit diesem Ansatz ist, dass

·         Das sendende und das empfangende Skript teilen gemeinsames Wissen über den Dialog-Kontext und wenn es da eine Änderung gibt, ist es nicht so einfach alle betroffenen Dateien zu aktualisieren und alles konsistent zu halten.

·         Es ist nicht so einfach Requests und Antworten in einen übergeordneten Dialogfluss einzubetten. Zum Beispiel: Die Anwendung soll es erlauben eine Information nachzuschlagen und dann sollte der Dialogfluss zum Eingabebildschirm zurückkehren (natürlich ohne dass bereits eingegebene Daten verloren gehen).

Für ideenreiche, ausgefeilte Web-Anwendungen brauchen wir fortgeschrittenere Entwurfsmuster um die Server Software strukturiert und wartbar zu halten.

4.2      Die grundlegenden Ideen

Die grundlegenden Ideen sind:

·         Web-Anwendungen zeigen ihren Output in Web-Seiten, die wir Fenster nennen.

·         Der gesamte Code, der dazu nötig ist den Fensterinhalt zu erzeugen und an den Client zu senden sowie der Code der nötig ist um die Benutzerreaktion auf das Fenster zu verarbeiten, wird in einer einzigen PHP Klasse gekapselt.

·         Ein Fensterobjekt kann andere Fenster öffnen (entweder in einem neuen Browser-Tab oder einem neuen Browser-Fenster).

·         Es gibt auch spezielle Fenster, die Dialoge genannt werden. Wenn ein Fenster einen Dialog öffnet, ist das Eltern-Fenster nicht für den Benutzer zugänglich bis der Dialog geschlossen wird.

·         Dialoge können weitere Sub-Dialoge öffnen und werden dann ebenfalls unzugänglich bis der Sub-Dialog schließt.

4.3      Design Richtlinien

Eine gute Architektur stellt eine Struktur für einen standardisierten Informationsfluss bereit. Die Komponenten der Architektur haben klar definierte (nicht überlappende) Verantwortungsbereiche. Daher gibt es klare Regeln wo welcher Teil der Anwendungslogik implementiert werden muss. Die Schnittstellen sind eng und einfach zu verstehen. Daten sind in denjenigen Objekten gespeichert, wo sie am häufigsten benötigt werden. Vor vielen Jahren prägte Larry Constantine die Definitionen von (geringer) Kopplung und (starker) Kohäsion für diesen Entwurfsansatz.

4.4      Informationsfluss

Die folgenden Diagramme zeigen die Interaktion und den Informationsfluss zwischen den wichtigsten Elementen der Architektur.

4.4.1     Starten des Hauptfensters

Das erste Diagramm zeigt was passiert, wenn eine Anwendung gestartet wird und sich das Hauptfenster der Anwendung öffnet.

Fig.1: Anwendung starten

Links oben sehen Sie dass der Client einen http:// Request (so etwas wie http://localhost/index.php)  zum Server sendet. Der Benutzer kann das durch das Eintippen des URL in das Adressfeld in seinem Browser tun, indem er auf einen Hyperlink klickt oder indem er einen Favoriten bzw. ein Lesezeichen im Browser auswählt.

Dadurch wird das Anwendungs Start-Script aufgerufen (index.php). Dieses erzeugt zuerst ein neues Session-Objekt. Das Session-Objekt hat einen automatisierten Persistenzmechanismus und wird das Ende der Transaktion überleben (es wird automatisch in einer Datei gespeichert). Daher kann das Session-Objekt dazu verwendet werden um Daten zu speichern, die verfügbar sein sollten wenn die nächste Benutzerinteraktion verarbeitet wird.

Das Start-Script erzeugt als nächstes ein neues Context-Objekt (nicht als Rechteck dargestellt). Ein Context-Objekt ist dafür verantwortlich, die Daten zu speichern, die im Kontext des Aufrufs eines Fensters wichtig sind. (Wenn das gleiche Fenster zur gleichen Zeit zweimal offen ist, gibt es zwei verschiedene Context-Objekte). Das Context-Objekt „weiß“ wer das Fenster geöffnet hat (der Eltern Kontext) und speichert die Werte des Modell-Objekts. Das Modell-Objekt umfasst die Daten die angezeigt und mit der Anwendung modifiziert werden.

Dann erzeugt das Start-Script das Fenster-Objekt und öffnet es.

Das Fenster-Objekt initialisiert sein Modell und erzeugt eine Struktur von Control-Objekten. Diese definieren die Struktur des Fenster-Layouts. Sie kapseln Dinge wie Ausgabefelder, Menüs, Bilder usw. Sie speichern Ausgabedaten (Zahlen, Texte, URLs) und am wichtigsten ist, dass sie sich selbst in HTML darstellen können.

Wie „weiß“ nun ein Fenster-Objekt wie es sein Modell-Objekt initialisieren soll und welche Control-Objekte es erzeugen soll? Für jedes einzelne Fenster in einer Anwendung erzeugen wir eine Klasse, die von der Fenster-Basisklasse abgeleitet ist. Die abgeleitete Klasse, wie ein CTSMainWindow, überschreibt die Funktionen initModel() und initWindow(). Dort ist spezifiziert wie ein CTSMainWindow aussehen wird, welche Eventhandler-Funktionen als Reaktion auf eine Benutzeraktion aufgerufen werden und welche Modelldaten es behandeln wird.

Das Fenster-Objekt wird dann die Control-Objekt mit (initialen) Daten füllen und sie dann dazu benutzen sich selbst als HTML Dokument darzustellen. Es antwortet das HTML zum Client und sichert dann den aktuellen Status des Context-Objects (welches die Modell-Daten enthält) im Session-Objekt, welches seinerseits über die PHP Standardmechanismen auf die Platte gesichert wird.

Die strichlierte Linie bedeutet das Ende der Server Transaktion. Der Benutzer kann nun das Hauptfenster der Anwendung in seinem Web-Browser sehen.

4.4.2     Benutzeraktionen in Fenstern

Das nächste Bild zeigt, was passiert wenn der Benutzer ein aktives Benutzerschnittstellen-Control anklickt um etwas zu aktualisieren, das im Fenster angezeigt wird.

Fig. 2 Benutzerinteraktion mit einem Fenster

Die Benutzerinteraktion beginnt immer mit einem Klick auf einen Hyperlink im Fenster (aus guten Gründen, die ich später beschreiben werde, sollten (Haupt-)Fenster keine Submit-Buttons enthalten).

Alle Hyperlinks auf einem Fenster sind so eingerichtet, dass sie ein „Dispatcher“-Script am Server aufrufen. Der URL liefert den Namen der Klasse für das zu öffnende Fenster, den ID des Fenster-Kontexts und den Namen der Eventhandler/Callback-Funktion.

Hinweis: Der Dispatcher verfügt über einen Sicherheitsmechanismus, der alle ungültigen URL-Requests zurückweist, die Hyperlinks darstellen, welche nicht Teil der Fensterstruktur sind, die im vorangegangenen Schritt gesendet wurde. Damit wird verhindert, dass Angreifer Hyperlinks manipulieren und so Funktionen aufrufen, die im aktuellen Kontext nicht erlaubt sind (z.B. weil sich der Benutzer noch nicht angemeldet hat).

Der Dispatcher holt den Kontext vom Session-Objekt, erzeugt das Fenster-Objekt und die Daten des Modell-Objekts werden mit den gleichen Werten initialisiert, die sie am Ende der vorhergehenden Transaktion hatten.

Das Fenster-Objekt baut dann wieder die Layout-Struktur der Control-Objekte auf. Dann wird die Eventhandler Callback-Funktion gestartet. Diese kann (abhängig von der Benutzeranfrage)

·         etwas berechnen oder lesen und dann den Inhalt, die Werte der Controls verändern.

·         Sie kann ein anderes Fenster oder einen Sub-Dialog öffnen (wird weiter unten gezeigt).

Im obigen Use-Case nehmen wir an, dass die Event-Funktion einfache einige lokale Veränderungen durchführt, Ergebniswerte in die Controls füllt und wir im gleichen Fenster bleiben wollen. Das Fenster-Objekt wandelt die Controls daher wie vorher in HTML um, sendet das HTML Dokument an den Client und speichert seinen Kontext in die Session.

Der Benutzer kann nun ein  aktualisiertes Fenster am Bildschirm sehen.

4.4.3     Einen Dialog von einem Fenster aus öffnen

Das nächste Bild zeigt, was passiert wenn der Benutzer ein aktives User-Interface-Control anklickt um ein Dialogfenster zur Dateneingabe zu öffnen. Damit wir das Konzept eines modalen Dialogs – ein Dialogfenster das zuerst geschlossen werden muss bevor das aufrufende Fenster wieder aktiv werden kann (so wie sie es etwa von File-Open Dialogen gewöhnt sind) – simulieren können, wird der Dialog im aktuellen Web-Browser Tab geöffnet, somit „verdeckt“ er das aufrufende Fenster indem er es überschreibt.

Fig. 3: Einen Dialog von einem Fenster öffnen

Der Hyperlink  vom zuvor geöffneten Fenster aktiviert den Dispatcher. Dieser liest wieder den Fenster-Kontext und erzeugt das Fenster-Objekt. Das Fenster wird dann die Event-Funktion aufrufen, die dafür verantwortlich ist, den Benutzer-Request zu behandeln und die vom Callback Parameter im Benutzer Request identifiziert ist.

Diese Event-Funktion wird ein neues Modell-Objekt für den Sub-Dialog vorbereiten. Dieses enthält alle Parameterdaten, die der Sub-Dialog vom Fenster braucht. Dann wird das neue Dialog-Objekt erzeugt und geöffnet. Der Kontext des Vorfahren wird im Context-Stack im persistenten Session-Objekt gesichert (Das erlaubt es uns später vom Dialog zurückzukehren und alle Fensterdaten, sind dann noch immer verfügbar).

Die Basisklasse für Dialoge ist von der Basisklasse für Fenster abgeleitet. Deshalb macht es das Dialog-Objekt genauso wie das Fenster-Objekt zuvor: Es wird die Control-Objekte erzeugen, wird dann die Controls mit Daten füllen (diese Daten können Daten sein, die der Dialog vom aufrufenden Fenster bekommen hat) und wird sie dann dazu verwenden um sie in einem HTML Dokument auszugeben. Es sendet HTML an den Client und sichert dann den aktuellen Status des Context-Objekts (einschließlich der Modell-Daten) in das Session-Objekt, welches seinerseits mit Standard PHP Mechanismen auf die Platte gesichert wird.

Hinweis: Der Kontext-Speicher im Session-Objekt ist als Stack organisiert und der Kontext des Vorfahren wird nicht überschrieben.

Der Benutzer kann nun den geöffneten Dialog am Bildschirm sehen.

4.4.4     Benutzeraktionen in Dialogen

Es kann Benutzeraktionen geben die den gleichen Dialog (mit aktualisierten Datenwerten) erneut anzeigen oder veranlassen dass sein Sub-Dialog geöffnet wird. Das funktioniert genauso wie es in den vorigen beiden Abschnitten beschrieben ist. Das deshalb – Sie haben es erraten – weil eine Dialog-Klasse alle Eigenschaften der Basisklasse für Fenster erbt und deshalb alles tun kann, was auch ein Fenster kann (und einige zusätzliche Dinge).

4.4.5     Einen Dialog schließen und zum Fenster zurückkehren

Ziemlich oft wird ein Benutzer Daten in einem Dialog eingeben, OK klicken, der Dialog schließt und das aufrufende Fenster erscheint wieder. Das folgende Bild zeigt wie es funktioniert.

 

Fig. 4: Den Dialog schließen und zum aufrufenden Fenster zurückkehren

Meistens wird der Benutzer einen Submit-Button klicken um den Dialog abzuschließen. Das veranlasst, dass die Formulardaten zum Server übertragen werden. Dort wird wieder der Dispatcher aufgerufen.

Der liest zuerst den Dialog-Kontext und erzeugt das Dialog-Objekt. Das Dialog-Objekt wird alle Eingabedaten in die Controls für Eingabefelder und Auswahlfelder füllen. Der Dialog wird dann seine Event-Funktion aufrufen, die dafür verantwortlich ist, den Klick auf den Submit-Button zu behandeln. Die Eventfunktion kann nun auf die Eingabedaten zugreifen und sie verarbeiten.

Wenn alles OK ist, wird sie die Ergebnisse des Dialogs in seinem Modell-Objekt speichern, seinen Kontext sichern und das aufrufende Fenster-Objekt erzeugen und öffnen.

Dieses wird die Control-Objekte erzeugen, wird dann die Controls mit Daten füllen (das sind die Datenwerte, die sie hatten bevor der Dialog geöffnet worden ist) und dann wird es eine Return-Event Handler-Funktion aufrufen um die Ergebnisse des Dialogs zu verarbeiten. Diese Funktion kann den alten Sub-Kontext lesen und kann Ergebnisdaten in die Fenster-Controls schreiben.

Anschließend verwendet das Fenster-Objekt die Controls um sie in ein HTML Dokument zu schreiben. Es gibt das HTML an den Client zurück und sichert dann den aktuellen Status des Kontext-Objekts (inklusive der Modell-Daten) ins Session-Objekt, das seinerseits mit standardisierten PHP Mechanismen auf der Platte gespeichert wird.

Der Benutzer kann nun das wiedereröffnete Fenster am Bildschirm sehen.

4.5      Verantwortlichkeit der Klassen

 

Klasse

Basisklasse

Verantwortlichkeit (Auszug)

Window

-

Verwaltet Modelldaten

Kennt die User-Interface Controls, die es enthält.

Füllt variable Daten (vom Modell) in die Controls

Gibt dem Context Objekt bekannt, welche Client-Requests im momentanen Zustand erlaubt sind.

Weiß wie es sich am Client öffnen kann (rendert sich selbst nach HTML)

Gibt dem Context Objekt bekannt, welche Client-Requests im momentanen Zustand erlaubt sind.

Kann alle gültigen Client-Requests vom Fenster, das beim Client angezeigt wird, verarbeiten.

Identifiziert Menüeinträge, Callback-Hyperlinks und triggert Eventhandler die an einen Button geknüpft sind.

Verwaltet die Daten getrennt wenn das Fenster in unterschiedlichen Kontexten gleichzeitig geöffnet wird.

Dialog

Window

Verarbeitet HTML Formulardaten und füllt eingegebene Daten in User-Interface Controls.

Kümmert sich darum, dass alle Daten von Eingabefeldern gemäß der spezifizierten Input-Klasse (numerisch, Text ohne Sonderzeichen, Datum etc.) überprüft werden.

Identifiziert Submit-Buttons in HTML Formularen und triggert Eventhandler die an den Button geknüpft sind.

Control

-

Speichert einen Wert.

Kann den Wert nach HTML rendern.

Entry Field

Control

Verwendet den Wert als Default Textinhalt. Kennt einen Tooltip.

Verarbeitet Client-Eingabedaten für das Control-Objekt.

Kennt seine Input-Klasse (numerisch, Text ohne Sonderzeichen, Datum etc.); prüft alle Eingaben gegen diese Klasse.

Rendert sich als <input> Tag nach HTML.

Image

Control

Kennt den URL des Bildes.

Rendert sich als <img> Tag nach HTML.

Pushbutton

Control

Verwendet den Wert als Button-Text.  Kennt einen Tooltip.

Weiß welche Funktion einen Klick auf den Button behandeln muss.

Rendert sich als <input type=submit> Tag nach HTML.

Menu Item

Control

Kennt seinen Menütext und einen Tooltip.

Weiß welche Funktion einen Klick auf den Menüeintrag behandeln muss.

Rendert sich als <a> Tag nach HTML.

Control Pane

Control

Nimmt einzufügende Control-Objekte entgegen und speichert sie.

Weiß wie ein eingefügtes Control-Objekt relativ zum vorigen Control-Objekt positioniert werden soll.

Kann seine gespeicherten Control-Objekte (unter Beachtung der relativen Positionierung) als <div> oder <span> nach HTML rendern.

Context

-

Hat eine eindeutige ID.

Kennt seine Fensterklasse.

Kennt sein Modellobjekt.

Kümmert sich darum dass alle unerwarteten Client-Requests und unerwartete Parameterwerte zurückgewiesen werden.

Kann ein Fenster oder einen Dialog auf das Modellobjekt öffnen.

 

 

5         Implementierung einer Beispielanwendung

5.1      Das logische Datenmodell

Das Modell beschreibt die wichtigsten Anwendungspezifischen Datenstrukturen, wie wir brauchen werden.

Das Entity/Relationship Diagramm des Domain-Modells sieht so aus:

Das Diagramm ist folgendermaßen zu lesen: Wir definieren einen Entitätstyp “Person” und einen Entitätstyp “Company”. Beide stehen miteinander in Beziehung. Eine Person arbeitet für eine und genau eine Firma (das stimmt zwar in der Realität oft nicht, aber für unser Beispiel reicht uns das so), für eine Firma arbeiten keine oder mehrere Personen.

Der Entitätstyp “Person” hat die folgenden Attribute:

·         FirstName

·         LastName

·         Title

·         Department

·         Position

·         Phone

·         Mobile

·         EMail

·         LastChange

Der Entitätstyp “Company” hat die folgenden Attribute:

·         CompanyName

·         Street

·         City

·         ZIPCode

·         Country

·         LastChange

Der Typ aller Attribute soll Zeichenkette sein.

Bitte glauben Sie mir, dass in einer realen Anwendung das Modell ein wenig ausgefeilter aussehen würde. Aber für unser Beispiel wird uns das so recht sein.

5.2      Der physische Datenbankentwurf

Der Datenbankentwurf beschreibt Datenbanktabellen und Beispieldaten, die wir brauchen um das Modell persistent zu machen, um Anwendungsdaten zu speichern und zu lesen. Wir werden MySQL als Datenbank-Maschine verwenden.

Zuerst erzeugen wir eine neue Datenbank (Lesen Sie im Abschnitt “Downloads” wie sie den Quellcode herunterladen können).

CREATE DATABASE Contacts;

USE Contacts;

DROP TABLE IF EXISTS Person;

DROP TABLE IF EXISTS Company;

 

SQL Script zum Anlegen der Datenbank

Hinweis: SQL Anweisungen können entweder über den MYSQL Kommandozeilen Client MySQL.exe oder über die bekannte Verwaltungsoberfläche PHPMyAdmin eingegeben werden. Siehe Abschnitt „Installations“.

Wir brauchen zwei Datenbanktabellen um Objekte des Domain-Modells persistent zu machen und zwei technische Tabellen.

5.2.1     Die Company-Tabelle

Wir verwenden SQL um die Tabellenstruktur für die Company-Tabelle zu erzeugen und etwas anfänglichen Inhalt einzufügen.

CREATE TABLE Company (

  Company_ID integer NOT NULL AUTO_INCREMENT ,

  CompanyName   varchar(64) NOT NULL,

  Street      varchar(32) NOT NULL,

  City        varchar(32) ,

  ZIPCode     varchar(16) ,

  Country     varchar(16) ,

  LastChange timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  PRIMARY KEY (Company_ID)

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

 

INSERT INTO Company SET Company_ID=1, CompanyName='Microsoft', City='Redmond', Country='USA';

INSERT INTO Company SET Company_ID=2, CompanyName='Apple', City='Cupertino', Country='USA';

 

select * from Company;

 

 

SQL Script zum Erzeugen der Company-Tabelle

Resultat des Scripts:

+------------+-------------+--------+-----------+---------+---------+---------------------+

| Company_ID | CompanyName | Street | City      | ZIPCode | Country | LastChange          |

+------------+-------------+--------+-----------+---------+---------+---------------------+

|          1 | Microsoft   |        | Redmond   | NULL    | USA     | 2012-01-13 19:27:35 |

|          2 | Apple       |        | Cupertino | NULL    | USA     | 2012-01-13 19:27:35 |

+------------+-------------+--------+-----------+---------+---------+---------------------+

 

5.2.2     Die Person-Tabelle

Die Person-Tabelle wird die Daten von Kontaktpersonen speichern. Die Fremdschlüsselreferenz implementiert die Beziehung ‘Person arbeitet für (mindestens eine und nur eine) Firma’. Sie wird verhindern, dass Personensätze erzeugt werden, die keine oder eine ungültige Beziehung zu einem Firmendatensatz haben. Sie wird auch verhindern, dass Firmendatensätze gelöscht werden solange Personendatensätze existieren, die sie referenzieren (Referenzielle Integrität).

DROP TABLE IF EXISTS Person;

 

CREATE TABLE Person (

  Person_ID   integer NOT NULL AUTO_INCREMENT,

  FirstName   varchar(32) ,

  LastName      varchar(32) NOT NULL,

  Title       varchar(32) ,

  Company_ID integer NOT NULL,

  Department varchar(32) ,

  Position      varchar(32) ,

  Phone       varchar(16) ,

  Mobile      varchar(16) ,

  EMail       varchar(255),

  FOREIGN KEY (Company_ID) REFERENCES Company(Company_ID),

  LastChange timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  PRIMARY KEY (Person_ID)

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

 

INSERT INTO Person SET

Person_ID=1,

FirstName='Bill',

LastName='Gates',

Position='Chairman',

Phone='001 123456789',

Company_ID=1;

 

INSERT INTO Person SET

Person_ID=2,

FirstName='Steve',

LastName='Ballmer',

Position='CEO',

Phone='001 234567891',

Company_ID=1;

 

INSERT INTO Person SET

Person_ID=3,

FirstName='Tim',

LastName='Cook',

Position='CEO',

Phone='001 345678912',

Company_ID=2;

 

select Person_ID, FirstName, LastName, Company_ID, Position, Phone, LastChange from Person;

 

SQL Skript zur Erzeugung der Person-Tabelle

Resultat des Skripts:

 

+-----------+-----------+----------+------------+----------+---------------+------------------

| Person_ID | FirstName | LastName | Company_ID | Position | Phone         | LastChange      

+-----------+-----------+----------+------------+----------+---------------+------------------

|         1 | Bill      | Gates    |          1 | Chairman | 001 123456789 | 2012-01-14 12:11

|         2 | Steve     | Ballmer  |          1 | CEO      | 001 234567891 | 2012-01-13 19:44

|         3 | Tim       | Cook     |          2 | CEO      | 001 345678912 | 2012-01-13 19:44

+-----------+-----------+----------+------------+----------+---------------+------------------

 

5.2.3     Die Beziehung “Firma beschäftigt Person”

Wie wird die Beziehung implementiert? Beachten Sie das Company_ID Feld in der Personentabelle. Dieses wird verwendet um den Primärschlüssel der Firma zu speichern für die die Person arbeitet. Die FOREIGN KEY Spezifikation sorgt dafür, dass ein Wert für den Company_ID nur ein ID eines existieren Company-Datensatzes sein kann.

5.2.4        Technische Tabellen

Wir brauchen auch eine Tabelle, damit wir gültige Benutzer-ID’s speichern können und eine Tabelle um Queries zu speichern (siehe Abschnitt CTSCompanyBrowserWindow).

USE Contacts;

 

DROP TABLE IF EXISTS user;

 

CREATE TABLE `user` (

  `userid`         varchar(16) NOT NULL,

  `password_enc`   varchar(32) NOT NULL,

  `lastname`    varchar(32) ,

  `firstname`      varchar(32),

  `preferences` text DEFAULT NULL,

  `email`       varchar(255),

  `LastChange`     timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 

PRIMARY KEY (`userid`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

 

INSERT INTO user SET userid='test', password_enc=MD5('test');

INSERT INTO user SET userid='demo', password_enc=MD5('demo');

 

select userid, password_enc, lastchange from user;

 

SQL Skript zum Erzeugen der User Tabelle

Resultat des Skripts:

+--------+----------------------------------+---------------------+

| userid | password_enc                     | lastchange          |

+--------+----------------------------------+---------------------+

| demo   | fe01ce2a7fbac8fafaed7c982a04e229 | 2012-01-14 19:49:43 |

| test   | 098f6bcd4621d373cade4e832627b4f6 | 2012-01-14 19:49:43 |

+--------+----------------------------------+---------------------+

 

DROP TABLE IF EXISTS GGFQuery;

 

CREATE TABLE GGFQuery

(

   id   integer AUTO_INCREMENT,

   owner      VARCHAR(16) NOT NULL,

   ETypeName     VARCHAR(64) NOT NULL,

   browser         VARCHAR(64) NOT NULL,

   qname   VARCHAR(128) NOT NULL,

   filter     TEXT,

   sorter     TEXT,

   thecolumns TEXT,

   columnsize TEXT,

   thechecksum   TEXT,

   PRIMARY KEY (id)

) ENGINE = InnoDB;

 

SQL Skript zum Erzeugen der Query Tabelle

5.3       Benutzeroberfläche / GUI

Hier beschreiben wir den Entwurf und die Implementierung der grafischen Benutzeroberfläche der Anwendung.

5.3.1     Beschreibung

Der Benutzer startet die Anwendung indem er

http://localhost/index.php

in das Adressfeld des Web-Browsers eingibt. Der Server wird antworten indem er das Hauptfenster anzeigt. Es bietet Menüs für

·         Login, Anmeldung

·         Logout, Abmeldung

·         Browse contacts, in Kontakten blättern

·         Browse company, in Firmen blättern

·         Das Anschauen eines About-Dialogs,

·         Das Fenster zu schließen

an. Der Login-Dialog erlaubt, eine Benutzerkennung und ein Passwort einzugeben.

Das Kontakte-Browser-Fenster, erlaubt es durch die gespeicherten Kontakte zu blättern. Es bietet Menüs an um neue Kontakte anzulegen und das Browser-Fenster zu schließen. Hyperlinks erlauben es Kontakte anzusehen, zu bearbeiten und einen Kontakt zu löschen. 

Der Kontakte Update-Dialog erlaubt es einen einzelnen Kontakt anzuschauen, einen Kontakt zu aktualisieren oder Daten für einen neuen Kontakt einzugeben. Die Firma einer Kontaktperson kann aus einer Liste ausgewählt werden. Wenn ein Firmendatensatz noch nicht existiert kann er von hier aus angelegt werden.

Das Firmen-Browser-Fenster, erlaubt es durch die gespeicherten Firmen zu blättern. Es bietet Menüs an um neue Firmen anzulegen und das Browser-Fenster zu schließen. Hyperlinks erlauben es Firmen anzusehen, zu bearbeiten und eine Firma zu löschen. 

Der Firmen Update-Dialog erlaubt es eine einzelne Firma anzuschauen, eine Firma zu aktualisieren oder Daten für eine neue Firma einzugeben.

5.3.2     GUI Layouts

Wir erzeugen die GUI Layouts mit dem ReinHTML Dialog Designer (http://www.ReinHTML.eu )

5.3.2.1     CTSMainWindow

Um die Dinge einfach zu halten, enthält das Hauptfenster nur einen Menübalken und eine Begrüßungsnachricht.

Hauptfenster

5.3.2.2       CTSLoginDialog

Der Login-Dialog bietet Eingabefelder für User-ID und Passwort.

 

Login-Dialog

5.3.2.3       CTSCompanyBrowserWindow

Das Firmen Browser-Fenster zeigt die Liste der gespeicherten Firmen. Wenn es mehr Einträge in der Datenbank gibt, als auf den Bildschirm passen, kann der Benutzer Seite für Seite durch die Liste blättern, indem er die Buttons auf der rechten Seite verwendet.

 

Firmen Browser-Fenster

Ein Klick auf einen Datensatz wird einen Dialog mit den Daten der Firma öffnen. Die Icons auf der linken Seite werden dazu verwendet Datensätze zu löschen und zu kopieren. Um einen neuen Datensatz anzulegen, klicken Sie auf „Insert“. Um das Fenster zu schließen klicken Sie auf „Close“. Der Rest sind von der Basisklasse geerbte Komfortfunktionen, die Sie ausprobieren können, sobald Sie die Anwendung zum Laufen gebracht haben.

5.3.2.4     CTSCompanyInsertDialog

In vielen Fällen ist es notwendig Initialwerte einzugeben wenn Sie einen neuen Datensatz erzeugen wollen. Es kann NOT NULL Felder geben oder es kann manchmal einfach nicht sinnvoll sein einen Datensatz mit vielen leeren Feldern zu speichern.

Dafür verwendet die Architektur einen Insert-Dialog. Er wird sich öffnen wenn Sie im Firmen Browser-Fenster auf „Insert“ klicken.

 

Firmen Insert-Dialog

Der Firmen Insert-Dialog enthält Eingabefelder für den Firmendatensatz. Beachten Sie dass er kein Feld für die Company_ID enthält weil diese automatisch erzeugt wird.

5.3.2.5     CTSCompanyUpdateDialog

Dieser Dialog erlaubt uns Firmendatensätze anzusehen und zu aktualisieren. Er wird geöffnet indem man einen Firmendatensatz im Browser-Fenster anklickt.

Firmen Update-Dialog

Hinweis: Der Firmen Update-Dialog zeigt auch die Company_ID . Sie ist ein read-only Feld.

Am Beispiel des Update-Dialogs kann man gut zeigen, warum es sinnvoll ist, für Updates von Datenbeständen das Konzept modaler Dialoge einzusetzen.

1.       Wenn im Update Dialog Änderungen vorgenommen werden, dann sollten diese natürlich anschließend in der Liste der Datensätze im CTSCompanyBrowserWindow sichtbar werden. Das geschieht praktisch automatisch, sobald der Update-Dialog geschlossen wird und der Dialogfluß zum Browser-Fenster zurückkehrt.

2.       Es ist auch für den Benutzer rasch transparent, dass das Eintippen von Änderungen in die Eingabefelder noch keine Änderung von Werten in der Datenbank nach sich zieht. Dazu ist erst ein Klick auf den OK Button im Update-Dialog erforderlich.

3.       Wenn der Update fehlgeschlagen ist (z.B. weil parallel derselbe Datensatz verändert wurde), dann kann einfach der Update-Dialog noch einmal angezeigt und der Benutzer erneut zur Eingabe aufgefordert werden. Es ist für diesen Sonderfall keine eigene Benutzeroberfläche notwendig.

4.       Das Konzept unterstützt die Implementierung einer optimistischen Transaktionslogik, die für einen reibungslosen Multuserbetrieb notwendig ist.  

5.3.2.6     CTSContactsBrowserWindow

Das Kontakte Browser-Fenster zeigt die Liste der gespeicherten Kontakte. Beachten Sie, dass in der Liste Daten aus der Personentabelle und der Firmentabelle (Spalte „Employer“) gezeigt werden.

Kontakte Browser-Fenster

5.3.2.7       CTSContactInsertDialog

Der Kontakte Insert-Dialog öffnet sich wenn der Benutzer im Kontakte Browser-Fenster auf „Insert“ klickt.

Kontakte Insert-Dialog

Er erlaubt es Daten einer neuen Kontaktperson einzugeben.  Durch die Auswahl einer Firma aus dem drop-down Feld am unteren Rand können Sie die Beziehung zum Arbeitgeber festlegen.

5.3.2.8     CTSContactUpdateDialog

Der Kontakte Update-Dialog erscheint, wenn der Benutzer auf einen Eintrag im Kontakte Browser-Fenster klickt.

Kontakte Update-Dialog

Hinweis: Der Update-Dialog zeigt die Person_ID. Das ist ein read-only Feld. Die Beziehung zu einer Firma kann geändert werden indem man eine andere Firma im drop-down Feld auswählt.

Beachten Sie den gelben Button zwischen OK und Cancel. Wenn er gedrückt wird, erzeugt er eine VCard für diese Person, die heruntergeladen und von Telefonbuch Applikationen in Smartphones und von den meisten E-Mail Clients verarbeitet werden kann.

Auf einem Symbian Telefon sieht das etwa so aus:

Einen Kontakt auf ein Symbian Telefon herunterladen

In iOS (iPhone, iPad) ist es nicht möglich VCards herunterzuladen und im Telefonbuch zu speichern. Aber es gilt wie immer: there is an App for that! (zum Beispiel Qrafter oder VCard Getter).

5.4      Code

Hier beschreibe ich die Anwendungslogik um alles zusammenzufügen. Der Code einer Web-Anwendung muss im Dokumentenverzeichnis des Web-Servers abgelegt werden (Siehe Abschnitt “Downloads” um den Quellcode herunterzuladen). Typische Installationen des Apache Web-Servers (wie sie zum Beispiel vom XAMPP Paket erzeugt werden) haben eine Directory-Struktur wie folgt:

·         Programdir

o   apache

o   htdocs

o   mysql

o   php

o  

“Programdir” ist das Installationsverzeichnis, das während der Installation des Web-Servers gewählt wurde.

Das Verzeichnis Programdir/htdocs ist dann der Ort wo wir unseren Code ablegen müssen. Dort brauchen wir drei Unterverzeichnisse Programdir/htdocs/GGF , das Framework-Verzeichnis,  Programdir/htdocs/GGFIcons , das Verzeichnis für Framework-Icons und Programdir/htdocs/CTS , das Anwendungsverzeichnis. Das ergibt die vollständige Verzeichnisstruktur:

·         Programdir

o   apache

o   htdocs

§  GGF

§  GGFIcons

§  CTS

o   mysql

o   php

o  

Aus Sicherheitsgründen dürfen die Subverzeichnisse /CTS und /GGF  nicht aus dem Internet erreichbar sein. Das ist besonders für das Verzeichnis /htdocs/GGF wichtig weil es Setup-Information (siehe unten) enthält.  Für Apache Web-Server werden Zugriffsrechte definiert in dem man eine .htaccess Datei in jedes dieser Verzeichnisse gibt. Einige Web-Hoster bieten alternativ auch interaktive Benutzeroberflächen an, um die Zugriffsrechte zu spezifizieren.

Die Verzeichnisse /htdocs/GGFIcons – und  /htdocs – müssen jedenfalls Lese-Zugriff für Benutzer, die auf die Website zugreifen, erlauben. Es hat aber Sinn das Auflisten von Verzeichnissen zu verbieten.

Beachten Sie dass die Zugriffsrechte, die mit einer .htaccess Datei angegeben werden PHP Skripte, die am Server ablaufen, nicht betreffen. Das ist der Grund warum ein index.php Skript die Datei GGF/GGF.php laden kann. Aber Benutzer im Internet können sie nicht mit einer URL wie http://yourserver/GGF/GGF.php oder etwa Ähnlichem öffnen.

5.4.1     Vorbereiten des Framework-Verzeichnisses

Das Verzeichnis /htdocs/GGF enthält das GGF Framework (Siehe Abschnitt “Downloads” zum Herunterladen). Aus Sicherheitsgründen darf dieses Verzeichnis nicht vom Internet aus zugänglich sein. Wir können die Zugriffsrechte mit der .htacess Datei einstellen.

deny from all


Datei: /htdocs/GGF/.htaccess

Im Verzeichnis gibt es nur eine Datei, die an unsere Anwendung angepasst werden muss. Die Datei /htdocs/GGF/GGFSetup.php wird dazu verwendet das Framework einzurichten.

 

/**

 * settings for Contacts application

 *

 * @todo

 * @package GGF

 * @version 4.0

 * @since 1.0

 * @author Gerald Zincke, Austria

 * @copyright 2005, 2011 Gerald Zincke

 */

ini_set("session.use_trans_sid", 0); // Do not add PHP Session ID automatically to relative links anhängen (this does not work for frames)

ini_set("session.use_cookies", 0);    // do not use local cookies

//ini_set("session.save_path","/home/vhosts/contacts.freetzi.com/mySessions"); // on a shared server for security reasons it makes sense redirect your session data to your private data area

                                                      // you need to supply the absolute server path to one of your directories

ini_set("session.use_only_cookies", 0);  // allow submission of Session ID via URL

 

 

 

$traceLevel = 2;   // 0: no error logging, 1: errors, 2: information

$largeListThreshold = 50; // if a rowset is larger than this, the browserwindow will show scroll buttons to allow a page-wise stepping through the row-set

                         // in a meaningful configuration this should always be bigger than $smallListPageSize

$smallListPageSize = 17; // this number is used by GGFControlSortDialog for the height of its listboxes

                         // and for GGFControlBrowserWindow for the initial page size (page-wise scrolling; more than $largeListTreshold lines to display)

                         // and if cookies are not supported

                  // for rowsets larger than $largeListTreshold this is the number of lines in a page. In a meaningful configuration this

                         // number of lines should fit on a 1024x768 screen, in a maxcimized browser window (without showing an elevator-scrollbar)

                         // be sure to leave room for button bars in the browser and large start- lines

$maxColumnSize = 132;    // maximum number of characters shown in a column of a GGFListArea

$HMItemHeight = 1.6;  // height of menu menu item. Used fror 2nd level flyout menus in CSS and GGFControls

 

// application specific values

$appPath = "CTS/";            // relative path to application classes

$APPIconsPath = "CTSIcons/"; // relative path to application specific icons, shapes and graphics

$http_type = "http";       // may also be set to "https", if web server supports that

$historyTable = "GGFHistory";    // this is the name of a db-table will be used to write history (see GGFDatabase)

$dbhost = "localhost";        // See GGFDatabase. May be of the form www.server.domain:port

$dbuser = "root";          // See GGFDatabase, you should change this for production

$dbpw = "";             // See GGFDatabase, you should change this for production

$dbname = "Contacts";         // See GGFDatabase

$appname ="Contacts";         // application name, used in window title etc.

$appversion = "1.0";       // application version, used in GGFAboutDialog

$mainwindow = "CTSMainwindow.php";

$goodbyefile = "GGFGoodbye.php";

$invalidContextFile = "GGFInvalidContext.php";

$webmasterMail = "info@ReinHTML.eu"; // this is used for feedback mails in the system tray

 

// application specific variables

$accountlimit = 250;

$maxOnlineFiles = 100;

$googleAnalyticsCode ='

';

$smtp = "email.aon.at";    // smtp server to send e-mails

 

?>


Skript: /htdocs/GGF/GGFSetup.php

Der Text in Fettdruck muss an die Anwendung angepasst werden:

·         appPath – der Pfad zu den Anwendungs-spezifischen Dateien; relativ vom Basisverzeichnis

·         APPIconsPath – ein Pfad relativ zum Basisverzeichnis zu einem Verzeichnis für Anwendungs-spezifische Icons. Dieser wird für die Contacts Anwendung nicht gebraucht.

·         dbhost – der Server, der die Datenbank hostet. Wenn die Datenbank auf der gleichen Maschine ist, wie der Web-Server muss das „localhost“ sein. 

·         dbuser, dbpw – Stellen Sie sicher dass Sie einen gültigen Datenbank-User (“theContactsApp” im obigen Beispiel) und ein Datenbank-Passwort verwenden, das sich vom Standard-User “root” unterscheidet.  Nicht vergessen: Der standard-User root, wie er bei den meisten Default-Installationen von MySQL angelegt wird, muss auch immer mit einem Passwort abgesichert werden, wenn eine MySQL Datenbank vom Internet aus zugänglich ist.

·         mainwindow – der Name des Skripts das das Hauptfenster implementiert

·         webmasterMail – das wird per Default für Benutzer-Support angeboten

5.4.2     Einrichten des Framework Icon-Verzeichnisses

Das Verzeichnis /htdocs/GGFIcons muss die Icon-Dateien, wie sie vom GGF Framework zur Verfügung gestellt werden, enthalten. Hier gibt es sonst nichts zu tun.

Stellen Sie sicher, dass dieses Verzeichnis vom Internet aus zugänglich ist, weil auf die Icon-Dateien vom Web-Browser des Benutzers aus zugegriffen wird.

5.4.3     Das Basis-Verzeichnis

Zuerst brauchen wir den Code für den Eintrittspunkt der Anwendung:

<?PHP

/** 

* Contacts main startup-file 

*  

* @package CTS 

* @version 1.0 

* @since 1.0 

* @author Gerald Zincke, Austria 

* @copyright 2012 Gerald Zincke 

*/

$GGFPath = "GGF/";      // relative path to framework classes

require $GGFPath."GGF.php";

$mainWindow = new CTSMainWindow(windowContext(0,"CTSMainWindow")->contextID);

$mainWindow->open();

?>

Skript: /htdocs/index.php

Das Skript wird als index.php oder als contacts.php im Dokumenten Verzeichnis des Web-Servers (normalerweise /htdocs in typischen Apache Web-Server Installationen) . Wir nehmen an, dass das das Basisverzeichnis für die Contacts Anwendung werden soll.

Im Basisverzeichnis der Contacts Anwendung werden einige weitere Dateien benötigt:

·         GGFFormats.css – Das CSS Stylesheet. Das ist Teil des GGF Frameworks und kann wie es ist verwendet werden.

·         GGFDispatch.php – Der Request-Dispatcher. Dieser ist auch Teil des GGF Frameworks und kann so verwendet werden, wie er ist.

·         GGFGoodbye.php – Das Skript das nach dem Schließen eines Fensters ausgeführt wird. Dieses ist auch Teil des GGF Frameworks und kann so verwendet werden, wie es ist. Aber es hat Sinn es ein wenig zu ändern.

·         GGFInvalidContext.php – Das Skript das nach einem Session Timeout ausgeführt wird. Dieses ist auch Teil des GGF Frameworks und kann so verwendet werden, wie es ist. Aber es hat Sinn es ein wenig zu ändern.

Sie können das Skript GGFGoodbye.php wie folgt anpassen:

<html>

   <head>

     <title>Closed Window</title>

   </head>

   <body onload="javascript:self.close()">

     <br>You can close this browser window/tab now.

   </body>

</html>

 

Skript: /htdocs/GGFGoodbye.php

Sie können das Skript GGFInvalidContext.php wie folgt anpassen:

<html>

   <head>

     <title>Invalid Context</title>

     <meta http-equiv="refresh" content="10; URL=index.php">

   </head>

   <body>

   The context of this window has expired or has become invalid.

   Please select <a href="index.php" target="_top">contacts application</a> to logon again.

   </body>

</html>

 

Skript: /htdocs/GGFInvalidContext.php

Der Rest des Codes und einige zusätzliche Dateien werden auf drei Unterverzeichnisse verteilt.

·         /htdocs/GGF – die Framework Dateien

·         /htdocs/GGFIcons – einige GIF und JPG Dateien, die vom Framework gebraucht werden

·         /htdocs/CTS – die Anwendungs-spezifischen Dateien

Aus Sicherheitsgründen dürfen die Subverzeichnisse /CTS und /GGF  nicht aus dem Internet erreichbar sein. Das ist besonders für das Verzeichnis /htdocs/GGF wichtig weil es Setup-Information (siehe unten) enthält.  Für Apache Web-Server werden Zugriffsrechte definiert in dem man eine .htaccess Datei in jedes dieser Verzeichnisse gibt. Einige Web-Hoster bieten alternativ auch interaktive Benutzeroberflächen an, um die Zugriffsrechte zu spezifizieren.

Die Verzeichnisse /htdocs/GGFIcons – und  /htdocs – müssen jedenfalls Lese-Zugriff für Benutzer, die auf die Website zugreifen, erlauben. Es hat aber Sinn das Auflisten von Verzeichnissen zu verbieten.

Beachten Sie dass die Zugriffsrechte, die mit einer .htaccess Datei angegeben werden PHP Skripte, die am Server ablaufen, nicht betreffen. Das ist der Grund warum ein index.php Skript die Datei GGF/GGF.php laden kann. Aber Benutzer im Internet können sie nicht mit einer URL wie http://yourserver/GGF/GGF.php oder etwa Ähnlichem öffnen.

5.4.4     Einrichten des Anwendungsverzeichnisses

Das Anwendungsverzeichnis enthält ein Skript für jedes Fenster und ein Skript um das Anwendungsmodell zu definieren. Aus Sicherheitsgründen darf das Anwendungsverzeichnis nicht aus dem Internet erreichbar sein. Wir können die Zugriffsrechte mit der .htacess Datei einstellen.

deny from all


Datei: /htdocs/CTS/.htaccess

5.4.4.1       Die Definition des Anwendungsmodells

Das Anwendungsmodell beschreibt die Strukturen der Datenobjekte, die vom Anwendungscode verwendet werden. Auf den ersten Blick sieht es vielleicht kompliziert aus, eine solche Beschreibung zu erzeugen. Aber es bringt einen großen Vorteil: Es ermöglicht dem Framework den gesamten SQL Code zu generieren!

Sie brauchen also kein einziges SELECT, INSERT oder UPDATE Statement mit ihren WHERE und ORDER BY Klauseln schreiben und nebenbei generiert das GGF Framework die BEGIN TRANSACTION, COMMIT und ROLLBACK Statements auch noch. Genau dort wo sie gebraucht werden um eine transaktionssichere Anwendung zu bauen.

<?PHP

/**

 * Class describing the ERmodel of the Contacts application

 *

 * It describes entitytypes "user", "company" and "person",

 * a relationshop type "Company employs Person" and an expanded

 * entitytype "Person_expanded" . It inherits the definition of

 * the entitytype "query".

 *

 * @package CTS

 * @version 1.0

 * @since 1.0

 * @author Gerald Zincke, Austria

 * @copyright 2012 Gerald Zincke

 */

class ContactsModel extends GGFERModel {

   protected function initialize() {

     global $ERModel;

     parent::initialize();

    

     /* create the model for the user table   */

     $user = new GGFEntityType("user");

     $user->addPrimaryKey(new GGFTextAttribute("userid"));

     $user->add(new GGFTextAttribute("userid"));

     $user->add(new GGFTextAttribute("password_enc"));

     $user->add(new GGFTextAttribute("lastname"));

     $user->add(new GGFTextAttribute("firstname","title/first name"));

     $user->add(new GGFTextAttribute("preferences"));

     $user->add(new GGFTextAttribute("email"));

     $user->add(new GGFTextAttribute("LastChange"));

     $user->setDefaultAttributeNames(array('userid','firstname','lastname','last_change'));

     $this->add($user);

    

     /* create the model for the company table*/

     $company = new GGFEntityType("Company");

     $company->addPrimaryKey(new GGFNumAttribute("Company_ID"));

     $company->add(new GGFNumAttribute("Company_ID"));

     $company->add(new GGFTextAttribute("CompanyName"));

     $company->add(new GGFTextAttribute("Street"));

     $company->add(new GGFTextAttribute("City"));

     $company->add(new GGFTextAttribute("ZIPCode"));

     $company->add(new GGFTextAttribute("Country"));

     $company->add(new GGFTextAttribute("LastChange"));

     $company->setDefaultAttributeNames(array('Company_ID','CompanyName','City','Country'));

     $this->add($company);

    

     /* create the model for the person table*/

     $person = new GGFEntityType("Person");

     $person->addPrimaryKey(new GGFNumAttribute("Person_ID"));

     $person->add(new GGFNumAttribute("Person_ID"));

     $person->add(new GGFTextAttribute("FirstName"));

     $person->add(new GGFTextAttribute("LastName"));

     $person->add(new GGFTextAttribute("Title"));

     $person->add(new GGFNumAttribute("Company_ID"));

     $person->add(new GGFTextAttribute("Department"));

     $person->add(new GGFTextAttribute("Position"));

     $person->add(new GGFTextAttribute("Phone"));

     $person->add(new GGFTextAttribute("Mobile"));

     $person->add(new GGFTextAttribute("EMail"));

     $person->add(new GGFTextAttribute("LastChange"));

     $person->setDefaultAttributeNames(

        array('Person_ID','FirstName','LastName','Phone','EMail')

     );  

     $this->add($person);

    

     /* create relationshiptype "Company employs Person"  */

     $company_person = new GGFRelationshipType(

           "Company employs Person",

           $company,array("Company_ID"),

           array(1,1,0,999999999),

           $person,array("Company_ID"));

     $this->addRel($company_person);

    

     /* create expanded entitytype "Person_expanded"  */

     $personExpanded = $person->createExpandedType();

     $personExpanded->

     getAttributeNamed('Company_ID.CompanyName')->setExternalName('Employer');

     $personExpanded->getAttributeNamed('Person.LastName')->setExternalName('Last Name');

     $personExpanded->

     setDefaultAttributeNames(

        array('Person.LastName', 'Person.FirstName', 'Person.Title', 'Person.EMail',

        'Company_ID.CompanyName')

     );

     $this->add($personExpanded);

   }

}

?>

 

Skript: /htdocs/CTS/contactsModel.php

Für jede Datenbanktabelle wird eine Modelldefinition gebraucht. Zusätzlich beschreibt das Skript die Beziehung zwischen Person und Firma. Darüber hinaus wird ein “expanded entity type” erzeugt. Das löst zwei typische Probleme für eine Datenbankanwendung:

·         Normalisierte Tabellen enthalten meist Fremdschlüssel. Aber in vielen Fällen will der Benutzer nicht diese (internen) Schlüsselwerte sehen, sondern vielmehr ist er an den Attributwerten des referenzierten Objekts interessiert. Zum Beispiel in einer Personenliste möchte der Benutzer nicht die interne Company_ID des Arbeitgebers einer Person sehen sondern den Firmennamen, die Firmenadresse etc.

·         Wenn ein Benutzer eine Liste von Datenbanksätzen filtern möchte ist in vielen Fällen ein Filtern nach den Spalten der Tabelle selbst nicht ausreichend. Zum Beispiel: „Ich werde nächste Woche nach Cupertino fahren. Gib mir alle meine Kontaktpersonen, die für eine Firma mit Standort Cupertino arbeiten“. Sie können so eine Liste nicht erzeugen indem Sie nach den Attributen der Personentabelle filtern. Die Filterkriterien müssen auch Werte von referenzierten Objekten enthalten. In unserer Contacts Anwendung kann der Benutzer Personen suchen indem er Eigenschaften ihres Arbeitgebers angibt. 

Mit der Modellbeschreibung kann das GGF Framework diese Anforderungen erfüllen. Es kann aus normalisierten Tabellen Listen erzeugen, die benutzerfreundliche Werte anstelle von internen Schlüsseln oder Codes enthalten. Und es stellt eine Filterfunktion zur Verfügung, die WHERE Klauseln generiert um mit Eigenschaften referenzierter Entitäten zu filtern. 

Damit wir die Dinge einfach halten gibt es nur eine Datei für jedes Fenster und jeden Dialog. Der ReinHTML Dialog Designer auf http://www.ReinHTML.eu  wird verwendet um Fenster oder Dialog-Layouts für die Anwendung zu generieren. Der vom Tool generierte Code benützt das GGF Framework, insbesondere die Basisklassen für Fenster.

5.4.4.2     Implementierung des Hauptfensters: CTSMainWindow.php

Wir leiten das Hauptfenster der Anwendung von der GGFControlMainWindow Klasse im  GGF Framework ab und erzeugen das Layout mit dem ReinHTML Dialog Designer. Das spart uns eine Menge Arbeit. Der Dialog Designer generiert das Gerüst der Klasse und im Besonderen die initWindow Funktion.

<?PHP

   /**

    * Main window class for the Contacts application

    * (goto http://www.ReinHTML.eu/RD to update the panel layout)

    *

    * @package CTS

    * @version 1.0

    * @since 1.0

    * @author Gerald Zincke, Austria

    * @copyright 2012 Gerald Zincke

    */

   class CTSMainWindow extends GGFControlMainWindow {

  

     function __construct($contextID) {

        parent::__construct($contextID);

     }

     function __destruct() {

    parent::__destruct();

     }

       

     /**

     * RD generated function initWindow

     * create the layout definition of the window

     */

     protected function initWindow($mc) {

     //*H1--generated code, do not touch. Use RD to update ---

     $this->CSSURL='GGFFormats.css';

     $this->windowTitle='Contacts';

     $cont0 = new GGFControlPane("_main","",""); $cont0->initP(array( "name" => "_main")); $cont0->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->setWindow($this);

     $this->pane = $cont0;

        $cont1 = new GGFHMenu("Mainmenu","",""); $cont1->initP(array( "name" => "Mainmenu")); $cont1->resetP(array( "value","tiptext","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont0->add($cont1);

           $cont2 = new GGFHMenu("DatabaseMenu","Database",""); $cont2->initP(array( "value" => "Database","name" => "DatabaseMenu")); $cont2->resetP(array( "tiptext","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont2);

              $cont3 = new GGFHMItem("Login","Login",""); $cont3->initP(array( "validator" => "isLoggedOff","callback" => "eventLogin","name" => "Login","value" => "Login","isDisabled" => "","readonly" => "","mandatory" => "")); $cont3->resetP(array( "target","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont2->add($cont3);

              $cont4 = new GGFHMItem("Logoff","Logoff",""); $cont4->initP(array( "validator" => "isLoggedOn","callback" => "eventLogoff","name" => "Logoff","value" => "Logoff","isDisabled" => "","readonly" => "","mandatory" => "")); $cont4->resetP(array( "target","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont2->add($cont4);

              $cont5 = new GGFHMItem("Close","Close",""); $cont5->initP(array( "callback" => "eventClose","name" => "Close","value" => "Close","isDisabled" => "","readonly" => "","mandatory" => "")); $cont5->resetP(array( "target","validator","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont2->add($cont5);

           $cont6 = new GGFEndPane("end:DatabaseMenu","",""); $cont6->initP(array( "name" => "end:DatabaseMenu")); $cont6->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont2->add($cont6);

           $cont7 = new GGFHMenu("Browse","Browse",""); $cont7->initP(array( "value" => "Browse","name" => "Browse")); $cont7->resetP(array( "tiptext","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont7);

              $cont8 = new GGFHMItem("Contacts","Contacts",""); $cont8->initP(array( "target" => "_blank","validator" => "isLoggedOn","callback" => "eventContacts","name" => "Contacts","value" => "Contacts","isDisabled" => "","readonly" => "","mandatory" => "")); $cont8->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont7->add($cont8);

              $cont9 = new GGFHMItem("Companies","Companies",""); $cont9->initP(array( "target" => "_blank","validator" => "isLoggedOn","callback" => "eventCompanies","name" => "Companies","value" => "Companies","isDisabled" => "","readonly" => "","mandatory" => "")); $cont9->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont7->add($cont9);

           $cont10 = new GGFEndPane("end:Browse","",""); $cont10->initP(array( "name" => "end:Browse")); $cont10->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont7->add($cont10);

           $cont11 = new GGFHMItem("About","About",""); $cont11->initP(array( "callback" => "eventAbout","name" => "About","value" => "About","tiptext" => "open the about dialog","isDisabled" => "","readonly" => "","mandatory" => "")); $cont11->resetP(array( "target","validator","size","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont11);

        $cont12 = new GGFEndPane("end:Mainmenu","",""); $cont12->initP(array( "name" => "end:Mainmenu")); $cont12->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont1->add($cont12);

        $cont0->newP();

        $cont14 = new GGFStaticfield("s1","Welcome to the Contacts Application !",""); $cont14->initP(array( "name" => "s1","value" => "Welcome to the Contacts Application !","isDisabled" => "","readonly" => "","mandatory" => "","fontFamily" => "Arial,sans-serif","fontSize" => "larger","fontStyle" =>  array(bold => "", italic => "", underline => ""), "lineHeight" => "")); $cont14->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background"));

        $cont0->add($cont14);

        $cont0->newP();

        $cont16 = new GGFNewLinePane("Notebox","",""); $cont16->initP(array( "name" => "Notebox","style" => "border:1px;")); $cont16->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont0->add($cont16);

           $cont17 = new GGFStaticfield("","Note:",""); $cont17->initP(array( "value" => "Note:","extraHTML" => "font-weight:bolder;","isDisabled" => "","readonly" => "","mandatory" => "")); $cont17->resetP(array( "name","size","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont16->add($cont17);

           $cont18 = new GGFStaticfield("","This is a demo installation. Data are restored to the initial state at every login.",""); $cont18->initP(array( "value" => "This is a demo installation. Data are restored to the initial state at every login.","isDisabled" => "","readonly" => "","mandatory" => "")); $cont18->resetP(array( "name","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont16->add($cont18);

        $cont19 = new GGFEndPane("end:Notebox","",""); $cont19->initP(array( "name" => "end:Notebox")); $cont19->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont16->add($cont19);

        $cont0->newP();

     $cont21 = new GGFEndPane("end:_main","",""); $cont21->initP(array( "name" => "end:_main")); $cont21->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->add($cont21);

     //****RD generated code, do not touch**********

     ….

     //*H2--end of generated code, do not touch code above. Use RD to update ----

   } // end initWindow    

     //*H3--RD-generated event handlers below. do not remove this line.         

             

   /**

   * RD generated function

   * Validator for control MenuItem(Login, , , ) 

   *

   */

   public function isLoggedOff() {

      return parent::isLoggedOff();   

   }

             

   /**

   * RD generated function

   * Validator for control MenuItem(Logoff, , , ) 

   *

   */

   public function isLoggedOn() {

      return parent::isLoggedOn();    

   }         

             

   /**

   * RD generated function

   * event handler for control MenuItem(Login, , , ) 

   *

   */

   protected function eventLogin() {

      // open the login window

     $myContext = &windowContext($this->myContextID,0);

     $myContext->openDialogOn("CTSLoginDialog", array(0,array('userid'=>"",'password'=>"")));

     exit;

   } 

  

             

   /**

   * RD generated function

   * event handler for control MenuItem(Logoff, , , ) 

   *

   */

   protected function eventLogoff() {

      $_SESSION["userid"] = "";

     unset($this->myModel["percentFull"]);

     $mc = &windowContext($this->myContextID,0);

     $mc->model = $this->myModel;

     $mc->save();

     $this->initWindow($mc);

   }

             

   /**

   * RD generated function

   * event handler for control MenuItem(Browse Contacts, , , ) 

   *

   */

   protected function eventContacts() {

      $myContext = &windowContext($this->myContextID, 0);

      $myContext->openWindowOn("CTSContactBrowserWindow", array());

   }

             

   /**

   * RD generated function

   * event handler for control MenuItem(Browse Companies, , , ) 

   *

   */

   protected function eventCompanies() {

      $myContext = &windowContext($this->myContextID, 0);

     $myContext->openWindowOn("CTSCompanyBrowserWindow", array());

   }

       

   /**

   * RD generated function

   * event handler for control MenuItem(About, , , ) 

   *

   */

   protected function eventAbout() {

     // display the about dialog

     $myContext = &windowContext($this->myContextID,0);

     $myContext->openDialogOn("GGFControlAboutDialog", 0);

     exit;

   }

             

   /**

   * RD generated function

   * event handler for control MenuItem(Close, , , ) 

   *

   */

   protected function eventClose() {

      parent::eventClose();

   }

  

} //end RD generated class CTSMainWindow

?>


file: /htdocs/CTS/CTSMainWindow.php

Wie Sie sehen können wird eine Menge Funktionalität (wie isLoggedOn, isLoggedOff, eventClose) von der Basisklasse geerbt.

5.4.4.3     Implementierung des Login-Dialogs: CTSLoginDialog.php

Der Login-Dialog bietet Eingabefelder für User-ID und Passwort. Er wird vom Hauptfenster durch Klick auf den Menüeintrag Login geöffnet, was die Funktion eventLogin in der CTSMainWindow Klasse aufruft. Die Klasse CTSLoginDialog ist von der Klasse GGFControlDialog im  GGF Framework abgeleitet und das Layout wurde wieder mit dem ReinHTML Dialog Designer erzeugt. Der Dialog Designer generiert das Gerüst der Klasse und im Besonderen die initWindow  Funktion.

<?PHP

   /**

    * RD generated window class CTSLoginDialog

    * (goto http://www.ReinHTML.eu/RD to update the panel layout)

    *

    * @package CTS

    * @version 1.0

    * @since 1.0

    * @author Gerald Zincke, Austria

    * @copyright 2012 Gerald Zincke

    */

   class CTSLoginDialog extends GGFControlDialog {

  

     function __construct($contextID) {

        parent::__construct($contextID);

        $this->ETypeName = 'user';

        $this->extraAttributesToUpdate = array();

     }

     function __destruct() {

        parent::__destruct();

     }

       

     /**

     * RD generated function initWindow

     * create the layout definition of the window

     */

     protected function initWindow($mc) {

    

     //*H1--generated code, do not touch. Use RD to update ---

     $this->CSSURL='GGFFormats.css';

     $this->windowTitle='Login to Contacts Application';

     $cont0 = new GGFControlPane("_main","",""); $cont0->initP(array( "name" => "_main")); $cont0->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","isFrameset","style","Foreground","Background"));

     $cont0->setWindow($this);

     $this->pane = $cont0;

        $cont1 = new GGFDialogFormPane("_mainform","",""); $cont1->initP(array( "name" => "_mainform")); $cont1->resetP(array( "demohtml","demohtml2","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","isFrameset","style","Foreground","Background"));

        $cont0->add($cont1);

           $cont1->newLine();

           $cont3 = new GGFStaticfield("s0","Please enter your User-ID and Password. Then click OK.to login.",""); $cont3->initP(array( "name" => "s0","value" => "Please enter your User-ID and Password. Then click OK.to login.","isDisabled" => "","readonly" => "","mandatory" => "")); $cont3->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background"));

           $cont1->add($cont3);

           $cont1->newP();

           $cont1->newP();

           $cont6 = new GGFStaticfield("s2","User-ID",""); $cont6->initP(array( "name" => "s2","value" => "User-ID","isDisabled" => "","readonly" => "","mandatory" => "")); $cont6->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background"));

           $cont1->newTable($cont6, '', '', '');

           $cont7 = new GGFEntryfield("userid","",""); $cont7->initP(array( "maxlength" => "32","name" => "userid","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont7->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background"));

           $cont1->newCell($cont7, '', '', '');

           $cont7->setAutofocus();

           $cont8 = new GGFStaticfield("s3","Password",""); $cont8->initP(array( "name" => "s3","value" => "Password","isDisabled" => "","readonly" => "","mandatory" => "")); $cont8->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background"));

           $cont1->newRow($cont8, '', '', '');

           $cont9 = new GGFPasswordEntryfield("password","",""); $cont9->initP(array( "maxlength" => "32","name" => "password","tiptext" => "Enter your Password here. ","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont9->resetP(array( "value","size","extraHTML","myContainer","tabindex","Foreground","Background"));

           $cont1->newCell($cont9, '', '', '');

           $cont10 = new GGFStaticfield("placeholder","",""); $cont10->initP(array( "name" => "placeholder","isDisabled" => "","readonly" => "","mandatory" => "")); $cont10->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont10, '', '', '');

           $cont11 = new GGFStaticfield("s5","* input mandatory",""); $cont11->initP(array( "name" => "s5","value" => "* input mandatory","isDisabled" => "","readonly" => "","mandatory" => "")); $cont11->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont11, '', '', '');

           $cont1->newP();

           $cont1->newP();

           $cont14 = new GGFControlPane("_buttonarea","",""); $cont14->initP(array( "name" => "_buttonarea")); $cont14->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","isFrameset","style","Foreground","Background"));

           $cont1->newTable($cont14, '', '', '');

              $cont15 = new GGFPushbutton("_OK","OK","60"); $cont15->initP(array( "callback" => "eventOK","validator" => "dataPlausible","name" => "_OK","value" => "OK","size" => "60","extraHTML" => "width:80px; overflow:hidden; ","tiptext" => "click to login","isDisabled" => "","readonly" => "","mandatory" => "")); $cont15->resetP(array( "myContainer","tabindex","Foreground","Background"));

              $cont14->add($cont15);

              $cont14->newSpace();

              $cont17 = new GGFPushbutton("_Cancel","Cancel","60"); $cont17->initP(array( "callback" => "eventClose","name" => "_Cancel","value" => "Cancel","size" => "60","tiptext" => "no login; close dialog","isDisabled" => "","readonly" => "","mandatory" => "")); $cont17->resetP(array( "validator","extraHTML","myContainer","tabindex","Foreground","Background"));

              $cont14->add($cont17);

           $cont18 = new GGFEndPane("end:_buttonarea","",""); $cont18->initP(array( "name" => "end:_buttonarea")); $cont18->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background"));

           $cont14->add($cont18);

        $cont19 = new GGFEndPane("end:_mainform","",""); $cont19->initP(array( "name" => "end:_mainform")); $cont19->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background"));

        $cont1->add($cont19);

     $cont20 = new GGFEndPane("end:_main","",""); $cont20->initP(array( "name" => "end:_main")); $cont20->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background"));

     $cont0->add($cont20);

     //****RD generated code, do not touch**********

    

     //*H2--end of generated code, do not touch code above. Use RD to update ---

   } // end initWindow    

     //*H3--RD-generated event handlers below. do not remove this line.         

   /**

   * RD generated function

   * Validator for control Pushbutton(_OK, OK, 60, width:80px; overflow:hidden; ) 

   *

  * @todo replace RD-generated template-code by desired functionality

   *

  */

   public function dataPlausible() {

      // ---- begin template code ----

     return TRUE;

     // ---- end template code ------

    

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_OK, OK, 60) 

   *

   * This checks the userid and password against the user table.

   * It will also restore the contents of the company and person table

   * (this is for security in the demo installation).

   *

   */

   protected function eventOK() {

      // handles OK event

     global $db;

     global $errorStack;

     if ($this->validateData()) {

          

        $userid = mysql_real_escape_string(strtolower($_POST["userid"]));

        $select_statement = "SELECT * FROM user WHERE userid='".$userid."' ";

        $result = $db->execSQL($select_statement);

        $err = mysql_errno();

       

        if (!$err==0) {

           $errmsg = $this->appname.": Error checking UserID, Password ";

           $errorStack->pushUsrMsg(1,$errmsg);

        } else {     

           $num_rows = mysql_num_rows($result);

           if($num_rows >0 ) {          

              $row = mysql_fetch_array($result);

              if (

                strcmp($row["password_enc"],

                substr(md5(mysql_real_escape_string($_POST["password"])),0,32)

              )==0) {

                $this->ok = TRUE;

                $_SESSION["userid"] = $userid;

                $this->eventClose();            

              } else {

                $errorStack->pushUsrMsg(1,"UserID/Password is wrong. ");

                $this->ok = FALSE;

              }

           } else {

              $errorStack->pushUsrMsg(1,"Login failed. ");

           }

        }

     }

   }         

   /**

   * RD generated function

   * event handler for control Pushbutton(_Cancel, Cancel, 60) 

   *

   */

   protected function eventClose() {

      // handles Cancel event

     parent::eventClose();

   }            

} //end RD generated class LoginDialog

?>


file: /htdocs/CTS/CTSLoginDialog.php

Wenn Der Benutzer den OK Button anklickt, wird die eventOK Funktion aufgerufen. Die Implementierung benutzt das Datenbankinterface des GGF Framework um User-ID und Passwort zu prüfen. Wenn etwas schiefgeht werden Fehlermeldungen einfach auf den Error-Stack gelegt, der vom Framework bereitgestellt wird. Das Framework sorgt dann dafür dass die Fehlermeldungen in einer Message-Box angezeigt werden.

5.4.4.4     CTSCompanyBrowserWindow

Das Firmen Browser-Window zeigt die Liste der gespeicherten Firmen. Wenn es in der Datenbank mehr Einträge gibt als auf den Bildschirm passen, kann der Benutzer durch die Liste blättern indem er die Buttons am rechten Rand verwendet. Wie Sie sehen können gibt es hier wirklich nicht viel zu tun. Fast alles wird von der Klasse GGFControlBrowserWindow im GGF Framework geerbt.

<?PHP

/**

 * Company browsing window

 *

 * @package CTS

 * @version 1.0

 * @since 1.0

 * @author Gerald Zincke, Austria

 * @copyright 2012 Gerald Zincke

 */

class CTSCompanyBrowserWindow extends GGFControlBrowserWindow {

 

   function __construct($contextID) {

     parent::__construct($contextID);

     $this->ETypeName = "Company";    // initialize the name of the main model object class of the window

     $this->enableCopy = true; // enables copy feature in browser window

   }

 

   /**

    * define event dialog-classes

    */

   protected function initModel($mc) {

     // prepare the model object

     parent::initModel($mc);

  

     $this->myModel["_updateDialogClass"] = "CTSCompanyUpdateDialog";

     $this->myModel["_insertDialogClass"] = "CTSCompanyInsertDialog";

     $this->myModel["_copyDialogClass"]   = "CTSCompanyInsertDialog";

     $this->myModel["_updateAfterInsert"] = FALSE;

     $mc->model = $this->myModel;

     $mc->save();

   }

 

}

?>


file: /htdocs/CTS/CTSCompanyBrowserWindow.php

Wir müssen dem Objekt den Namen des Entitätstyps (“Company”) bekanntgeben dass im Fenster angezeigt werden soll. Die Struktur des Entitätstyps “Company” wurde bereits im Modell unserer Anwendung definiert (Siehe Kapitel "Das logische Datenmodell" und "Die Definition des Anwendungsmodells ".) Wir müssen dem Modellobjekt mitteilen welche Sub-Dialog Klassen für Standard Aktionen wie aktualisieren, einfügen oder kopieren eines Firmendatensatzes verwendet werden sollen.

5.4.4.5     CTSCompanyInsertDialog

In vielen Fällen ist es nötig Initialisierungsdaten anzugeben wenn man einen neuen Datensatz anlegt. Es kann NOT NULL Felder geben, die immer ausgefüllt sein müssen oder es kann einfach nicht sinnvoll einen Datensatz mit vielen leeren Feldern abzuspeichern. Dafür braucht die Architektur einen Insert Dialog. Er öffnet sich wenn Sie auf  „Insert“ im Menü im Firmen Browser-Fenster klicken.

Die Klasse CTSCompanyInsertDialog wird von der Klasse GGFControlInsertDialog im GGF Framework abgeleitet und das Layout wird wieder mit dem ReinHTML Dialog Designer erzeugt.  Der Dialog Designer erzeugt das Gerüst der Klasse und generiert insbesondere die initWindow Funktion.

<?PHP

   /**

    * RD generated window class CTSCompanyInsertDialog

    * (goto http://www.ReinHTML.eu/RD to update the panel layout)

    *

    * @package CTS

    * @version 1.0

    * @since 1.0

    * @author Gerald Zincke, Austria

    * @copyright 2012 Gerald Zincke

    */

   class CTSCompanyInsertDialog extends GGFControlInsertDialog {

  

   function __construct($contextID) {

    parent::__construct($contextID);

    $this->ETypeName = 'Company';

    $this->extraAttributesToUpdate = array("CompanyName", "Street", "ZIPCode", "City", "Country" );

   }

   function __destruct() {

    parent::__destruct();

   }

       

   /**

   * RD generated function initWindow

   * create the layout definition of the window

   */

   protected function initWindow($mc) {

    

     //*H1--generated code, do not touch. Use RD to update ---

     $this->CSSURL='GGFFormats.css';

     $this->windowTitle='Update Company';

     $cont0 = new GGFControlPane("_main","",""); $cont0->initP(array( "name" => "_main")); $cont0->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->setWindow($this);

     $this->pane = $cont0;

        $cont1 = new GGFDialogFormPane("_mainform","",""); $cont1->initP(array( "name" => "_mainform")); $cont1->resetP(array( "demohtml","demohtml2","formScriptName","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont0->add($cont1);

           $cont1->newLine();

           $cont1->newP();

           $cont4 = new GGFStaticfield("s1","Enter data for new Company and click OK.",""); $cont4->initP(array( "name" => "s1","value" => "Enter data for new Company and click OK.","isDisabled" => "","readonly" => "","mandatory" => "")); $cont4->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont4, '', '', '');

           $cont1->newP();

           $cont6 = new GGFStaticfield("s3","Company Name",""); $cont6->initP(array( "name" => "s3","value" => "Company Name","isDisabled" => "","readonly" => "","mandatory" => "")); $cont6->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont6, '', '', '');

           $cont7 = new GGFEntryfield("CompanyName","","32"); $cont7->initP(array( "maxlength" => "64","inputClass" => "1","name" => "CompanyName","size" => "32","tiptext" => "The Name of the company","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont7->resetP(array( "autofocus","value","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont7, '', '', '');

           $cont7->setAutofocus();

           $cont8 = new GGFStaticfield("s4","Street",""); $cont8->initP(array( "name" => "s4","value" => "Street","isDisabled" => "","readonly" => "","mandatory" => "")); $cont8->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont8, '', '', '');

           $cont9 = new GGFEntryfield("Street","","32"); $cont9->initP(array( "maxlength" => "32","inputClass" => "1","name" => "Street","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont9->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont9, '', '', '');

           $cont10 = new GGFStaticfield("s5","Zipcode",""); $cont10->initP(array( "name" => "s5","value" => "Zipcode","isDisabled" => "","readonly" => "","mandatory" => "")); $cont10->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

            $cont1->newRow($cont10, '', '', '');

           $cont11 = new GGFEntryfield("ZIPCode","","8"); $cont11->initP(array( "maxlength" => "16","inputClass" => "5","name" => "ZIPCode","size" => "8","tiptext" => "postal code for address","isDisabled" => "","readonly" => "","mandatory" => "")); $cont11->resetP(array( "autofocus","value","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont11, '', '', '');

           $cont12 = new GGFStaticfield("s6","City",""); $cont12->initP(array( "name" => "s6","value" => "City","isDisabled" => "","readonly" => "","mandatory" => "")); $cont12->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont12);

           $cont13 = new GGFEntryfield("City","","32"); $cont13->initP(array( "maxlength" => "32","inputClass" => "1","name" => "City","size" => "32","tiptext" => "name of city in address","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont13->resetP(array( "autofocus","value","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont13);

           $cont14 = new GGFStaticfield("s7","Country",""); $cont14->initP(array( "name" => "s7","value" => "Country","isDisabled" => "","readonly" => "","mandatory" => "")); $cont14->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont14, '', '', '');

           $cont15 = new GGFDropDown("Country","Austria|Germany|Switzerland|UK|USA",""); $cont15->initP(array( "selections" => "Austria","returnIndex" => "","name" => "Country","value" => "Austria|Germany|Switzerland|UK|USA","isDisabled" => "","readonly" => "","mandatory" => "")); $cont15->resetP(array( "vsize","keys","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont15, '', '', '');

           $cont16 = new GGFStaticfield("placeholder","",""); $cont16->initP(array( "name" => "placeholder","isDisabled" => "","readonly" => "","mandatory" => "")); $cont16->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont16, '', '', '');

           $cont17 = new GGFStaticfield("s8","* input is mandatory",""); $cont17->initP(array( "name" => "s8","value" => "* input is mandatory","isDisabled" => "","readonly" => "","mandatory" => "")); $cont17->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont17, '', '', '');

           $cont1->newP();

           $cont19 = new GGFControlPane("_buttonarea","",""); $cont19->initP(array( "name" => "_buttonarea")); $cont19->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont19, '', '', '');

              $cont20 = new GGFPushbutton("_OK","OK","60"); $cont20->initP(array( "callback" => "eventOK","validator" => "dataPlausible","name" => "_OK","value" => "OK","size" => "60","extraHTML" => " width:80px; overflow:hidden ","tiptext" => "save data in database")); $cont20->resetP(array( "myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont19->add($cont20);

              $cont21 = new GGFPushbutton("_Cancel","Cancel","60"); $cont21->initP(array( "callback" => "eventClose","name" => "_Cancel","value" => "Cancel","size" => "60","tiptext" => "do not save data in database")); $cont21->resetP(array( "validator","extraHTML","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont19->add($cont21);

           $cont22 = new GGFEndPane("end:_buttonarea","",""); $cont22->initP(array( "name" => "end:_buttonarea")); $cont22->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont19->add($cont22);

        $cont23 = new GGFEndPane("end:_mainform","",""); $cont23->initP(array( "name" => "end:_mainform")); $cont23->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont1->add($cont23);

     $cont24 = new GGFEndPane("end:_main","",""); $cont24->initP(array( "name" => "end:_main")); $cont24->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->add($cont24);

     //****RD generated code, do not touch**********

    

     //*H2--end of generated code, do not touch code above. Use RD to update ---

   } // end initWindow    

     //*H3--RD-generated event handlers below. do not remove this line.         

   /**

   * RD generated function

   * Validator for control Pushbutton(_OK, OK, 60,  width:80px; overflow:hidden ) 

   *

  * @todo replace RD-generated template-code by desired functionality

  * @return boolean

   *

  */

   public function dataPlausible() {

      // ---- begin template code ----

     return TRUE;

     // ---- end template code ------

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_OK, OK, 60) 

   *

  */

   protected function eventOK() {

      parent::eventOK();

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_Cancel, Cancel, 60) 

   *

   */

   protected function eventClose() {

     parent::eventClose();

   }

             

  

} //end RD generated class CTSCompanyInsertDialog

?>


file: /htdocs/CTS/CTSCompanyInsertDialog.php

Hier gibt es wirklich nicht viel zu tun. Die Funktionen eventClose und eventOK werden von der Basisklasse geerbt.

5.4.4.6     CTSCompanyUpdateDialog

Dieser Dialog erlaubt es uns Firmendatensätze anzusehen und zu aktualisieren. Er wird geöffnet in dem man im Firmen Browser-Fenster auf einen Firmendatensatz klickt.

Die Klasse CTSCompanyUpdateDialog ist von der Klasse GGFControlUpdateDialog im  GGF Framework abgeleitet und das Layout wurde mit dem ReinHTML Dialog Designer erzeugt.  Der Dialog Designer generiert das Gerüst der Klasse und besonders die initWindow Funktion.

Für diesen Dialog war etwas manuelle Programmierung notwendig um die Listbox mit den Daten der Personen zu füllen, die für die ausgewählte Firma arbeiten. Das wird mit der fillControls Funktion gemacht. Die Standard-Eingabefelder des Formulars werden mit Aufruf der geerbten Funktion gefüllt. Dann wird ein „Filter“ Objekt für Personen definiert, weil wir in der Listbox nur diejenigen Personen haben wollen, die zur angezeigten Firma gehören. Dann können wir die geerbte Funktion fillSelectionControlER verwenden um die Listbox mit den Daten zu füllen, die vom Filterobjekt ausgewählt werden.

<?PHP

   /**

    * RD generated window class CTSCompanyUpdateDialog

    * (goto http://www.ReinHTML.eu/RD to update the panel layout)

    *

    * @package CTS

    * @version 1.0

    * @since 1.0

    * @author Gerald Zincke, Austria

    * @copyright 2012 Gerald Zincke

    */

   class CTSCompanyUpdateDialog extends GGFControlUpdateDialog {

  

   function __construct($contextID) {

    parent::__construct($contextID);

    $this->ETypeName = 'Company';

    $this->extraAttributesToUpdate = array();

   }

   function __destruct() {

    parent::__destruct();

   }

       

   protected function fillControls($mc) {

     parent::fillControls($mc);

    

     $filter = new GGFERFilter($this->ERModel->entityTypeNamed('Person'));

     $filter->add(new GGFSqlFilterClause('AND', 'Company_ID', '=', $this->myModel[1]['Company_ID'])); 

 

     $this->fillSelectionControlER("employees", "Person",

     array('FirstName', 'LastName', 'Department', 'Position'),

     array(12,16,4,4),$filter);

   }

  

/**

   * RD generated function initWindow

   * create the layout definition of the window

   */

   protected function initWindow($mc) {

    

     //*H1--generated code, do not touch. Use RD to update ---

     $this->CSSURL='GGFFormats.css';

     $this->windowTitle='Update Company';

     $cont0 = new GGFControlPane("_main","",""); $cont0->initP(array( "name" => "_main")); $cont0->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->setWindow($this);

     $this->pane = $cont0;

        $cont1 = new GGFDialogFormPane("_mainform","",""); $cont1->initP(array( "name" => "_mainform")); $cont1->resetP(array( "demohtml","demohtml2","formScriptName","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont0->add($cont1);

           $cont1->newLine();

           $cont1->newP();

           $cont4 = new GGFStaticfield("s1","Enter data and click OK.",""); $cont4->initP(array( "name" => "s1","value" => "Enter data and click OK.","isDisabled" => "","readonly" => "","mandatory" => "")); $cont4->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont4, '', '', '');

           $cont1->newP();

           $cont6 = new GGFStaticfield("s2","ID",""); $cont6->initP(array( "name" => "s2","value" => "ID","isDisabled" => "","readonly" => "","mandatory" => "")); $cont6->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont6, '', '', '');

           $cont7 = new GGFEntryfield("Company_ID","123","9"); $cont7->initP(array( "maxlength" => "10","inputClass" => "5","name" => "Company_ID","value" => "123","size" => "9","isDisabled" => "1","readonly" => "1","mandatory" => "","Background" => "#E6E6E6")); $cont7->resetP(array( "autofocus","extraHTML","tiptext","myContainer","tabindex","Foreground","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont7, '', '', '');

           $cont8 = new GGFStaticfield("s3","Company Name",""); $cont8->initP(array( "name" => "s3","value" => "Company Name","isDisabled" => "","readonly" => "","mandatory" => "")); $cont8->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont8, '', '', '');

           $cont9 = new GGFEntryfield("CompanyName","","32"); $cont9->initP(array( "maxlength" => "64","inputClass" => "1","name" => "CompanyName","size" => "32","tiptext" => "The Name of the company","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont9->resetP(array( "autofocus","value","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont9, '', '', '');

           $cont9->setAutofocus();

           $cont10 = new GGFStaticfield("s4","Street",""); $cont10->initP(array( "name" => "s4","value" => "Street","isDisabled" => "","readonly" => "","mandatory" => "")); $cont10->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont10, '', '', '');

           $cont11 = new GGFEntryfield("Street","","32"); $cont11->initP(array( "maxlength" => "32","inputClass" => "1","name" => "Street","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont11->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont11, '', '', '');

           $cont12 = new GGFStaticfield("s5","Zipcode",""); $cont12->initP(array( "name" => "s5","value" => "Zipcode","isDisabled" => "","readonly" => "","mandatory" => "")); $cont12->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont12, '', '', '');

           $cont13 = new GGFEntryfield("ZIPCode","","8"); $cont13->initP(array( "maxlength" => "16","inputClass" => "5","name" => "ZIPCode","size" => "8","tiptext" => "postal code for address","isDisabled" => "","readonly" => "","mandatory" => "")); $cont13->resetP(array( "autofocus","value","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont13, '', '', '');

           $cont14 = new GGFStaticfield("s6","City",""); $cont14->initP(array( "name" => "s6","value" => "City","isDisabled" => "","readonly" => "","mandatory" => "")); $cont14->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont14);

           $cont15 = new GGFEntryfield("City","","32"); $cont15->initP(array( "maxlength" => "32","inputClass" => "1","name" => "City","size" => "32","tiptext" => "name of city in address","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont15->resetP(array( "autofocus","value","extraHTML","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont15);

           $cont16 = new GGFStaticfield("s7","Country",""); $cont16->initP(array( "name" => "s7","value" => "Country","isDisabled" => "","readonly" => "","mandatory" => "")); $cont16->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont16, '', '', '');

           $cont17 = new GGFDropDown("Country","Austria|Germany|Switzerland|UK|USA",""); $cont17->initP(array( "selections" => "Austria","returnIndex" => "","name" => "Country","value" => "Austria|Germany|Switzerland|UK|USA","isDisabled" => "","readonly" => "","mandatory" => "")); $cont17->resetP(array( "vsize","keys","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont17, '', '', '');

           $cont18 = new GGFStaticfield("placeholder","",""); $cont18->initP(array( "name" => "placeholder","isDisabled" => "","readonly" => "","mandatory" => "")); $cont18->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont18, '', '', '');

           $cont19 = new GGFStaticfield("s8","* input is mandatory",""); $cont19->initP(array( "name" => "s8","value" => "* input is mandatory","isDisabled" => "","readonly" => "","mandatory" => "")); $cont19->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont19, '', '', '');

           $cont20 = new GGFStaticfield("s7","Employees",""); $cont20->initP(array( "name" => "s7","value" => "Employees","isDisabled" => "","readonly" => "","mandatory" => "")); $cont20->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont20, '', '', '');

           $cont1->newLine();

           $cont22 = new GGFPseudoPushbutton("browse"," open Browser ",""); $cont22->initP(array( "callback" => "eventContacts","target" => "_blank","name" => "browse","value" => " open Browser ","isDisabled" => "","readonly" => "","mandatory" => "")); $cont22->resetP(array( "validator","url","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->add($cont22);

           $cont1->newSpace();

           $cont24 = new GGFListbox("employees","","360"); $cont24->initP(array( "vsize" => "5","returnIndex" => "","name" => "employees","size" => "360","extraHTML" => "overflow:scroll;","isDisabled" => "","readonly" => "1","mandatory" => "")); $cont24->resetP(array( "selections","keys","value","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont24, '', '', '');

           $cont1->newP();

           $cont26 = new GGFControlPane("_buttonarea","",""); $cont26->initP(array( "name" => "_buttonarea")); $cont26->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont26, '', '', '');

              $cont27 = new GGFPushbutton("_OK","OK","60"); $cont27->initP(array( "callback" => "eventOK","validator" => "dataPlausible","name" => "_OK","value" => "OK","size" => "60","extraHTML" => " width:80px; overflow:hidden ","tiptext" => "save data in database")); $cont27->resetP(array( "myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont26->add($cont27);

              $cont28 = new GGFPushbutton("_Cancel","Cancel","60"); $cont28->initP(array( "callback" => "eventClose","name" => "_Cancel","value" => "Cancel","size" => "60","tiptext" => "do not save data in database")); $cont28->resetP(array( "validator","extraHTML","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont26->add($cont28);

           $cont29 = new GGFEndPane("end:_buttonarea","",""); $cont29->initP(array( "name" => "end:_buttonarea")); $cont29->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont26->add($cont29);

        $cont30 = new GGFEndPane("end:_mainform","",""); $cont30->initP(array( "name" => "end:_mainform")); $cont30->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont1->add($cont30);

     $cont31 = new GGFEndPane("end:_main","",""); $cont31->initP(array( "name" => "end:_main")); $cont31->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->add($cont31);

     //****RD generated code, do not touch**********

  

   //*H2--end of generated code, do not touch code above. Use RD to update ---

   } // end initWindow    

     //*H3--RD-generated event handlers below. do not remove this line.         

   /**

   * RD generated function

   * Validator for control Pushbutton(_OK, OK, 60,  width:80px; overflow:hidden ) 

   *

   * @return boolean

   *

   */

   public function dataPlausible() {

      // ---- begin template code ----

     return TRUE;

     // ---- end template code ------

   }

  

   /**

   * RD generated function

   * event handler for control PseudoPushbutton(browse,open Browser) 

   *

   */

   protected function eventContacts() {

      $et = $this->ERModel->entityTypeNamed($this->ETypeName);

      $f = $et->foreignKeyFilter($this->myModel[1],"Company employs Person");

 

      $myContext = &windowContext($this->myContextID, 0);

     $myContext->openWindowOn("CTSContactBrowserWindow", array("_where" => $f));

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_OK, OK, 60) 

   *

   */

   protected function eventOK() {

      // handles OK event

     $this->ok = $this->validateData();

     $this->saveModifications();

     if ($this->ok) {

        $this->eventClose();

     }

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_Cancel, Cancel, 60) 

   *

   */

   protected function eventClose() {

      // handles Cancel event

     parent::eventClose();

   }

             

 

} //end RD generated class CTSCompanyUpdateDialog

?>


file: /htdocs/CTS/CTSCompanyUpdateDialog.php

Der Eventhandler für den Cancel Button ist geerbt. Siehe die eventClose Funktion. Sie ruft die geerbte Funktion saveModifications auf. Diese Funktion erzeugt die gesamte  SQL für uns. Sie wird übrigens nicht nur das Update durchführen sondern kümmert sich auch um die Vermeidung von Double-Update Problemen.

Die eventContacts Funktion öffnet ein Browser-Fenster für Personen. Dieses Browser-Fenster soll nicht jede Person in der Datenbank anzeigten sondern nur die Personen, die für die Firma im Firmen Update-Dialog arbeiten. Dafür erzeugen wir ein Filterobjekt in dem wir unseren Entitätstyp um einen foreignKeyFilter via der "Company employs Person"-Beziehung fragen. Der Filter wird beim Aufruf von openWindowOn als Teil des Modellobjekts mitgegeben.

5.4.4.7     CTSContactsBrowserWindow

Das Contacts Browser-Fenster zeigt eine Tabelle von Personen in der Datenbank. Es handelt sich nicht wirklich um die Personentabelle sondern eine sogenannte erweiterte Entität (expanded entity;"Person_expanded") die nicht nur Personendaten sondern auch Daten des Arbeitgebers der Person enthält. Der Abschnitt “Die Definition des Anwendungsmodells” erklärt, was erweiterte Entitäten sind.

<?PHP

/**

 * Contacts browsing window

 *

 * @package CTS

 * @version 1.0

 * @since 1.0

 * @author Gerald Zincke, Austria

 * @copyright 2012 Gerald Zincke

 */

class CTSContactBrowserWindow extends GGFControlBrowserWindow {

 

   function __construct($contextID) {

       parent::__construct($contextID);

       $this->ETypeName = "Person_expanded"; // initialize the name of the main model object class of the window

       $this->enableCopy = true;   // enables copy feature in browser window

   }

 

   /**

    * define event dialog-classes

    */

   protected function initModel($mc) {

        // prepare the model object

     global $appname;

     parent::initModel($mc);

  

     $this->myModel["_updateDialogClass"] = "CTSContactUpdateDialog";

     $this->myModel["_insertDialogClass"] = "CTSContactInsertDialog";

     $this->myModel["_copyDialogClass"]   = "";

     $this->myModel["_updateAfterInsert"] = true;

     $this->windowTitle = "Contacts - Browser ";

     $mc->model = $this->myModel;

     $mc->save();

   }

 

}

?>


file: /htdocs/CTS/CTSContactBrowserWindow.php

Hier gibt es wieder nicht viel für das Kontakte Browser-Fenster zu implementieren. In der initModel Funktion geben wir an, welche Sub-Dialog Klassen verwendet werden.

5.4.4.8     CTSContactInsertDialog

Der Insert-Dialog öffnet sich, wenn der Benutzer auf das Insert Menü im Kontakte Browser-Fenster klickt. Die Klasse  CTSContactInsertDialog ist von der Klasse GGFControlInsertDialog im  GGF Framework abgeleitet und das  Layout wurde wieder mit dem ReinHTML Dialog Designer definiert.  Der Dialog Designer generiert das Gerüst der Klasse und im Besonderen die initWindow Funktion.

<?PHP

   /**

    * RD generated window class CTSContactInsertDialog

    * (goto http://www.ReinHTML.eu/RD to update the panel layout)

    *

    * @todo comment, cleanup, version, test

    * @package CTS

    * @version 1.0

    * @since 1.0

    * @author Gerald Zincke, Austria

    * @copyright 2012 Gerald Zincke

    */

   class CTSContactInsertDialog extends GGFControlInsertDialog {

  

   function __construct($contextID) {

    parent::__construct($contextID);

    $this->ETypeName = 'Person';

    $this->extraAttributesToUpdate =

    array("FirstName","LastName","Title","Company_ID",

    "Department","Position","Phone","Mobile","EMail");

   }

   function __destruct() {

    parent::__destruct();

   }

  

   protected function fillControls($mc) {

     parent::fillControls($mc);

  

     $this->fillSelectionControlER("Company_ID", "Company", array('CompanyName',

     'Street', 'City', 'Country'), array(24,16,16,16));

   }

  

   /**

   * RD generated function initWindow

   * create the layout definition of the window

   */

   protected function initWindow($mc) {

    

     //*H1--generated code, do not touch. Use RD to update ---

     $this->CSSURL='GGFFormats.css';

     $this->windowTitle='Create new Contact';

     $cont0 = new GGFControlPane("_main","",""); $cont0->initP(array( "name" => "_main")); $cont0->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->setWindow($this);

     $this->pane = $cont0;

        $cont1 = new GGFDialogFormPane("_mainform","",""); $cont1->initP(array( "name" => "_mainform")); $cont1->resetP(array( "demohtml","demohtml2","formScriptName","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont0->add($cont1);

           $cont1->newLine();

           $cont1->newP();

           $cont4 = new GGFStaticfield("s1","Enter data for new contact and click OK.",""); $cont4->initP(array( "name" => "s1","value" => "Enter data for new contact and click OK.","isDisabled" => "","readonly" => "","mandatory" => "")); $cont4->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont4, '', '', '');

           $cont1->newP();

           $cont6 = new GGFStaticfield("s2","First Name",""); $cont6->initP(array( "name" => "s2","value" => "First Name","isDisabled" => "","readonly" => "","mandatory" => "")); $cont6->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont6, '', '', '');

           $cont7 = new GGFEntryfield("FirstName","","32"); $cont7->initP(array( "maxlength" => "32","inputClass" => "1","name" => "FirstName","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont7->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont7, '', '', '');

           $cont7->setAutofocus();

           $cont8 = new GGFStaticfield("s3","Last Name",""); $cont8->initP(array( "name" => "s3","value" => "Last Name","isDisabled" => "","readonly" => "","mandatory" => "")); $cont8->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont8, '', '', '');

           $cont9 = new GGFEntryfield("LastName","","32"); $cont9->initP(array( "maxlength" => "32","inputClass" => "1","name" => "LastName","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont9->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont9, '', '', '');

           $cont10 = new GGFStaticfield("s4","Title",""); $cont10->initP(array( "name" => "s4","value" => "Title","isDisabled" => "","readonly" => "","mandatory" => "")); $cont10->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont10, '', '', '');

           $cont11 = new GGFEntryfield("Title","","16"); $cont11->initP(array( "maxlength" => "64","inputClass" => "1","name" => "Title","size" => "16","isDisabled" => "","readonly" => "","mandatory" => "")); $cont11->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont11, '', '', '');

           $cont12 = new GGFStaticfield("s5","Position",""); $cont12->initP(array( "name" => "s5","value" => "Position","isDisabled" => "","readonly" => "","mandatory" => "")); $cont12->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont12, '', '', '');

           $cont13 = new GGFEntryfield("Position","","32"); $cont13->initP(array( "maxlength" => "32","inputClass" => "1","name" => "Position","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont13->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont13, '', '', '');

           $cont14 = new GGFStaticfield("s6","Department",""); $cont14->initP(array( "name" => "s6","value" => "Department","isDisabled" => "","readonly" => "","mandatory" => "")); $cont14->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont14, '', '', '');

           $cont15 = new GGFEntryfield("Department","","32"); $cont15->initP(array( "maxlength" => "64","inputClass" => "1","name" => "Department","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont15->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont15, '', '', '');

           $cont16 = new GGFStaticfield("s6","Mobile",""); $cont16->initP(array( "name" => "s6","value" => "Mobile","isDisabled" => "","readonly" => "","mandatory" => "")); $cont16->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont16, '', '', '');

           $cont17 = new GGFEntryfield("Mobile","","16"); $cont17->initP(array( "maxlength" => "32","inputClass" => "5","name" => "Mobile","size" => "16","isDisabled" => "","readonly" => "","mandatory" => "")); $cont17->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont17, '', '', '');

           $cont18 = new GGFStaticfield("s6","Phone",""); $cont18->initP(array( "name" => "s6","value" => "Phone","isDisabled" => "","readonly" => "","mandatory" => "")); $cont18->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont18, '', '', '');

           $cont19 = new GGFEntryfield("Phone","","16"); $cont19->initP(array( "maxlength" => "32","inputClass" => "5","name" => "Phone","size" => "16","isDisabled" => "","readonly" => "","mandatory" => "")); $cont19->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont19, '', '', '');

           $cont20 = new GGFStaticfield("s6","E-Mail",""); $cont20->initP(array( "name" => "s6","value" => "E-Mail","isDisabled" => "","readonly" => "","mandatory" => "")); $cont20->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont20, '', '', '');

           $cont21 = new GGFEntryfield("EMail","","32"); $cont21->initP(array( "maxlength" => "64","inputClass" => "14","name" => "EMail","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont21->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont21, '', '', '');

           $cont22 = new GGFStaticfield("placeholder","",""); $cont22->initP(array( "name" => "placeholder","isDisabled" => "","readonly" => "","mandatory" => "")); $cont22->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont22, '', '', '');

           $cont23 = new GGFStaticfield("s8","* input is mandatory",""); $cont23->initP(array( "name" => "s8","value" => "* input is mandatory","isDisabled" => "","readonly" => "","mandatory" => "")); $cont23->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont23, '', '', '');

           $cont24 = new GGFFieldsetPane("Employer","",""); $cont24->initP(array( "name" => "Employer")); $cont24->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont24, '', '', '');

              $cont25 = new GGFStaticfield("s6","Company",""); $cont25->initP(array( "name" => "s6","value" => "Company","isDisabled" => "","readonly" => "","mandatory" => "")); $cont25->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont24->newRow($cont25, '', '', '');

              $cont24->newSpace();

              $cont27 = new GGFDropDown("Company_ID","",""); $cont27->initP(array( "returnIndex" => "","name" => "Company_ID","isDisabled" => "","readonly" => "","mandatory" => "")); $cont27->resetP(array( "vsize","selections","keys","value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont24->newCell($cont27, '', '', '');

           $cont28 = new GGFEndPane("end:Employer","",""); $cont28->initP(array( "name" => "end:Employer")); $cont28->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont24->add($cont28);

           $cont1->newP();

           $cont30 = new GGFControlPane("_buttonarea","",""); $cont30->initP(array( "name" => "_buttonarea")); $cont30->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont30, '', '', '');

              $cont31 = new GGFPushbutton("_OK","OK","60"); $cont31->initP(array( "callback" => "eventOK","validator" => "dataPlausible","name" => "_OK","value" => "OK","size" => "60","extraHTML" => " width:80px; overflow:hidden ","tiptext" => "save data in database")); $cont31->resetP(array( "myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont30->add($cont31);

              $cont32 = new GGFPushbutton("_Cancel","Cancel","60"); $cont32->initP(array( "callback" => "eventClose","name" => "_Cancel","value" => "Cancel","size" => "60","tiptext" => "do not save data in database")); $cont32->resetP(array( "validator","extraHTML","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont30->add($cont32);

           $cont33 = new GGFEndPane("end:_buttonarea","",""); $cont33->initP(array( "name" => "end:_buttonarea")); $cont33->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont30->add($cont33);

        $cont34 = new GGFEndPane("end:_mainform","",""); $cont34->initP(array( "name" => "end:_mainform")); $cont34->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont1->add($cont34);

     $cont35 = new GGFEndPane("end:_main","",""); $cont35->initP(array( "name" => "end:_main")); $cont35->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->add($cont35);

     //****RD generated code, do not touch**********

    

     //*H2--end of generated code, do not touch code above. Use RD to update ---

   } // end initWindow    

     //*H3--RD-generated event handlers below. do not remove this line.         

   /**

   * RD generated function

   * event handler for control Pushbutton(_OK, OK, 60,  width:80px; overflow:hidden ) 

   *

  */

   protected function eventOK() {

      parent::eventOK();

   }

             

   /**

   * RD generated function

   * Validator for control Pushbutton(_OK, OK, 60,  width:80px; overflow:hidden ) 

   *

  * @todo replace RD-generated template-code by desired functionality

  * @return boolean

   *

  */

   public function dataPlausible() {

      // ---- begin template code ----

     return TRUE;

     // ---- end template code ------

    

   }

             

   protected function validateData() {

     return true;

   }

               

   /**

   * RD generated function

   * event handler for control Pushbutton(_Cancel, Cancel, 60) 

   *

  */

   protected function eventClose() {

     parent::eventClose();

   }

  

} //end RD generated class CTSContactInsertDialog

?>


file: /htdocs/CTS/CTSContactInsertDialog.php

Der Dialog erlaubt die Eingabe der Daten einer neuen Kontaktperson.  Und durch die Auswahl einer Firma vom  Drop-Down am unteren Rand können Sie eine Beziehung zum Arbeitgeber – einer Firma - herstellen.

5.4.4.9     CTSContactUpdateDialog

Der Kontakte Update-Dialog erscheint wenn der Benutzer auf einen Eintrag im Kontakte Browser-Fenster klickt. Die Klasse CTSContactUpdateDialog ist abgeleitet von der Klasse GGFControlUpdateDialog im  GGF Framework und das Layout wurde wieder mit dem ReinHTML Dialog Designer erzeugt.  Der Dialog Designer generiert das Gerüst der Klasse und insbesondere die  initWindow Funktion.

<?PHP

   /**

    * RD generated window class CTSContactUpdateDialog

    * (goto http://www.ReinHTML.eu/RD to update the panel layout)

    *

    * @package CTS

    * @version 1.0

    * @since 1.0

    * @author Gerald Zincke, Austria

    * @copyright 2012 Gerald Zincke

    */

   class CTSContactUpdateDialog extends GGFControlUpdateDialog {

  

   function __construct($contextID) {

    parent::__construct($contextID);

    $this->ETypeName = 'Person';

    $this->extraAttributesToUpdate = array();

   }

   function __destruct() {

    parent::__destruct();

   }

  

   protected function fillControls($mc) {

     parent::fillControls($mc);

  

     $this->fillSelectionControlER("Company_ID", "Company", array('CompanyName',

     'Street', 'City', 'Country'), array(24,16,16,16));

   }

  

   /**

   * RD generated function initWindow

   * create the layout definition of the window

   */

   protected function initWindow($mc) {

    

     //*H1--generated code, do not touch. Use RD to update ---

     $this->CSSURL='GGFFormats.css';

     $this->windowTitle='Update Contact';

     $cont0 = new GGFControlPane("_main","",""); $cont0->initP(array( "name" => "_main")); $cont0->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->setWindow($this);

     $this->pane = $cont0;

        $cont1 = new GGFDialogFormPane("_mainform","",""); $cont1->initP(array( "name" => "_mainform")); $cont1->resetP(array( "demohtml","demohtml2","formScriptName","myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont0->add($cont1);

           $cont1->newLine();

           $cont1->newP();

           $cont4 = new GGFStaticfield("s1","Enter data and click OK.",""); $cont4->initP(array( "name" => "s1","value" => "Enter data and click OK.","isDisabled" => "","readonly" => "","mandatory" => "")); $cont4->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont4, '', '', '');

           $cont1->newP();

           $cont6 = new GGFStaticfield("s2","ID",""); $cont6->initP(array( "name" => "s2","value" => "ID","isDisabled" => "","readonly" => "","mandatory" => "")); $cont6->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont6, '', '', '');

           $cont7 = new GGFEntryfield("Person_ID","","8"); $cont7->initP(array( "maxlength" => "12","inputClass" => "5","name" => "Person_ID","size" => "8","isDisabled" => "","readonly" => "1","mandatory" => "","Background" => "#D8D8D8")); $cont7->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont7, '', '', '');

           $cont8 = new GGFStaticfield("s2","First Name",""); $cont8->initP(array( "name" => "s2","value" => "First Name","isDisabled" => "","readonly" => "","mandatory" => "")); $cont8->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont8, '', '', '');

           $cont9 = new GGFEntryfield("FirstName","","32"); $cont9->initP(array( "maxlength" => "32","inputClass" => "1","name" => "FirstName","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont9->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont9, '', '', '');

           $cont10 = new GGFStaticfield("s3","Last Name",""); $cont10->initP(array( "name" => "s3","value" => "Last Name","isDisabled" => "","readonly" => "","mandatory" => "")); $cont10->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont10, '', '', '');

           $cont11 = new GGFEntryfield("LastName","","32"); $cont11->initP(array( "maxlength" => "32","inputClass" => "1","name" => "LastName","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "1")); $cont11->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont11, '', '', '');

           $cont12 = new GGFStaticfield("s4","Title",""); $cont12->initP(array( "name" => "s4","value" => "Title","isDisabled" => "","readonly" => "","mandatory" => "")); $cont12->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont12, '', '', '');

           $cont13 = new GGFEntryfield("Title","","16"); $cont13->initP(array( "maxlength" => "64","inputClass" => "1","name" => "Title","size" => "16","isDisabled" => "","readonly" => "","mandatory" => "")); $cont13->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont13, '', '', '');

           $cont14 = new GGFStaticfield("s5","Position",""); $cont14->initP(array( "name" => "s5","value" => "Position","isDisabled" => "","readonly" => "","mandatory" => "")); $cont14->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont14, '', '', '');

           $cont15 = new GGFEntryfield("Position","","32"); $cont15->initP(array( "maxlength" => "32","inputClass" => "1","name" => "Position","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont15->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont15, '', '', '');

           $cont16 = new GGFStaticfield("s6","Department",""); $cont16->initP(array( "name" => "s6","value" => "Department","isDisabled" => "","readonly" => "","mandatory" => "")); $cont16->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont16, '', '', '');

           $cont17 = new GGFEntryfield("Department","","32"); $cont17->initP(array( "maxlength" => "64","inputClass" => "1","name" => "Department","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont17->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont17, '', '', '');

           $cont18 = new GGFStaticfield("s6","Mobile",""); $cont18->initP(array( "name" => "s6","value" => "Mobile","isDisabled" => "","readonly" => "","mandatory" => "")); $cont18->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont18, '', '', '');

           $cont19 = new GGFEntryfield("Mobile","","16"); $cont19->initP(array( "maxlength" => "32","inputClass" => "18","name" => "Mobile","size" => "16","isDisabled" => "","readonly" => "","mandatory" => "")); $cont19->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont19, '', '', '');

           $cont20 = new GGFStaticfield("s6","Phone",""); $cont20->initP(array( "name" => "s6","value" => "Phone","isDisabled" => "","readonly" => "","mandatory" => "")); $cont20->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont20, '', '', '');

           $cont21 = new GGFEntryfield("Phone","","16"); $cont21->initP(array( "maxlength" => "32","inputClass" => "18","name" => "Phone","size" => "16","isDisabled" => "","readonly" => "","mandatory" => "")); $cont21->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont21, '', '', '');

           $cont22 = new GGFStaticfield("s6","E-Mail",""); $cont22->initP(array( "name" => "s6","value" => "E-Mail","isDisabled" => "","readonly" => "","mandatory" => "")); $cont22->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont22, '', '', '');

           $cont23 = new GGFEntryfield("EMail","","32"); $cont23->initP(array( "maxlength" => "64","inputClass" => "14","name" => "EMail","size" => "32","isDisabled" => "","readonly" => "","mandatory" => "")); $cont23->resetP(array( "autofocus","value","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont23, '', '', '');

           $cont24 = new GGFStaticfield("placeholder","",""); $cont24->initP(array( "name" => "placeholder","isDisabled" => "","readonly" => "","mandatory" => "")); $cont24->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newRow($cont24, '', '', '');

           $cont25 = new GGFStaticfield("s8","* input is mandatory",""); $cont25->initP(array( "name" => "s8","value" => "* input is mandatory","isDisabled" => "","readonly" => "","mandatory" => "")); $cont25->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newCell($cont25, '', '', '');

           $cont26 = new GGFFieldsetPane("Employer","",""); $cont26->initP(array( "name" => "Employer")); $cont26->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont26, '', '', '');

              $cont27 = new GGFStaticfield("s6","Company",""); $cont27->initP(array( "name" => "s6","value" => "Company","isDisabled" => "","readonly" => "","mandatory" => "")); $cont27->resetP(array( "size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont26->newRow($cont27, '', '', '');

              $cont26->newSpace();

              $cont29 = new GGFDropDown("Company_ID","",""); $cont29->initP(array( "returnIndex" => "","name" => "Company_ID","isDisabled" => "","readonly" => "","mandatory" => "")); $cont29->resetP(array( "vsize","selections","keys","value","size","extraHTML","tiptext","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont26->newCell($cont29, '', '', '');

           $cont30 = new GGFEndPane("end:Employer","",""); $cont30->initP(array( "name" => "end:Employer")); $cont30->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont26->add($cont30);

           $cont1->newP();

           $cont32 = new GGFControlPane("_buttonarea","",""); $cont32->initP(array( "name" => "_buttonarea")); $cont32->resetP(array( "myContainer","controls","disabled","inTable","tabindex","bodyExtraHTML","frameID","paneType","isFrameset","style","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont1->newTable($cont32, '', '', '');

              $cont33 = new GGFPushbutton("_OK","OK","60"); $cont33->initP(array( "callback" => "eventOK","validator" => "dataPlausible","name" => "_OK","value" => "OK","size" => "60","extraHTML" => " width:80px; overflow:hidden ","tiptext" => "save data in database")); $cont33->resetP(array( "myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont32->add($cont33);

              $cont32->newSpace();

              $cont35 = new GGFImagePushbutton("getVCF","get VCard",""); $cont35->initP(array( "imageURL" => "GGFIcons/GGFmail.jpg","callback" => "eventVCFDownload","name" => "getVCF","value" => "get VCard","extraHTML" => "vertical-align:middle;","tiptext" => "download a VCF file to import into your local phonebook or contact list","isDisabled" => "","readonly" => "","mandatory" => "")); $cont35->resetP(array( "valuex","valuey","validator","size","myContainer","tabindex","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont32->add($cont35);

              $cont32->newSpace();

              $cont37 = new GGFPushbutton("_Cancel","Cancel","60"); $cont37->initP(array( "callback" => "eventClose","name" => "_Cancel","value" => "Cancel","size" => "60","tiptext" => "do not save data in database")); $cont37->resetP(array( "validator","extraHTML","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

              $cont32->add($cont37);

           $cont38 = new GGFEndPane("end:_buttonarea","",""); $cont38->initP(array( "name" => "end:_buttonarea")); $cont38->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

           $cont32->add($cont38);

        $cont39 = new GGFEndPane("end:_mainform","",""); $cont39->initP(array( "name" => "end:_mainform")); $cont39->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

        $cont1->add($cont39);

     $cont40 = new GGFEndPane("end:_main","",""); $cont40->initP(array( "name" => "end:_main")); $cont40->resetP(array( "value","size","extraHTML","tiptext","myContainer","tabindex","isDisabled","readonly","mandatory","Foreground","Background","fontFamily","fontSize","fontStyle","lineHeight"));

     $cont0->add($cont40);

     //****RD generated code, do not touch**********

      

     //*H2--end of generated code, do not touch code above. Use RD to update ---

   } // end initWindow    

     //*H3--RD-generated event handlers below. do not remove this line.         

               

   /**

   * RD generated function

   * Validator for control Pushbutton(_OK, OK, 60,  width:80px; overflow:hidden ) 

   *

   * @todo replace RD-generated template-code by desired functionality

   * @return boolean

   *

   */

   public function dataPlausible() {

      // ---- begin template code ----

     return TRUE;

     // ---- end template code ------

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_OK, OK, 60) 

   *

   */

   protected function eventOK() {

      // handles OK event

     $this->ok = $this->validateData();

     $this->saveModifications();

     if ($this->ok) {

        $this->eventClose();

     }

   }

             

   /**

   * RD generated function

   * event handler for control Pushbutton(_Cancel, Cancel, 60) 

   *

   */

   protected function eventClose() {

      // handles Cancel event

     parent::eventClose();

   }

  

   /**

   * create a VCard for the person and download it

   *

   * @global GGFDatabase $db

   */

   protected function eventVCFDownload(){

     global $db;

     $contact = $this->myModel[1];

    

     $et = $this->ERModel->entityTypeNamed('Person');

     $res = $et->relatedEntitiesVia($contact, "Company employs Person");

       

     if ($db->OK()) {

        $company = $res[0];

        header("Content-disposition: attachment; filename=".$contact["LastName"].'_'.$contact["FirstName"].".vcf");

        header("Content-type: application/octet-stream");

       

        echo sprintf(

"BEGIN:VCARD

VERSION:2.1

N;LANGUAGE=de:%s;%s

FN:%1

ORG:%s;%s

TITLE:%s

TEL;WORK;VOICE:%s

TEL;CELL;VOICE:%s

ADR;WORK;CHARSET=Windows-1252:%s

EMAIL;PREF;INTERNET:%s

REV:%s

END:VCARD",

           $contact["LastName"],$contact["FirstName"],

           $contact["Title"].' '.$contact["FirstName"].' '.$contact["LastName"],

           $company["CompanyName"], $contact["Department"],

           $contact["Title"],

           $contact["Phone"],

           $contact["Mobile"],

           ";;".$company["Street"].";".$company["City"].";;".$company["ZIPCode"].";".$company["Country"],

           $contact["EMail"],

           $contact["Person_ID"]

        );

     }

     exit(0);

  }

} //end RD generated class CTSContactUpdateDialog

?>


file: /htdocs/CTS/CTSContactUpdateDialog.php

Die Eventhandler Funktionen sind ähnlich denen in den Klassen zuvor. Die Funktion eventVCFDownload erzeugt eine VCard-Datei aus den Kontaktdaten und stellt sie zum Download zum Client bereit. Siehe Abschnitt CTSContactUpdateDialog .

6         Installationsprozeduren

Angenommen, sie haben die Contacts Anwendung heruntergeladen. Wie bringen Sie sie zum Laufen? Es gibt verschiedene Möglichkeiten.

·         Führen Sie sie lokal auf Ihrem PC aus

·         Führen Sie sie auf einem Web-Server via Internet aus

Siehe die folgenden Unterkapitel.

6.1      Die Contacts Anwendung auf Ihrem PC

Dieser Abschnitt beschreibt  wie Sie die Contacts Anwendung auf Ihrem PC zum Laufen bringen. Wenn Sie bereits einen Web-Server und eine MYSQL Datenbank Engine lokal laufen haben, überspringen Sie den nächsten Abschnitt und machen Sie im Abschnitt „Die Anwendung am lokalen Web-Server“ weiter .

6.1.1     Web-Server und Datenbank installieren

Der einfachste Weg um einen Web-Server und eine Datenbank zu installieren ist ein vordefiniertes WAMP (oder LAMP für Linux) Package vom Internet zu besorgen. Ich bevorzuge XAMPP. Sie finden es unter http://www.apachefriends.org/en/xampp-windows.html  und http://www.apachefriends.org/en/xampp-windows.html#646  (oder siehe http://www.apachefriends.org/en/xampp-linux.html für Linux).

Verwenden Sie die Installationsanweisungen auf http://www.apachefriends.org/en/xampp-windows.html#522 . Die Installation ist eine Sache von weniger als einer Minute.

Hinweis: Wenn das Starten von XAMPP nicht funktioniert, prüfen Sie, ob Sie schon einen Web-Server auf Ihrer Maschine installiert haben. Einige Programme werden mit eingebautem Web-Server geliefert. In diesem Fall müssen Sie sicherstellen, dass dieser geschlossen wird bevor sie XAMPP starten.

6.1.2     Die Anwendung am lokalen Web-Server einrichten

Ihr Web-Server wird von einem Web-Browser über die URL http://localhost erreichbar sein. Diese zeigt per Default den Inhalt des “Dokumenten”-Verzeichnisses des Web-Servers. In einer XAMPP Installation können Sie dieses Verzeichnis unter \XAMPP\htdocs finden.

Expandieren Sie die heruntergeladenen Anwendungsdateien in dieses Verzeichnis. Es expandiert in die Verzeichnisse

o   htdocs

§  GGF

§  GGFIcons

§  CTS

Siehe Abschnitt “5.4 Code” für weitere Details wie sie die Dateien für die Contacts Web-Anwendung dort abspeichern müssen. Die Web-Anwendung kann dann mit der URL http://localhost/index.php gestartet werden.

In jedem Fall müssen Sie die Datenbank anlagen bevor Sie die Web-Anwendung starten können. Es müssen einige Datenbanktabellen angelegt werden. Dafür können Sie das interaktive PHPmyAdmin Werkzeug verwenden, das mit XAMPP mitgeliefert wird. Es kann via http://localhost/phpmyadmin/ aufgerufen werden. Dort wählen Sie das SQL-Tab aus.

PHPMyAdmin: Das SQL-Tab

Kopieren Sie das SQL Skript contacts_database.SQL von der Download-Datei hierher. Es wird alle benötigten Datenbankstrukturen anlegen.

Ein letzter Schritt ist es die Setup-Datei der Anwendung GGF/GGFSetup.php anzupassen. Suchen Sie die folgenden 4 Zeilen:

$dbhost = "localhost";        // See GGFDatabase. May be of the form www.server.domain:port

$dbuser = "root";          // See GGFDatabase, you should change this for production

$dbpw = "";                // See GGFDatabase, you should change this for production

$dbname = "Contacts";         // See GGFDatabase

Datei GGFSetup.php: Default Parameter für den Datenbankzugriff ersetzen

In den meisten Fällen wird der Datenbank-Server die gleiche Maschine sein wie der Web-Server und die Datenbank-Engine kann via localhost erreicht werden. Aber das hängt von Ihrem Server-Setup ab. Sie müssen den Eintrag für den Datenbankbenutzer dbuser ändern. Aus Sicherheitsgründen sollte dies niemals die MySQL Default Benutzerkennung „root“ sein. Und natürlich sollte er kein leeres Passwort haben.

Sie können SQL benutzen um eine neue Datenbankbenutzerkennung anzulegen.

CREATE USER dbuser IDENTIFIED BY PASSWORD 'password';

Das Passwort der Benutzerkennung root wird geändert mit:

SET PASSWORD FOR root = PASSWORD('some password');

Die Zugriffsrechte für den Datenbankbenutzer werden geändert mit:

GRANT ALL ON contacts.* TO dbuser ;

6.2      Die Anwendung auf einem Web-Server im Internet ausführen

Wenn Sie schon einen Web-Server im Internet haben, überspringen Sie das nächste Kapitel und machen Sie im Abschnitt „Die Anwendung auf den Web-Server“ weiter.

6.2.1     Einen Web Server im Internet einrichten

Es gibt eine Vielzahl von Providern. Die Prozedur wird ein wenig unterschiedlich sein aber so ähnlich wie es unten gezeigt wird. Wenn Sie das Angebot von www.freewebhosting.com nutzen wollen, müssen Sie Folgendes tun:

Beispiel Website einrichten:  Eine Sub-Domain bei einem Gratis Web-Hoster einrichten

Beispiel Web-Site einrichten:  Ihren Account anlegen

 

Beispiel Web-Site einrichten: User IDs und Passworte erhalten

Schließlich haben Sie

·         Einen WWW Server ( in unserem Beispiel http://contacts.freetzi.com ) mit Userid und Passwort für den

·         Zugriff auf die Wartungsanwendung (hier “Account Manager” auf http://freetzi.freewebhostingarea.com ) und Zugriff auf einen

·         FTP Server (in unserem Beispiel FTP://freetzi.com) mit Userid und Passwort.

·         Sowie Zugriff auf eine Datenbankmanagement-Anwendung (in unserem Beispiel  http://freetzi.freewebhostingarea.com/pma/) mit Userid und Passwort die von Accountmanager bereitgestellt werden.

Der FTP Server gibt Ihnen read/write Zugriff um Skripts und andere Dateien auf  Ihren WWW Server hochzuladen.

Die Datenbankmanagement-Anwendung erlaubt es Tabellen, die ihre Anwendung verwendet, anzulegen und zu initialisieren.

6.2.2     Die Anwendung auf den Web-Server bringen

Es hängt von Ihrem Provider ab, wie das Dokumentenverzeichnis auf dem Web-Server erreicht werden kann. In den meisten Fällen geht das via FTP. Für den Dateitransfer dorthin können sie FTP Tools wie Filezilla verwenden. Einige Provider bieten auch Web-basierte Filetransfer-Lösungen. Aber für Windows Benutzer ist es wahrscheinlich am bequemsten den Windows Datei-Explorer zu verwenden.

Geben Sie einfach die URL Ihres FTP Servers (in unserem Beispiel FTP://freetzi.com) in die Adresszeile des Windows Datei-Explorers ein.

FTP Fenster: Zugriff auf den Web-Server mit dem Datei-Explorer

Sie werden um ihre FTP Benutzerkennung und das Passwort für Ihren FTP Server Account (siehe oben) gefragt.

Sobald Sie verbunden sind können Sie Verzeichnisse anlegen und Dateien dorthin kopieren genauso wie Sie es in einem lokalen Verzeichnis machen würden. Wählen Sie zuerst das “Dokumenten”-Wurzelverzeichnis des Web-Servers.

Dann expandieren Sie die heruntergeladenen Anwendungsdateien in dieses Verzeichnis. Kopieren Sie sie also in die Verzeichnisse

o   Documents-root

§  GGF

§  GGFIcons

§  CTS

Hinweis: Vergessen Sie nicht die Zugriffsrechte für die Sub-Verzeichnisse zu setzen. Web-Benutzer dürfen keine read, listing oder gar write Rechte auf den GGF Ordner und den CTS Ordner haben. Das wird durch das Hinaufladen einer .htaccess Datei in den betreffenden Order bewirkt.

deny from all


file: /htdocs/GGF/.htaccess

deny from all


file: /htdocs/CTS/.htaccess

Hinweis: Zugriffsrechte, die Sie über den Eigenschaften-Dialog im Explorer vergeben können, gelten nur für Prozesse die am Server laufen bzw. Benutzer die am Server angemeldet sind.

Siehe Abschnitt “5.4 Code” für weitere Details über die Abspeicherung der Dateien der Contacts Web-Anwendung in den Verzeichnissen.

FTP Fenster: Web-Server mit installierten Dateien

Die Web-Anwendung über die URL Ihres Servers gestartet werden. Zum Beispiel: http://contacts.freetzi.com/index.php .

Jedenfalls müssen Sie die Datenbank anlagen bevor sie Web-Anwendung starten kann. Einige Datenbanktabellen müssen angelegt werden. Die meisten Web-Space Provider bieten das interaktive Werkzeug PHPmyAdmin an. Sehen Sie in der Dokumentation nach, wie Sie es aufrufen können. In unserem Beispiel kann es via http://freetzi.freewebhostingarea.com/pma/ aufgerufen werden.

Datenbank Management mit PHPMyAdmin

Nun könnten Sie das SQL Tab auswählen und das SQL Skript contacts_database.SQL vom Download-Archiv hineinkopieren. Aber viele Web-Hosters schränken ein welche Datenbanknamen verwendet werden können. In diesem Fall können Sie nicht den Datenbanknamen “CONTACTS” verwenden und es ist nicht möglich die ersten drei Kommandos des SQL Skripts auszuführen:

Drop database if exists contacts;

CREATE DATABASE CONTACTS;

USE CONTACTS;

SQL Skript: contacts_database.SQL – die ersten 3 Zeilen

In diesem Fall tun Sie Folgendes. Löschen Sie zuerst die ersten drei Zeilen aus dem SQL Skript.

Dann aktivieren Sie die Datenbank. In unserem Beispiel existiert die Datenbank 407421 bereits und sie ist an der linken Seite des Fensters angeführt. Klicken Sie darauf um sie zu benutzen. Dann aktivieren Sie den SQL Tab.

PHPMyAdmin: Der SQL Tab

Nun können Sie das SQL Skript contacts_database.SQL hier hineinkopieren.

PHPMyAdmin Window: Datenbank anlegen

Klicken Sie dann auf “Go”. Das wird alle benötigten Datenbankstrukturen erzeugen.

Ein letzter Schritt ist dann noch die Anwendungs Setup-Datei GGF/GGFSetup.php anzupassen. Suchen Sie die folgenden 4 Zeilen:

$dbhost = "localhost";        // See GGFDatabase. May be of the form www.server.domain:port

$dbuser = "root";          // See GGFDatabase, you should change this for production

$dbpw = "";                // See GGFDatabase, you should change this for production

$dbname = "Contacts";         // See GGFDatabase

Datei GGFSetup.php: Default Parameter für den Datenbankzugriff

In den meisten Fällen wird der Datenbank-Server die gleiche Maschine sein wie der Web-Server und die Datenbank-Engine kann via localhost erreicht werden. Aber das hängt von Ihrem Web-Hoster ab. Sie müssen den Eintrag für den Datenbankbenutzer dbuser ändern. Aus Sicherheitsgründen sollte dies niemals die MySQL Default Benutzerkennung „root“ sein. Der Web-Hoster sollte Ihnen den Datenbankbenutzer und das Passwort geben (dbpw).

$dbhost = "localhost";     // See GGFDatabase. May be of the form www.server.domain:port

$dbuser = "407421";           // See GGFDatabase, you should change this for production

$dbpw = "XYZ123";          // See GGFDatabase, you should change this for production

$dbname = "407421";           // See GGFDatabase

Datei GGFSetup.php: Beispielparameter für den Datenbankzugriff

Nun kann die Web-Anwendung über den URL Ihres Servers gestartet werden. Zum Beispiel: http://contacts.freetzi.com  .

Wenn Sie solche Fehlermeldungen bekommen:

Fatal error: require(): Failed opening required 'GGF/GGF.php' (include_path='.:/usr/share/pear:/usr/share/php') in /home/vhosts/contacts.freetzi.com/index.php on line 13

Fehlermeldung: Zugriffsrechte für die Web-Server/PHP-Task fehlen

und Sie sicher sind, dass die Datei am Server existiert, dann ist das normalerweise ein Problem mit den Zugriffsrechten. Wahrscheinlich lief der FTP Server am Host der benutzt wurde um Ihre Dateien am Server anzulegen, mit anderen Benutzerrechten als die Web-Server Task. Sie können das einfach beheben. Im FTP Fenster öffnen Sie das Kontextmenü der Datei oder des Verzeichnisses und erlauben Lesezugriff für alle.

Hinweis: Die Leseberechtigung für die Datei gilt nur für Server Tasks, nicht für den Internet Zugriff. Ihre .htaccess Datei (siehe oben) hindert Benutzer daran Ihren Quellcode und Setup-Daten zu lesen.

7         Downloads

Laden Sie den gesamten Quellcode der Contacts Anwendung hier herunter.

Die neuesten Versionen des GGF Frameworks finden Sie hier.