Verwenden eines eigenen Darstellungsmusters

Verwenden eines eigenen Darstellungsmusters in Hilo (Windows Store-Apps mit JavaScript und HTML)

[ Dieser Artikel richtet sich an Windows 8.x- und Windows Phone 8.x-Entwickler, die Windows-Runtime-Apps schreiben. Wenn Sie für Windows 10 entwickeln, finden Sie weitere Informationen unter neueste Dokumentation]

Aus: Umfassende Entwicklung einer Windows Store-App mit JavaScript: Hilo

Leitfaden-Logo

Vorherige Seite | Nächste Seite

In Hilo haben wir das Muster mit überwachendem Controller (ein Model-View-Presenter- oder MVP-Muster) verwendet, um die Unterstützung für die HTML-Vorlagenerstellung sicherzustellen. Dank des Musters mit überwachendem Controller konnten die Zuständigkeiten der Ansicht und des Presenters getrennt werden. Auf den meisten Hilo-Seiten haben wir außerdem das Vermittlungsmuster verwendet, um die Darstellungszuständigkeiten zu trennen und zu koordinieren. Aufgrund dieser Trennung konnte das Testen der App vereinfacht und der Code verständlicher gestaltet werden.

Download

Herunterladen des Hilo-Beispiels
Buch herunterladen (PDF)

Anweisungen zum heruntergeladenen Code finden Sie unter Erste Schritte mit Hilo.

Sie erfahren Folgendes:

  • Erzielen von Vorteilen für Windows Store-Apps mit JavaScript durch die Verwendung von MVP
  • Empfohlene Verfahren zur Anwendung des MVP-Musters: überwachender Controller und Vermittlungsmuster
  • So wird's gemacht: Isolieren von Zuständigkeiten der Presenterklasse
  • So wird's gemacht: Seitenübergreifendes Freigeben von Ansichten für allgemeine UI-Elemente wie die Schaltfläche "Zurück" und den Titel

Betrifft

  • Windows-Runtime für Windows 8
  • Windows-Bibliothek für JavaScript
  • JavaScript

MVP und das Muster mit überwachendem Controller

Beim MVP-Muster werden die Geschäftslogik, die UI und das Darstellungsverhalten getrennt.

  • Das Modell stellt den Zustand und die Abläufe von Geschäftsobjekten dar, die von der App geändert werden.
  • Die Ansicht (HTML und CSS) definiert die Struktur, das Layout und die Darstellung des Inhalts, der für Benutzer auf dem Bildschirm angezeigt wird. Über die Ansicht werden die Steuerelemente auf der Seite verwaltet und Benutzerereignisse an eine Presenterklasse weitergeleitet.
  • Der Presenter enthält die Logik zum Reagieren auf Ereignisse, Aktualisieren des Modells und Ändern des Zustands der Ansicht, falls erforderlich.

Wenn Sie die UI für eine JavaScript-App deklarativer gestalten, wird damit das Trennen der Zuständigkeiten der Ansicht von der Presenterklasse vereinfacht. Ferner kann die Ansicht per Datenbindung direkt mit den Daten der Anwendung (dem Modell) interagieren. Dies sind die Hauptfunktionen des Musters mit überwachendem Controller, bei dem es sich um eine Art von MVP-Muster handelt. Weitere Informationen finden Sie auf der Website von Martin Fowler unter Supervising controller (Überwachender Controller). Weitere Informationen zu MVP finden Sie unter Model-View-Presenter-Muster.

In Hilo entspricht der Controller dem Presenter von MVP. Um das Verständnis zu erleichtern, wird bei der Beschreibung dieses Musters der Ausdruck überwachender Presenter verwendet.

Beim Muster mit überwachendem Presenter interagiert die Ansicht direkt mit dem Modell, um ohne Eingriff des Presenters eine einfache Datenbindung durchzuführen, die deklarativ definiert werden kann. Das Modell verfügt über keinerlei Informationen zur Ansicht. Das Modell wird vom Presenter aktualisiert. Dieser ändert den Zustand der Ansicht nur, wenn eine komplexe UI-Logik erforderlich ist, die nicht deklarativ angegeben werden kann.

