Entwicklung eines benutzerdefinierten Steuerelements unter Verwendung der Windows-Bibliothek für JavaScript (WinJS)

Wenn Sie Windows Store-Apps mit JavaScript entwickelt haben, kennen Sie höchstwahrscheinlich die Windows-Bibliothek für JavaScript (WinJS). Diese Bibliothek enthält eine Reihe von CSS-Formatvorlagen, JavaScript-Steuerelementen und Dienstprogrammen, mit denen Sie unkompliziert Apps entwickeln können, die den UX-Richtlinien für den Windows Store entsprechen. Zu den von WinJS bereitgestellten Dienstprogrammen gehört ein Satz von Funktionen, die Sie zur Entwicklung von benutzerdefinierten Steuerelementen in Ihrer App verwenden können.

Sie können beliebige Muster oder Bibliotheken verwenden, um JavaScript-Steuerelemente zu schreiben, die Bibliotheksfunktionen in WinJS sind hierfür nur eine Möglichkeit von vielen. Der Hauptvorteil bei der Verwendung von WinJS zum Erstellen von Steuerelementen ist, dass Sie eigene Steuerelemente entwickeln können, die zuverlässig mit den anderen in der Bibliothek enthaltenen Steuerelementen zusammenarbeiten. Die Muster für die Entwicklung und Anwendung Ihres Steuerelements stimmen mit denen aller Steuerelemente im WinJS.UI-Namespace überein.

In diesem Beitrag zeige ich Ihnen, wie Sie mit Unterstützung konfigurierbarer Optionen, Ereignisse und öffentlicher Methoden eigene Steuerelemente erstellen können. Wenn Sie sich speziell für die Entwicklung von XAML-Steuerelementen interessieren, hierzu wird in Kürze ein eigener Beitrag folgen.

Einfügen eines JavaScript-basierten Steuerelements in eine HTML-Seite

Zunächst möchte ich noch einmal das Einfügen eines WinJS-Steuerelements in eine Seite beschreiben. Dafür gibt es zwei verschiedene Methoden: imperativ (indem ausschließlich und auf unauffällige Weise JavaScript verwendet wird) oder deklarativ (indem das Steuerelement mithilfe der zusätzlichen Attribute von HTML-Elementen in die HTML-Seite eingefügt wird). Die letztere Methode ermöglicht das Erstellen von Tools, die Entwurfszeitoptionen wie das Ziehen von Steuerelementen aus einer Werkzeugleiste bereitstellen können. Weitere Informationen finden Sie unter Schnellstart: Hinzufügen von WinJS-Steuerelementen und -Stilen.

In diesem Artikel wird das Erstellen eines JavaScript-Steuerelements beschrieben, das das deklarative Verarbeitungsmodell in WinJS nutzen kann. Führen Sie zum deklarativen Einfügen eines Steuerelements in Ihre Seite die folgenden Schritte aus:

  1. Fügen Sie WinJS-Verweise in Ihre HTML-Seite ein, da Ihr Steuerelement APIs aus diesen Dateien verwenden wird.

     <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
    
  2. Fügen Sie nach den obigen Scripttag-Verweisen einen Verweis zu einer Scriptdatei ein, die Ihr Steuerelement enthält.

     <script src="js/hello-world-control.js"></script>
    
  3. Rufen Sie im JavaScript-Code der App WinJS.UI.processAll() auf. Diese Funktion analysiert Ihr HTML und instanziiert alle deklarativen Steuerelemente, die sie findet. Falls Sie die App-Vorlagen in Visual Studio verwenden, wird WinJS.UI.processAll() für Sie in default.js aufgerufen.

  4. Fügen Sie das Steuerelement deklarativ in die Seite ein.

     <div data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}"></div>
    

Ein einfaches JavaScript-Steuerelement

Wir werden nun ein sehr einfaches Steuerelement erstellen: das „Hello World“-Steuerelement. Dies ist der JavaScript-Code für das Steuerelement. Erstellen Sie in Ihrem Projekt eine neue Datei mit dem Namen „hello-world-control.js“ und dem folgenden Code:

 function HelloWorld(element) {
    if (element) {
        element.textContent = "Hello, World!";
    }
};

