Arbeiten mit Daten in der Konsole

Manager

Die Verwaltung der aktuellen Daten kann über den Manager Alvine.Package.Console.Host.dataManager erfolgen. Nachdem ein Connector angelegt wurde, kann ein Arbeitskontext WorkingContext mit der Methode Manager.init(connector, key, path) erstellt werden.

Der connector definiert die Verbindung zur API.

Der Parameter key dient zum Zugriff auf die ausgewählten Daten.

Das path definiert den Ausschnitt, der aktuell Verfügbar sein soll. Dies ist wichtig, wenn zum Beispiel eine komplette Sammlung mit vielen Datensätzen geladen wird, aber nur ein Datensatz aktuell bearbeitet werden soll.

Das Ergebnis von Manager.init(connectorOrUrl, key, path) ist ein Objekt vom Typ WorkingContext.

Das WorkingContext-Objekt kann jederzeit über den Schlüssel key mit der Methode Manager.getWorkingContext(key) abgerufen werden.

Mit Manager.removeWorkingContext(key) wird ein WorkingContext-Objekt aus dem Manager entfernt. Dabei wird das WorkingContext zurückgesetzt (alle Daten entfernt). Zugriffe auf Referenzen eines entfernten Contextes erzeugen eine Exception.

var url = "/api/account/whoami?facet=personal,addresses,relationship";
var connector = new Alvine.Package.Console.Platform.Storage.Connector(url);

var datakey = 'myDataKey';
var promise = Alvine.Package.Console.Host.dataManager.init(connectorOrUrl, datakey);

promise.then(workingcontext => {
     // code
});

Mit der Methode Manager.post(connector) werden die aktuellen im Connector gespeicherten Daten an die API übertragen.

Hinweis

Die Daten aus der Registry und des WorkingContext werden nicht übertragen. Diese müssen vorher mit WorkingContext.commit() eingereicht werden.

Nach dem Senden können die Daten des Connector mit der Methode Manager.reset(connector, full) zurückgesetzt werden. Es wird die Historie gelöscht und falls full gleich true ist, wird auch das gespeicherte Datum gelöscht.

Hinweis

Der aktuellen Daten und die Historie des WorkingContext sind davon nicht betroffen.

Mit der Methode Manager.getOpenHistories(filter) lassen sich alle offenen Änderungen abrufen. Über den Parameter filter lassen sich die gewünschten Ergebnismengen holen.

Manager.getOpenHistories({
    path: 'example.path.to.dataset',
    context: '/example/context',
    key: 'mykey'
});

Die Filter path und context werden auf gleichheit geprüft, beim Schlüsselfilter key wird auf den Anfang der Zeichenkette geprüft. Mit dem Filter key würden im obigen Beispiel einträge mit Schlüsseln mykey1, mykey2, mykey usw zurückgegeben. Hingegen würde ein Eintrag mit dem Schlüssel _mykey nicht zurückgegeben.

Connector

Ein Connector legt die Verbindung zwischen der Server-API und der Konsole fest. Im folgenden Beispiel wird ein Connector auf die whoami-API definiert. Werden Daten über den Connector von der API abgerufen, so speichert der Connector diese im localStorage mit dem Prefix cc.

Ein Connector verfügt über eine Historie der Änderungen. Bei einer Änderung der Daten im Browser wird eine Historie mit dem Prefix cch im localStorage angelegt.

url = "/api/account/whoami?facet=personal,addresses";
connector = new Alvine.Package.Console.Platform.Storage.Connector(url);

Das Connector-Objekt implementiert die Observer-Schnittstelle mit Connector.attachObserver(observer), Connector.containsObserver(observer), Connector.detachObserver(observer, recursive), Connector.detachObserver(recursive), Connector.preventNotification(recursive), Connector.promoteNotification() und Connector.notifyObserver().

Ein Connector besitzt eine eindeutige ID, die über die Methode Connector.getID() geholt werden kann. Diese eindeutige ID wird aus der URL erzeugt und ist für jede URL eindeutige. Zwei Connectoren mit der gleichen URL haben die gleiche ID und verwenden den gleichen Speicherplatz im localStorage.

Dadurch kann auch beim Neuladen der Seite auf die Daten des Connectors zugegriffen werden.

Falls die Daten in einer anderen Form, als vom Server geliefert, gebraucht werden, so kann man die Daten mit jeweils einem Lese- und einem Schreib-Callback bearbeiten.

