Skip to main content
Skip table of contents

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.

JAVA
// 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
JAVA
// 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
JAVA
// 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
JAVA
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.

JAVA
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.

JAVA
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

GROOVY
/** 
 * 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.

CODE
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.

CODE
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:

CODE
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

JAVA
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:

JAVA
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.

CODE
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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:

JAVA
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 +=:

JAVA
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:

JAVA
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.

JAVA
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.

JAVA
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. 

JAVA
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.

JAVA
// 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.

JAVA
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:

JAVA
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.

JAVA
// 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

JAVA
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.

JAVA
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
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):

JAVA
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
JAVA
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
JAVA
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 anstatt double, 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
// 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
// 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
// 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
// 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
// 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
// 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.

GROOVY
// 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.

GROOVY
// 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:

CODE
[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

JAVA
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.

JAVA
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:

JAVA
// 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:

JAVA
// 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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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.

JAVA
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

JAVA
// 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

JAVA
// 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

JAVA
// 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.

GROOVY
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.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.