EDD9ADFBC6E745A6AD7251F8A545020A
  • Thomas Pollinger
  • 09.05.2019
  • DE

SmartExtensions: Funktionsaufbau und Arbeitsweise

Nachdem wir uns den RQL-Connector inkl. Session und den Objektaufbau angesehen haben. Werden wir uns jetzt dem Aufbau und der Funktionsweise der Komponenten-Bibliothek zuwenden. Es soll aufgezeigt werden wie man Parameter übergibt, welche Rückgabewerte möglich sind und wie man an weitere Information zur Laufzeit kommen kann.
 

Struktur

Man findet die s.g. Components-Libraries ganz in der Nähe des RQL-Connector. Parallel zum Verzeichnis controller liegt libraries und dort findet man die Funktions-Bibliotheken. Der Aufbau der Dateinamen ist auch angelehnt an den RQL-Connector:

rql.connector.functions.[Kapitel und Abschnitt aus der RQL-Dokumentation].js

und das sieht dann so aus:

rql.connector.functions.asynchronous.processes.js
rql.connector.functions.clipboard.js
rql.connector.functions.content.elementdata.js
rql.connector.functions.content.pages.js
rql.connector.functions.contentclasses.general.js
rql.connector.functions.favorites.js
...

Somit ist man schnell in der Lage zu entscheiden, welche Funktions-Blöcke man innerhalb der Erweitung eingebunden werden sollen. Dabei gilt die Ladereihenfolge Connector vor Funktionen.
 

Funktionsaufbau

Der Aufbau einer solchen Funktion hat auch ein Schema, welches für die Paramenter-Übergabe immer 1:1 das gleiche von Funktion zu Funktion bleibt. Hier mal ein Beispiel einer solchen Funktion für eine Abfrage der Applikations-Server-Daten:

/* ----- ----- ----- ----- ----- ----- ----- ----- */
/**
 * Determining Application Server Data
 * RQL documentation 16.0.3
 * 
 * You can display important data of the application server. You have to know the server GUID.
 * 
 * @author Thomas Pollinger 
 * @version {build-release}
 * 
 * @param {object} rqlConnectorObj
 * @param {object} requestParam
 * @param {function} callbackFunc
 * 
 * @returns {string}
 */
function getServerVersion(rqlConnectorObj, requestParam, callbackFunc) {
    let thisFunction = {
        Name: arguments.callee.name,
        DebugMode: rqlConnectorObj.debugMode
    }
    thisFunction.DebugMode && console.log(`\n\n${arguments.callee.name}()\nfn =>`);
    let rqlRequestTemplate = `<ADMINISTRATION><EDITORIALSERVER action='load' guid='${requestParam.EditorialServerGuid}' mainlicense='0' /></ADMINISTRATION>`;
    let rqlRequestBody = $.parseXML(rqlRequestTemplate);
    rqlConnectorObj
        .sendRql(
            (new XMLSerializer()).serializeToString(rqlRequestBody),
            false,
            function (rqlResponse) {
                let processingEditorialServer = $(rqlResponse).find("EDITORIALSERVER");
                let responseData = $(processingEditorialServer).attr("version");
                rqlConnectorObj.info.version = $(processingEditorialServer).attr("version").split(" - ");
                rqlConnectorObj.info.version[1]
                    .split(".")
                    .forEach(
                        elementData => {
                            rqlConnectorObj.info.version.push(parseInt(elementData, 10));
                        }
                    );
                if (callbackFunc) {
                    thisFunction.DebugMode && console.info(`Received response from SOAP/RQL Connector Object.`);
                    thisFunction.DebugMode && console.log(`Type: ${typeof responseData}`);
                    thisFunction.DebugMode && console.log(responseData);
                    thisFunction.DebugMode && console.log(`<= fn\n\n\n`);
                    callbackFunc(responseData);
                } else {
                    thisFunction.DebugMode && console.warn(`No callback function defined at ${thisFunction.Name}().\nResponse:`);
                    thisFunction.DebugMode && console.log(`Type: ${typeof responseData}`);
                    thisFunction.DebugMode && console.log(responseData);
                    thisFunction.DebugMode && console.log(`<= fn\n\n\n`);
                    return (responseData);
                }
            }
        );
}
/* ----- ----- ----- ----- ----- ----- ----- ----- */

Der Aufbau unterteilt sich in mehrere Bereiche:

jsDoc

/**
 * Determining Application Server Data
 * RQL documentation 16.0.3
 * 
 * You can display important data of the application server. You have to know the server GUID.
 * 
 * @author Thomas Pollinger 
 * @version {build-release}
 * 
 * @param {object} rqlConnectorObj
 * @param {object} requestParam
 * @param {function} callbackFunc
 * 
 * @returns {string}
 */

Funktions-Kopf

