65.9K
CodeProject 正在变化。 阅读更多。
Home

创世记:使用 WinJS 和 XML 创建动态对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (2投票s)

2012年11月18日

CPOL

8分钟阅读

viewsIcon

16364

操作指南:在运行时根据 XML 文件创建 JavaScript 对象。

引言

欢迎阅读最新的“创建论”系列文章。该系列文章关注使用常见的微软技术创建的各种构造。本文将介绍如何基于 XML 文件创建 JavaScript 对象。文章将假定您具备 JavaScript、WinJS 和 XML 的基础知识。

问题所在 

我正在开发一个 Windows 8 应用,需要一组易于维护的值供 JavaScript 使用。我最初的尝试是创建一个包含所需值的 Constants 对象。随着常量数量的增加,这很快变得难以管理。为了解决这个问题,一个类似于 ASP.Net 的 web.config 文件会很棒。但问题是我不想像 ASP.NET 网站那样,受限于通过某种 AppSettings 属性来访问它。我更倾向于一个 JavaScript 对象,它包含我需要的属性,但数据来自配置文件。我该如何实现呢?

解决方案 

解决我问题的办法是在运行时创建一个 JavaScript 对象,该对象根据 XML 文件包含属性和值。JavaScript 的一些特性使得这个解决方案可行:

  1. JavaScript 是一种高度动态的语言。考虑到这一点,在运行时创建对象应该就像 Eddie Van Halen 弹吉他一样得心应手。
  2. 可以使用普通的数组语法来访问和操作对象属性。
  3. JavaScript 可以访问服务器数据。AJAX 就是这个缩写的缩写。
  4. 既然已经获得了 XML 文件,就可以像解析普通 HTML 文档一样对其进行解析。

结合这四点实现,我就可以在运行时使用 XML 文件的数据来创建一个对象。下一节将解释如何做到这一点。

实现 

实现这个功能有三个基本步骤。WinJS 提供的机制将非常有帮助。首先,需要获取 XML 文件。这可以通过 WinJS.xhr 函数来实现。更多关于此函数的信息可以在 MSDN 上找到:“WinJS.xhr 函数 (Windows)”。下一步是解析 XML 文件,然后根据从文件中检索到的数据创建 Constants 对象。这些步骤将在以下各节中更详细地解释。

配置文件 

要使用该文件,需要先创建它。下面是文件中的示例内容:

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
  <Constant1>
    <add name="String" value="Value 1" />
    <add name="Integer" value="1" />
  </Constant1>
  <Constant2>
    <Boolean>
      <add name="IsTrue" value="true" />
      <add name="IsFalse" value="false" />
    </Boolean> 
  </Constant2>
  <Constant3>
    <add name="SubConstant" value="SubValue" />
    <NextSubConstant>
      <add name="NextSub" value="NextSubValue" />
    </NextSubConstant>
  </Constant3>
</Configuration>  

可以看到,这只是一个普通的 XML 文件。有各种嵌套的节点和属性,以展示此解决方案支持的一些可能的节点结构。在这篇文章中,文件名是“config.xml”,根节点是“Configuration”。

数据 

WinJS.xhr 函数用于获取 XML 文件。此函数接受一个对象来配置请求,并返回一个 Promise。如下所示:

WinJS.xhr({
    url: "config.xml",
    headers: {
        "If-Modified-Since": "Wed, 2 Feb 2000 11:11:11 GMT"
    },
    responseType: "document"
}).done(function(result) {
    if (result.status === 200) {
        var data = result.responseXML;
        var root = data.querySelector("Configuration");
        WinJS.Namespace.define("ST.Constants");
        iterate(root, ST.Constants);
    } else {
        /* ... Handle missing document ... */
    }
}); 

概述 

上面展示的内容可以被认为是 jQuery AJAX 的升级版。调用 xhr 函数,传入一个对象来配置 XMLHttpRequest。唯一必需的属性是 url。其他可选属性可用于进一步配置 XMLHttpRequest。这很像 jQuery 的 ajax 方法,但在这种情况下,会返回一个 Promise 对象。返回的 Promisedone 方法用于处理返回的 XML 文档。

