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.
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.
2 Anforderungen an die Beispiel-Anwendung
3 Elemente des Entwicklungsprozesses
4 Eine objektorientierte Architektur für Web-Anwendungen
4.4.1 Starten des Hauptfensters
4.4.2 Benutzeraktionen in Fenstern
4.4.3 Einen Dialog von einem Fenster aus öffnen
4.4.4 Benutzeraktionen in Dialogen
4.4.5 Einen Dialog schließen und zum Fenster zurückkehren
4.5 Verantwortlichkeit der Klassen
5 Implementierung einer Beispielanwendung
5.2 Der physische Datenbankentwurf
5.2.3 Die Beziehung “Firma beschäftigt Person”
5.3.2.3 CTSCompanyBrowserWindow
5.3.2.4 CTSCompanyInsertDialog
5.3.2.5 CTSCompanyUpdateDialog
5.3.2.6 CTSContactsBrowserWindow
5.3.2.7 CTSContactInsertDialog
5.3.2.8 CTSContactUpdateDialog
5.4.1 Vorbereiten des Framework-Verzeichnisses
5.4.2 Einrichten des Framework Icon-Verzeichnisses
5.4.4 Einrichten des Anwendungsverzeichnisses
5.4.4.1 Die Definition des Anwendungsmodells
5.4.4.2 Implementierung des Hauptfensters: CTSMainWindow.php
5.4.4.3 Implementierung des Login-Dialogs: CTSLoginDialog.php
5.4.4.4 CTSCompanyBrowserWindow
5.4.4.5 CTSCompanyInsertDialog
5.4.4.6 CTSCompanyUpdateDialog
5.4.4.7 CTSContactsBrowserWindow
5.4.4.8 CTSContactInsertDialog
5.4.4.9 CTSContactUpdateDialog
6.1 Die Contacts Anwendung auf Ihrem PC
6.1.1 Web-Server und Datenbank installieren
6.1.2 Die Anwendung am lokalen Web-Server einrichten
6.2 Die Anwendung auf einem Web-Server im Internet
ausführen
6.2.1 Einen Web Server im Internet einrichten
6.2.2 Die Anwendung auf den Web-Server bringen
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.
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.
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.
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.
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.
Die folgenden Diagramme zeigen die Interaktion
und den Informationsfluss zwischen den wichtigsten Elementen der Architektur.
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.
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.
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.
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).
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.
|
Klasse |
Basisklasse |
Verantwortlichkeit (Auszug) |
|
- |
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. |
|
|
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. |
|
|
- |
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. |
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.
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.
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 |
+------------+-------------+--------+-----------+---------+---------+---------------------+
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
+-----------+-----------+----------+------------+----------+---------------+------------------
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.
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
Hier
beschreiben wir den Entwurf und die Implementierung der grafischen
Benutzeroberfläche der Anwendung.
Der
Benutzer startet die Anwendung indem er
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.
Wir
erzeugen die GUI Layouts mit dem ReinHTML Dialog Designer (http://www.ReinHTML.eu )
Um die
Dinge einfach zu halten, enthält das Hauptfenster nur einen Menübalken und eine
Begrüßungsnachricht.

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

Login-Dialog
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.
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.
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.
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
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.
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).
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.
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
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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 .
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.
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 .
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.
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 ;
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.
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.
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.
Laden Sie
den gesamten Quellcode der Contacts Anwendung hier
herunter.
Die
neuesten Versionen des GGF Frameworks finden Sie hier.