function getServerVersion(rqlConnectorObj, requestParam, callbackFunc) {
    let thisFunction = {
        Name: arguments.callee.name,
        DebugMode: rqlConnectorObj.debugMode
    }
    thisFunction.DebugMode && console.log(`\n\n${arguments.callee.name}()\nfn =>`);
    let rqlRequestTemplate = `<ADMINISTRATION><EDITORIALSERVER action='load' guid='${requestParam.EditorialServerGuid}' mainlicense='0' /></ADMINISTRATION>`;
    let rqlRequestBody = $.parseXML(rqlRequestTemplate);

Abfrage und Verarbeitung

    rqlConnectorObj
        .sendRql(
            (new XMLSerializer()).serializeToString(rqlRequestBody),
            false,
            function (rqlResponse) {
                let processingEditorialServer = $(rqlResponse).find("EDITORIALSERVER");
                let responseData = $(processingEditorialServer).attr("version");
                rqlConnectorObj.info.version = $(processingEditorialServer).attr("version").split(" - ");
                rqlConnectorObj.info.version[1]
                    .split(".")
                    .forEach(
                        elementData => {
                            rqlConnectorObj.info.version.push(parseInt(elementData, 10));
                        }
                    );

Ergebnis-Rückgabe

                if (callbackFunc) {
                    thisFunction.DebugMode && console.info(`Received response from SOAP/RQL Connector Object.`);
                    thisFunction.DebugMode && console.log(`Type: ${typeof responseData}`);
                    thisFunction.DebugMode && console.log(responseData);
                    thisFunction.DebugMode && console.log(`<= fn\n\n\n`);
                    callbackFunc(responseData);
                } else {
                    thisFunction.DebugMode && console.warn(`No callback function defined at ${thisFunction.Name}().\nResponse:`);
                    thisFunction.DebugMode && console.log(`Type: ${typeof responseData}`);
                    thisFunction.DebugMode && console.log(responseData);
                    thisFunction.DebugMode && console.log(`<= fn\n\n\n`);
                    return (responseData);
                }
            }
        );
}
/* ----- ----- ----- ----- ----- ----- ----- ----- */

Da sich das komplette Framework noch in der Entwicklung befindet, sind noch nicht alle Hilfsfunktionen und sich wiederholende Blöcke ausgelagert. Dies wird über die Zeit noch umgebaut und damit werden die Dateien noch schlanker.
 

Debug-Mode

Jede Funktion kann in einen s.g. DebugMode versetzt werden. Wenn dieser Modus aktiviert wird, werden alle wichtigen Informationen in die Browser-Konsole geschrieben. Damit ist man immer in der Lage zu sehen, wenn etwas nicht so klappt wie man möchte, wo es evtl. klemmen könnte.

...
function getServerVersion(rqlConnectorObj, requestParam, callbackFunc) {
    let thisFunction = {
        ...
        DebugMode: rqlConnectorObj.debugMode
    }
    thisFunction.DebugMode && console.log(`\n\n${arguments.callee.name}()\nfn =>`);
    ...
    rqlConnectorObj
        .sendRql(
            ...
                if (callbackFunc) {
                    thisFunction.DebugMode && console.info(`Received response from SOAP/RQL Connector Object.`);
                    thisFunction.DebugMode && console.log(`Type: ${typeof responseData}`);
                    thisFunction.DebugMode && console.log(responseData);
                    thisFunction.DebugMode && console.log(`<= fn\n\n\n`);
                    ...
                } else {
                    thisFunction.DebugMode && console.warn(`No callback function defined at ${thisFunction.Name}().\nResponse:`);
                    thisFunction.DebugMode && console.log(`Type: ${typeof responseData}`);
                    thisFunction.DebugMode && console.log(responseData);
                    thisFunction.DebugMode && console.log(`<= fn\n\n\n`);
                    ...
                }
            }
        );
}

Der Debug-Mode wird immer am RQL-Connector gesetzt und damit versetzt alles komplett in diesen Modus. Das ist notwendig, damit man das Zusammenspiel der RQL-Abfragen innerhalb des Frameworks sehen kann.
 

Parameter-Übergabe

Die Übergabe der Parameter (Werte) an die Funktion ist auch kurz und knapp bzw. bei allen Funktionen gleich:

function getServerVersion(rqlConnectorObj, requestParam, callbackFunc) {
...
}

es wird immer das rqlConnectorObj, die requestParam und eine callbackFunc übergeben. Das rqlConnectorObj wird einmalig zentral angelegt. Die requestParam entsprechen einer JSON-Objekt-Struktur, immer entsprechend zu der jeweiligen Funktion. Die callbackFunc ist optional und hier wird einfach die Funktion übergeben, welche nach Ende der Funktion ausgeführt werden soll.

Der Vorteil dieser einheitlichen Parameter-Übergabe ist, dass man diese ohne Probleme erweitern kann, ohne dass man den Überblick verliert. Man muss auch nicht jeden einzelnen Parameter innerhalb des Funktionskopf einzeln definieren. 
 

Verarbeitung

Der Block indem die RQL-Abfrage und Verarbeitung der Ergebnisse erfolgt, hat auch ein festes Muster. Damit ist man in der Lage, schnell und einfach weitere Funktionen zu erstellen. Hier mal ein paar Beispiele:

rqlConnectorObj
    .sendRql(
        (new XMLSerializer()).serializeToString(rqlRequestBody),
        false,
        function (rqlResponse) {
            let processingEditorialServer = $(rqlResponse).find("EDITORIALSERVER");
            let responseData = $(processingEditorialServer).attr("version");
            rqlConnectorObj.info.version = $(processingEditorialServer).attr("version").split(" - ");
            rqlConnectorObj.info.version[1]
                .split(".")
                .forEach(
                    elementData => {
                        rqlConnectorObj.info.version.push(parseInt(elementData, 10));
                    }
                );

oder:

rqlConnectorObj
    .sendRql(
        (new XMLSerializer()).serializeToString(rqlRequestBody),
        false,
        function (rqlResponse) {
            let processingDrive = $(rqlResponse).find("DRIVE");
            let responseData = [];
            processingDrive
                .each(
                    function () {
                        let elementData = {
                            Drive: encodeURIComponent(`${$(this).attr("name")}:\\`),
                            Name: encodeURIComponent($(this).attr("name"))
                        }
                        responseData.push(elementData);
                    }
                );

oder so:

rqlConnectorObj
    .sendRql(
        (new XMLSerializer()).serializeToString(rqlRequestBody),
        false,
        function (rqlResponse) {
            let processingDrive = $(rqlResponse).find("DRIVE");
            let processingDirectories = $(rqlResponse).find("DIRECTORIES");
            let processingFolder = $(rqlResponse).find("FOLDER");
            let responseData = [];
            processingFolder
                .each(
                    function () {
                        let elementData = {
                            Drive: encodeURIComponent($(processingDrive).attr("name")),
                            Directory: encodeURIComponent($(processingDirectories).attr("name")),
                            Name: encodeURIComponent($(this).attr("name")),
                            LiteralPath: encodeURIComponent($(this).attr("path"))
                        }
                        responseData.push(elementData);
                    }
                );

Auch hier gibt es noch vieles, was man besser, optimierter und schöner machen kann. Dies wird auch über die Zeit Stück für Stück optimiert und verbessert.
 

Rückgabewerte

Die Rückgabewerde sind überschaubar:

  • Value (String/Int)
  • Array
  • Object

Damit ist die Verarbeitung der Daten in der Regel auch sehr einfach und man kann innerhalb der Erweiterungs-Logik (Plug-Ins) sich ebenfalls auf einen standardisierten Ablauf konzentrieren. Alle RÜckgaben werden in einer JSON-Struktur abgebildet, welche auch über alle Funktionen einheitlich ist. Diese Struktur wird innerhalb des Verarbeitungsblocks erzeugt. Hier mal einige Beispiele:

let responseData = $(processingEditorialServer).attr("version");

oder:

processingDrive
    .each(
        function () {
            let elementData = {
                Drive: encodeURIComponent(`${$(this).attr("name")}:\\`),
                Name: encodeURIComponent($(this).attr("name"))
            }
            responseData.push(elementData);
        }
    );
responseData
    .sort(
        function (a, b) {
            let valueA = a.Name.toLowerCase(),
                valueB = b.Name.toLowerCase();
            if (valueA < valueB) {
                return -1;
            }
            if (valueA > valueB) {
                return 1;
            }
            return 0;
        }
    );

oder so:

let processingDrive = $(rqlResponse).find("DRIVE");
let processingDirectory = $(rqlResponse).find("DIRECTORY");
let responseData = {
    Drive: encodeURIComponent($(processingDrive).attr("name")),
    LiteralPath: encodeURIComponent($(processingDirectory).attr("name")),
    Name: encodeURIComponent($(processingDirectory).attr("newdir"))
}

Durch die einheitliche Form der Rückgabe, wird die Verarbeitung innerhalb der Erweitungs-Logik einfacher und standardisierter.


Fazit

Wie schon letztes mal erwähnt, ist der RQL-Connector für das Framework der zentrale Dreh- und Anglepunkt, wenn es um die Kommunikation mit dem Management Server geht. Jedoch ohne gezielte RQL-Abfragen kommt man nicht an die gewünschten Daten. Dazu habe ich mich im ersten Wurf für den Aufbau von Funktionen entschieden. Damit man schnell und einfach verständlich an eine Lösung kommt. Es gibt viele Möglichkeiten dies zu tun, welche vermutlich sich über die Zeit auch integrieren werden. Somit ist hier noch nicht das letzte Wort gesprochen und ich bin für Vorschläge offen und freue mich diese zu diskutieren :)

Viel Spaß beim ausprobieren, denn der Source-Code liegt bereits im GitHub frei zum runterladen.

Im nächsten Artikel beschäftigen wir uns mit SmartExtensions: Browser-Console und Debugging ;)


Über den Autor:
Thomas Pollinger

... ist Senior Site Reliability Engineer bei der Vodafone GmbH in Düsseldorf. Seit dem Jahr 2007 betreut er zusammen mit seinen Kollegen die OpenText- (vormals RedDot-) Plattform Web Site Management für die deutsche Konzernzentrale.

Er entwickelt Erweiterungen in Form von Plug-Ins und PowerShell Skripten. Seit den Anfängen in 2001 (RedDot CMS 4.0) kennt er sich speziell mit der Arbeitweise und den Funktionen des Management Server aus.