Grundlagen
Grundlagen
Dieser Abschnitt beschreibt die wichtigsten Elemente der skriptbasierten Programmierung in CURSOR-CRM. Die Programmierung erfolgt dabei in der Skriptsprache Groovy, welche an die Programmiersprache Java angelehnt ist. Für die Skriptprogrammierung in CURSOR-CRM, wie sie im Maskenskript oder CURSOR BPM eingesetzt wird, empfiehlt es sich auf die reine Java Syntax zu beschränken und keinerlei Sprachspezifika von Groovy einzusetzen.
In jedem Projekt gibt es kundenindividuelle Geschäftsprozesse, deren Logiken sich trotz größter Flexibilität und Konfigurierbarkeit nicht mit einem Standardsystem abbilden lassen. Typische Anforderungen dieser Art sind u. a.:
Feldwertabhängiges Customizing (Feldeigenschaften) für Masken, Listen- und Baumdarstellung
Prüfung von Benutzereingaben
Automatisches Befüllen von Feldern
Benutzerinteraktion mit (konfigurierbaren) Dialog-Masken
Speichern von Datensätzen (Listen- und Detailansicht)
Definition eigener Buttons mit Aufruf von Masken-Skripten
Öffnen des Internet-Browsers mit vorgegebener URL
Aufruf von System-Suchen, ggf. mit integrierter DB-Funktion
Aufrufe von Workflows
Externe Aufrufe, inkl. Aufruf von Webservices
Zur Abbildung dieser Anforderungen wurde die Skriptsprache Groovy integriert, mit deren Hilfe (einfache) Logiken, wie die beispielhaft erwähnten, direkt vom Administrator des Kunden umgesetzt werden können.
Das Berechtigungskonzept (Feldrechte) kann durch Skripte nicht umgangen werden.
Hier finde Sie eine Liste von Fragen und Antworten rund um das Thema Groovy als Skriptsprache im CURSOR-CRM.
Werte
Die in einem Computer enthaltenen Informationen werden intern durch Einsen und Nullen repräsentiert, die wiederum durch elektrische Bauelemente (ein/aus) oder Magnete (Ausrichtung Nord/Süd) simuliert werden. Diese Einsen und Nullen nennt man auch 'Bits'. Auf einer abstrakteren Betrachtungsebene werden Ketten von Einsen und Nullen betrachtet. Eine solche Kette kann zum Beispiel eine Zahl, einen Text oder einen Kundendatensatz darstellen, ein Worddokument oder ein Musikstück. Die Bedeutung kann man ihr nicht ansehen, folglich wird sie separat mitverwaltet. Bei Dateien gibt der Dateiname Auskunft über die Bedeutung. Bitketten, die ein Programm gerade verarbeitet, nennt man "Werte" und bezeichnet mit 'Typ' die Art von ihr repräsentierten Information.
Ein häufig vorkommender Datentyp ist der String. Er enthält Text als einfache Aneinanderreihung von Zeichen (Buchstaben, Ziffern, Satzzeichen, …). Er beinhaltet keine Strukturelemente, die man als Wörter, Zeilen oder Absätze deuten könnte. Sogar der Zeilenumbruch ist lediglich ein Zeichen. Die Art und Weise, wie hinter den Kulissen die Zeichen im String codiert sind, unterscheidet sich von System zu System, aber das Grundprinzip ist immer dasselbe: kurze Abschnitte der Bitkette stellen Nummern dar, das zur Nummer gehörende Zeichen ist durch den Unicode-Standard definiert (http://www.unicode.org/ ).
Für Zahlen gibt traditionell mehrere Datentypen. In der Regel belegt eine Zahl 32 oder 64 Bits. Die interne Repräsentation ist recht trickreich. Ein und dieselbe Bitkette, als Ganzzahl interpretiert, hat einen ganz anderen Zahlenwert als wenn man sie als Gleitkommazahl liest (oder gar als String). Der Vorteil dieser Datentypen besteht darin, dass der Prozessor Rechenoperationen automatisch und sehr schnell durchführt, da sie in Form elektronischer Schaltungen realisiert sind.
Auch ein einzelnes Bit kann man sinnvoll verwenden, nämlich als Ja/Nein-Entscheidung. Der Datentyp heißt Boolean.
Schließlich gibt es noch Werte, die andere Werte enthalten. Dies sind Objekte und Arrays, die in den nächsten Abschnitten besprochen werden.
Und es gibt eine Art Platzhalterwert namens null, der für Nichts steht, nichts enthält und seinen eigenen Datentyp hat.
In einem Programmtext stellt man Werte anders dar, nämlich durch Zeichenfolgen, letztlich also Strings (der gesamte Programmtext ist ja selbst ein String). Das Programm übersetzt sie dann in die notwendige interne Bit-Darstellung. Zum Beispiel steht eine reine Ziffernfolge für eine Ganzzahl. Ist aber ein Dezimalpunkt darin, dann ist eine Gleitkommazahl gemeint. Eine Zeichenfolge zwischen Anführungszeichen wird in einen String übernommen.
Die fundamentalsten Wertetypen in Java sind in der folgenden Tabelle zusammengestellt.
Typ | Inhalt | Beispiel |
---|---|---|
Integer | Ganzzahl | 4711 |
Double | Reelle Zahl | 3.14 (beachte: Dezimalpunkt statt Komma, keine Tausenderpunkte) |
BigDecimal | Reelle Zahl mit hoher Genauigkeit | 3.14 (siehe Double) |
Boolean | Ja/Nein-Wert | Nur zwei Werte möglich: true und false |
String | Zeichenketten | "Hallo Welt!" |
Variablen
Eine Variable ist ein benannter Speicherplatz zur Ablage eines beliebigen Wertes. Die Variable muss typsicher erstellt werden (siehe Code-Konventionen).
Das Speichern erfolgt mit dem Zuweisungsoperator '=' (für Vergleiche wird stattdessen das doppelte Gleichheitszeichen verwendet).
Zum Verwenden des abgelegten Wertes setzt man einfach den Variablennamen im Sinne eines Platzhalters ein. Groovy erlaubt zwar die Verwendung nicht definierter Variablen, dies kann aber zu Fehlern führen und ist im Sinne unserer Code-Konventionen verboten.
// Ein einfaches Skript zur Verwendung von Variablen
Integer a = 3; // Der int-Wert 3 wird unter dem Namen "a" gespeichert
Integer b = a + 1; // Der int-Wert 4 wird unter dem Namen "b" gespeichert
ScriptUtils.info("Der Inhalt von b ist " + b); // Ausgabe
Integer a = 7; // Der in a gespeicherte Wert wird durch einen anderen Wert ersetzt
Für umfangreichere Skripte existieren Abwandlungen der Variablenverwaltung mit dem Zweck, Fehler auszuschließen.
Variablen Sichtbarkeit
Die Sichtbarkeit von Variablen bezieht sich normalerweise auf eine Methode oder einen Code-Block, der mit geschweiften Klammern { ... } umschlossen ist.
In Groovy besteht auch die Möglichkeit, dass Variablen als global erkannt und definiert werden. Diese Art der Programmierung - ob absichtlich oder versehentlich - ist nicht zu empfehlen.
In den folgenden Beispielen wird dieses Verhalten aufgezeigt und für die möglichen gewünschten Verhalten ein Beispiel erstellt. Die Log-Ausgabe muss konfiguriert werden (siehe unten im Abschnitt "Konfiguration für die Log-Ausgaben").
schlechter Code
// Skript 1 zur Verwendung von Variablen in Methoden / Variablen Sichtbarkeit
// Variable wird unbeabsichtigt ueberschrieben!
void myMethod()
{
ScriptUtils.error(s); // noch: "global"
s = "in Methode gespeichert";
ScriptUtils.error(s); // jetzt: "in Methode gespeichert"
}
// Skriptausfuehrung beginnt an dieser Stelle:
s = "global";
ScriptUtils.error(s); // noch: "global"
myMethod();
ScriptUtils.error(s); // jetzt: "in Methode gespeichert"
War die vorherige Logik gewünscht, wäre dies die bessere Lösung
guter Code 1
// Korrektur von Skript 1 unter der Massgabe, dass das Verhalten im vorherigen Skript beabsichtigt war
/**
* Rueckgabe des berechneten Textes
* @param s Parameter fuer die Methode beinhaltet Demo Daten
*
* @return Der berechnete Wert auf Basis der uebergebenen Information
*/
String myMethod(String s)
{
ScriptUtils.info(s); // Ausgabe des übergebenen Wertes "global"
s = "in Methode gespeichert";
ScriptUtils.info(s); // Ausgabe geänderten Wertes: "in Methode gespeichert"
return s;
}
// Skriptausfuehrung beginnt an dieser Stelle:
String s = "global";
ScriptUtils.info(s); // noch: "global"
s = myMethod(s); // hier erfolgt die Zuweisung des Wertes aus der Methode myMethod
ScriptUtils.info(s); // jetzt: "in Methode gespeichert"
War die vorherige Logik nicht gewünscht, schafft die Deklaration einer lokalen Variable Abhilfe.
guter Code 2
Skript 2 zur Verwendung von Variablen in Methoden
Eine "globale" Variable und eine lokale Variable
/**
* Unabhängige Methode
* Die Variable s überdeckt hier die äußere Variable s
*/
void myMethod()
{
String s; // Lokale Variable wird erzeugt
ScriptUtils.info(s); // jetzt: "null"
s = "in Methode gespeichert";
ScriptUtils.info(s); // jetzt: "in Methode gespeichert"
}
// Skriptausfuehrung beginnt an dieser Stelle:
String s = "global";
ScriptUtils.info(s); // jetzt: "global"
myMethod();
ScriptUtils.info(s); // immer noch: "global"
Für jede Variable muss der korrekte Datentyp angegeben werden.
Integer age = 33; // Alter
Double bodyHeight = 183.0; // Körpergröße
Boolean vegetarian = true; // Vegetarier?
String filename = "C:\\Dokumente\\XYZ\\XYZ.doc";
Kommentare
Kommentare sind Notizen, die keinen Einfluss auf die Skriptausführung haben. Man hinterlegt sie im Skript, um seine Funktion und die dahinterstehende Absicht zu erläutern,
Am Ende einer jeden Zeile des Scripts kann man einen Kommentar hinzufügen, den man mit einem doppelten Schrägstrich einleitet. Kommentare über mehrere Zeilen hinweg leitet man mit /* ein und beendet sie mit */ (den einzeiligen Kommentar braucht man nicht ausdrücklich zu beenden, da er sich einfach bis zum Zeilenende erstreckt).
Der Scripteditor erkennt Kommentare und hebt sie durch eine separate Schriftfarbe von den Befehlen ab.
Double c = a + b; // Hier werden zwei Zahlen addiert
/* Das ist ein Kommentar über
mehrere Zeilen*/
Tipp
Die Angabe von Autor und Datum zu jeder Methode erleichtert die Fehleranalyse bei Problemen oder Fehlfunktionen.
Beispiel
Dokumentationskommentar einer Methode
/**
* Ich bin ein Kommentar und erläutere die Methode - Kürzel 01.01.2015
*/
void myMethod()...
Ausgabe
Konfiguration für die Log-Ausgaben
Das Logging muss für die unterschiedlichen Aufrufstellen im CRM aktiviert werden. Hierbei unterscheidet sich generell die Konfiguration aus dem RichClient-Maskenskript, dem WebClient-Maskenskript und dem BPM.
Allgemein gilt:
Ist DEBUG aktiviert werden auch INFO, WARN und ERROR mit ausgegeben.
Ist INFO aktiviert werden auch WARN und ERROR mit ausgegeben.
Ist WARN aktiviert wird auch ERROR mit ausgegeben.
ERROR wird immer ausgeben.
Rich Client-Maskenskript
Im Rich-Client muss für das Logging in der Datei <Client-Verzeichnis>\custom\checkLog4j_add.bat einer der folgenden Einträge aufgenommen werden. Danach muss der RichClient neu gestartet werden, damit die Einstellung aktiv wird. Sie ist dann für alle RichClient-Maskenskripte aktiv. Das Log wird in die Datei <ClientLog-Verzeichnis>\<Benutzerkürzel>_CarmenClient.log geschrieben.
rem Aktivieren des WARN Log-Levels
echo log4j.category.de.cursor.jevi.client.swingclient.util.scripting.ScriptingHelper=WARN >> %LogFile%
rem Aktivieren des INFO Log-Levels
echo log4j.category.de.cursor.jevi.client.swingclient.util.scripting.ScriptingHelper=INFO >> %LogFile%
rem Aktivieren des DEBUG Log-Levels
echo log4j.category.de.cursor.jevi.client.swingclient.util.scripting.ScriptingHelper=DEBUG >> %LogFile%
Soll die Konfiguration wieder deaktiviert werden, muss die Zeile gelöscht oder auskommentiert werden und der RichClient wieder neu gestartet werden.
Web Client-Maskenskript
Für das Logging des Maskenskripts im WebClient zu erweitern ist einer der Befehl im <JBoss-Verzeichnis>\bin folgenden abzusetzen. Das Log wird in die Datei <JBoss-Verzeichnis>\standalone\log\Webclient.log geschrieben.
jboss-logging.bat add de.cursor.jevi.web.module.maskscripting.util.ScriptingHelper WARN
jboss-logging.bat add de.cursor.jevi.web.module.maskscripting.util.ScriptingHelper INFO
jboss-logging.bat add de.cursor.jevi.web.module.maskscripting.util.ScriptingHelper DEBUG
Soll das Logging wieder deaktiviert werden, muss der jeweilige Eintrag wieder entfernt werden:
jboss-logging.bat remove de.cursor.jevi.web.module.maskscripting.util.ScriptingHelper
Dieses Logging greift sofort für alle WebClient-Maskenskripte, nachdem es aktiviert wurde.
BPM
In BPM kann das Logging über den Schalter Log-Level im jeweiligen Prozess aktiviert werden
GUI-Ausgaben und Logging
Zur Ausgabe von Werten in einem Dialogfenster dient in Skript-Klasse DialogUtils. Bei BPM/Masken Skripten kann über Skriptklasse ScriptUtils in die Protokollierung geschrieben werden.
Beispiel
Der Befehl
DialogUtils.showMessageDialog("Nachricht", "Max & Moritz");
erzeugt den folgenden Dialog:
Soll in einem Programm in die Protokolldatei Process.log (je nach Kontext auf dem Client oder dem Server) geschrieben werden, müsste man das so schreiben:
ScriptUtils.error("Fehlermeldung"); // Fehlermeldungen werden immer in die Datei geschrieben
ScriptUtils.warn("Warnungstext"); // Hierzu muss im Client bzw. Server das Warn-Logging aktiviert werden.
ScriptUtils.info("Hinweistext"); // Hierzu muss im Client bzw. Server das Info-Logging aktiviert werden.
ScriptUtils.debug("Debug Informationen"); // Hierzu muss im Client bzw. Server das Debug-Logging aktiviert werden.
Generell ist es bei der Verwendung von aufwendigeren Log-Texten sinnvoll zu prüfen, ob das jeweilige Log-Level auch aktiviert ist, da der Aufbau einer Ausgabe sich negativ auf die Performance auswirken kann.
if (ScriptUtils.isWarnEnabled())
{
String message = "Warning muesste aufwendig berechnet werden";
ScriptUtils.warn(message);
}
if (ScriptUtils.isInfoEnabled())
{
String message = "Info muesste aufwendig berechnet werden";
ScriptUtils.info(message);
}
if (ScriptUtils.isDebugEnabled())
{
String message = "Debug muesste aufwendig berechnet werden";
ScriptUtils.debug(message);
}
Methoden
Zur Deklaration von Methoden gibt es folgende Gründe:
Wiederkehrende Programmteile sollen nicht immer wieder programmiert, sondern an einer Stelle angeboten werden. Änderungen an der Funktionalität lassen sich dann leichter durchführen, wenn der Code lokal zusammengefasst ist.
Komplexe Programme werden in kleine Teilprogramme zerlegt, damit die Komplexität des Programms heruntergebrochen wird. Damit ist der Kontrollfluss leichter zu erkennen.
Eine Methode besteht aus mehreren Bestandteilen.
Der Methodenkopf besteht aus einem Rückgabetyp, dem Funktionsnamen und einer optionalen Parameterliste.
Der Methodenrumpf wird mit geschweiften Klammern
{…}
abgegrenzt.
void printText(String text) // Methodenkopf mit Parametern
{
DialogUtils.showMessageDialog("Nachricht", text); // Methodenrumpf
}
Listing: Definition einer einfachen Methode printText ohne Rückgabetyp
Die Methode printText("Hallo Welt!") erzeugt den Dialog:
Die Methode kann nur im Client verwendet werden (sie verwendet spezifische Methoden des Clients) und beispielsweise beim Laden eines Datensatzes aufgerufen werden.
void entryLoaded()
{
printText("Hallo Welt!");
}
Nachfolgend ist eine Methodendefinition mit der Übergabe eines logischen Parameters dargestellt. Der Parameter visible
kann in der Methode wie eine normale Variable verwendet werden.
void setFieldsVisible(Boolean visible)
{
FieldUtils.setVisible(visible, "Freedate4.Customer", "FreeText5.Customer");
}
Die folgende Methode sum()
berechnet die Summe der beiden als Parameter übergebenen Variablen a
und b
. Anschließend wird das Ergebnis an die Aufrufstelle zurückgegeben.
int sum(int a, int b)
{
return a + b;
}
Lexikalische Konventionen
Typ | Inhalt |
---|---|
Sonderzeichen | () {} [] ; , . |
Sonderzeichen für die Operatoren | = > < ! ~ ? : & | + - * / ^ % |
Ersatzdarstellung der Sonderzeichen | \ |
Konstante Zeichenketten (Strings) | " |
Zeilentrenner | \n |
Tabulator | \t |
Backslash | \\ |
Doppelte Anführungszeichen (Doppelte Hochkomma) | \" |
Tabelle: Lexikalische Konventionen
Typ | Schreibweise | Beispiel |
---|---|---|
Variablennamen | mit Kleinbuchstaben beginnend | index |
Methodennamen | mit Kleinbuchstaben beginnend | show() |
Symbolische Konstanten | Alle Buchstaben groß | MAXIMUM |
Tabelle: Styleguide-Konventionen
Operatoren
Als Operatoren bezeichnet man die Symbole, die man in Ausdrücken (also Berechnungen im weitesten Sinne) verwendet. Dieser Abschnitt stellt Ihnen die für das Skripting nützlichen Operatoren vor.
Einige Operatoren kennen Sie sicherlich: die Zeichen für die Grundrechenarten Addition und Subtraktion, + und -. Vom Alltagsgebrauch leicht abweichend sind in Java die Symbole für Multiplikation und Division; wie in vielen anderen Programmiersprachen verwendet man hier aus historischen Gründen den Stern * und den Schrägstrich /, da diese seinerzeit auf Schreibmaschinentastaturen verfügbar waren.
Eine besondere Rolle spielt das Gleichheitszeichen =. Es dient nicht etwa einem Vergleich, sondern nur der Zuweisung eines Rechenergebnisses zu einer Variablen.
Integer a;
a = 12 * 3; // a erhält den Wert 36
Ein weiterer nützlicher Operator ist der Divisionsrest. Er gilt nur für ganze Zahlen (int
) und wird durch ein Prozentzeichen dargestellt. Dafür fällt bei der Division von int-Zahlen der Rest automatisch weg, das das Ergebnis ebenfalls int
ist.
Integer zahl, tsnd, rest;
zahl = 1234567;
tsnd = zahl / 1000; // 1234
rest = zahl % 1000; // 567
Außer mit Zahlen kann Java auch mit booleschen Werten und Zeichenfolgen 'rechnen', wofür es eigene Symbole gibt.
So sind Vergleiche, zum Beispiel das Größerzeichen >, ebenfalls Operatoren und haben als Ergebnis jeweils einen booleschen Wert, der wiederum in booleschen Ausdrücken und booleschen Variablen verwendet werden kann.
Boolean x;
x = (a > b); // "größer"
x = (a < b); // "kleiner"
x = (a >= b); // "größer oder gleich"
x = (a <= b); // "kleiner oder gleich"
x = (a == b); // "ist gleich"
x = (a != b); // "ungleich"
Das 'Rechnen' innerhalb der booleschen Werte (die Boolesche Algebra) erfolgt mit folgenden Operatoren:
Boolean x;
x = (a || b ); // "oder" (true, wenn a oder b oder beide(!) true sind)
x = (a && b); // "und" (nur true, wenn sowohl a als auch b true sind)
x = !a; // "nicht" (macht aus true false und umgekehrt)
Um zum Inhalt einer Variablen etwas hinzuzuaddieren, gibt es den speziellen Zuweisungsoperator +=:
Integer a = 0;
a = a + 10; // Erhöht den Inhalt von a um 10
a += 10; // Dasselbe, aber kürzer und deutlicher
Analoge Konstrukte gibt es für die anderen Rechenoperatoren. Das Hinzuaddieren und Abziehen von Eins kommen übrigens so oft vor, dass dafür nochmals eigene Operatoren ++ und -- eingeführt wurden. So bedeutet i++ 'erhöhe i um 1'. Zuletzt sei ein dreistelliger Operator erwähnt, der der WENN-Formel aus Excel entspricht. Er besteht aus Fragezeichen und Doppelpunkt. Um Unklarheiten zu vermeiden, sollte man stets eine Klammer um den Ausdruck setzen:
String s;
s = (a > b) ? "größer" : "kleiner" ; // Excel: WENN(a > b; "größer"; "kleiner")
Eine Übersicht über alle Operatoren (einschließlich einiger oben nicht genannter):
Operator | Typ | Beschreibung |
---|---|---|
++, -- | Zahlen | Inkrement und Dekrement |
+ | Zahlen, String | unäres Plus |
- | Zahlen | unäres Minus |
~ | int, Integer | bitweises Komplement |
(Typ) | Jeder | Cast |
==,!= | Primitiv | Gleich-/Ungleichheit von Werten |
==,!= | Objekt | Gleich-/Ungleichheit von Referenzen (nicht als Wertevergleich geeignet) |
& | int, Integer | bitweises Und |
&& | Boolean | logisches Und |
^ | int, Integer | bitweises Xor |
| | int, Integer | bitweises Oder |
|| | Boolean | logisches Oder |
a ? b : c | Alles | Bedingungsoperator |
= | Jede | Zuweisung |
*=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |= | Jede | Zuweisung mit Operation |
Bedingte Befehlsausführung
Die Ausführung eines Anweisungsblocks kann davon abhängig gemacht werden, ob eine bestimmte Bedingung erfüllt ist oder nicht.
Hierzu dient die if-Anweisung. Sie besteht aus dem Schlüsselwort if, dem zwingend ein Ausdruck mit dem Typ boolean
in Klammern folgt. Darauf folgt der auszuführende (oder nicht auszuführende) Anweisungsblock, der wie immer mit {…} umgeben wird.
if (i < 10)
{
ScriptUtils.info("Hinweis: i ist kleiner 10");
}
Die if-Anweisung kann ergänzt werden um einen zweiten Anweisungsblock, der dann ausgeführt wird, wenn die Bedingung nicht erfüllt ist. Er wird über das Schlüsselwort else angebunden.
if (i < 10)
{
ScriptUtils.info("i ist kleiner 10");
}
else
{
ScriptUtils.info("i ist größer oder gleich 10");
}
Die Skriptsprache von CURSOR-CRM unterstützt auch die in Java bekannte switch-Anweisung. Je nach Wert des Ausdrucks springt sie eine Stelle innerhalb des Anweisungsblocks an. Dies ist nützlich, wenn es mehr als zwei Möglichkeiten gibt. Beachten Sie, dass die Abarbeitung des Anweisungsblocks nicht automatisch beim nächsten case endet. Das nächste case dient nur als mögliche Einsprungstelle, ist aber kein Befehl. Dies leistet der break-Befehl, der in der Regel am Ende jeden case-Abschnitts steht.
switch (anzahlSuchergebnisse)
{
case 0:
// Ohne break würden zusätzlich auch
// die folgenden Anweisungen ausgeführt
ScriptUtils.info( "Leider kein Wert gefunden." );
break;
case 1:
ScriptUtils.info( "Genau ein Nachschlagewert gefunden." );
break;
default:
// default gilt, wenn kein case zutrifft. Es muss zuletzt kommen.
ScriptUtils.info( "Es wurden mehrere Werte gefunden." );
break;
}
Schleifen
Schleifen dienen dazu, eine Anweisung oder einen Anweisungsblock mehrmals abzuarbeiten.
Die einfachste Schleife ist die while-Schleife. Sie besteht aus dem Schlüsselwort while, einer Bedingung und einem Anweisungsblock, der auch als Rumpf der Schleife bezeichnet wird. Die Bedingung wird vor dem ersten Durchlaufen des Rumpfes geprüft, und danach erneut vor jedem weiteren Durchlauf. Ist sie false, dann wird das Programm im Bereich nach der Schleife fortgesetzt.
// while-Schleifen
int i = 1; // Zähler beginnt mit dem Wert 1
while (i <= 3) // Bedingung
{
ScriptUtils.info("Zähler: " + i); // Zeigt nacheinander die Zahlen 1, 2, 3
i++; // Zähler wird um 1 erhöht
}
// Hier geht es weiter, sobald i den Wert 4 erreicht hat.
Weit häufiger wird in Programmen jedoch die for-Schleife genutzt. Sie ist im Wesentlichen identisch mit der while-Schleife. Der Unterschied besteht darin, dass ihr Kopf neben der Bedingung zwei Anweisungen enthält. Die eine wird vor der eigentlichen Schleife ausgeführt, die andere nach jedem Schleifendurchlauf. Traditionell verwendet man sie zur Vorbelegung und Veränderung eines Zählers, den man zudem eigens für die Schleife deklarieren kann. Die folgende for-Schleife ist äquivalent zur oben dargestellten while-Schleife, außer dass die Variable i nach Abschluss der Schleife gelöscht wird.
for (int i = 1; i <= 3; i++)
{
ScriptUtils.info("Zähler: " + i); // Zeigt nacheinander die Zahlen 1, 2, 3
}
Schließlich gibt es noch eine dritte Schleife, die zwar ebenfalls das Schlüsselwort for benutzt, aber unmittelbar auf die Elemente eines Arrays und Listen zugreift, ohne dass man eine Zählvariable braucht. Sie wird im folgenden Abschnitt genauer dargestellt und hat folgende Form:
for (String value : list)
{
ScriptUtils.info("Wert: " + value); // Zeigt nacheinander die Werte der Liste
}
Arrays
Ein Array (auch Feld, Reihung oder Liste genannt) ist ein spezieller Datentyp, der mehrere Werte (Elemente genannt) zu einer Einheit zusammenfasst. Ein Element eines Arrays verhält sich wie eine gewöhnliche Variable, deren Name aus dem Namen des Arrays und der Platznummer besteht. Platznummern beginnen bei 0, wie Etagennummern in Gebäuden. Die Größe und damit die Anzahl der Elemente in einem Array werden bei der Initialisierung festgelegt und können nicht mehr geändert werden.
// Anlegen von einfachen Arrays
// ACHTUNG!
// an dieser Stelle führt die Java-Syntax zu einem Fehler! Es ist also nicht zulässig die folgende Form zu verwenden:
// Integer[] a = {1, 2, 3, 5, 7, 12};
Integer[] a = [1, 2, 3, 5, 7, 12]; // Sechs Elemente
Integer[] b = new Integer[10]; // 10 Elemente vom Wert null
String[] c = new String[10]; // 10 Elemente vom Wert null
// Anzahl Elemente in einem Array
ScriptUtils.info(a.length); // ==> 6
// Lesende Schleife über ein Array
for (Integer x : a)
{
ScriptUtils.info(x);
}
// Schreibende Schleife
for (int i = 0; i < a.length; i++)
{
a[i] = i * i;
}
// Inhalt von a ist nun: {0,1,4,9,16,25,36,49,64,81}
Die Anzahl der Elemente eines Arrays kann nicht mehr nachträglich geändert werden. Ein verwandter Datentyp, der dynamisch angepasst werden kann, ist die ArrayList. Wie auch in Java kann und sollte man hier sogenannte Generics angeben. Diese dienen der Übersicht und sollen verhindern, dass nicht typsicher gearbeitet wird. Sind beliebige Objekt zu speichern, kann natürlich der Typ Object gewählt werden
List<String> stringList = new ArrayList<>();
stringList.add("text1");
stringList.add("text2");
...
List<Object> objectList = new ArrayList<>();
objectList.add("test");
objectList.add(1729);
...
Ein anderer Array-Datentyp ist die HashMap. Bei ihr werden die Elemente über Namen statt Nummern angesprochen. Anders als beim Array stehen bei der HashMap die Platznamen in geschweiften Klammern und müssen Strings sein. Inhalte können hinzugefügt und entfernt werden.
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar"); // Ablage des Strings "bar" unter dem Schlüssel "foo"
map.put("zzz", 3); // Ablage der Zahl 3 unter dem Schlüssel "zzz"
Object s = map.get("foo"); // Abrufen des Schlüssels "foo" => s = "bar"
Object z = map.get("zzz"); // Abrufen des Schlüssels "zzz" => z = 3
// Der Abruf eines nicht vorhandenen Schlüssels ergibt null
Object x = map.get("test"); // x erhält den Wert null
// Schleife über eine HashMap über das key-Set
for (String key : map.keySet()) // hier ist der Datentyp des keys wichtig!
{
ScriptUtils.info(s + " ist " + map.get(key));
}
// Schleife über eine HashMap mit dem Entry-Set
for (Map.Entry<String, Object> entry : map.entrySet())
{
ScriptUtils.error(entry.getKey() + " ist " + entry.getValue());
}
Verwenden von Java-Klassen
Beim Programmieren begegnet man häufig wiederkehrenden Aufgabenstellungen, beispielsweise das Rechnen mit Zeiten und Tagen.
Die Programmiersprache Java enthält zahlreiche fertige Lösungen in Form von sogenannten Klassen. Sie sind unter http://docs.oracle.com/javase/7/docs/api/ dokumentiert.
Dieselben Klassen stehen auch in der Skriptsprache von CURSOR-CRM zur Verfügung.
Hier ein Beispiel zur Nutzung der Klasse java.sql.Timestamp
, die das Rechnen mit Datum-Zeit-Werten unterstützt:
java.sql.Timestamp a, b;
a = new java.sql.Timestamp(0); // Ein Timestamp-Objekt mit dem internen Wert 0 wird erzeugt
b = a; // b enthält nun dasselbe Timestamp-Objekt wie a
a.setTime(3600000); // Das Objekt wird auf ein Uhr (3600000 Millisekunden) gestellt.
ScriptUtils.info(a); // Zeigt einen Zeiptpunkt am 1.1.1970 an.
ScriptUtils.info(b); // Zeigt natürlich dasselbe an, da a und b auf dasselbe Objekt verweisen.
Der import-Operator ermöglicht, eine Klasse im restlichen Programm unter ihrem "Kurznamen" anzusprechen. Dies ist die letzte Komponente des ausgeschriebenen Namens.
Im obigen Beispiel hätte man auch schreiben können (wobei dieser Import im Standard bereits enthalten ist und komplett weggelassen werden kann):
import java.sql.Timestamp; // Import der Klasse java.sql.Timestamp => von nun an genügt der Kurzname "Timestamp"
Timestamp a, b;
a = new Timestamp(0); // Ein Timestamp-Objekt mit dem internen Wert 0 wird erzeugt
...
Code-Konventionen
Code-Konventionen beschreiben Regeln für die Bearbeitung von Skripten, welche die Wartbarkeit und Zusammenarbeit fördern sollen. Die Regeln orientieren sich an den Code-Konventionen der Entwicklungsabteilung der CURSOR Software AG.
Allgemeine Regeln
Formatierung
Eine Codezeile ist - wie in Java - mit einem Semikolon abzuschließen.
Bei Schleifen wie z. B. if, for, while ist immer ein Code-Block verwenden, auch wenn der Inhalt des Blocks "nur" eine Code-Zeile ist.
Die öffnende Klammer wird immer in die neue Zeile geschrieben, der enthaltene Code mit 2 Leerzeichen eingerückt.
Parameter werden mit einem Leerzeichen nach dem Komma getrennt
Der Code sollte nie aus dem Bildschirm ragen. 120 Zeichen pro Zeilen ist ein guter Wert.
Beim Umbruch einer Zeile wird die neue Zeile weiter eingerückt.
Leerzeilen können den Code lesbarer machen
Formatierungsbeispiele
String tableName = "myTable";
List<String> fieldNames = fillFieldNames("myTable");
String selectSql = "SELECT ";
String delimiter = "";
for (String name : names)
{
selectSql += delimiter + name;
delimiter = ", ";
}
selectSql += " FROM " + tableName;
String longLine = "Das ist eine lange Zeile, die umgebrochen werden muss, weil sie zu lang ist, " +
"in der nächsten Zeile ist sie eingerückt";
...
/**
* @return The inintialized names list
*/
List<String> fillFieldNames(String tableName)
{
List<String> names = new ArrayList<>();
names.add("Pk");
names.add("RightPk");
names.add("UpdateUser");
if (ScriptUtils.equal(tableName, "Customer"))
{
names.add("RightPk");
}
return names;
}
Code-Struktur und Namenskonventionen
Einstiegs-Methoden sollten am Anfang des Skripts stehen.
Der Code soll dokumentiert sein. Dies kann auch in der Landessprache oder Englisch erfolgen.
In der C1-Entwicklung ist in Englisch zu dokumentieren.Alter Code sollte nicht auskommentiert, sondern gelöscht werden. Dieser ist in der Skript-Historie einsehbar.
Eine Methode sollte nicht über mehrere Bildschirmseiten gehen. Hier ist Code sinnvoll aufzuteilen und in private Methoden mit sprechenden Namen auszulagern.
Jede fachliche Anforderung sollte in unterschiedlichen Methoden ausgelagert werden
Einstiegs-Methoden bleiben kurz und übersichtlich
Am Namen der Methode sollte direkt der Zweck erkennbar sein
Variablen- und Methodennamen sollten sprechend gewählt werden
In der Methoden-Beschreibung sollte beschrieben werden, für was die Methode und deren Variablen da sind.
Der Name sollte mit Kleinbuchstaben anfangen, weitere Wortteile mit Großbuchstaben beginnen ("camelCase").
Für Boolsche-Variablen wird die Vorsible "is" empfohlen. z.B. isFieldAvailable
Beispiele: searchCount, matchCode, sumQuoteValue, isEmployeeAdminOrReportAdmin
Beispiel für Code-Struktur und Namenskonventionen
void entryLoaded()
{
updateGUI();
featureXYZ();
}
boolean entryBeforeSave()
{
return validateProduct();
}
/**
* @return true for projects with type "INTERN". Otherwise false.
*/
boolean isInternalProject()
{
return ScriptUtils.equal(FieldUtils.getValue("TypeKey.Project"), "INTERN");
}
void updateGUI()
{
boolean isInternalProject = isInternalProject();
if (isInternalProject)
{
GUIUtils.setText(I18nUtils.i18nCustom("Project.Internal.Marker.Text", SessionConstants.LOCALE), "Matchcode.Project");
}
//...
}
/**
* Feature-Request XYZ. See https://inhouse.cursor.de:18443/webclient/link/C2_SUP_NEU/13e2jbr1dpd8gjisSUPG
* ADMIN 28.05.2020
*/
void featureXYZ()
{
//...
}
/**
* Validate project before saving
*/
boolean validateProduct()
{
boolean isInternalProject = isInternalProject();
ILookup chanceSuccessKey = FieldUtils.getValue("ChanceSuccessKey.Project");
int chance = (ScriptUtils.isEmpty(chanceSuccessKey)) ? 0 : LookupUtils.getKey(chanceSuccessKey);
if (isInternalProject && chance < 50)
{
DialogUtils.showMessageDialog(I18nUtils.i18nCustom("Project.InternalChanceWarning.Title", SessionConstants.LOCALE),
I18nUtils.i18nCustom("Project.InternalChanceWarning.Text", SessionConstants.LOCALE, chance), GUIConstants.WARNING_MESSAGE);
return false;
}
return true;
}
Coding
Der Code soll reine Java Syntax nutzen, auch wenn Groovy als Skript-Sprache verwendet wird
Java Code ist immer abwärtskompatibel
Neue Spracherweiterungen von Java können genutzt werden (Foreach-Schleife, ...-Operator (Vargs), ...)
Die Programmierung sollte in Englisch erfolgen
Der Code wird kürzer
Bei ausländischen Kunden kann der Code von jedem gelesen werden
Anforderungen im Code zu kommentieren (Ticketnummer, Umsetzer, Datum)
Auf das Zwischenspeichern von Variablen sollte soweit möglich verzichtet werden (setVariable)
Zustandslose Programmierung ist fehlerfreier
Auf die Nutzung von nativen Datentypen sollte verzichtet werden, da sonst leere Zahlenfelder (null-Werte) nicht korrekt abgebildet werden.
Double
,Integer
,Boolean
anstattdouble
,int
,boolean
Wiederverwendbarer Code sollte in Skript-Bibliotheken ausgelagert werden
Eine fachliche Änderung von Methoden wirkt sich auf alle Verwendungsstellen aus. Hier sollte eine neue Methode erstellt werden.
Eine Skript-Methode sollte über Tests geprüft werden.
Vor der Veröffentlichung der Bibliothek ist auf die korrekt Benennung der Bibliothek, Methoden und Variablen zu achten, da diese dann nicht mehr geändert werden können.
Coding-Probleme
Abweichendes Coding zur Java-Syntax ist mit Groovy ein Problem, falls irgendwann einmal Groovy durch eine neue/aktuellere Skript-Sprache ersetzt wird oder werden muss. Ebenso kann dieser Code nicht einfach in den CRM-Standard übernommen werden, sondern muss erst in die korrekte Java-Syntax konvertiert werden.
Semikolon
Groovy erlaubt es, das Semikolon zum Abschluss einer Anweisung am Ende der Zeile wegzulassen. Jeder Zeilenumbruch ist automatisch der Abschluss einer Anweisung. In Groovy ist es daher schwierig, mehrzeilige Anweisung zu erstellen.
// Groovy-Syntax
String value = "Meine Zeichenkette"
// Java-Syntax
String value = "Meine Zeichenkette";
Listen und Arrays
In Groovy können Listen über die Java-Array-Schreibweise mit wenig Code erzeugt werden. Je nach Variablen-Definition ändert sich das Verhalten zwischen Listen und Arrays, was erschwert, den genauen Inhalt einer Variable zu ermitteln. Diese Schreibweise erstellt ebenfalls keine Typ-Sichern-Listen. Der Nachteil der Java-Schreibweise wiederum liegt im längeren Code.
// Groovy-Syntax
list1 = [1, "abc", LookupUtils.toLookup("KEY")] // gemischte listen sollte man dringend vermeiden
String[] list2 = ["a", "b", "c"] // diese Liste ist ein Array
// Java-Syntax
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Schleifen for-in
Vor Java 6 gab es die for-each-Schleife noch nicht. In Groovy wurde dies durch die for-in Schleife gelöst. Man sollte die Syntax von Java verwenden, um kompatibel zu bleiben.
// Groovy-Syntax for-in
for (item in list)
{
item.doSomething();
}
// Java-Syntax for each
for (item : list)
{
item.doSomething();
}
Variablen-Definition
In Skripten sollten anonyme Variablen vermieden werden. In Groovy definiert das 'def'-Schlüsselwort eine globale Variable für das Skript. Die Sichtbarkeit dieser Variable unterscheidet sich somit von allen anderen Variablen. Der Code wird komplexer, unverständlicher und schwerer zu warten. Die Seiteneffekte und Fehler im Code nehmen dadurch zu. Man kann dieser Variablen zur Laufzeit verschiedene Objekt-Klassen zuweisen, so dass man den Inhalt der Variablen nur schwer abschätzen kann.
Zudem hat das Schlüsselwort 'def' in BPM-Skripten durch die Activiti-Engine noch eine Zusätzliche Behandlung zu Variablen Prozess-Definition. Diese Sonderbehandlung wurde aber in der neueren Activiti-Version entfernt.
// Groovy-Syntax Variable, nicht nutzen.
def var = "abc"
var = 1
// Java-Syntax Variable
String var = "abc";
var = 1; // Laufzeitfehler
Klassen-Definition
Um Code im Skript besser zu strukturieren, kann man eigene Klassen dort anlegen. Somit ist es möglich, Logik an Objekte zu binden. Im Prinzip ist der objekt-orientierte Ansatz sehr gut. Dieser Ansatz hat aber auch einen großen Nachteil. Werden Variablen von dieser Klasse erzeugt und als Prozess-Variable gespeichert, so ist die Klassendefinition nach Ende der Skriptausführung nicht mehr gültig und in der nächsten Prozess-Aktion kommt es zu einem Laufzeitfehler. Wert-Klassen dürfen also nicht in einem Prozess-Skript verwendet werden. Logik-Klassen lagert man besser in die Skript-Bibliothek aus, so dass man diese Logik wiederverwenden und dokumentieren kann. Ansonsten müsste die Klassen in jedem Skript neu definitiert werden.
// Groovy-Syntax
class MyClass
{
String var;
public MyClass(String var)
{
this.var = var;
}
public doSomething()
{
...
}
}
MyClass myClass = new MyClass("abc");
ProcessUtils.setVariable("myClass", myClass); // führt im Prozess-Fortgang zu Laufzeitfehlern!
Closures
Closures sind Zeiger auf anonyme oder benannte Methoden. Closures verbleiben im Speicher der JVM und werden nicht aufgeräumt, wenn noch eine Referenz darauf existiert. Am Ende eines Groovy-Skripts wird das Closure nicht freigegeben sondern evtl. durch einen späteren GC-Lauf, aber nur wenn Groovy irgendwann auch die Referenz auf das Skript freigibt. Der Code bleibt somit länger im Speicher und blockiert Ressourcen.
Durch die Verwendung von Closures erhöht sich auch die Kompilierdauer für das Skript um ca. 10%. Daher sollte man hier auf normale Skript-Methoden oder auf die Skriptbibliothek ausweichen.
// Groovy-Syntax
pow { int i -> i * i }
int p = pow(2)
// Java-Syntax
int pow(int i)
{
return i * i;
}
int p = pow(2)
Exceptions
Während der Ausführung eines Skriptes kann es zu unerwarteten Fehlern kommen. Diese Fehler könnten gefangen und behandelt werden.
Im Prozessumfeld macht das Abfangen von Exception aber Probleme. Durch den aufgetretenen Fehler kann es sein, dass die Transaktion im Server abgebrochen wurde. Wird nun die Exception gefangen und im Skript weitergearbeitet, so wird die Ausführung im Prozess am Ende der Skript-Aktionen spätestens auf diesen Transaktionsfehler laufen. Jeder Datenzugriff im Skript kann ohne Transaktion nicht mehr funktionieren. Soll ein Prozess gezielt durch eine Exception beendet werden, ist dies durch den Aufruf von ScriptUtils.throwException()
möglich. Die Transaktion wird dadurch beendet und dem Anwender eine sprechende Fehlermeldung präsentiert.
// Prozess-Skript
try
{
WorkSpaceScriptUtils.save(...);
}
catch(Exception e)
{
ScriptUtils.throwException("Das Speichern des Datensatzes ist fehlgeschlagen!", e);
}
Eine Exception sollte auf keinen Fall dazu benützt werden, um Logik innerhalb einer Methode zu steuern, was auch mit geeigneten Rückgaberwerten möglich wäre. Dadurch verschlechtert sich die Performance um das 100-fache oder mehr.
// schlecht
void verify(Object var)
{
if(!(var instanceof IContainer))
{
throw new Exception("Expected IContainer variable");
}
}
try
{
verify(var);
doSomething();
}
catch(Exception e)
{
handlerError();
}
// gut
boolean verify(Object var)
{
return var instanceof IContainer;
}
if(verify(var))
{
doSomething();
}
else
{
handlerError();
}
Maskenscripting
Beeinflussung der Performance
Scripting kann sehr stark die Performance eines Systems beeinflussen. Daher ist es sehr wichtig genau zu wissen, was man tut und dass man sich über die Auswirkungen der erstellten Skripte bewusst ist.
Callback-Methoden
Callback-Methoden sind die sogenannten "Einstiegspunkte", um auf Aktionen oder Ereignissen reagieren zu können. Eine mögliche Reaktion wäre z. B. das Manipulieren eines Feldinhaltes, welches durch die Änderung eines anderen Feldes (Ereignis) ausgelöst wird.
Es sollten nur die Callback-Methoden definiert bzw. verwendet werden, die auch wirklich benötigt werden. Sobald eine Callback-Methode definiert ist, wird sie vom System auch aufgerufen. Durch das Weglassen nicht verwendeter Callback-Methoden wird die Abarbeitung des Scripting beschleunigt, da weniger Aufrufe durchzuführen sind.
Detailansicht und Listenansicht
Die Callback-Methoden können in verschiedenen Bereichen wie Detailansicht und Listenansicht aufgerufen werden. Der Bereich Listenansicht umfasst dabei sowohl die Liste im Haupt - als auch im Unterbereich.
Bei der Skriptausführung muss darauf geachtet werden, dass in der Listenansicht nicht alle Felder Detailansicht zur Verfügung stehen.
Grundlegendes
Das CURSOR-CRM Scripting wird in verschiedenen Bereichen (Masken, BPM, Workflows, Web Services, TAPI Dialog) verwendet. Der Skript-Editor unterstützt den Programmier bei der Auswahl der geeigneten Skript-Methoden und Befehle.
Tastenkombinationen
Editor für Maskenskript starten: STRG+ALT+UMSCHALT+S
Autovervollständigung: STRG+LEERTASTE
Nomenklatur Suchen für Skripte
Suchen, welche in Maskenskripten Verwendung finden, sollten wie folgt abgelegt werden:
[Partnernummer][Kürzel des Moduls]_[Script]_[Entität]_[sprechender Name der Suche]
Über die Berechtigungen sollten diese Suchen vor Manipulation Dritter geschützt werden. Die Suchen sind als Systemsuche zu markieren.
Erste Schritte im Maskenskript
Jede Entität in CURSOR-CRM kann mehrere Skript enthalten. Um diese zu bearbeiten, öffnen sie eine Maske und rufen den Menüpunkt Administration/Skript-Editor auf. Nach dem Speichern und Schließen ist das geänderte Skript übrigens sofort aktiv und Sie können es ausprobieren. Sie müssen also nicht erst die Maske verlassen und neu öffnen oder ähnliches.
Ein Maskenskript besteht aus Abschnitten, die Methoden genannt werden. Dies wird im Detail weiter unten erläutert. Sie können eigene Methoden anlegen. Die definierten Event-Handler sind spezielle Methoden, die von der Anwendung aufgerufen werden. Damit eigene Methoden angesprochen werden, müssen diese in der Callback-Methode aufgerufen werden.
Als Beispiel wollen wir einmal auf den Fall reagieren, dass der Anwender in der Maske Geschäftspartner das Feld Kurzname
ändert. Wenn Sie bei gedrückter Maustaste auf das Feld Kurzname
fahren, erscheint unten links der interne Feldname MatchCode.Customer
, den wir im Skript brauchen. Öffnen Sie mit dem Menüpunkt den Scripteditor. Falls Sie nun schon ein Skript vorfinden, dann sollten Sie den Editor schnell wieder schließen und sich eine andere Maske suchen; ansonsten können Sie folgendes in das Skript schreiben
void fieldValueChanged(String fieldName, Object oldValue, Object newValue)
{
if(ScriptUtils.equal(fieldName,"MatchCode.Customer"))
{
DialogUtils.showMessageDialog("Hinweis", "Sie haben den Kurznamen geändert!", false);
FieldUtils.setBackgroundColor(Color.RED, "MatchCode.Customer");
}
}
Speichern Sie das Skript, und ändern Sie den Kurznamen des Geschäftspartners.
Maskenskripte in der Partnerschicht übersteuern
Über Partnermodule können verschiedene Maskenskripte für einen Entität vorliegen. Änderungen an diesen Maskenskripten ist beim Kunden nicht möglich. Sind größere Änderungen nötig, so kann im Maskenskript die Ausführung der Skripte in der Partnerschicht (C1) deaktiviert werden. Dazu existiert für jede Callback-Methode eine entsprechende Hilfsmethode, welche die Ausführung der Partner-Maskenskripte steuert. Hier ist es ebenfalls möglich, kundenspezifischen Maskenskripte vor der Ausführung der Partner-Maskenskripte zu platzieren.
boolean fieldValueChangedBeforeC1(String fieldName, Object oldValue, Object newValue)
{
if(...)
{
... // Code vor der Partnerschicht ausführen
return false // Partner-Maskenskripte werden deaktiviert
}
return true; // Partner-Maskenskripte werden ausgeführt
}
Event-Handler
Als Event-Handler bezeichnet man den Einstiegspunkt zum Reagieren auf bestimmte Ereignisse. Ein Ereignis kann z. B. das Öffnen einer Maske bzw. das Laden oder die Neuanlage eines Datensatzes sein. Wird so ein Ereignis von dem Benutzer in der Oberfläche ausgelöst, so kann mit den sogenannten Callback-Methoden darauf reagiert werden. Eine Auflistung aller vorhandenen Methoden befindet sich im Anhang.
Scripting in der Detailansicht
Vergleichen von Feldinhalten
An dem folgenden exemplarischen Beispiel werden die unterschiedlichen Datentypen der unterschiedlichen Feldtypen (z. B. Textfeld, Zahlenfeld oder Nachschlagefeld) auf der Maske erläutert. Bei juristischen Personen (PersonType = PersonType-C) soll das Feld Name1 schreibgeschützt sein. Die korrekte Umsetzung würde in etwa so aussehen:
// Prüfung auf den Schlüsselwert. Der Wert im Feld PersonType ist vom Type ILookup,
// so dass der Schlüsswert über die LookupUtils ermittelt werden muss.
if (LookupUtils.getKey(FieldUtils.getValue("PersonType.Customer")).equals("PersonType-C")) { ... }
Soll hingegen das Feld MatchCode geprüft werden, müsste das Script so aussehen:
// MatchCode.value ist bereits ein Text, so dass direkt auf die equals()-Methode
// von java.lang.String zugegriffen werden kann.
if (FieldUtils.getValue("MatchCode.Customer").equals("someValue")) { ... }
Um dies zu vereinfachen, wurde die Methode ScriptUtils.equals(Object o1, Object o2)
implementiert und somit entfällt eine Typunterscheidung in der Parameterliste.
if (ScriptUtils.equals(PersonType.value, "PersonType-C")) { ... }
if (ScriptUtils.equals(MatchCode.value, "someValue")) { ... }
Scripting in der Listenansicht
Beim Scripting in der Listenansicht sind nur die die Felder der sichtbaren Spalten direkt im Zugriff. Soll auf weitere Daten zugegriffen werden, so müssen diese im Suchergebnis enthalten sein.
Die Callback-Methode beforeRenderTable()
wird bei in der Tabelle oft aufgerufen. Daher sollten diese überhaupt nur bei Bedarf deklariert werden und außerdem keine performancekritischen Operationen durchführen, wie z.B. das Ausführen von Suchen.
Die Editierbarkeit von Zellen
Über Skripting ist es möglich, in Zellen einen Schreibschutz zu setzen. Ein bestehender Schreibschutz kann aber nicht augehoben werden. Die gilt z.B. wenn Felder über Satz- oder Feldrechte schreibgeschützt sind. Hier kann in der Callback-Methode beforeRenderTable()
der Befehle TableUtils.setReadOnly()
genutzt werden.
beforeRenderTable()
{
TableUtils.setReadOnly(true, "DelegatedTo.Activity", "DelegatedBy.Activity");
)
Die Darstellung einzelner Zellen
Damit ist in erster Linie die farbliche Gestaltung (Schrift- und Hintergrundfarbe) gemeint. Ein interessanter Aspekt ist die Möglichkeit, den in der Listenansicht angezeigten Wert zu ändern. Dies bezieht sich nur auf die Anzeige, der eigentliche Wert wird nicht geändert.
beforeRenderTable()
{
TableUtils.setBackgroundColor(Color.YELLOW, "DelegatedTo.Activity", "DelegatedBy.Activity");
TableUtils.setForegroundColor(Color.BLUE, "DelegatedTo.Activity", "DelegatedBy.Activity");
TableUtils.setRendererText("Mein Text", "DelegatedTo.Activity", "DelegatedBy.Activity");
)
Löschen von Daten in der Listenansicht
Beim Löschen in der Listenansicht wird für jede selektierte Zeile die Callback-Methode beforeDelete()
aufgerufen, mit der das Löschen verhindert werden kann.
Änderung von Daten in der Listenansicht
Bei Änderungen von Feldwerten in der Listenansicht wird die Callback-Methode fieldValueChanged()
aufgerufen.
void fieldValueChanged(String fieldName, Object oldValue, Object newValue)
{
if (MaskUtils.isTableView() && ScriptUtils.equal(fieldName,"Name1.Customer"))
{
String matchCode = StringUtils.toUpper(FieldUtils.getValue("Name1.Customer"));
FieldUtils.setValue(matchCode, "MatchCode.Customer");
}
}
siehe Methoden:
Scripting im Baum
Mit dem Scripting im Baum haben Sie die Möglichkeit die Darstellung der Zweige zu verändern/hervorzuheben. Hierzu steht die Callback-Methode beforeRenderTree()
bereit.
void beforeRenderTree()
{
IContainer container = MaskUtils.getCurrentEntry();
if (ScriptUtils.equal("O", WorkSpaceScriptUtils.getValue(container, "ActStatusKey.Activity")))
{
TreeUtils.setBackgroundColor(GUIConstants.COLOR_RED);
}
}
Siehe Methode:
Scripting für Externe Aufrufe
Externe Aufrufe können über das Menü Administration Externe Aufrufe definiert werden. Über das Feld Id kann der Aufruf eindeutig identifiziert werden. Externe Aufrufe werden im Maskenskript mit der Methode LogicScriptUtils.externalInvocation(String extInvID)
aufgerufen.
Im Beispiel wird der Aufruf Teamviewer beim Betätigen des Buttons mit dem Namen klick gestartet.
void buttonClicked(String buttonName)
{
if (StringUtils.equal(buttonName, "klick"))
{
LogicScriptUtils.externalInvocation("Teamviewer");
}
else
{
DialogUtils.showMessageDialog("Hinweis", "Button hat keine Funktion.", false);
}
}
Mit Hilfe externer Aufrufe ist es auch möglich, bestimmte Skripte auszuführen. Der Aufruftyp INI, EXE oder URL führt für eine bestimmte Entität ein Skript aus. Anhand der id des Externen Aufrufs, der im Skript zur Verfügung steht, können für verschiedene Aufrufe unterschiedliche Skripte hinterlegt werden.
String externalInvocation(String id)
{
IContainer container = MaskUtils.getCurrentEntry();
if (ScriptUtils.equal("ExternerAufruf", id))
{
return "Kurzname: " + WorkSpaceScriptUtils.getValue(container, "MatchCode.Customer");
}
return "";
}
Im oben beispielhaft angegebenen Skript wird für den externen Aufruf mit der "id des INI-Aufrufs" die id als Text zurückgegeben. Für alle anderen Aufrufe wird immer ein leerer Text zurückgegeben.
Nachschlagefelder und Schlüsselfelder lesen und ändern
In CURSOR-CRM unterscheiden wir auf technischer Ebene
Nachschlagefelder: Schlagen auf Entitäten nach; Beispiel:
Hauptansprechpartner in der Aktivität.Schlüsselfelder: bieten eine einfache Auswahl Kurztext/Langtext;
Beispiel:CustTypeKey
Die meisten Schlüsselfelder haben alphanumerische Schlüsselwerte, also Strings
Manche Schlüsselfelder haben Zahlen als Schlüsselwert, zum Beispiel Projektstatus.
Feldinhalt auslesen
// Schlüsselfelder
String key = LookupUtils.getKey(FieldUtils.getValue("PersonTypeKey.Customer")); // Nachschlagefeld auf Personentyp
String description = LookupUtils.getDescription(FieldUtils.getValue("PersonTypeKey.Customer")); // Wertetyp String
Integer intergerKey = LookupUtils.getKey(FieldUtils.getValue("ProjectstatusKey.Opportunity")); // Nachschlagefeld auf Anfragen Status, nummerisches Nachschlagefeld
// Pk auslesen, z.B. für die Übergabe an eine Suche
String pk = LookupUtils.getPk(FieldUtils.getValue("PersonTypeKey.Customer"));
// Beliebiges Feld des zugeordneten Datensatzes auslesen
String freetext23 = LookupUtils.getValueFromLookup(FieldUtils.getValue("DefaultContactPerson.Activity"), "Freetext23.ContactPerson");
// Auf den zuletzt gespeicherten Feldwert zugreifen
IContainer entry = MaskUtils.getCurrentEntry();
String matchCode = WorkSpaceScriptUtils.getValue(entry, "MatchCode.Customer");
Feldinhalt ändern
// Schlüsselwert "MM" (Marketing-Mitarbeiter) eintragen.
// Man beachte, dass dasselbe Feld zweimal benannt wird.
FieldUtils.setValue(LookupUtils.lookup("MM", "FunctionKey.Employee", false), "FunctionKey.Employee");
// Nachschlagefeld ändern
// Der einzutragende Wert *muss* aus einer Suche stammen
IScriptWorkSpace ws = WorkSpaceScriptUtils.search("C2Script_ContactPerson_Search_MatchCode", "CURSOR Software AG");
IContainer entry = WorkSpaceScriptUtils.getEntry(ws, 0);
ILookup lookup = LookupUtils.convertToLookup(entry, "DefaultContactPerson.Activity");
FieldUtils.setValue("DefaultContactPerson.Activity", lookup);
Auslesen von Schlüssel/Nachschlagewerten in Suchergebnissen
// Verwende searchSingleValue() analog zu Feld.value
IScriptWorkSpace ws = WorkSpaceScriptUtils.search("C2Script_Customer_SearchMatchCode", "CURSOR Software AG");
IContainer entry = WorkSpaceScriptUtils.getEntry(ws, 0);
ILookup personType = WorkSpaceScriptUtils.getValue(entry, "PersonTypeKey.Customer");
String key = LookupUtils.getKey(personType);
String description = LookupUtils.getDescription(personType);
String pk = LookupUtils.getPk(personType);
Nachschlageverhalten beeinflussen
Die beforeLookup()
- Methode wird vor dem Nachschlagen bzw. Validieren von Nachschlagefeldern aufgerufen. Mit Hilfe des Parameters fieldName
kann das Feld bestimmt werden, auf dem nachgeschlagen wird. Dadurch können für ein Nachschlagefeld unterschiedliche Nachschlagesuchen hinterlegt werden.
void beforeLookup(String fieldName)
{
if (ScriptUtils.equal(fieldName, "DefaultLocation.Activity"))
{
FieldUtils.addPreselectionValues(fieldName, "LookupEntityLocaltion_Custom", FieldUtils.getValue("DefaultContactPerson.Activity"));
}
}
Im Beispiel wird das Nachschlage-Verhalten für das Feld Anschlussobjekt (DefaultLocation.Activity
) in der Aktivität modifiziert. doLookup()
gilt übrigens auch für Schlüsselfelder.