WinJS.Utilities.markSupportedForProcessing(HelloWorld);

Fügen Sie das Steuerelement anschließend in den Text der Seite ein und verwenden Sie dabei das folgende Markup:

 <div data-win-control="HelloWorld"></div>

Wenn Sie die App ausführen, sehen Sie, dass das Steuerelement geladen wurde und den Text „Hello, World!“ im Textkörper Ihrer Seite anzeigt.

Der einzige WinJS-spezifische Bestandteil dieses Codes ist der Aufruf an WinJS.Utilities.markSupportedForProcessing, der den Code als kompatibel für die Verwendung mit deklarativen Verfahren kennzeichnet. Auf diese Weise teilen Sie WinJS mit, dass dieser Code vertrauenswürdig ist, um Inhalte in Ihre Seite zu injizieren. Weitere Informationen dazu finden Sie in der MSDN-Dokumentation für die Funktion WinJS.Utilities.markSupportedForProcessing.

Warum sollten zur Erstellung eines Steuerelements die WinJS-Dienstprogramme oder überhaupt eine Bibliothek verwendet werden?

Gerade habe ich beschrieben, wie Sie ein deklaratives Steuerelement erstellen können, ohne WinJS wirklich zu verwenden. Sehen Sie sich nun den folgenden Codeabschnitt an, in dem für den Großteil der Implementierung WinJS ebenfalls nicht verwendet wird. Dies ist ein komplexeres Steuerelement mit Ereignissen, konfigurierbaren Optionen und öffentlichen Methoden:

 (function (Contoso) {
    Contoso.UI = Contoso.UI || {};

    Contoso.UI.HelloWorld = function (element, options) {
        this.element = element;
        this.element.winControl = this;

        this.blink = (options && options.blink) ? true : false;
        this._onblink = null;
        this._blinking = 0;

        element.textContent = "Hello, World!";
    };

    var proto = Contoso.UI.HelloWorld.prototype;

    proto.doBlink = function () {
        var customEvent = document.createEvent("Event");
        customEvent.initEvent("blink", false, false);

        if (this.element.style.display === "none") {
            this.element.style.display = "block";
        } else {
            this.element.style.display = "none";
        }

        this.element.dispatchEvent(customEvent);
    };

    proto.addEventListener = function (type, listener, useCapture) {
        this.element.addEventListener(type, listener, useCapture);
    };

    proto.removeEventListener = function (type, listener, useCapture) {
        this.element.removeEventListener(type, listener, useCapture);
    };

    Object.defineProperties(proto, {
        blink: {
            get: function () {
                return this._blink;
            },

            set: function (value) {
                if (this._blinking) {
                    clearInterval(this._blinking);
                    this._blinking = 0;
                }
                this._blink = value;
                if (this._blink) {
                    this._blinking = setInterval(this.doBlink.bind(this), 500);
                }
            },
            enumerable: true,
            configurable: true
        },

        onblink: {
            get: function () {
                return this._onblink;
            },
            set: function (eventHandler) {
                if (this._onblink) {
                    this.removeEventListener("blink", this._onblink);
                    this._onblink = null;
                }
                this._onblink = eventHandler;
                this.addEventListener("blink", this._onblink);
            }
        }
    });

    WinJS.Utilities.markSupportedForProcessing(Contoso.UI.HelloWorld);
})(window.Contoso = window.Contoso || {}); 

Viele Entwickler erstellen Steuerelemente auf diese Art, also unter Verwendung von anonymen Funktionen, Konstruktorfunktionen, Eigenschaften und benutzerdefinierten Ereignissen. Wenn Sie und Ihr Team damit zufrieden sind, machen Sie es ruhig so! Für viele Entwickler könnte dieser Code jedoch etwas verwirrend sein. Vielen Webentwicklern sind die verwendeten Techniken nicht vertraut. In diesem Fall sind Bibliotheken sehr hilfreich, da sie das Schreiben des Codes vereinfachen können.

