SAP UI5 und Bestätigungsfenster: Nochmals zum Kontext

Wunder geschehen - jeder Programmierer wird Ihnen das sagen. (anstelle der Inschrift)

Guten Tag!

Ein Beispiel wird einfach sein.

Es gibt eine Ansicht:

<mvc:View 
    controllerName="MyController" 
    xmlns="sap.m" 
    xmlns:core="sap.ui.core" 
    xmlns:mvc="sap.ui.core.mvc">
    <Button text="!" press="handlePress" />
    <Table 
        items="{view>/list/items}" 
        mode="MultiSelect" 
        selectionChange="handleTableSelection">
        <columns>
            <Column>
                <Text text="" />
            </Column>
            <Column>
                <Text text="" />
            </Column>
        </columns>
        <items>
            <ColumnListItem>
                <cells>
                    <ObjectIdentifier title="{view>id}" />
                    <Text text="{view>description}" />
                </cells>
            </ColumnListItem>
        </items>
    </Table>
</mvc:View>

und der gleiche "ausgetrickste" Controller:

sap.ui.controller("MyController", {
        
    onInit: function () {
        this.__oViewModel = new sap.ui.model.json.JSONModel({
            list: {
                items: [
                    { id: 1, description: 'one' },
                    { id: 2, description: 'two' },
                    { id: 3, description: 'three' }
                ],
                selected: []
            }
        });
        this.getView().setModel(this.__oViewModel, "view");
    },

    handlePress: function (oEvent) {
        sap.m.MessageToast.show(this.__oViewModel.getProperty('/list/selected').join(', '));
    },

    handleTableSelection: function (oEvent) {
        const aSelectedCtx = oEvent.getSource().getSelectedContexts(),
            aSelected = aSelectedCtx.map(o => o.getObject().id);
        this.__oViewModel.setProperty('/list/selected', aSelected);
    }

});

Um zu sehen und sich zu verhalten, wird dies alles erwartet:

Bild

Wenn Sie auf die Schaltfläche klicken, werden die ausgewählten Bezeichner mit einem Komma angezeigt (die Prüfung auf nicht leere Arrays und andere UI-Kleinigkeiten bleibt außerhalb des Geltungsbereichs des Artikels).

Aber stellen Sie sich das vor, indem Sie auf die Schaltfläche "Los geht's!" Klicken. Sie müssen nicht nur die Bezeichner der ausgewählten Elemente anzeigen, wie dies jetzt geschieht, sondern sie auch aus der Datenbank löschen.

Solche Dinge müssen natürlich nur nach zusätzlicher Bestätigung durch den Benutzer durchgeführt werden.

Nun, triviale Angelegenheit - fügen Sie ein Ansichtsfragment für den Bestätigungsdialog hinzu:

ConfirmDialog.fragment.xml

<core:FragmentDefinition
    xmlns='sap.m'
    xmlns:core='sap.ui.core' >
    <Dialog
        id='confirmDialog'
        title=''
        type='Message'
        state='Warning'>
        <content>
            <Label text='  ?' />
        </content>
        <beginButton>
            <Button
                text=''
                press='handleConfirmBtn'/>
        </beginButton>
        <endButton>
            <Button
                text=''
                press='handleCancelBtn'/>
        </endButton>
    </Dialog>
</core:FragmentDefinition>

Und der Controller dazu:

ConfirmDialog.controller.js

sap.ui.define([
    "sap/ui/base/ManagedObject"
], function (ManagedObject) {
    "use strict";

    return ManagedObject.extend("project.ConfirmDialog", {

        constructor: function (oView, fnConfirmBtn) {
            this.__oView = oView;
            this.__fnConfirmBtn = fnConfirmBtn;
        },

        exit: function () {
            delete this.__oView;
            delete this.__fnConfirmBtn;
        },

        open: function () {
            const oView = this.__oView;
            let oDialog = oView.byId("confirmDialog");

            if (!oDialog) {
                const oFragmentController = {
                    handleConfirmBtn: () => {
                        this.__fnConfirmBtn();
                        oDialog.close();
                    },
                    handleCancelBtn: () => {
                        oDialog.close();
                    }
                };
                oDialog = sap.ui.xmlfragment(oView.getId(), "project.view.fragment.ConfirmDialog", oFragmentController);
                oView.addDependent(oDialog);
            }
            oDialog.open();
        }

    });
});

Wie Sie sehen, ist auf der Ebene der Beispiele des SAP UI5 SDK alles recht klassisch.
Um dieses Dialogfeld aufzurufen, definieren wir den Handler unserer Hauptschaltfläche wie folgt neu:

handlePress: function (oEvent) {
    const aSelected = this.__oViewModel.getProperty('/list/selected');
    this.__confirmDialog = new ConfirmDialog(this.getView(), () => {
        aSelected.forEach(o => {
            //    
        });
        this.__confirmDialog.exit();
    });
    this.__confirmDialog.open();
}

