HTML5 显而易见是软件开发的下一大热门。起初以 Web 应用程序而为人所知的 HTML5 最终将桌面应用程序的强大功能 — 通过拖放、画布、视频和音频等设施完成 — 引入了浏览器。HTML5 集各种技术(特别是规范)于一体,形成了一个囊括 HTML、JavaScript 和层叠样式表(CSS)在内的功能强大的 API。以下是一些 HTML5 要点:
- 画布
- 拖放
- 地理位置*
- 内联编辑
- Web workers*
- Web 存储*
- 消息传递
- 离线应用
- 视频和音频*
- Web 套接字*
要注意地理位置和离线应用这样的前瞻性功能。(我用星号标记的功能从技术上讲不是 HTML5 规范的一部分呢,不过一般通俗地使用术语 HTML5 来包含我列出的所有功能。参见 参考资料 了解更多信息。)
从某些方面看,HTML5 就是下一个 Java。在 20 世纪 19 年代后期,Java 语言大受欢迎,主要是因为其编写一次,到处运行 的功能使开发人员免于具体选择(或移植到)Windows®、Mac 或 Linux®。HTML5 允许您编写一次,在任何(现代)浏览器中运行,因此您无需在 iOS、Android 和 Chrome 之间做出选择。
HTML5 可能就是下一个 Java,但是不会取代它。Java 技术为服务器端编程提供丰富的生态系统。而最初基于 HTML 的 JSF 允许您便捷地使用 HTML5,如同至今为止通过 JSF 使用 HTML4 一样。您将获得所有强大的 JSF 功能,比如,除了 HTML5 之外,还有 facelets 模板、复合组件和内置 Ajax。
在本文中,您将学习如何使用 JSF2 创建 HTML5 复合组件。在下一篇 JSF 2 fu 文章中,我将向您展示如何创建 HTML5 组件的一个库。
使用 HTML5 其实更多地是涉及到 JavaScript 而非 HTML。也就是说,您需要有一个很好的 JavaScript 调试器。我建议使用 Google Chrome 的内置开发工具中自带的调试器(参见 参考资料),如图 1 所示:
图 1. 使用 Chrome 开发工具调试 JavaScript
在 图 1 所示的 Chrome 调试器中,在 canvas 组件下面有一个包含 JavaScript 代码的面板。
现在,有了一个较好的 JavaScript 调试器,您只需要一个可支持 HTML5 的浏览器。较流行的浏览器的大部分最新版本都可以很好地支持 HTML5。(微软似乎在其即将发布的 Internet Explorer 9 中具有良好的 HTML5 支持。)
HTML5 canvas 是一种成熟的 2D 绘图界面,足以完成 Plants vs. Zombies 和 Quake II 这样的游戏。我对 HTML5 canvas 的使用,如图 2 所示,可能没有那么深入,但是作为指导已经足够了:
图 2. 一个简单的 HTML5 canvas 示例
我向 HTML5 canvas 添加了一些 JavaScript 来实现 图 2 所示的简单绘画应用程序。在移动鼠标时,画布左上角的读出器中显示鼠标坐标。在画布中拖动鼠标时,使用蓝色画笔绘图。
图 2 中所示的应用程序是一个 JSF 应用程序。图 3 显示其目录结构:
图 3. canvas 示例的目录结构
应用程序的 facelet 在 web/WEB-INF/index.xhtml 中定义,且应用程序的 JavaScript 位于 web/resources/application/paintingCanvas.js 中。清单 1 显示 index.xhtml:
清单 1. 使用
<canvas> tag (WEB-INF/index.xhtml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>#{msgs.windowTitle}</title>
</h:head>
<h:body style="background: #fefeef">
<h:outputScript library="application" name="paintingCanvas.js"
target="head" />
<h3>#{msgs.heading}</h3>
<canvas width="400px" height="400px" id="paintingCanvas">
Canvas not supported.
</canvas>
</h:body>
</html> |
清单 1 中的 index.xhtml 文件是一种 HTML5 多语言文档(参见 参考资料),因为它有一个 HTML5 文档类型/名称空间和格式标准的 XHTML 语法 — 这正是我酝酿 facelets 和 HTML5 所必需的。
我使用 <h:outputScript> 标记导入相应的 JavaScript。最后,我开始使用 HTML5 canvas 元素。如果浏览器不明白 <canvas> 标记,它会显示一条 Canvas not supported 消息。<canvas> 标记不会高亮显示;所有有趣的代码在相应的 JavaScript 中,如清单 2 所示:
清单 2. 绘图画布 JavaScript (resources/application/paintingCanvas.js)
window.addEventListener("load", function() {
var canvas, context, painting;
function init() {
canvas = document.getElementById("paintingCanvas");
if (canvas == null) return;
context = canvas.getContext("2d");
if (context == null) return;
painting = false;
context.strokeStyle = "#00f";
context.lineWidth = 3;
context.font = "15px Helvetica";
}
init();
canvas.addEventListener("mousedown", function(ev) {
painting = true;
context.beginPath();
context.moveTo(ev.offsetX, ev.offsetY);
}, false);
canvas.addEventListener("mousemove", function(ev) {
updateReadout(ev.offsetX, ev.offsetY);
if (painting) {
paint(ev.offsetX, ev.offsetY);
}
function updateReadout(x, y) {
context.clearRect(0, 0, 100, 20);
context.fillText("x: " + x + ", y: " + y, 5, 15);
}
function paint(x, y) {
context.lineTo(ev.offsetX, ev.offsetY);
context.stroke();
}
}, false);
canvas.addEventListener("mouseup", function() {
painting = false;
context.closePath();
}, false);
}, false); |
清单 2 使用鼠标光标读出器实现简单的绘画,如 图 2 所示。当页面加载时,我使用 document.getElementById() 获取对画布的引用。从画布中,我获取对画布上下文的引用。我在后续的事件处理程序中使用该内容,我使用 JavaScript 闭包或 Java 开发人员所指的匿名内部类实现它。
如果您使用过 Abstract Window Toolkit (AWT),就会了解画布内容是对 AWT 的图形内容的模仿。毕竟,绘制二维的形状、图像和文本有这么多方式。在 清单 2 中,我使用蓝色的描边风格 初始化上下文,并设置了线宽和字体。之后所需的操作就只是移动、描边、重复、鼠标上下拖动。
学习了 HTML5 canvas 基础知识之后,我将向您展示如何创建一个 JSF 2 HTML5 复合组件。
接下来,我要实现一个使用 HTML5 canvas 的 JSF 2 复合组件。我需要复合组件实现以下要求:
- 拥有可配置的(通过标记属性)宽度、高度、画笔颜色、线宽和 CSS 样式
- 将组件标记的正体作为 canvas not supported 消息
- 自动包括 canvas 的 JavaScript
- 在一个页面中支持多个 canvas 组件
使用 canvas 复合组件的应用程序如图 4 所示:
图 4. 动态 canvas 复合组件
图 4 中所示的应用程序有三个 canvas 组件,每个组件的配置都有所不同。从左边开始,前两个是绘图画布,类似于 图 2 中显示的那一个。最右边的画布仅绘制一个笑脸。
清单 3 显示 图 4 中显示的页面的标记:
清单 3. 使用 canvas 复合组件(WEB-INF/index.xhtml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:h5="http://java.sun.com/jsf/composite/html5">
<h:head>
<meta charset="UTF-8" />
<title>#{msgs.windowTitle}</title>
</h:head>
<h:body style="background: #fefeef">
<h3>#{msgs.heading}</h3>
<h5:canvas id="paintingCanvas" width="300px" height="300px"
penColor="#7F7" lineWidth="7">
#{msgs.canvasUnsupported}
</h5:canvas>
<h5:canvas id="secondPaintingCanvas" width="300px" height="300px"
style="border: thick solid red">
#{msgs.canvasUnsupported}
</h5:canvas>
<h5:canvas id="smileyCanvas" library="application" script="smiley.js"
width="300px" height="300px"
style="border: thin solid red">
#{msgs.canvasUnsupported}
</h5:canvas>
</h:body>
</html> |
在 清单 3 中,canvas 组件导入适当的 JavaScript — 不同于 清单 1,其中我手动使用 HTML5,且必须显式地导入相关的 JavaScript。页面创建者可以使用 canvas 组件的可选 library 和 script 属性来制定一个画布的 JavaScript,或者他们可以依赖于默认的 JavaScript。在 清单 3 中,我为笑脸画布使用 script 属性。我为示例中最左边的两个画布使用默认的 JavaScript(resources/html5/canvasDefault.js,实现绘图画布)。
清单 4 显示 canvas 复合组件的实现:
清单 4. canvas 复合组件(resources/html5/canvas.xhtml)
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="id"/>
<composite:attribute name="width"/>
<composite:attribute name="height"/>
<composite:attribute name="library" default="html5"/>
<composite:attribute name="script" default="canvasDefault.js"/>
<composite:attribute name="style" default="border: thin solid blue"/>
<composite:attribute name="penColor" default="#7777FF"/>
<composite:attribute name="lineWidth" default="2"/>
</composite:interface>
<composite:implementation>
<canvas id="#{cc.id}"
width="#{cc.attrs.width}"
height="#{cc.attrs.height}"
style="#{cc.attrs.style}">
<composite:insertChildren/>
</canvas>
<h:outputScript library="#{cc.attrs.library}"
name="#{cc.attrs.script}"/>
<script>
#{cc.attrs.script}.init('#{cc.id}',
'#{cc.attrs.penColor}',
'#{cc.attrs.lineWidth}')
</script>
</composite:implementation>
</html> |
在 清单 4 中,我为 canvas 复合组件声明了 8 个属性,其中 5 个具有默认值。组件的实现包含一个 HTML5 canvas,带有从组件的相关属性中配置的 ID、宽度、高度和样式。这满足了我对 canvas 组件的第一个要求(可配置属性)。
canvas 复合组件将其子元素
— 即将 <h5:canvas> 标记正体中的任何内容 — 插入到 HTML5 canvas 标记(<canvas>)中。这表示,如果浏览器不支持 HTML5 canvas,标记正体中的文本会显示出来。这满足了第二个要求(将组件标记的正体作为 canvas not supported 消息)。
canvas 组件包含一个 <h:outputScript> 标记,该标记导入 canvas 的 JavaScript,这由 canvas 组件的 library 和 script 属性指定。注意,library 和 script 属性的默认设置分别为 html5 和 canvasDefault.js。这满足了第三个要求(自动导入 canvas 的 JavaScript)。
最后,canvas 组件调用一个名为 init() 的 JavaScript 方法,传递 canvas 的 ID、画笔颜色和线宽。init() 方法获取并初始化 canvas 的上下文。注意我说的是方法,而非函数,因为 init() 方法属于一个对象。该对象名源自于 canvas 组件的 script 属性。例如,对于 清单 3 中的笑脸画布,我指定 smiley.js 的一个 script 值,因此笑脸画布组件会调用 smiley.js.init()
—
js 对象的 init() 方法,包含在一个名为 smiley 的对象中。如果您不显式地指定 script 值,其默认设置为 canvasDefault.js,因此 JavaScript 方法会是 canvasDefault.js.init()。调用这些方法,而非全局函数,这使我满足了第四个要求:在一个页面中支持多个画布。
canvas 组件的默认 JavaScript 如清单 5 所示:
清单 5. 默认的 canvas JavaScript (resources/html5/canvasDefault.js)
if (!canvasDefault) var canvasDefault = {}
if (!canvasDefault.js) {
canvasDefault.js = {
init : function(canvasId, penColor, lineWidth) {
var canvas, context, painting;
canvas = document.getElementById(canvasId);
if (canvas == null) {
alert("Canvas " + canvasId + " not found")
}
context = canvas.getContext("2d")
if (context == null)
return;
painting = false;
context.strokeStyle = penColor
context.lineWidth = lineWidth
context.font = "15px Helvetica"
canvas.addEventListener("mousedown", function(ev) {
painting = true
context.beginPath()
context.moveTo(ev.offsetX, ev.offsetY)
}, false)
canvas.addEventListener("mousemove", function(ev) {
updateReadout(ev.offsetX, ev.offsetY)
if (painting) {
paint(ev.offsetX, ev.offsetY)
}
function updateReadout(x, y) {
context.clearRect(0, 0, 100, 20)
context.fillText("x: " + x + ", y: " + y, 5, 15)
}
function paint(x, y) {
context.lineTo(ev.offsetX, ev.offsetY)
context.stroke()
}
}, false)
canvas.addEventListener("mouseup", function() {
painting = false
context.closePath()
}, false)
}
}
} |
在 清单 5 中,我创建一个名为 canvasDefault 的对象,包含一个名为 js 的对象,该对象包含一个 init() 方法。我这样做是为了为 init() 方法创建名称空间,这样它就不会被另一个全局 init() 函数所覆盖。这样一来,我就可以在一个页面中含有多个画布,所有画布都有其自己的 init() 函数的实现。
清单 6 显示笑脸画布的 JavaScript:
清单 6. 笑脸画布的 JavaScript (resources/application/smiley.js)
if (!smiley) var smiley = {}
if (!smiley.js) {
smiley.js = {
init : function(canvasId, penColor, lineWidth) {
var canvas, context
canvas = document.getElementById(canvasId);
if (canvas == null) {
alert("Canvas " + canvasId + " not found")
}
context = canvas.getContext("2d");
if (context == null)
return
// smiley face code originally downloaded
// from thinkvitamin.com
// Create the face
context.strokeStyle = "#000000";
context.fillStyle = "#AAAAFF";
context.beginPath();
context.arc(100,100,50,0,Math.PI*2,true);
context.closePath();
context.stroke();
context.fill();
// eyes
context.strokeStyle = "#000000";
context.fillStyle = "#FFFFFF";
context.beginPath();
context.arc(80,80,8,0,Math.PI*2,true);
context.closePath();
context.stroke();
context.fill();
context.fillStyle = "#0000FF";
context.beginPath();
context.arc(80,80,5,0,Math.PI*2,true);
context.closePath();
context.fill();
context.strokeStyle = "#000000";
context.fillStyle = "#FFFFFF";
context.beginPath();
context.arc(120,80,8,0,Math.PI*2,true);
context.closePath();
context.stroke();
context.fill();
context.fillStyle = "#0000FF";
context.beginPath();
context.arc(120,80,5,0,Math.PI*2,true);
context.closePath();
context.fill();
// nose
context.fillStyle = "#000000";
context.beginPath();
context.moveTo(93,100);
context.lineTo(100,93);
context.lineTo(107,100);
context.closePath();
context.fill();
// smile
context.strokeStyle = "#000000";
context.beginPath();
context.moveTo(70,110);
context.quadraticCurveTo(100,150,130,110);
context.closePath();
context.stroke();
}
}
} |
清单 6 继续使用 清单 5 中使用过的同一命名空间约定。其他画布的后续 JavaScript 必须遵循相同的约定。
在本文中,我介绍了 HTML5 并展示了如何实现一个 JSF 2 HTML5 canvas 复合组件,使 JSF 开发人员和页面创建者更便捷地使用 HTML5 canvas。我展示了如何将从 JSF 表达式语言获取的信息传递给与复合组件相关的 JavaScript,以及如何为 JavaScript 函数创建名称空间,以便相同命名的函数不会相互冲突。在下一期的 JSF 2 fu 中,我将向您展示如何实现另一个 HTML5 组件,以及如何将多个 HTML5 组件放在一个可重用的库中,您可以在一个 JAR 文件中将该库分发给其他开发人员。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 本文样例代码 | j-jsf2fu-1010.zip | 49KB | HTTP |
学习
- Exploring HTML5 with JavaServer Faces 2.0:查看由 Roger Kitain 主讲的这个幻灯片,他是共同规范的领导者。
- HTML5Rocks:这是 Google 的 HTML5 站点,提供教程和其他 HTML5 资源。
- HTML5 标记引用:W3schools 记录 HTML5 的元素,包括 canvas 元素。
- The HTML5 Specification:HTML5 标准的官方规范 — 自 2010 年 9 月以来仍在制定中 — 就包含在这里。
- HTML5 Tutorial:该站点发布大量 HTML5 教程。
- JavaScript DOM:JavaScript DOM 的 Javadoc 式文档
-
“XHTML5 in a nutshell”(Sergey Mavrody,The WHATWG Blog,2010 年 7 月):阅读了解多语言 HTML5。
- PhoneGap:用于构建跨平台移动应用程序的一个 JavaScript 框架。
-
Google Chrome Developer Tools:Google Chrome 开发工具包含一个 JavaScript 调试器。
-
developerWorks Java technology 专区:这里有数百篇关于 Java 编程各个方面的文章。
讨论
- 加入 My developerWorks 社区。查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。

David Geary 是一名作家、演讲家和顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是关于 Java 的畅销书籍,而 Core JSF(与 Cay Horstman 合著)是关于 JSF 的畅销书。他还是 GWT Solutions 一书的作者。David 经常在各大会议和用户组发表演讲。他从 2003 年开始就一直是 NFJS tour 的定期演讲人,并且在 Java University 教授课程,三次当选为 JavaOne 之星。