Tipp  Falls für Ihre App Änderungen am Modell unterstützt werden sollen, müssen Sie in der App möglicherweise einen Beobachter implementieren. Über WinJS kann die Ansicht bei Änderungen im Modell per Bindung aktualisiert werden. Umgekehrt ist dies jedoch nicht möglich. Sie können die Implementierung mithilfe des observableMixin-Objekts durchführen.
 

Hier sehen Sie die Beziehungen zwischen Ansicht, Modell und Presenter in diesem Pattern. Im Diagramm werden indirekte Referenzen durch blaue Linien dargestellt. Die blaue Linie vom Modell zur Ansicht stellt die deklarative Datenbindung dar. Die blaue Linie vom Modell zum Presenter stellt die Ereignisaktivierung dar.

Muster mit überwachendem Controller

Das Muster mit überwachendem Presenter wurde in Hilo implementiert. Wir haben diese Implementierung anstelle der passiven Ansicht (ein weiteres MVP-Muster) gewählt, um die Unterstützung für HTML-Vorlagen sicherzustellen. Auf diese Weise war es einfacher, die Zuständigkeiten der Ansicht und des Presenters zu trennen. Die Einfachheit des Codes hat dabei Vorrang vor einer guten Testbarkeit erhalten. Trotzdem war die Testbarkeit für das Projekt ein wichtiger Aspekt. Es wurde beispielsweise auch die Entscheidung getroffen, für jedes Steuerelement auf einer Seite einen Presenter zu erstellen. Dies führte zur einer besseren Testbarkeit des Codes. Ein weiterer Vorteil dieses Ansatzes war die klare Trennung der Zuständigkeiten. Die Möglichkeit der Zuweisung klarer, expliziter Rollen hat dabei den Aufwand für zusätzlich erforderlichen Code wett gemacht.

Hinweis  Das Model-View-ViewModel-Muster (MVVM) wurde nicht implementiert, weil die Bindung in zwei Richtungen nicht unterstützt wird.
 

Die Presenterklassen in Hilo enthalten die Logik zum Reagieren auf Ereignisse, Aktualisieren des Modells und Ändern des Zustands der Ansicht, falls erforderlich. In Hilo werden Presenterklassen unter Verwendung von Dateinamenkonventionen angegeben. Die Presenterklasse für das ListView-Steuerelement der Hubseite wird z. B. in listViewPresenter.js implementiert, und die Presenterklasse für das FlipView-Steuerelement wird in flipviewPresenter.js implementiert. Die Namen der Presenterklassen entsprechen den Dateinamen.

In Hilo wurden WinJS-Vorlagen für die deklarative Bindung verwendet, z. B. wie im hier gezeigten Beispiel für eine Vorlage. Diese Vorlage ist einem ListView-Steuerelement (nicht dargestellt) zum Anzeigen von Bildern auf der Hubseite angefügt. Die Eigenschaften url, name und className für die einzelnen Bilder sind deklarativ an das Modell gebunden. Weitere Informationen zu Vorlagen finden Sie unter Verwenden von Steuerelementen.

Hilo\Hilo\hub\hub.html


<div id="hub-image-template" data-win-control="WinJS.Binding.Template">
    <div data-win-bind="style.backgroundImage: url.backgroundUrl; alt: name; className: className" class="thumbnail">
    </div>
</div>


Zum Testen der Presenterklassen verfügt der Presenter im MVP-Muster über einen Verweis auf die Ansichtsschnittstelle, anstatt über eine konkrete Implementierung der Ansicht. In diesem Muster können Sie die echte Ansicht leicht durch eine Pseudoimplementierung der Ansicht ersetzen, um Tests durchführen zu können. Dieser Ansatz wird auch bei Komponententests für Hilo verwendet. Weitere Informationen finden Sie unter Test und Bereitstellung. Im unten angegebenen Testcode wird ein ListViewPresenter-Element mit einem Verweis auf ein Pseudosteuerelement (Specs.WinControlStub) erstellt, anstatt auf ein tatsächliches UI-Steuerelement.

Hilo\Hilo.Specifications\specs\hub\ListViewPresenter.spec.js


