HTML5

编写面向业务的 JavaScript Web 应用程序

Frank Prößdorf

Dariusz Parys

 

Microsoft 投入大量精力 HTML5 和 JavaScript 作为键升级为 Windows 开发人员,并且没有可用于生成生产应用程序的许多高质量库和框架。在本文中,我们将创建一个基本的面向业务的应用程序可以作为起点到现有的选项更深层次的 digging — 并让您体验 JavaScript 可以是内容相当有趣编码质量。

应用程序将编目产品并将它们划分为类别,而产品和类别可以创建、 更新和删除 (CRUD),如中所示图 1。除了这些典型的 CRUD 操作,应用程序将处理其他标准的任务: 国际化、 验证的应用程序的输入和键盘控制。该应用程序的最重要方面之一是它将使用 HTML5 本地存储区以允许脱机编辑。我们将不会进入此此处的所有详细信息,但您可以上找到此示例应用程序的完整代码 codeplex

The Products Overview List
图 1 的产品概述列表

开始使用

那么,该如何着手编写面向业务的 JavaScript 应用程序?公认的一种方法是使用模型-视图-控制器 (MVC) 结构。这已成功地利用了像 Ruby on Rails、 Django 或 ASP 的框架中。NET MVC。MVC 允许一个严格的结构和应用程序如视图和业务逻辑的问题分离出来。因为它是真正方便地编写的代码混乱,全力在一个位置,这是用 JavaScript 尤其重要。我们经常不得不提醒自己不可以做到这一点还可以尝试并编写干净、 可读性和可重用的代码。有几个框架构建 MVC 结构,最值得注意的是 Backbone.jsEyeballs.jsSammy.js

对于此示例应用程序我们使用 Sammy.js,主要是因为我们已经知道,但还很小,编写得很好,因为测试并不会着手实施,我们需要的所有事情。它不会为您提供一个隐式的 MVC 结构,但它使您可以轻松地在其基本基础上构建。该组织目前有唯一的依赖项是 jQuery ,即我们是否仍要使用 DOM 操作的库。我开始使用的目录结构如下所示:

- public
  - js
      app.js
    + controllers
    + models
    + helpers
    + views
  + templates
  - vendor
    - sammy
        sammy.js
    - jquery
        jquery.js

我们将可能通过 JavaScript 代码模板目录中,呈现的所有模板文件和所有的 JavaScript 代码用于呈现视图目录中的这些模板相关。

应用程序文件

我们创建实际的 Sammy.js app.js—here 加载控制器和将其路由中的应用程序进行初始化 (请参阅图 2)。 我们倾向于命名空间的所有变量 (控制器、 模型和等) 创建。 在这种情况下,我们选择调用此命名空间 karhu,我们正在编目其产品的公司名称。

图 2 Karhu.app

karhu.app = $.sammy(function() {
  this.element_selector = '#main';
  this.use(Sammy.Mustache, 'mustache');
  this.use(Sammy.NestedParams);
  this.use(Sammy.JSON); 
  this.helpers(karhu.ApplicationHelper);
  this.helpers({ store: karhu.config.store });
  karhu.Products(this);
});
$(function() {
  karhu.app.run('#/products');
});

第一步是加载插件,如 Mustache,这是一个模板呈现引擎。 然后初始化帮助 (karhu。ApplicationHelper) 和控制器 (karhu。产品)。 定义应用程序并加载所有 DOM 元素后,可以运行该应用程序并定向到初始的传送,这是所有产品的索引。

写作测试

之前向您展示如何产品控制器的工作方式,并显示所有产品,我们想要简要转到如何 JavaScript 应用程序的质量可以大大增加通过测试。 随着我们进入有关开发示例应用程序,每个主要步骤之前我们先编写验收测试,以确保代码实际上会起作用。 这样还可以避免回归测试,保证正确写入之前也仍然函数的所有内容。 对于更复杂的代码,我们编写单元测试,并尝试涵盖大多数运行代码时可能发生的情况。 编写验收测试的最简单且最具可读性的方法之一是使用 Capybara 与 Selenium,但一次无头的浏览器喜欢 PhantomJS 都可用作 Capybara 驱动程序,将可能更有意义的 Selenium,而不是那些使用如它们快得多。

对于我们的第一个方案 (图 3),我们将测试是否我们可以看到产品的列表。

图 3: 测试方案