WinJS und andere Bibliotheken verbessern nicht nur die Lesbarkeit, sie lösen außerdem viele kleinere Probleme, über die Sie dann gar nicht mehr nachdenken müssen, wie z. B. die effiziente Verwendung von Prototypen, Eigenschaften und benutzerdefinierten Ereignissen. Sie optimieren die Speichernutzung und helfen Ihnen, häufige Fehler zu vermeiden. WinJS ist nur ein Beispiel, Sie können sich natürlich auch für andere Bibliotheken entscheiden. Als konkretes Beispiel dafür, wie hilfreich eine Bibliothek sein kann, sehen Sie sich nach dem Lesen dieses Artikels den Code in diesem Abschnitt noch einmal an, und vergleichen Sie ihn mit der Implementierung des gleichen Steuerelements am Ende des Artikels, die WinJS-Dienstprogramme verwendet.

Ein Grundmuster für JavaScript-Steuerelemente in WinJS

Dies ist ein minimales, bewährtes Muster zum Erstellen eines JavaScript-Steuerelements mithilfe von WinJS.

 (function () {
    "use strict";

    var controlClass = WinJS.Class.define(
            function Control_ctor(element) {
                this.element = element || document.createElement("div");
                this.element.winControl = this;

                this.element.textContent = "Hello, World!"
            });
    WinJS.Namespace.define("Contoso.UI", {
        HelloWorld: controlClass
    });
})();

Fügen Sie das Steuerelement in der Seite deklarativ ein:

 <div data-win-control="Contoso.UI.HelloWorld"></div>

Einiges davon kann neu für Sie sein, besonders, wenn Sie bisher noch keine Erfahrung mit WinJS haben, daher zeige ich Ihnen Schritt für Schritt, was genau passiert.

  1. Wir umschließen den Code in diesem Beispiel mit einem häufig verwendeten Muster in JavaScript, das als sofort ausgeführte Funktion bezeichnet wird.

     (function () {
    …
    })();
    

    Wir können so sicherstellen, dass unser Code eigenständig ist und keine unbeabsichtigten Variablen oder globalen Zuweisungen hinterlässt. Beachten Sie, dass es sich hierbei um eine allgemeine Best Practice und um eine Änderung handelt, die auch am ursprünglichen Quellcode vorgenommen werden sollte.

  2. Der ECMAScript 5-Strict-Modus (ES5) wird durch die Anweisung "use strict" am Anfang unserer Funktion aktiviert. Wir verwenden dies als Best Practice bei allen Vorlagen für Windows Store-Apps, um die Überprüfung auf Fehler und die Kompatibilität mit zukünftigen Versionen von JavaScript zu verbessern. Auch dies ist eine allgemeines Best Practice und sollte auch auf den ursprünglichen Quellcode angewendet werden.

  3. Wenden wir uns nun dem WinJS-spezifischen Code zu. WinJS.Class.define() wird aufgerufen, um eine Klasse für das Steuerelement zu erstellen, die unter anderem den Aufruf „markSupportedForProcessing()“ für uns behandelt und außerdem die zukünftige Erstellung von Eigenschaften im Steuerelement erleichtert. Es handelt sich tatsächlich nur um ein einfaches Hilfsmittel im Zusammenhang mit der standardmäßigen Object.defineProperties-Funktion.

  4. Ein Konstruktor mit dem Namen „Control_ctor“ wird definiert. Wenn WInJS.UI.processAll() von default.js aufgerufen wird, prüft es mithilfe des data-win-controll-Attributs das Markup auf der Seite auf referenzierte Steuerelemente, identifiziert unser Steuerelement und ruft diesen Konstruktor auf.

  5. Innerhalb des Konstruktors wird zusammen mit dem Objekt des Steuerelements ein Verweis zu dem Element auf der Seite und ein Verweis zum Objekt des Steuerelements gespeichert.

    • Der Abschnitt element || document.createElement("div") wird zur Unterstützung des imperativen Modells verwendet. Dies ermöglicht das spätere Anhängen eines Steuerelements an ein Element auf der Seite.
    • Es hat sich bewährt, auf diese Weise einen Verweis zum Element auf der Seite beizubehalten sowie mithilfe der Einstellung element.winControl einen Verweis vom Element zum Objekt des Steuerelements. Beim Hinzufügen von Funktionen wie Ereignissen können einige Bibliotheksfunktionen so unmittelbar genutzt werden. Sie brauchen sich keine Sorgen wegen Arbeitsspeicherverlusten aufgrund von zirkulären Objekt/DOM-Elementverweisen zu machen, da Internet Explorer 10 dies vermeidet.
    • Der Konstruktor modifiziert den Textinhalt des Steuerelements, um den Text „Hello, World!“ einzurichten, der auf der Seite angezeigt wird.
  6. Schließlich wird WinJS.Namespace.define() zur Veröffentlichung der Steuerelementklasse und zur öffentlichen Verfügbarmachung des Steuerelements verwendet, damit der Code unserer App darauf zugreifen kann. Ansonsten müssten wir eine andere Lösung finden, um das Steuerelement verfügbar zu machen und hierfür den globalen Namespace für Code außerhalb der Inlinefunktion, in der wir arbeiten, verwenden.