describe("when snapped", function () {

    var el;

    beforeEach(function () {
        var appView = {};
        el = new Specs.WinControlStub();
        el.winControl.addEventListener = function () { };

        var listviewPresenter = new Hilo.Hub.ListViewPresenter(el, appView);
        listviewPresenter.setViewState(Windows.UI.ViewManagement.ApplicationViewState.snapped);
    });

    it("the ListView should use a ListLayout", function () {
        expect(el.winControl.layout instanceof WinJS.UI.ListLayout).equal(true);
    });

});


[Oben]

Vermittlungsmuster

Auf den meisten Hilo-Seiten wird das Vermittlungsmuster zum Trennen und Koordinieren von Darstellungsaspekten verwendet. Im Vermittlungsmuster verwenden wir eine Vermittlungskomponente zum Koordinieren des Verhaltens mehrerer Objekte (Partner), die miteinander nur indirekt über das Vermittlungsobjekt kommunizieren. Dies ermöglicht eine lose Kopplung der Presenter. Jedes Partnerobjekt verfügt nur über eine Zuständigkeit.

Vermittlungsmuster

Jeder Presenter ist für einen bestimmten Teil der Seite (ein Steuerelement) und das damit verbundene Verhalten zuständig. Ein seitenspezifischer Presenter (die Vermittlungskomponente) koordiniert die anderen Presenter (steuerelementspezifisch) und empfängt weitergeleitete Ereignisse. Auf den meisten Hilo-Seiten werden DOM-Elemente in der ready-Funktion der Seite mithilfe von document.querySelector abgerufen. Anschließend werden die DOM-Elemente an den steuerelementspezifischen Presenter übergeben. Die UI für die Detailseite enthält z. B. ein AppBar-Steuerelement, einen Bildstreifen (basierend auf einem ListView-Steuerelement) und ein FlipView-Steuerelement. In der ready-Funktion für detail.js werden die DOM-Elemente für jedes Steuerelement abgerufen, und dann wird jedes Element an eine neue Instanz des jeweiligen Presenters übergeben.

Hilo\Hilo\detail\detail.js


ready: function (element, options) {

    var query = options.query;
    var queryDate = query.settings.monthAndYear;
    var pageTitle = Hilo.dateFormatter.getMonthFrom(queryDate) + " " + Hilo.dateFormatter.getYearFrom(queryDate);
    this.bindPageTitle(pageTitle);

    var hiloAppBarEl = document.querySelector("#appbar");
    var hiloAppBar = new Hilo.Controls.HiloAppBar.HiloAppBarPresenter(hiloAppBarEl, WinJS.Navigation, query);

    var filmstripEl = document.querySelector("#filmstrip");
    var flipviewEl = document.querySelector("#flipview");

    var flipviewPresenter = new Hilo.Detail.FlipviewPresenter(flipviewEl);
    var filmstripPresenter = new Hilo.Detail.FilmstripPresenter(filmstripEl);


    var detailPresenter = new Hilo.Detail.DetailPresenter(filmstripPresenter, flipviewPresenter, hiloAppBar, WinJS.Navigation);
    detailPresenter.addEventListener("pageSelected", function (args) {
        var itemIndex = args.detail.itemIndex;
        options.itemIndex = itemIndex;
    });

    detailPresenter
        .start(options)
        .then(function () {
            WinJS.Application.addEventListener("Hilo:ContentsChanged", Hilo.navigator.reload);
        });
},


Nachdem die steuerelementspezifischen Presenter erstellt wurden, werden sie an eine neue Instanz des seitenspezifischen Presenters übergeben.

Hinweis  Die ready-Funktion ist Teil des Navigationsmodells und wird automatisch aufgerufen, wenn der Benutzer zu einem Seitensteuerelement navigiert. In Hilo verwenden wir die Seitensteuerelemente zum Abrufen von DOM-Elementen und Instanziieren der Abhängigkeiten, aber die Seitensteuerelemente entsprechen nicht einem bestimmten Teil von MVP. Weitere Informationen finden Sie unter Erstellen von Seiten und Navigieren zwischen Seiten.
 

WinJS.Namespace.define macht die Presenterobjekte zur Verwendung in der App verfügbar. Dies ist der Code, mit dem dieses Verfahren für den Presenter der Detailseite durchgeführt wird.

Hilo\Hilo\detail\detailPresenter.js


WinJS.Namespace.define("Hilo.Detail", {
    DetailPresenter: WinJS.Class.mix(DetailPresenter, WinJS.Utilities.eventMixin)
});


