用Windows JavaScript 库创建自定义控件

如果您已经用Javascript开发过windows store app程序,那么您肯定使用过Windows Javascript 库(WinJS)。这个库提供了一组CSS样式、Javascript控件以及工具, 可以帮助您快速地创建符合UX准则的windows store app。 这里工具是一组函数,您可以使用这组函数来创建自定义控件。

您可以使用任何您喜欢的模式或者库来编写JavaScript控件,WinJS提供的库函数仅仅是您的选择之一。使用WinJS库创建自定义控件的一个最大的好处是该自定义控件与其他的控件能够协同工作。且这种模式与WinJS命名空间的任何控件的创建模式是一致的。

在这篇文章中,我将会介绍如何使用winJS库构建自定义的控件。如果您对XAML控件的开发同样感兴趣的话您可以关注后续的博客。

首先,让我们来了解一下如何在页面里包含WinJS控件。有两种不同的方式:第一种是命令式的(即单独使用JavaScript);另一种是声明式的(即使用HTML元素附加的属性添加控件到HTML页面)。后一种方式提供了设计时的体检,如可以直接从工具箱拖动控件到页面,具体您可以参考MSDN的文档:MSDN quick start for
adding WinJS controls and styles

在本节中,我将会详细向您介绍如何在一个页面生成JavaScript控件,具体包含以下几个步骤:

1. 在您的HTML页面添加WinJS引用,因为您的控件需要用到这些文件中的API

Microsoft.WinJS.1.0/js/base.js

Microsoft.WinJS.1.0/js/ui.js

2. 在上述代码后,添加包含您定义的控件的脚本文件的引用:

 <script src="js/hello-world-control.js"></script>
  1.      

3.在您的应用程序的JavaScript代码中调用WinJS.UI.processAll()函数。该函数可以解析HTML和对页面上控件进行声明和实例化。如果您在创建windows store app的时候用的是Visual studio上所提供的模版,您会发现该函数已经在Default.js内自动调用。

4.以下列方式在页面使用控件:

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

一个简单的JavaScript控件

现在我们来创建一个非常简单的控件:Hello World控件。首先在您的项目中添加一个hello-world-control.js文件并将下面的JavaScript代码添加到该文件:

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

然后在您的主页面以如下方式将该控件添加到页面:

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

当您运行这段程序的时候,您会在主页面看到该控件已经被加载并且在页面呈现“Hello,World!”文本

这段代码的特殊之处是调用了WinJS里面的WinJS.Utilities.markSupportedForProcessing函数,您可以在HTML元素中直接调用这个函数。即相当于告诉WinJS您向页面添加了可信任的内容。更多信息请参考MSDN文档的介绍:WinJS.Utilities.markSupportedForProcessing

为什么使用WinJS工具、库来创建控件

上面代码只是向您介绍怎么来创建JavaScript控件,但是并没有使用到WinJS. 现在请看下面的代码段,该代码段声明了一个复杂的控件,其中包括事件、可配置的选项以及公共方法等:

 (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 || {});
 

许多开发者是通过上述方式来创建控件(即使用匿名函数、构造函数、属性、自定义事件),如果您的开发团队觉得使用这种方式很得心应手的话,那么您可以继续使用这个方法。但是对于大多数的开发人员来说,这些代码显得过于复杂。且许多的Web开发者并不熟悉此项技术。使用库,可以简化此类代码的编写。

除了提高程序的可读性,WinJS和其他的库也注意到了许多微妙的问题(如有效利用原型、属性和自定义事件)。通过这些库的使用,可以优化内存的使用率并帮您避免一些常见的错误。WinJS库就是这样的一个例子,但是使不使用的选择权在您。有关库给您创建控件带来的简便,建议您在看完这篇博客后重新来审视本节中的代码并且与使用WinJS后的代码来进行比较。

基于WinJS的JavaScript控件

下面是一个用WinJS来创建JavaScript控件的一个小的例子:

 (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
 });
 })();
 

并在页面中以如下方式声明控件:

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

也许您并不熟悉这些,尤其是第一次接触WinJS,下面来我们来做下详细的解释。

1. 将代码包含在一个立即执行的函数中,这是JavaScript常见的模式:     

 (function () {
 
 …
 
 })();

这样做的目的是确保我们的代码是自包含的,并且不会创建任何无意义的全局变量,这是一个最佳的模式。