Die mit Connector.setReadDataCallback(callback) gesetzte Funktion callback wird nach dem Empfangen der Daten aufgerufen und muss das gewünschte Dataset zurückgeben. Vor dem Senden an den Server wird die mit Connector.setWriteCallback(callback) gesetzte Funktion aufgerufen.

Connector.setReadDataCallback(dataset => {
  // do something
  return dataset;
});


Connector.setWriteDataCallback((dataset, history) => {
  // do something
  return dataset;
});

Über die Methode Connector.setWriteCallback(callback) kann das Senden der Daten selber implementiert werden. Werden die Daten über Manager.post(connector) abgeschickt und ein Write-Callback wurde definiert, so wird der Funktion der aktuelle Datensatz, die URL und die Historie übergeben.

Als Rückgabewert sollte ein Promise zurückgegeben werden. Das Promise ist dann das Ergebnis von Manager.post(connector).

Connector.setWriteCallback((dataset, url history) => {
  // do something

  // Wichtig: Als Array zurückgeben (erster Eintrag ist das Dataset, zweiter die URL)
  return new Promise;
});

Historie

Die Historie ist eine Collection mit den Diffs der Änderungen. Jeder Eintrag entspricht einer Änderung.

kind - indicates the kind of change; will be one of the following:
  N - indicates a newly added property/element
  D - indicates a property/element was deleted
  E - indicates a property/element was edited
  A - indicates a change occurred within an array
path - the property path (from the left-hand-side root)
lhs - the value on the left-hand-side of the comparison (undefined if kind === 'N')
rhs - the value on the right-hand-side of the comparison (undefined if kind === 'D')
index - when kind === 'A', indicates the array index where the change occurred
item - when kind === 'A', contains a nested change record indicating the change that occurred at the array index

Die Methode History.getKey() gibt den Schlüssel, History.getUrl() die Url der dazugehörigen Connection, History.getPath() den Pfad zum Datensatz und History.getContext() den Context zurück.

WorkingContext

Ein WorkingContext wird vom Manager über Manager.init(connectorOrUrl, key, path) erstellt und kapselt den Zugriff auf die aktuellen Arbeitsdaten. Das Ergebnis von Manager.init() ist ein Promise mit dem WorkingContext als Wert.

Alle initialisierten Kontexte lassen sich über die Methode Manager.getWorkingContexts() holen. Das Ergebnis ist eine Alvine.Types.Map mit den WorkingContext Objekten.

var workingcontexts=Alvine.Package.Console.Host.dataManager.getWorkingContexts();

Ein einzelner Kontext lässt sich über den key und die Methode Manager.getWorkingContext(key) holen.

var datakey = 'myDataKey';
var workingcontext=Alvine.Package.Console.Host.dataManager.getWorkingContext(datakey);

Mit der Methode WorkingContext.activate(registryKey) wird eine Arbeitskopie der Daten in
der Alvine.Registry mit dem Schlüssel registryKey angelegt.

var registryKey='formdata';
workingcontext.activate(registryKey);

// Überprüfen der aktivierung
Alvine.Registry.get(registryKey);

Änderungen können mit der Methode WorkingContext.revert(registryKey) rückgängig gemacht werden. Wird ein registryKey übergeben, so wird neben der internen Datenstruktur das Ergbenis auch in die Registry rückgängig gemacht.

Fertige Änderungen können schließlich mit WorkingContext.commit() an den Connector übergeben werden.

Die aktuelle Historie lässt sich mit WorkingContext.getHistory() auslesen.

Mit der Methode WorkingContext.applyDiff(history, revert) kann eine Historie auf einen WorkingContext angewendet werden. Wird für den Parameter revert der Wert true übergeben, so wird linke und rechte Seite in der Historie Seite getauscht.

Beispiel

Das folgende Beispiel lädt die erste Seite der Produktdaten mit drei Einträgen. Die Daten werden dann in die Registry in den Schlüssel liste übertragen

var page = 1;
var url = "/api/commerce/item/search?q=iid>0&fields=iid,name,masterNumber&count=10&page="+page;
var registryListKey = 'list';

var connector = new Alvine.Package.Console.Platform.Storage.Connector(url);
Alvine.Package.Console.Host.dataManager.init(connector, 'items', 'dataset').then(workingContext => {
    workingContext.activate(registryListKey);
    console.log('workingContext', workingContext);
}).catch(e => console.log('error', e));