Wenn der Presenter der Detailseite erstellt wird, weist seine Konstruktorfunktion die steuerelementspezifischen Presenter den Presentereigenschaften der Detailseite zu. Dieses Muster ist typisch für die verschiedenen Hilo-Presenterklassen.

Hilo\Hilo\detail\detailPresenter.js


function DetailPresenterConstructor(filmstripPresenter, flipviewPresenter, hiloAppBar, navigation) {
    this.flipview = flipviewPresenter;
    this.filmstrip = filmstripPresenter;
    this.hiloAppBar = hiloAppBar;
    this.navigation = navigation;

    Hilo.bindFunctionsTo(this, [
        "bindImages"
    ]);
},


Die start-Funktion des Presenters wird über ready aufgerufen. Mit der start-Funktion wird eine Abfrage ausgeführt, um die erforderlichen Bilder abzurufen. Anschließend wird bindImages aufgerufen, und die Abfrageergebnisse werden übergeben. Weitere Informationen zu Dateisystemabfragen in Hilo finden Sie unter Verwenden des Abfrage-Generator-Musters.

Hilo\Hilo\detail\detailPresenter.js


start: function (options) {
    var self = this;
    this.query = options.query;

    return this.query.execute()
        .then(function (images) {

            var result = findImageByIndex(images, options.itemIndex, options.itemName);
            var storageFile = images[options.itemIndex];
            // If the file retrieved by index does not match the name associated
            // with the query, we assume that it has been deleted (or modified)
            // and we send the user back to the last screen.
            if (isNaN(result.actualIndex)) {
                self.navigation.back();
            } else {
                self.bindImages(images);
                self.gotoImage(result.actualIndex, options.picture);
            }
        });

},


Mit der bindImages-Funktion in detailPresenter.js werden Ereignishandler für Ereignisse wie imageInvoked registriert. Der Handler erhält Ereignisse, die von den steuerelementspezifischen Presenterklassen ausgehen. Auf der Detailseite zeigt sich das gleiche Verhalten, unabhängig davon, ob Benutzer im Bildstreifen oder im FlipView-Steuerelement auf ein Bild klicken. Die Behandlung des Ereignisses wird also in der Vermittlungskomponente der Seite koordiniert.

Hilo\Hilo\detail\detailPresenter.js


bindImages: function (images) {

    this.flipview.bindImages(images);
    this.flipview.addEventListener("pageSelected", this.imageClicked.bind(this));

    this.filmstrip.bindImages(images);
    this.filmstrip.addEventListener("imageInvoked", this.imageClicked.bind(this));

    this.hiloAppBar.enableButtons();
},


Um die Bilder an das Steuerelement zu binden, ruft bindImages die bindImages-Funktion in den steuerelementspezifischen Presentern auf. Die Bilder sind mit der itemDataSource-Eigenschaft des Steuerelements an das Steuerelement gebunden.

Hilo\Hilo\detail\flipViewPresenter.js


bindImages: function (images) {
    this.bindingList = new WinJS.Binding.List(images);
    this.winControl.itemDataSource = this.bindingList.dataSource;
},


Weitere Informationen zum Binden an Datenquellen finden Sie unter Verwenden von Steuerelementen.

[Oben]

Trennen der Zuständigkeiten für das AppBar-Element und den Seitenkopf

Für häufig vorkommende Seitenelemente, z. B. AppBar und den Seitenkopf (ein HTMLControl-Element), haben wir wiederverwendbare Steuerelemente implementiert, die auf mehreren Seiten verwendet werden können. Wir haben die Dateien für diese Steuerelemente aus den seitenbezogenen Unterordnern in den Ordner \Hilo\controls verschoben. Die AppBar-Steuerelemente werden im Ordner Hilo\controls\HiloAppBar definiert, und das Seitenkopf-Steuerelement wird im Ordner Hilo\controls\Header definiert. Der Seitenkopf enthält zusätzlich ein Schaltflächensteuerelement "Zurück". Durch das Verschieben der Presenterklassen für diese Steuerelemente aus dem seitenbezogenen Code konnte eine klare Trennung erreicht werden.