Definieren der Optionen des Steuerelements

Um unser Beispiel etwas interessanter zu machen, fügen wir dem Steuerelement Unterstützung für konfigurierbare Optionen hinzu. In diesem Fall fügen wir eine Option hinzu, mit der der Benutzer den Inhalt blinken lassen kann.

 var controlClass = WinJS.Class.define(
            function Control_ctor(element, options) {
                this.element = element || document.createElement("div");
                this.element.winControl = this;

                // Set option defaults
                this._blink = false;

                // Set user-defined options
                WinJS.UI.setOptions(this, options);

                element.textContent = "Hello, World!"
            },
            {
                _blinking: 0,

                blink: {
                    get: function () {
                        return this._blink;
                    },

                    set: function (value) {
                        if (this._blinking) {
                            clearInterval(this._blinking);
                            this._blinking = 0;
                        }
                        this._blink = value;
                        if (this._blink) {
                            this._blinking = setInterval(this._doBlink.bind(this), 500);
                        }
                    }
                },

                _doBlink: function () {
                    if (this.element.style.display === "none") {
                        this.element.style.display = "block";
                    } else {
                        this.element.style.display = "none";
                    }
                },
            });

    WinJS.Namespace.define("Contoso.UI", {
        HelloWorld: controlClass
    });

Konfigurieren Sie beim Einfügen des Steuerelements in die Seite die Blinkoption diesmal mithilfe des data-win-options-Attributs:

 <div data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}">
</div>

Wir haben folgende Änderungen am Code vorgenommen, um Unterstützung für Optionen hinzuzufügen:

  1. Optionen werden ans Steuerelement übergeben, über einen Parameter (namens „options“) in der Konstruktorfunktion.
  2. Standardeinstellungen werden konfiguriert, indem private Eigenschaften der Klasse verwendet werden.
  3. WinJS.UI.setOptions() wird aufgerufen, um das Objekt des Steuerelements zu übergeben. Dieser Aufruf setzt Standardwerte für konfigurierbare Optionen im Steuerelement außer Kraft.
  4. Eine öffentliche Eigenschaft (namens „blink“) wird für die neue Option hinzugefügt.
  5. Eine Funktionalität wurde hinzugefügt, und der Text blinkt auf dem Bildschirm (in der Praxis wäre es in diesem Fall besser, die Stile nicht hart zu kodieren, sondern stattdessen eine CSS-Klasse umzuschalten).

Der entscheidende Teil ist in diesem Beispiel der Aufruf von WinJS.UI.setOptions(). SetOptions, eine Hilfsprogrammfunktion, zirkuliert durch jedes Feld im Optionsobjekt und weist ihren Wert einem gleichnamigen Feld im Zielobjekt zu, das dem ersten Parameter von setOptions entspricht.