Als nächstes soll der Zugriff auf einen Datensatz erfolgen. Unter der Annahme das ein Produkt mit der Produktnummer (iid) 500 enthalten ist, erfolgt die initialisierung dieser Daten mittels folgendem Code.

var iid = 500;
var registryFormKey = 'formdata';

Alvine.Package.Console.Host.dataManager.init(connector, 'item-'+iid, 'dataset.'+iid).then(workingContext => {
    workingContext.activate(registryFormKey);
}).catch(e => console.log('error', e));

Jetzt befinden sich die Daten des Produktes iid=500 in der Registry mit dem Schlüssel formdata.

Änderungen an den Daten erfolgen über direkte Manipulation der Daten in der Registry. In der folgenden Anweisung wird das Feld name auf den wert test gesetzt.

Alvine.Registry.get('formdata').set('name','test');

Dies führt dazu, dass im localStorage-Objekt ein Historie-Objekt angelegt wird. Wird die Seite neu geladen und die Registry über den obigen Code initialisiert, so hat der Schlüssel name den Wert test.

Zu diesem Zeitpunkt ist der Wert auf dem Server noch immer eine leere Zeichenkette.

Soll eine Auswahl der geänderten Daten - zum Beispiel in einem Dropdown - angezeigt werden, so kann auf die Daten mittels der Methode Manager.getOpenHistories(filter) zugegriffen werden. Im folgende Code werden die Änderungen geholt und die Registry auf die Werte des ersten Eintrags gesetzt.

// Änderungen im selben Kontext auslesen
var opens = Alvine.Package.Console.Host.dataManager.getOpenHistories({context: Alvine.Package.Console.Host.getLocation().getContext()});
// opens ist vom Type Alvine.Types.Collection

// Alle Schlüssel der Änderungen ausgeben
opens.each(history => {
    console.log(history.getKey());
});

// Einen Eintrag auswählen und Daten holen
var index = 0;
var selected = opens.getIndex(index);

Alvine.Package.Console.Host.dataManager.init(connector, selected.getKey(), selected.getPath()).then(workingContext => {
    console.log('workingContext', workingContext);
    workingContext.activate('formdata');
}).catch(e => console.log('error', e));

Wir können die Änderungen jetzt mittels workingContext.revert() zurücknehmen. In diesem Fall ändert sich die Registry nicht automatisch. Hierzu ist ein gesonderter Befehl workingContext.activate(registryFormKey) notwendig.

Hinweis

Andere WorkingContext-Objekte die auf die gleichen Daten zugreifen, haben zu diesem Zeitpunkt noch die Originaldaten und nicht die hier gemachten Änderungen.

Um die Änderungen in die Hauptdaten zu integrieren wird die Methode workingContext.commit() aufgerufen. Ab diesem Zeitpunkt haben alle WorkingContext-Objekte auf diese Daten zugriff.

Zu diesem Zeitpunkt ist der Wert auf dem Server noch immer eine leere Zeichenkette.

Jetzt erfolgt die Übertragung an den Server mittels Manager.post(connector).


connector.setWriteCallback((currentData, url, history) => {

    var set;

    set = new Set;

    history.each(h => {

        h.forEach(d => {

            var iid, path;

            path = d.path;

            // Path ist in diesem Fall data.dataset.data.<iid>.data.....
            if(!path||!path[3]) {
                return;
            }

            iid = path[3];
            // Alle zu speichernden IID sammeln
            if(iid>0) {
                set.add(iid);
            }
        });
    });

    var items = currentData.get('dataset');

    promises = [];
    set.forEach(iid => {

        var item = items.get(iid);
        // Hier die geänderten Daten dann an den Server senden
        promises.push(
          fetch("/api", {
            method: "POST",
            mode: "cors",
            //...
        }));

    });

    return Promise.all(promises);

});

workingContext.commit().then(() => {

    Alvine.Package.Console.Host.dataManager.post(connector)
       .then(result => {
       console.log(result);
           // Historie löschen
           //Alvine.Package.Console.Host.dataManager.reset(connector);
       }
       ).catch(e=>console.log(e));

}).catch(error => {

});

UML-Diagramm

Im folgenden ist der Ablauf und das Zusammenspiel der einzelnen Objekte als Sequenzdiagramm abgebildet.

uml diagram