Das heißt, hier erstellen wir unseren Dialog, indem wir als Parameter die "Haupt" -Ansicht, in der sie sich befinden wird, und die Bestätigungshandlerfunktion übergeben.

Hier kamen wir zu den interessantesten.

Überprüfen Sie vor dem Einschalten noch einmal den Code des Dialog-Controllers.

Sehen Sie etwas Seltsames?

Nein?

Schalten Sie dann den Trace-Modus ein und klicken Sie nach Auswahl der ersten beiden Elemente auf die Schaltfläche.

        handlePress: function (oEvent) {
            const aSelected = this.__oViewModel.getProperty('/list/selected');  
            // aSelected = [1,2], ,  
            this.__confirmDialog = new ConfirmDialog(this.getView(), () => {
                aSelected.forEach(o => {
                    //   aSelected = [1,2]
                });
                this.__confirmDialog.exit();
            });
            this.__confirmDialog.open();
        }

Hurra, alles funktioniert! Es scheint, was ist der Haken? Es ist möglich, dass der Benutzer unersättlich wird und etwas anderes löschen möchte. Versuchen wir, seine Aktionen vorherzusagen: Wählen Sie das dritte Element aus und klicken Sie auf „Los geht's!“:

        handlePress: function (oEvent) {
            const aSelected = this.__oViewModel.getProperty('/list/selected');  
            // aSelected = [3]
            this.__confirmDialog = new ConfirmDialog(this.getView(), () => {
                aSelected.forEach(o => {
                    //   aSelected = [1,2] ???

Warum [1,2], frag mich dich. Es ist gut für dich, es gibt jemanden zu fragen.

Und als ich zum ersten Mal ein so hässliches Verhalten des Dolmetschers sah, begann ich still an mir selbst zu zweifeln. Ich habe lange Zeit mit verschiedenen Frameworks gearbeitet, aber ich bin noch nie auf so etwas gestoßen: Es ist bekannt, dass const die Unveränderlichkeit von Objekten und Arrays während ihrer gesamten Existenz nicht garantiert - aber aSelected wird nirgendwo anders erwähnt. Also tauchte er auf, enteignete sich und wurde nun zu einem Rückruf weitergeleitet.

Aber ich werde dich lange nicht quälen.

Es geht um die Kontexte und Abschlüsse, die alle Programmierer von js lieben.

Das heißt, während der ersten Ausführung von handleConfirmBtn haben wir immer noch einen Link zu seinem Handler zusammen mit dem gesamten Kontext (einschließlich aSelected). Und mit nachfolgenden Löschbestätigungen wird sie gerufen.

Die Art und Weise, den Fehler zu korrigieren, war nicht so einfach und eindeutig. Es reichte nicht aus, nur die oFragmentController-Deklaration zu verschieben (nach dem ersten Aufruf ging der Kontext verloren). Und das Folgende war der prägnanteste Weg (ich werde nur den Code der offenen Methode angeben):

open: function () {
    const oView = this.__oView;
    let oDialog = oView.byId("confirmDialog");

    if (!oDialog) {
        const oFragmentController = {
            handleConfirmBtn: function () {
                this.__fnConfirm();
                oDialog.close();
            },
            handleCancelBtn: function () {
                oDialog.close();
            }
        };
        oDialog = sap.ui.xmlfragment(oView.getId(), "project.view.fragment.ConfirmDialog", oFragmentController);
        oDialog.controller = oFragmentController;
        oView.addDependent(oDialog);
    }
    oDialog.controller.__fnConfirm = this.__fnConfirmBtn.bind(this);
    oDialog.open();
}

Achten Sie auf die letzten 4 ausführbaren Zeilen: Auf diese Weise "rolle" ich den Zeiger auf den aktuellen Handler zusammen mit dem richtigen Kontext.

Im Großen und Ganzen mussten der Originalversion nur ein paar Zeilen hinzugefügt werden.

Es war möglich, einen zu treffen
oDialog.setBeginButton(new sap.m.Button({ text: '', press: this.__fnConfirmBtn }));

Aber hier wird jedes Mal, wenn Sie einen Dialog öffnen, eine neue Schaltfläche erstellt. Dies ist nicht in Ordnung.

Ich habe auch die Option in Betracht gezogen, den Rückruf über oDialog.getBeginButton () zu aktualisieren. AttachPress, aber es hängt nur ein zusätzlicher Handler, und es war widerlich, alle über .detachPress verfügbaren in einer Zeile zu entfernen.

Hier kam ein solches Abenteuer fast aus heiterem Himmel heraus (weit entfernt vom ersten ... ah, ui5!). PS Übergabe

einer Funktion an die Konstruktorfunktion des Objekts, in der eine der Methoden ein Controller-Objekt enthält, das Verweise auf Handlerfunktionen enthält. Es scheint, dass was schief gehen könnte?

Im Großen und Ganzen ist die beschriebene Situation ein Merkmal der Sprache, und das UI5-Framework erlaubt es einfach nicht, sie schön zu lösen.

All Articles