使用 mODELcLASSjs 进行声明式和响应式约束验证






4.94/5 (7投票s)
通过使用基于模型的框架 mODELcLASSjs,避免在 JavaScript 前端 Web 应用中进行约束验证的样板代码。
简介
本教程是开放获取书籍《使用纯 JavaScript 构建前端 Web 应用》中的一个章节的简短版本。我们展示了如何使用基于模型的开发框架 mODELcLASSjs 来构建具有约束验证的单类前端 Web 应用,该框架有助于避免重复的代码结构(“样板代码”)。前端 Web 应用程序可以由任何 Web 服务器提供,但它在用户自己的计算机设备(智能手机、平板电脑或笔记本电脑)上执行,而不是在远程 Web 服务器上执行。通常,但并非必需,前端 Web 应用程序是单用户应用程序,不与其他用户共享。
我们展示了如何在定义为 mODELcLASS 元类实例的模型类中声明性地表达完整性约束,以及如何在应用程序的模型代码层中验证数据,并在用户界面层执行响应式约束验证。如果您想了解它的工作原理,您可以从我们的服务器运行本文讨论的 mODELcLASS 验证应用程序。
本教程中提供的 JavaScript 数据管理应用程序的简单形式仅处理一种对象类型(“图书”),并支持四种标准数据管理操作(创建/查找/更新/删除)。它改进了纯 JavaScript 验证教程中讨论的验证应用程序,通过避免模型层中的样板代码
-
用于检查和设置器,以及
-
用于数据存储管理
借助基于模型的开发框架 mODELcLASSjs。
背景
在模型-视图-控制器(MVC)范例中,用户界面(UI)被称为“视图”,而“控制器”一词表示集成 UI 代码与模型类所需的胶水代码,或者,用 MVC 的术语来说,就是集成“视图”与“模型”。使用基于模型的开发方法,应用程序的模型类是通过对应用程序的数据模型进行编码来获得的,该数据模型通常以 UML 类图的形式表示。由于模型层负责定义和验证约束以及管理数据存储,因此我们需要可重用的模型代码以通用方式处理此问题,以避免针对每个类和每个属性的约束验证样板代码,以及针对每个类的用于数据存储管理的样板代码。这时 mODELcLASSjs 就派上用场了。它提供了一个通用的检查方法来验证属性约束,并且通用的存储管理方法 add、update 和 destroy 分别用于创建新的(持久化的)对象/行、更新现有对象/行以及删除它们。
我们不应混淆 MVC 范例中使用的“模型”一词(许多 Web 开发框架也采用此术语)以及 UML 和其他建模语言中使用的“模型”一词。前者指的是应用程序的模型类,而后者指的是模型概念,无论是作为对现实世界某一部分的简化描述,还是作为用于构建的设计蓝图。
在基于模型的工程中,模型是设计和实现系统的基础,无论要构建的系统是软件系统还是其他类型的复杂系统,如制造机器、汽车或组织。
在基于模型的软件开发中,我们区分三种类型的模型
- 与解决方案无关的领域模型,描述现实世界的一个特定部分,并在需求和领域工程中从系统分析或开发项目的初始阶段产生;
- 与平台无关的设计模型,指定一个逻辑系统设计,是从精炼阶段的设计活动产生的;
- 与平台相关的实现模型(包括数据模型),这是实现阶段技术系统设计的结果。
领域模型是设计软件系统的基础,通过创建一个与平台无关的设计模型,该设计模型又是在创建实现模型并将其编码成所选平台的语言的基础上实现系统的基础。对于信息建模,我们首先创建一个领域信息模型,然后从中派生出一个信息设计模型,最后将信息设计模型映射到所选平台的数据模型。通过面向对象编程(OOP)方法,我们将这个数据模型编码成模型类的形式,这些模型类是设计和实现数据管理用户界面(UI)的基础。
mODELcLASSjs 允许直接编码信息设计模型,从而促进了基于模型的应用程序开发。
类的概念在面向对象编程中是基础。对象是类的实例(或由类进行分类)。一个类定义了实例化它的对象的属性和方法。
JavaScript 中没有显式的类概念。但是,类可以通过两种方式定义
-
以构造函数的形式,它允许使用
new
运算符创建类的实例。这是 Mozilla JavaScript 文档中推荐的传统方法。 -
以工厂对象的形式,它使用预定义的
Object.create
方法创建类的实例。
由于我们通常需要定义类层次结构,而不仅仅是单个类,所以这两种替代方法不能在类层次结构中混合使用,并且我们在构建应用程序时必须做出选择。使用 mODELcLASSjs,您选择第二种方法,它具有以下优点
-
属性被声明(带有属性标签、范围和许多其他约束)
-
支持对象池
-
支持多重继承和多重分类
使用 mODELcLASSjs
我们展示了如何使用基于模型的开发框架 mODELcLASSjs 构建一个具有约束验证的单类数据管理应用程序,以避免样板模型代码。与纯 JavaScript 验证教程相比,我们处理相同的问题:展示 1) 如何在模型类中定义约束,2) 如何基于模型类中定义的约束在用户界面中执行响应式验证。使用 mODELcLASSjs 的主要区别在于定义约束变得更加简单。纯 JavaScript 验证教程中使用的检查方法不再需要。由于约束是以纯声明性方式定义的,因此它们的文本编码直接对应于它们在信息设计模型中的表达。这意味着我们可以直接编码信息设计模型,而无需先从中创建数据模型。
与我们纯 JavaScript 验证教程一样,我们应用程序的目的是管理图书信息。信息项和约束在以下信息设计模型中进行了描述。
编码设计模型
现在我们展示如何编码设计模型所示的十个完整性约束。
-
对于
Book
类定义的四个属性中的前三个,我们有一个强制值约束,由基数表达式 [1] 表示。但是,由于 mODELcLASSjs 中的属性默认是强制的,所以我们不需要为它们编码任何内容。只有对于edition
属性,我们需要使用键值对optional: true
来编码它是可选的,如下面类定义中的edition
属性声明所示。 -
isbn
属性被声明为Book
的标准标识符。我们在isbn
属性声明中使用键值对stdid: true
来编码这一点(以及隐含的唯一性约束),如下面的类定义所示。 -
isbn
属性有一个模式约束,要求其值匹配 ISBN-10 格式,该格式仅允许 10 位数字字符串或后跟“X”的 9 位数字字符串。我们使用键值对pattern:/\b\d{9}(\d|X)\b/
和由patternMessage:"The ISBN must be a 10-digit string or a 9-digit string followed by 'X'!"
定义的特殊约束违反消息来编码此约束。 -
title
属性有一个字符串长度约束,最大长度为 50 个字符。这使用max: 50
进行编码。 -
year
属性有一个区间约束,最小值为 1459,最大值不固定,而是由实用函数nextYear()
提供。我们可以使用键值对min: 1459
和max: util.nextYear()
来编码此约束。 -
最后,有四个范围约束,每个属性一个。我们使用相应的键值对来编码它们,例如
range:"NonEmptyString"
。
这导致了模型类 Book
的以下定义
Book = new mODELcLASS({
typeName: "Book",
properties: {
isbn: {range:"NonEmptyString", stdid: true, label:"ISBN", pattern:/\b\d{9}(\d|X)\b/,
patternMessage:"The ISBN must be a 10-digit string or a 9-digit string followed by 'X'!"},
title: {range:"NonEmptyString", max: 50, label:"Title"},
year: {range:"Integer", min: 1459, max: util.nextYear(), label:"Year"},
edition: {range:"PositiveInteger", optional: true, label:"Edition"}
}
});
对于这样的模型类定义,mODELcLASSjs 提供了通用的数据管理操作(Book.add
、Book.update
、Book.destroy
等)以及属性检查和设置器(Book.check
和 object.set
)。
定义模型类后,您可以通过调用 mODELcLASS 提供的 create
方法来创建实例化它的新“模型对象”。
var book1 = Book.create({isbn:"006251587X", title:"Weaving the Web", year: 2000});
然后,您可以应用以下所有由 mODELcLASS 预定义属性和方法
-
type
属性,用于检索对象的直接类型; -
toString()
方法,用于序列化对象; -
set(
prop, val)
方法,用于在检查所有属性约束后设置对象属性; -
isInstanceOf(
Class)
方法,用于测试对象是否是模型类的实例。
以下示例说明了这些 mODELcLASS 功能的使用
console.log( book1.type.typeName); // "Book"
console.log( book1.toString()); // "Book{ isbn:"006251587X", ...}"
book1.set("year", 1001); // "IntervalConstraintViolation: Year must not be smaller than 1459"
book1.set("year", 2001); // change the year to 2001
console.log( book1.isInstanceOf( Book)); // true
项目设置
本项目 MVC 的文件夹结构与我们纯 JavaScript 验证教程中的相同。此外,使用的库文件也相同。
应用程序的起始页首先通过加载 Pure CSS 基础文件(来自 Yahoo 站点)和我们的 main.css
文件来处理页面样式,这借助了两个 link
元素(在第 6 行和第 7 行),然后加载了几个 JavaScript 库文件(在第 8-12 行),包括 mODELcLASS 文件、来自 src/ctrl
文件夹的应用程序初始化脚本 initialize.js
以及来自 src/model
文件夹的模型类 Book.js
。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="UTF-8" />
<title>JS Frontend Validation App Example</title>
<link rel="stylesheet" type="text/css"
href="http://yui.yahooapis.com/combo?pure/0.3.0/base-min.css" />
<link rel="stylesheet" type="text/css" href="css/main.css" />
<script src="lib/browserShims.js"></script>
<script src="lib/util.js"></script>
<script src="lib/errorTypes.js"></script>
<script src="lib/mODELcLASS.js"></script>
<script src="src/ctrl/initialize.js"></script>
<script src="src/model/Book.js"></script>
</head>
<body>
<h1>Example: Public Library</h1>
<h2>mODELcLASS Validation App</h2>
<p>This app supports the following operations:</p>
<menu>
<li><a href="listBooks.html"><button type="button">List all books</button></a></li>
<li><a href="createBook.html"><button type="button">Add a new book</button></a></li>
<li><a href="updateBook.html"><button type="button">Update a book</button></a></li>
<li><a href="deleteBook.html"><button type="button">Delete a book</button></a></li>
<li><button type="button" onclick="Book.clearData()">Clear database</button></li>
<li><button type="button" onclick="Book.createTestData()">Create test data</button></li>
</menu>
</body>
</html>
用户界面
用户界面(UI)与我们纯 JavaScript 验证教程中的相同。只有一个区别。对于响应式约束验证,其中使用输入事件处理程序来检查用户输入的约束,使用的是通用的检查函数 Book.check
。
pl.view.createBook = { setupUserInterface: function () { var formEl = document.forms['Book'], submitButton = formEl.commit; submitButton.addEventListener("click", this.handleSubmitButtonClickEvent); formEl.isbn.addEventListener("input", function () { formEl.isbn.setCustomValidity( Book.check("isbn", formEl.isbn.value).message); }); formEl.title.addEventListener("input", function () { formEl.title.setCustomValidity( Book.check("title", formEl.title.value).message); }); ... }, };
虽然用户输入的验证通过提供即时反馈增强了 UI 的可用性,但表单提交时的验证对于捕获无效数据更为重要。因此,如以下程序列表所示,事件处理程序 handleSubmitButtonClickEvent()
再次执行属性检查。
handleSubmitButtonClickEvent: function () { var formEl = document.forms['Book']; var slots = { isbn: formEl.isbn.value, title: formEl.title.value, year: formEl.year.value, edition: formEl.edition.value }; // set error messages in case of constraint violations formEl.isbn.setCustomValidity( Book.check( "isbn", slots.isbn).message); formEl.title.setCustomValidity( Book.check( "title", slots.title).message); formEl.year.setCustomValidity( Book.check( "year", slots.year).message); formEl.edition.setCustomValidity( Book.check( "edition", slots.edition).message); // save the input data only if all of the form fields are valid if (formEl.checkValidity()) { Book.add( slots); } }
结论
在消除了约束验证和数据存储管理方法的模型层样板代码后,UI 中仍然需要大量的样板代码。在后续的文章中,我们将介绍一种避免 UI 样板代码的方法。
历史
- 2015 年 9 月 9 日,修正了引用和格式。
- 2014 年 10 月 27 日,创建了第一个版本。
- 2014 年 10 月 29 日,添加了使用示例;