配置 XMLHttpRequest

WinJS.xhr({
    url: "config.xml",
    headers: {
        "If-Modified-Since": "Wed, 2 Feb 2000 11:11:11 GMT"
    },
    responseType: "document"
})  

传递给 xhr 函数的参数是一个对象。如前所述,此对象用于配置 XMLHttpRequest。这里将 url 设置为 XML 文件的路径。然后,使用 headers 属性,其中包含 “If-Modified-Since” 日期。这确保了 XML 文件的获取。最后一个属性是 responseType,用于确保此调用的结果是一个可以被 JavaScript 解析的文档。

处理 XML 文件

.done(function(result) {
    if (result.status === 200) {
        var data = result.responseXML;
        var root = data.querySelector("Configuration");
        WinJS.Namespace.define("ST.Constants");
        iterate(root, ST.Constants);
    } else {
        /* ... Handle missing document ... */
    }
});

下一步是处理返回的 XML 文件。xhr 函数返回一个 Promise 对象。该对象包含一个 done 方法,可用于处理 xhr 函数调用的结果。与通常使用 XMLHttpRequests 的情况一样,第一步是确保请求成功。使用传递到 done 处理程序的 result,可以确定请求的状态。此 result 对象就是 XMLHttpRequest。它包含一些有用的属性,其中有 statusresponseXML。成功的状态是 200。如果 status 是 200,函数将继续处理结果,首先从 result 对象获取 responeXML。由于 responseType 设置为“document”,responseXML 的内容是可以通过 JavaScript 解析的 XML 文档。该文档的迭代将在下面更详细地描述,但在此之前还有两个步骤。迭代方法接受两个参数。第一个参数是要处理的节点。要开始,需要处理根节点,所以这是检索到的内容。第二个参数是要与节点一起处理的对象。该对象旨在是静态的,因此使用 WinJS.Namespace.define 方法来创建它。将这两个参数传递给 iterate 方法。下一节将提供更多详细信息。

迭代

有毛发、牙齿、肉体和骨骼,但 iterate 方法才是精髓。该方法内包含了 XML 文件的遍历以及对 Constants 对象的添加。下面是实现:

function iterate(node, ns) {
   if (node && node.childNodes) {
      if (node.childNodes.length > 0) {
         var nodes = node.childNodes;
         var len = nodes.length;
         for (var i = 0; i < len; i++) {
            var sub = nodes[i];
            if (sub.nodeType === sub.ELEMENT_NODE) {
               var name = sub.nodeName;
               if (sub.childNodes.length > 0) {
                  ns[name] = {};
                  iterate(sub, ns[name]);
               } else {
                  var attName = sub.getAttribute("name");
                  var attValue = sub.getAttribute("value");
                  //Key/Value Attribute
                  if (attName && attValue) {
                     ns[attName] = attValue;
                  //Node Name/Value
                  } else if (attValue && node.nodeName && !attName) {
                     ns[name] = attValue;
                  } else {
                     /* ... Handle Data Not Available ... */
                  }
               }
            }
         }
      } else if (node) {
         var name = node.nodeName;
         var attName = node.getAttribute("name");
         var attValue = node.getAttribute("value");
         //Key/Value Attribute
         if (attName && attValue) {
            ns[attName] = attValue;
            //Node Name/Value
         } else if (attValue && node.nodeName && !attName) {
            ns[name] = attValue;
            //Log Warning
         } else {
            /* ... Handle Data Not Available ... */
         }
      }
   }
}

上面是 iterate 方法的实现。该方法对当前节点、子节点和属性进行各种验证检查。还有处理丢失数据的空间。

处理无子节点

该方法首先检查给定的节点及其子节点是否存在。如果存在任何子节点,则继续处理子节点。如果没有子节点,该方法会检查确保存在一个节点,并在成功时尝试获取 namevalue 属性。如下所示:

else if (node) {
   var name = node.nodeName;
   var attName = node.getAttribute("name");
   var attValue = node.getAttribute("value");
   //Key/Value Attribute
   if (attName && attValue) {
      ns[attName] = attValue;
      //Node Name/Value
   } else if (attValue && node.nodeName && !attName) {
      ns[name] = attValue;
      //Log Warning
   } else {
      /* ... Handle Data Not Available ... */
   }
}

这里有三种可能的路径可供选择:

  1. 如果 namevalue 属性都存在,则更新 Constants 对象。将一个新属性添加到对象中。这部分利用了 JavaScript 对象属性可以通过普通数组语法访问和操作的优势。新属性以 name 属性的值创建,并被赋值为 value 属性的值。
  2. 如果存在 value 属性和节点名称,但不存在 name 属性,则使用节点名称更新 Constants 对象。新属性的名称是节点名称。此新属性的值是 value 属性的值。
  3. 如果以上两种情况都不存在,则没有可用数据,因为此解决方案目前是这样实现的。这是一个处理这种情况的好地方,可以记录发生的事件、添加默认数据,或者以任何其他必要的方式进行处理。

带子节点的处理

如果给定的节点存在子节点,iterate 方法将继续处理子节点。下面是相关代码片段:

if (node.childNodes.length > 0) {
   var nodes = node.childNodes;
   var len = nodes.length;
   for (var i = 0; i < len; i++) {
      var sub = nodes[i];
      if (sub.nodeType === sub.ELEMENT_NODE) {
         var name = sub.nodeName;
         if (sub.childNodes.length > 0) {
            ns[name] = {};
            iterate(sub, ns[name]);
         } else {
            var attName = sub.getAttribute("name");
            var attValue = sub.getAttribute("value");
            //Key/Value Attribute
            if (attName && attValue) {
               ns[attName] = attValue;
               //Node Name/Value
            } else if (attValue && node.nodeName && !attName) {
               ns[name] = attValue;
            } else {
               /* ... Handle Data Not Available ... */
            }
         }
      }
   }
}

检查节点以确保它有子节点。如果确实有,则在简单的 for 循环中处理每个子节点。将当前子节点存储起来,然后检查它是否是一个元素节点。如果是,该方法会检查此节点的子节点。如果它们存在,则使用子节点的名称为给定的 Constants 对象创建一个新属性。然后再次调用 iterate 方法,这次传入当前子节点和 Constants 对象的新属性。这为添加 XML 文件中的嵌套节点提供了支持。

如果当前子节点没有子节点,它将按照上一节“处理无子节点”中的描述进行处理。

结果 

正如您可能已经意识到的,此解决方案会在运行时创建一个对象。在调试对象结构时,可以使用 Watch 窗口进行查看:

Solution Outcome in Watch Window while debugging 

这表明 Constants 对象的结构反映了 XML 文件的结构。

最初设想的增强功能

使用此解决方案提供了许多好处。它灵活、可扩展且性能良好。它在 XML 文件中提供了几乎无限级别的嵌套(除了在创建原型链时可能出现的调用堆栈限制)。由于配置文件是本地于应用程序的,因此没有跨网络造成的性能损失。除了这些优点之外,仍然可以进行一些改进。

属性值

到目前为止,常量的值已设置为 XML 文件中的属性值。也可以使用节点值。

数据类型

并非所有值都适合作为 String。可以在迭代中添加额外的逻辑,使值具有适当的数据类型(例如,如果值应为 Integer,则将其转换为 Integer;如果值应为 Date,则将其转换为 Date)。

缓存

这会在运行时创建一个对象。不要每次应用启动时都创建同一个对象。首次创建对象时,将其存储起来。然后,当应用再次加载时,从存储中检索它。只在发生更改时创建此对象。

结论 

本文介绍了一种通过更新 XML 文件中的值来轻松配置对象的方法。像大多数事情一样,这个解决方案有利有弊,但它能完成工作,并且做得很好。不用说,乐队很开心,对象也已栩栩如生。

感谢您的阅读,请查看我的其他创建论文章

历史

  • 2012-11-17:首次创建。
  • 2013-03-03:更新了 Flickr 图片。
© . All rights reserved.