In unserem Beispiel konfigurieren wir das options-Objekt mithilfe des data-win-options-Arguments und übergeben den Wert „true“ für das Feld „blink“. Der Aufruf von setOptions() in der Konstruktorfunktion erkennt dann das Feld namens „blink“ und kopiert seinen Wert in ein gleichnamiges Feld im Objekt des Steuerelements. Wir haben eine Eigenschaft namens „blink“ definiert, die eine setter-Funktion bereitstellt. Unsere setter-Funktion wird durch setOptions() aufgerufen und legt das _blink-Member des Steuerelements fest.

Unterstützung für Ereignisse hinzufügen

Nachdem wir nun die ungemein nützliche Blinkoption implementiert haben, werden wir eine Ereignisunterstützung hinzufügen, damit wir immer dann reagieren können, wenn ein Blinken auftritt:

 var controlClass = WinJS.Class.define(
            function Control_ctor(element, options) {
                this.element = element || document.createElement("div");
                this.element.winControl = this;

                // Set option defaults
                this._blink = false;

                // Set user-defined options
                WinJS.UI.setOptions(this, options);

                element.textContent = "Hello, World!"
            },
            {
                _blinking: 0,
                _blinkCount: 0,

                blink: {
                    get: function () {
                        return this._blink;
                    },

                    set: function (value) {
                        if (this._blinking) {
                            clearInterval(this._blinking);
                            this._blinking = 0;
                        }
                        this._blink = value;
                        if (this._blink) {
                            this._blinking = setInterval(this._doBlink.bind(this), 500);
                        }
                    }
                },

                _doBlink: function () {
                    if (this.element.style.display === "none") {
                        this.element.style.display = "block";
                    } else {
                        this.element.style.display = "none";
                    }
                    this._blinkCount++;
                    this.dispatchEvent("blink", {
                        count: this._blinkCount
                    });
                },
            });

    WinJS.Namespace.define("Contoso.UI", {
        HelloWorld: controlClass
    });

    // Set up event handlers for the control
    WinJS.Class.mix(Contoso.UI.HelloWorld,
        WinJS.Utilities.createEventProperties("blink"),
        WinJS.UI.DOMEventMixin);

Fügen Sie das Steuerelement wie zuvor in die Seite ein. Beachten Sie, dass wir dem Element eine Identität hinzugefügt haben, damit wir es später abrufen können:

 <div id="hello-world-with-events"
    data-win-control="Contoso.UI.HelloWorld"
    data-win-options="{blink: true}"></div>

Nachdem wir diese Änderungen durchgeführt haben, können wir nun einen Ereignislistener anhängen, der auf „Blink“-Ereignisse lauscht. (Hinweis: Ich habe in diesem Beispiel document.getElementById das Alias $ gegeben):

 $("hello-world-with-events").addEventListener("blink",
        function (event) {
            console.log("blinked element this many times: " + event.count);
        });

Wenn Sie diesen Code ausführen, sehen Sie eine Benachrichtigung, die alle 500 Millisekunden an das JS Console-Fenster in Visual Studio ausgegeben wird.

Um dieses Verhalten zu unterstützen, wurden 3 Änderungen am Steuerelement vorgenommen:

  1. Es erfolgt ein Aufruf an WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.Utilities.createEventProperties("blink")); dieser erstellt eine „onblink“-Eigenschaft, die die Benutzer programmgesteuert einstellen können, oder an die deklarativ auf der HTML-Seite gebunden werden kann.
  2. Ein Aufruf an WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.UI.DOMEventMixin) fügt dem Steuerelement die Funktionen addEventListener, removeEventListener und dispatchEvent hinzu.
  3. Das Blinkereignis wird ausgelöst, indem that.dispatchEvent("blink", {element: that.element}); aufgerufen wird, und ein benutzerdefiniertes Ereignisobjekt wird über ein Elementfeld erstellt.
  4. Ein Ereignishandler wird angehängt, um auf das Blinkereignis zu lauschen; als Antwort greift er auf das Elementfeld des benutzerdefinierten Ereignisobjekts zu.