Feature: Products
  In order to know which products I have
  As a user
  I want to see a list of products
  Scenario: list products
    Given a category "Trees" with the description "Plants"
      And a product "Oak" with the description "Brown"
                                         and the price "232.00€"
                                         that is valid to "12/20/2027"
                                         and belongs to the category "Trees"
      And a product "Birch" with the description "White"
                                         and the price "115.75€"
                                         that is valid to "03/01/2019"
                                         and belongs to the category "Trees"
    When I go to the start page
    Then I should see "Trees"
      And I should see "Oak"
      And I should see "Brown"
      And I should see "232.00€"
      And I should see "12/20/2027"
      And I should see "Birch"

对于单元测试,有很多不同的可能性。 我们用来处理 jspec,因为它是类似于 Ruby 的 rspec,我们之前使用了其中的不同而不同。 现在支持的已否决 jspec Jasmine,因此我们这里使用的。 它非常出色的工作,并包括一个 rake 任务,使其很容易地运行验收测试旁边。 下面是一个示例应用程序的单元测试如下所示:

describe("Product", function() {
  describe("attachCategory", function() {
    it("should assign itself its category", function() {
      var categories = [{id: 1, name: 'Papiere'}, {id: 2, name: 'Baeume'}];
      var attributes = {id: 1, name: 'Fichte', category_id: 2};
      var product = new karhu.Product(attributes, categories);
      expect(product.category.
name).toEqual('Baeume');
    });   
  });
});

定义控制器

一旦我们完成方案我们开始写入的控制器,这是非常简单刚开始:

karhu.Products = function(app) {
  app.get('#/products', function(context) {
    context.get('/categories', {}, function(categories) {
      context.get('/products', {}, function(products) {
        products = products.map(function(product) { return new karhu.Product(
          product, categories); });
        context.partial('templates/products/index.mustache', {products: products});
      });
    });
  });
};

时刻只有一个定义的路由,即每个产品的 # 工艺路线上获取。一旦在 URL 中的位置哈希改为 /products,将运行回调。因此,如果您将工艺路线追加到您的 URL (如 http://localhost:4567/index.html#/products) 时,将执行附加的回调。相同时执行的操作第一次启动该应用程序,因为我们在 app.js 中定义的初始的路径指向同一个路由。

内部路由我们检索的类别和产品通过不要只是基本 AJAX GET 请求到我们后端的助手。一旦我们检索此数据,我们将其映射到 JavaScript 对象,然后呈现 index.mustache 模板内的那些对象。这将致使它们在 < div id ="main"> HTML 标记,将作为根 element_selector,app.js 文件中定义。

定义模型

我们需要将数据映射到 JavaScript 对象,以便我们可以与他们属于并呈现的旁边该产品,如下所示的类别名称的类别相关联的产品:

karhu.Product = function(attributes, categories) {
  _.extend(this, attributes);
  attachCategory(this, categories);
  function attachCategory(product, categories) {
    product.category = _.find(categories, function(category) {
      return parseInt(category.id, 10) === parseInt(product.category_id, 10);
    });
  }
};

我们扩展了具有该产品的所有属性的对象,然后我们将产品的类别附加到该对象。我们保留以使其私有函数闭包内的 attachCategory。请注意,在这段代码的情况下提供的下划线功能使用 Underscore.js。此库定义可枚举接口,用于帮助器的功能,并可帮助您编写易于阅读的、 简明的代码。

图 4 模型将显示给用户。

Interacting with the Product Model in the Web App
图 4 与 Web 应用程序中的产品模型交互

呈现模板

与刚才所示的模型,我们不需要其他查看图层对象因为呈现逻辑是非常简单 — — 它只是我们创建的产品对象进行迭代并显示的每个,包括我们事先附加的类别名称的属性。将呈现逻辑免 mustache 模板的外观所示图 5。

图 5 Mustache 模板