Sowohl das AppBar-Steuerelement als auch der Seitenkopf sind als Seitensteuerelemente definiert, von denen das empfohlene Navigationsmodell für eine Windows Store-App unterstützt wird. Um sie als Seitensteuerelemente zu definieren, wird die Verwendung des HTMLControl-Elements im HTML-Code angegeben und im zugeordneten JavaScript das WinJS.UI.Page.define-Element aufgerufen.

In der Hilo-Implementierung ist für den HTML-Code für eine bestimmte Seite nur ein Abschnittsverweis auf das AppBar-Steuerelement oder Schaltflächensteuerelement "Zurück" erforderlich. Dies ist z. B. der HTML-Code für das AppBar-Steuerelement auf der Hubseite und der Detailseite.

Hilo\Hilo\hub\hub.html


<section id="image-nav" data-win-control="WinJS.UI.HtmlControl" data-win-options="{uri: '/Hilo/controls/HiloAppBar/hiloAppBar.html'}"></section>


Die Hauptansicht für das AppBar-Steuerelement ist auf der hier referenzierten HTML-Seite hiloAppBar.html enthalten. In diesem HTML-Code sind zwei Schaltflächen für das AppBar-Steuerelement angegeben, eine für den Befehl zum Zuschneiden und eine für den Befehl zum Drehen.

Hilo\Hilo\controls\HiloAppBar\hiloAppBar.html


<div id="appbar" data-win-control="WinJS.UI.AppBar" data-win-options="{sticky: false}">
    <button
        data-win-control="WinJS.UI.AppBarCommand"
        data-win-options="{id:'rotate', icon:'rotate', section: 'selection', disabled: true}"
        data-win-bind="{disabled: isCorrupt}"
        data-win-res="{winControl: {label:'RotateAppBarButton.Name'}}">
    </button>
    <button
        data-win-control="WinJS.UI.AppBarCommand"
        data-win-options="{id:'crop', icon:'crop', section: 'selection', disabled: true}"
        data-win-bind="{disabled: isCorrupt}"
        data-win-res="{winControl: {label:'CropAppBarButton.Name'}}">
    </button>
</div>


Die CSS- und JavaScript-Dateien, die hiloAppBar.html zugeordnet sind, sind an demselben Projektspeicherort vorhanden, nämlich im Ordner Hilo\controls\HiloAppBar.

[Oben]

Trennen der Zuständigkeiten für das ListView-Steuerelement

Für Seiten mit ListView-Steuerelementen, z. B. die Hubansicht, konnte die dem ListView-Steuerelement zugeordnete Ansicht nicht einfach von der eigentlichen Seite getrennt werden, da das ListView-Steuerelement normalerweise eng mit dem gesamten Seitenverhalten und der UI verknüpft ist. Stattdessen wurde ein ListView-Presenter für jede Seite erstellt, um eine Trennung zu erzielen. Die Hubseite verfügt beispielsweise über eine in listViewPresenter.js angegebene ListViewPresenter-Klasse, die zusammen mit anderen Hubdateien im Ordner \Hilo\hub enthalten ist.

Von der ListViewPresenter-Klasse werden alle Zustände und Ereignisse behandelt, die dem ListView-Steuerelement einer bestimmten Seite zugeordnet sind. Beispielsweise wird der Ansichtsstatus festgelegt, und die Datenquelle für das ListView-Steuerelement wird im ListViewPresenter-Element festgelegt. Vom ListView-Steuerelement erhaltene Ereignisse werden an die Presenterklasse der Seite weitergeleitet. Auf der Hubseite wird der imageNavigated-Ereignishandler verwendet, um das iteminvoked-Ereignis des ListView-Steuerelements zu behandeln. Anschließend wird ein neues Ereignis zur generischen Behandlung in HubPresenter ausgelöst.

Hilo\Hilo\hub\listViewPresenter.js


imageNavigated: function (args) {
    var self = this;
    args.detail.itemPromise.then(function (item) {
        self.dispatchEvent("itemInvoked", {
            item: item,
            itemIndex: args.detail.itemIndex
        });
    });
},


Das ausgelöste Ereignis wird von der itemClicked-Funktion im HubPresenter -Element (nicht dargestellt) behandelt.

[Oben]

 

 

Anzeigen:
© 2017 Microsoft