2. 函数起始处的“use strict”声明意味着使用ECMAScript 5 strict mode这个模式。这种做法是windows store app 的一个最佳的模式,它可以提高错误的检查效率以及与JavaScript的下一个版本的兼容性。

3. 现在我们来解释一些WinJS独有的代码。WinJS.Class.define() 用来创建一个类,这个类可以帮助我们调用markSupportedForProcessing() 函数,也可以简化对属性的创建。

4. 定义了一个名为Control_ctor的构造函数。WinJS.UI.processAll () 是在Default.js中调用的,它会扫描页面上所有data-win-control属性中的内容, 找到我们的控件, 调用构造函数。

5. 在构造函数中,对页面元素的引用被存储在element这个参数中。然后把当前这个对象的实例(this)存储在element的winControl属性中。

  • ·element || document.createElement("div") 用来支持命令式模型, 允许用户之后将一个控件附加在页面的某个元素上
  • ·这样维护页面元素的引用以及通过设置element.winControl来将该元素作为控件的一个引用来说是一个很好的方式。您不必担心对DOM对象循环引用会造成内存泄露,因为Internet Explorer 10会帮您关注这些。
  • ·该构造函数可以定义该控件的文本并将其设置为”Hello,World!”。

6. WinJS.Namespace.define() 函数能使该控件是公共的且能被您项目中任何代码访问到。如果您不使用该函数,那么您需要在内联函数之外使用全局命名空间。

定义控件的options

将可配置的选项添加到自定义控件中会使我们的例子显得更有趣。下面的例子中会添加一个选项来控制内容以闪烁的形式出现。

 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
 });
 

接下来,我们将这个控件添加到页面,您可以在data-with-option属性里面配置以上选项:

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

为了使控件支持可配置选项,我们对代码作了如下更改:

1. 该选项是作为构造函数的参数来传递给控件的

2. 该选项的默认值是该类的私有属性的值

3. 调用WinJs.UI.SetOptions()函数可以重写该选项的默认配置

4. 添加一个公共属性(名为blink)作为新的选项

5. 通过添加一个函数来将控件的文本与blink选项相关联,从而控制文本以闪烁的形式出现。

在此示例中担当重任的部分是对WinJS.UI.setOptions()函数的调用。该函数能将选项中每个字段的值赋值到目标对象,目标对象是该函数的第一个参数。

在上述例子中,我们通过data-with-options来将控件的blink选项值设置为”true”。通过在构造函数中对setOptions()函数的调用就可以将该值传递给控件。即我们定义了一个名为blink的属性并定义了setter函数。该setter函数会被setOptions()调用并且将blink属性值赋值到控件。

为控件添加事件

我们已将为控件添加了一个blink选项,现在我们来为控件添加一个事件,该事件在blink的属性值改变时触发。

 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);
 

现在我们给元素添加一个id,这样我们可以通过该id访问该元素。

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

添加一个事件监听器来监听“blink”事件:

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

 当您运行这段代码的时候,您会在visual studio 的JS控制台窗口看到一条每隔500毫秒输出的消息。

以下四个更改使得控件支持这个行为:

1. 调用了WinJS.Class.mix(Contoso.UI.HelloWorld,WinJS.Utilities.createEventProperties("blink"));创建了一个”onblink”属性,用户可以通过后台代码改变它或者可以在html页面的声明中绑定它。

2. 对WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.UI.DOMEventMixin) 的调用将addEventListener, removeEventListener, 与dispatchEvent函数添加到了控件。

3. 通过对that.dispatchEvent("blink", {element: that.element})的调用blink事件就会被触发。并且该自定义事件是由元素的某个字段创建的。

4. 附加事件处理程序以监听blink事件。且对dispatchEvent()的调用仅仅在您已经在控件的构造函数中设置this.element时才会有效。

创建公共事件

下面我们来创建一个公共的函数即doBlink()函数来改变blink的值:

 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);
 

如果您需要通过javascript来调用doBlink()函数,那么您需要引用该控件。如果您是命令式的创建该控件,那么您可能已经添加了该控件的引用。如果您是以声明性的方式来进行的,那么您可以通过html元素的winControl属性访问该控件。例如,你可以通过以下代码访问:

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

总结与回顾

通过上面的讨论我们为建立自定义控件做了以下工作:

  1. 将控件包含到页面
  2. 为控件创建options
  3. 为控件创建事件并响应该事件
  4. 为控件创建公共方法

希望上述内容为您在windows store app创建基于JavaScript控件有一定的帮助。如果您有问题请到论坛提问。