<h2>Products</h2>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Description</th>
      <th>Price</th>
      <th>Valid To</th>
      <th>Category</th>
    </tr>
  </thead>
  <tbody>
    {{#products}}
      <tr>
        <td>{{name}}</td>
        <td>{{description}}</td>
        <td>{{unit_price}}</td>
        <td>{{valid_to}}</td>
        <td>{{#category}}{{name}}{{/category}}</td>
      </tr>
    {{/products}}
  </tbody>
</table>

呈现的输出所示图 6。

Rendered HTML Output from the Mustache Template
图 6 Mustache 模板中呈现的 HTML 输出

将特定模型控制器代码移动到模型

它是一种品位给定一个控制器多少责任并为模型代码重构多少可以。 如果我想要编写的代码图 5 以更加以模型为中心的方式,我做的事情可能类似于图 7

图为 7 多个模型中心的做法

控制器

karhu.Products = function(app) {
  app.get('#/products', function(context) {
    karhu.Product.all(function(products) {
      context.partial('templates/products/index.mustache', {products: products});
    });
  });
};

型号

karhu.Product.all = function(callback) {
  karhu.backend.get('/categories', {}, function(categories) {
    karhu.backend.get('/products', function(products) {
      products = products.map(function(product) { return new karhu.Product(product, categories); });
      callback(products);
    });
  });
};

标准任务

有大量非常常见 Web 开发和 JavaScript 应用程序上工作时将重复发生的任务。 我们想说明我们如何解决这些任务并解决我们遇到的问题。 像往常一样,有若干不同的方法可用于问题。

身份验证大多数应用程序,包括这本,通过让用户在记录中添加基本的安全。 因为 HTTP 是无状态的所以您需要重新发送的每个请求的身份验证。 您可以采取措施这一点通过用户首次登录时保存一个标记,然后此后对于每个请求使用该令牌。 我的选择是在本地存储区中保存一个标记,一旦用户有登录成功,并再为附加到 XMLHttpRequest 标题发送该令牌。 要执行此操作的代码所示图 8。 此代码被 stashed 中所使用的帮助器的功能,如前面提到的后端模型。

图 8 用户登录时保存标记

this.get = function(url, data, success, error) {
  sendRequest('get', url, data, success, error);
};
function authenticate(xhr) {
  var token = '';
  if(karhu.token) {
    token = karhu.token;
  } else if(karhu.user && karhu.password) {
    token = SHA256(karhu.user + karhu.password);
  }
  karhu.token = token;
  xhr.setRequestHeader("X-Karhu-Authentication", 'user="' + karhu.user + '", token="' + karhu.token + '"');
};
function sendRequest(verb, url, data, success, error) {
  $.ajax({
    url: url,
    data: data,
    type: verb,
    beforeSend: function(xhr) {
      authenticate(xhr);
    },
    success: function(result) {
      success(result);
    }
  });
}

图 9 显示保存的用户令牌。

X-Karhu-Authentication Included in an HTTP Request
图 9 X-Karhu-验证 HTTP 请求中包含

如果用户只需登录,则必须提供用户名和密码可用。 如果用户先前登录,您有一个已保存的令牌。 这两种方法,您将附加为标题的标记或用户名/密码组合,并且如果请求成功,您知道用户身份验证成功。 否则后, 端将只返回一个错误。 这种方法是实施相对简单,我们遇到的唯一问题是代码变得有点复杂,无法读取数据。 若要解决此问题,我们要求助手重构到单独的模型。 到后端模型抽象请求是库的非常常见,作为,例如,与 Backbone.js 库所在的核心部分。 身份验证代码通常是唯一的每个应用程序,它总是取决于后端和它需要发送的前端。

国际化 (I18n) 国际化是常见的任务对于 Web 应用程序,和 jquery.global.js 通常用来在 JavaScript 应用程序中实现它。 此库用于数字和日期的格式提供的方法,并允许您翻译字典使用当前区域设置的字符串。 一旦加载此字典中,这是一个简单的 JavaScript 对象键和翻译后的值,的唯一需要注意设置数字和日期格式。 要做到这一点的合理位置是模型中之前呈现模板对象。 在产品模型中它看上去像下面这样:

var valid_to = Date.parse(product.valid_to);
product.valid_to = $.global.format(valid_to, "d");

图 10 显示德语作为显示语言。

Switching the Language to German
图 10 将语言切换为德语

验证在 JavaScript 中开发的好处之一是您可以授予用户实时反馈。因此有必要使用这种可能性来验证数据之前将其发送到后端。请注意仍有必要验证后端中的数据,因为可能有不使用前端的请求。JQuery 库 jquery.validate.js 通常用于进行验证。它在窗体上提供一套规则,并适当的输入字段上显示错误,如果内容不匹配规则。其意义到模型的结构这些有效性规则我们已经有了,因此每个模型都有一个验证函数,返回这些规则。这里是我们类别的模型的有效性规则无法显示方式:

karhu.Category = function() {
  this.validations = function() {
    return {
      rules: {
        'category[name]': {
          required: true,
          maxlength: 100
        }
      }
    };
  };
};

图 11 显示错误可能的显示方式。

A Validation Error on Creation of a New Category
图 11 A 上的一个新的类别创建的验证错误

验证将进一步转。禁止导航离开尚未提交窗体。用户必须实际提交有效数据或主动取消数据项 (请参阅图 12)。

A Red Flash Notification Warns User of Unsubmitted Form Data
图 12 A 红色闪存通知,警告用户的未提交的表单数据

对于脱机编辑缓存对象有时用户需要脱机工作。允许对此属于最集中、 最复杂的应用程序。所有对象都需要,以便应用程序处于离线状态后它们可以正确排序、 分页和筛选缓存的时间提前。需要队列中的所有操作发生之前缓存对象,以便这些操作可以应用于对象,只要它们高速缓存。此外需要那里我们实际上处于离线状态,以便当我们回到联机状态时,离线完成的所有内容都可以对进行修补通过后端到后填充第二个队列。图 13 显示应用程序脱机。

The Application Response When Taken Offline
图 13 时脱机应用程序响应

有许多需要除了已经复杂缓存和排队过程解决的问题。例如,当脱机工作时,会创建一个对象,它无法更新或删除不需要进一步的代码,因为它没有 id。我们合作围绕它现在只是禁止这些操作创建的对象,而脱机。对于该同样的原因,创建类别而离线不能用于创建产品。我只是不显示这些类别的用于创建产品的可用类别列表中。通过使用临时 id 以及重新安排脱机队列,可能会解决这些类型的问题。

此外,也需要进行缓存的可用要么和模板。这可通过缓存清单定义在 HTML5 如果目标浏览器组支持它,或只需通过加载要么并将它们放入本地存储区。这是非常简单,与 Sammy.js,如下所示:

context.load('templates/products/index.mustache', {cache: true});

集成到 Windows

Internet Explorer 9 非常适合于运行 HTML5 应用程序。此外,它使 Web 应用程序能够以本机方式集成到 Windows 7 任务栏,增强了应用程序与显示通知、 集成导航和提供跳转列表支持的可能性。这种集成的跳转列表非常简单,在其最简单窗体只是一个 meta 标签属性的声明。这是完全 Karhu,这使得用户可以方便地访问他们需要的东西中使用的方法。您可以直接转到视图中添加产品,添加类别、 产品概述和类别概述的跳转列表任务 (请参阅图 14)。下面的代码演示如何声明一个简单的跳转列表任务:

<meta name="msapplication-task"
      content="name=Products;
      action-uri=#/products;
      icon-uri=images/karhu.ico" />

Jump List Tasks
图 14 跳转列表任务

您可以全方位了解固定托管指针和在 Windows 7 集成构建我固定的网站,哪有有关 Web 应用程序,如浏览器通知和动态 jumplists 使用 JavaScript 的其他想法。很容易地将更多的功能添加到您的 Web 应用程序,只需少量的额外工作。获得更好地了解该主题的其他起始点是 MSDN JavaScript 语言参考和前面提到的库和框架的文档。

总结

此时,我们已经实现的所有示例应用程序的基本要求。具有足够的时间,我们可能还注意前面所提到的问题。(如身份验证、 国际化和处理业务逻辑的任务需要进行编码,独立于框架和库,它们是其实只是一个起点。

如果您始终编写测试,并注意到结构中的应用程序,编写生产就绪 JavaScript 应用程序可以继续发展是,在我们看来,唯一可能但值得进行投资的目标。起步非常容易,但务必保持基本干净代码检查和重构必要。如果满足这些要求,JavaScript 使您有机会编写非常优雅和维护应用程序。

Frank Prößdorf 是兼职的 Web 开发商和创始人之一的 NotJustHosting 谁喜欢使用拼音和 JavaScript。他热情是发现并播放用新技术。因为他是兴奋的机会参与和共享代码和观点,他定期支持开放源代码软件。除了他的工作,他喜欢旅游、 sailing 和播放网球。通过他认识 Prößdorf github 配置文件或他网站

Dariusz Parys 是在 Microsoft 德国开发推广人员进行 Web 开发强的重点放在即将举行的技术。目前他编写大量的 JavaScript 和 HTML5。您可以通过他博客联系 Parys downtocode.net

这要归功于以下的技术专家审阅这篇文章: Aaron Quint