Ich weise an dieser Stelle darauf hin, dass Aufrufe von dispatchEvent() nur dann funktionieren, wenn Sie im Konstruktor Ihres Steuerelements this.element eingestellt haben. Die internen Komponenten des eingemischten Ereignisses machen es erforderlich, dass es auf das Element im DOM zugreift. Hier handelt es sich um einen der vorher erwähnten Fälle, in denen im Objekt des Steuerelements ein Elementmitglied erforderlich ist. Dies gestattet es Ereignissen, in einem DOM Level 3 event Muster zu übergeordneten Elementen aufzusteigen.

Verfügbarmachen von öffentlichen Methoden

Als letzte Änderung an unserem Steuerelement fügen wir eine öffentliche doBlink()-Funktion hinzu, die jederzeit aufgerufen werden kann, um ein Blinken zu erzwingen.

 var controlClass = WinJS.Class.define(
            function Control_ctor(element, options) {
                this.element = element || document.createElement("div");
                this.element.winControl = this;

                // Set option defaults
                this._blink = false;

                // Set user-defined options
                WinJS.UI.setOptions(this, options);

                element.textContent = "Hello, World!"
            },
            {
                _blinking: 0,
                _blinkCount: 0,

                blink: {
                    get: function () {
                        return this._blink;
                    },

                    set: function (value) {
                        if (this._blinking) {
                            clearInterval(this._blinking);
                            this._blinking = 0;
                        }
                        this._blink = value;
                        if (this._blink) {
                            this._blinking = setInterval(this.doBlink.bind(this), 500);
                        }
                    }
                },

                doBlink: function () {
                    if (this.element.style.display === "none") {
                        this.element.style.display = "block";
                    } else {
                        this.element.style.display = "none";
                    }
                    this._blinkCount++;
                    this.dispatchEvent("blink", {
                        count: this._blinkCount
                    });
                },
            });
    WinJS.Namespace.define("Contoso.UI", {
        HelloWorld: controlClass
    });

    // Set up event handlers for the control
    WinJS.Class.mix(Contoso.UI.HelloWorld,
        WinJS.Utilities.createEventProperties("blink"),
        WinJS.UI.DOMEventMixin);

Hier handelt es sich nur um eine Konventionsänderung: Wir können den Namen der „_doBlink“-Funktion in „doBlink“ ändern.

Sie benötigen für Ihr Steuerelement einen Verweis zum Objekt, um die doBlink()-Funktion über JavaScript aufzurufen. Wenn Sie das Steuerelement imperativ erzeugt haben, verfügen Sie möglicherweise bereits über einen Verweis. Wenn Sie deklarative Verfahren anwenden, können Sie mithilfe einer winControl-Eigenschaft des HTML-Elements Ihres Steuerelements auf das Objekt des Steuerelements zugreifen. Beispielsweise können Sie, mit dem gleichen Markup wie oben, folgendermaßen auf das Objekt des Steuerelements zugreifen:

$("hello-world-with-events").winControl.doBlink();

Zusammenfassung

Wir haben nun die wichtigsten Aspekte beim Implementieren eines Steuerelements kennengelernt:

  1. Das Steuerelement in eine Webseite einfügen.
  2. Konfigurationsoptionen übergeben.
  3. Verteilen und Reagieren auf Ereignisse.
  4. Verfügbarmachen von Funktionen über öffentliche Methoden.

Ich hoffe, dass diese Anleitung zum Erstellen eines einfachen JavaScript-basierten benutzerdefinierten Steuerelements hilfreich war! Sollten bei der Arbeit an Ihren Steuerelementen weitere Fragen auftreten, besuchen Sie das Windows-Entwicklungscenter und stellen Sie sie in den Foren. Speziell für XAML-Entwickler erscheint in Kürze ein Beitrag, der das gleiche Verfahren für die Entwicklung von XAML-Steuerelementen erläutert.

Jordan Matthiesen 
Programmmanager, Microsoft Visual Studio