级别: 中级 James R. Fuller, 技术主管, FlameDigital Limited & Webcomposite s.r.o.
2009 年 6 月 22 日 即便在 20 年之后,Web 仍然在继续重新定义自己。Internet 正在从超文本文档系统转变成与功能全面的操作系统相类似的东西。在本文中,作者将着重讨论新兴的基于云的操作系统中所缺失的一项关键功能:基于标准的 Web 剪贴板。使用 AtomPub 和 AtomClip XUL Firefox 扩展构建 Web 剪贴板。
 |
常用缩略语
- CTO:首席技术官(Chief Technology Officer)
- DOM:文档对象模型(Document Object Model)
- GUI:图形用户界面(Graphical user interface)
- HTML:超文本标记语言(Hypertext Markup Language)
- HTTP:超文本传输协议(Hypertext Transfer Protocol)
- UI:用户界面(User interface)
- XHTML:可扩展超文本标记语言(Extensible Hypertext Markup Language)
- XML:可扩展标记语言(Extensible Markup Language)
- XUL:XML 用户界面语言(XML User Interface Language)
|
|
如今的现代操作系统由一些广为认知的组件构成,其中之一便是 GUI。GUI 提供了窗口、拖放功能、菜单和基于鼠标的交互。其中大多数 GUI 创新都是在 20 世纪 80 年代开发出来的,而在此之后便鲜有添加新的基本特性。
如今,GUI 正在经历一场变革。的确,全新的计算机交互方式 — 在一定程度上得益于智能电话和类似设备的出现 — 正在创建激动人心的体验。但是,最广泛、最彻底的变化是当今操作系统的基础,因为您期望操作系统提供的许多服务现在都已由 Web 服务提供。确实,由于应用程序和操作系统都直接使用 Web 服务,因此您可能会争论说这是基于 Web 的操作系统的开始。
缺少的特性:Web 剪贴板
Web 操作系统工具集中似乎缺少了一个特性,即基于 Web 的剪贴板的概念。过去曾有一些针对此工具的努力。
举例来说,2006 年 3 月,Microsoft® CTO Ray Ozzie 演示了 Live Clipboard 的概念(见 参考资料)。其理念是,Microsoft 将支持它的所有软件插入到 Live Clipboard 中,以允许在 Web 应用程序中剪切和粘贴对象及数据类型。Live Clipboard 展示了 Web 剪贴板的模样,但它仅仅停留在计划阶段。
现在,许多全异的商业在线服务模拟了 Web 剪贴板。一些比较流行的服务包括 Clipmarks.com、Friendspaste.com 和 Pastebin。缺少标准化的方法意味着系统会倾向于供应商锁定的场景。为了实现一些良好的采用特性,您应该努力利用现有工具和技术来完成工作。在本文中,我
将讨论这样一个工具,称作 AtomClip。
安装 AtomClip
在展示如何构建 Web 剪贴板应用程序之前,我建议您下载源文件(参见 下载)。解压 .zip 文件,并依照其中的 README 文件操作。(如果您没有 Apache Ant,可以只使用针对 Mozilla Firefox 的预先构建的 atomclip.xpi 扩展)。
一切安装就绪之后,通过单击任何 Web 页面中的元素,您应该能够在 Firefox 浏览器中看到新选项。出现的上下文菜单包含三个选项 —
Image to AtomClip、Copy text to AtomClip 和 Copy link to AtomClip
— 各选项都能将资源复制到一个已有的 XML 数据库中。第四个选项 —
Copy from AtomClip
— 将枚举之前剪贴的项目,并允许在本地剪贴板中粘贴所选择的项目。
AtomClip
我的 AtomClip 目标是合理的:使用开放标准和已有技术实现一个 Web 剪贴板,用于剪切简单的内容并为较为复杂的数据类型和对象提供一个基础。我使用 Firefox 扩展作为客户机应用程序,并使用由 eXist XML 数据库提供的 Atom XML 提要作为 Web 存储(见 参考资料)。图 1
展示了整个 AtomClip Web 剪贴板应用程序。
图 1. AtomClip 服务器和客户机体系结构
此处,您看到了一个多用户场景。在该场景中,许多浏览器都在执行剪切操作,这允许用户混合 Web 剪贴板并在它们之间复制和粘贴。复制通过扩展仅发生在 Firefox 中,而数据则被保存到了一个 Atom XML 提要中。粘贴之前剪切的项目只涉及使用 Atom 提要并将项目放置到本地剪贴板上。
我在 Firefox 中以 XUL 扩展的形式开发了 AtomClip 客户端(见 参考资料),它实现了所有以用户为中心的功能。服务器组件采用 eXist XML 数据库的形式。我选择 eXist 的原因是它绑定了一个 Atom Publication Standard (AtomPub) 实现。Firefox 浏览器中的 Copy 操作允许 AtomClip 构建 Atom 条目并将它发送给 eXist 数据库。AtomClip 使用基本 HTTP 消息,并通过标准 URL 构建将条目添加到 Atom 提要中,如 AtomPub 定义所示。
Atom entry 元素的有效负载包含三种可能的内容类型之一:图形、链接或文本。每种内容类型都将相应的 HTML 元素插入到 <content/> 元素中:
image 包含一个图像标记。例如:<img src="" alt="" height="" width=""/>
link 包含一个锚点链接标记。例如:<a href=""></a>
text 包含 XHTML 原始文本。例如:<div/>
我还重用 Atom 的各个基本元素来提供一些基本的元数据:
<title/> 包含 image、text 或 link,并且用于标识剪贴类型。
<link/> 包含在其中剪切数据的源 Web 页面的 URL。
<content/> 包含剪切数据。
Atom 允许插入外部名称空间元素,因此可以定义一种特殊用途的 XML 格式来表示剪贴,并将它嵌入到 <content/> 元素中:
<clip type="image|text|link" srcURL="http://www.example.com">
<content>
<img src="" alt="" height="" width=""/>
</content>
</clip>
|
服务器:使用 eXist 数据库的 Atom 库
服务器端组件需要能够使用 AtomPub 进行通信,并生成 Atom 提要。提供一个支持 XQuery 的 XML 数据库也非常有用,这样您便可以选择和聚合提要。我们将使用 eXist,它能够支持 AtomPub。
AtomPub 是一种基于 HTTP 的协议,用于创建和更新 Web 资源。您需要调用针对适当 URL 的适当的 HTTP 操作来使用 AtomPub。您将使用三个 AtomPub 服务,每个服务都有它自己的 URL:
- http://localhost:8080/exist/atom/introspect/clipboard:针对此 URL 的 HTTP
GET 将返回一个 Atom 服务文档。服务文档包含所有 Atom 提要的 URL 链接。
- http://localhost:8080/exist/atom/content/clipboard:针对此 URL 的 HTTP
GET 操作将返回一个 Atom 提要文档。当前存储在 Atom 集合中的每个剪切在提要文档中都有相应的条目。
- http://localhost:8080/exist/atom/edit/clipboard:创建一个包含元数据和剪切内容的 XML 文档,并调用针对此 URL 的 HTTP
POST,这将返回 HTTP 状态码 201 ("Created") 响应码和一个表示新 Atom 条目(剪切)的响应文档。
在 eXist 中,Atom XML 提要附加到集合中(我使用顶级集合,称作 clipboard)。从 eXist 获取 Atom XML 提要涉及使用针对适当 URL 的 HTTP GET 操作。Atom 提要将包含针对各剪切的条目,这就是 eXist 需要做的所有工作。
客户端:AtomClip Firefox 扩展
此解决方案的客户端是一个 Firefox XUL 扩展,它将添加用于复制到 Atom 提要的 AtomClip 上下文菜单选项。您希望能够在 Web 页面上选择进行复制的项目,因此您需要扩展上下文菜单(右键单击 Web 页面可访问上下文菜单)。
第一次进行 Firefox 开发会比较艰难。以下要点有助于您更加顺利地进行 Firefox 扩展开发:
现在,您已经了解了开发 Firefox 扩展的基础知识,现在来看看 AtomClip 扩展的结构。清单 1 所示的 XUL 将通知 Firefox 显示哪些 chrome(即 UI 元素)。
清单 1. 上下文菜单 XUL chrome
<popup id="contentAreaContextMenu">
<menuitem id="clipboard-text"
label="copy text to atomclip" accesskey="H"
insertafter="context-sep-stop"
class="menuitem-iconic"
image="chrome://atomclip/content/atom.gif"
oncommand="CopyToClipboard(document.popupNode);
postAtomRequest(atomurl,TextAtomEntry(document.popupNode),
user,password)"/>
<menuitem id="clipboard-image"
label="copy image to atomclip" accesskey="H"
insertafter="context-sep-stop"
class="menuitem-iconic"
image="chrome://atomclip/content/atom.gif"
oncommand="CopyToClipboard(document.popupNode);
postAtomRequest(atomurl,ImageAtomEntry(document.popupNode),
user,password)"/>
<menuitem id="clipboard-link"
label="copy link to atomclip" accesskey="H"
insertafter="context-sep-stop"
class="menuitem-iconic"
image="chrome://atomclip/content/atom.gif"
oncommand="CopyToClipboard(document.popupNode);
postAtomRequest(atomurl,LinkAtomEntry(document.popupNode),
user,password)"/>
<menu id="atomclip-context-menu"
class="menu-iconic"
image="chrome://atomclip/content/atom.gif"
label="copy from atomclip">
<menupopup id="atomclip-context-paste"
onpopupshowing="addSubMenu();">
<menuitem label="empty" />
</menupopup>
</menu>
</popup>
|
三个 <menuitem/> 元素定义 “复制到 AtomClip” 操作并通过 oncommand 属性链接它们的 JavaScript 行为。最后一个 <menu/> 元素用于 “从 AtomClip 复制” 操作,它是一个内嵌菜单,其中列出了之前的剪切操作。
AtomClip 扩展最基本的形式就是一个 AtomPub 客户端,因此它必须能够将 AtomPub 格式的请求发送给 AtomPub 服务器。XUL 使用 JavaScript 代码。清单 2 展示了如何实现获取 Atom 提要和发送新 Atom 条目的函数。
清单 2. 获取 Atom XML 提要
function getAtomRequest(url,user,passwd) {
var httpRequest;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
if (httpRequest.overrideMimeType) {
httpRequest.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject) { // IE
try {
httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e) {
try {
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e) {}
}
}
if (!httpRequest) {
alert('Giving up :( Cannot create an XMLHTTP instance');
return false;
}
httpRequest.onreadystatechange = function() { alertGetContents(httpRequest); };
httpRequest.open('GET', url, true);
httpRequest.setRequestHeader('Authorization', 'Basic '+btoa(user+':'+passwd));
httpRequest.send(null);
}//end getAtomRequest
|
如果来自服务器的响应没有 XML mime-type 头部,则一些版本的 Mozilla 浏览器不能正常运行。对于这种情况,httpRequest.overrideMimeType('text/xml'); 语句将覆盖发送给服务器的头部,强制 text/xml 作为 mime-type。
注意,您将使用回调函数处理响应 alertGetContents(httpRequest)。我将在稍后讨论它,它用于管理 Copy from AtomClip 菜单清单行为。另一个创建函数 postAtomRequest 将使用 HTTP POST 创建一个新的 Atom 条目,从而提供一个 Atom XML 文档来表示剪切,如 清单 3 所示。
清单 3. 创建新条目
function postAtomRequest(url,payload,user,passwd) {
var httpRequest;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
if (httpRequest.overrideMimeType) {
httpRequest.overrideMimeType('text/atom');
// See note below about this line
}
}
else if (window.ActiveXObject) { // IE
try {
httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e) {
try {
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e) {}
}
}
if (!httpRequest) {
alert('Giving up :( Cannot create an XMLHTTP instance');
return false;
}
httpRequest.onreadystatechange = function() { alertPostContents(httpRequest); };
httpRequest.open('POST', url, true);
httpRequest.setRequestHeader('Authorization', 'Basic '+btoa(user+':'+passwd));
httpRequest.setRequestHeader('Content-Type', 'application/atom+xml')
httpRequest.send(payload);
}//end postAtomRequest
|
eXist 引擎需要将 Content 类型设置为 application/atom+xml;否则,它不会对 XML 文档执行任何操作。关于这两个 JavaScript 函数唯一需要注意的一点是,您需要设计 HTTP 验证头部。
清单 4 显示了 HTTP GET 版本函数的回调函数。它的作用是列出所有之前的剪切,并通过 Firefox 上下文菜单将它们提供为内嵌菜单。
清单 4. 从 AtomClip 复制
function alertGetContents(httpRequest) {
if (httpRequest.readyState == 4) {
if (httpRequest.status == 201 || httpRequest.status == 200) {
//alert(httpRequest.responseText);
var clipboardsmenupopup = document.getElementById('atomclip-context-paste');
//remove all menuitems
while(clipboardsmenupopup.childNodes.length > 0)
{
clipboardsmenupopup.removeChild(clipboardsmenupopup.childNodes[0]);
}
var entries = httpRequest.responseXML.getElementsByTagName('entry');
for (var i = 0; i < entries.length ; i++)
{
var item = document.createElement('menuitem');
var children = entries[i].childNodes;
var title = children[4].textContent;
var sourcepage = children[5].textContent;
var data = children[6].firstChild;
if(title=='link')
{
item.setAttribute('oncommand',
"copyFromAtomClip('"+data.firstChild.href+"');");
item.setAttribute('label',
title+":"+data.firstChild.href);
}else if(title =='image')
{
item.setAttribute('oncommand',
"copyFromAtomClip('"+data.firstChild.src+"');");
item.setAttribute('label',
title+":"+data.firstChild.src);
item.setAttribute('class','menuitem-iconic');
item.setAttribute('image',
data.firstChild.src);
}else
{
item.setAttribute('oncommand',
"copyFromAtomClip('"+escape(data.firstChild.textContent)+"');");
item.setAttribute('label',
title+":"+data.firstChild.textContent);
}
clipboardsmenupopup.appendChild(item);
}
} else {
alert('There was a problem with the request.');
alert('status:'+httpRequest.status);
}
}
}//end alertGetContents
|
这个函数首先清除之前创建的所有菜单项,以确保您拥有最新的数据集。然后,它通过强大的 DOM 遍历 Atom XML 文档,并根据内容是图像、链接还是文档来生成具体的菜单选项。举例来说,如果是图像,则生成一个小图像来放置在上下文菜单中,以提供缩略视图。
每种内容类型还有它自己的函数,这些函数可用于创建 Atom 条目。清单 5 演示了文本版本。
清单 5. Atom 条目创建
function TextAtomEntry(element){
var value = document.commandDispatcher.focusedWindow.getSelection().toString();
var xmlstring = '<entry xmlns="http://www.w3.org/2005/Atom">'
xmlstring += '<title>text</title>'
xmlstring += '<link>'+document.commandDispatcher.focusedWindow.location.href+'</link>'
xmlstring += '<content type="xhtml">'
xmlstring += '<div xmlns="http://www.w3.org/1999/xhtml">'
xmlstring += value
xmlstring += '</div>'
xmlstring += '</content>'
xmlstring += '</entry>';
return xmlstring;
}//end TextAtomEntry
|
title 元素定义内容类型(图像、链接或文本);link 元素包含剪切项目的页面并使用 document.commandDispatcher.focusedWindow.location.href 来确定它。它的值是有效负荷内容本身。
两个函数(位于 清单 6 中)便利与系统剪贴板之间的复制操作,以便您在执行 “复制到” 操作时可以获取剪贴板中的内容。从 Atom 提要中复制时,您必须使用提要中的数据并将它放置在剪贴板中,以便于进行粘贴。
清单 6. Utility 函数
function copyFromAtomClip(data){}
function CopyToClipboard(element){}
|
注意:要创建 AtomClip 扩展,您可以使用包含的 Ant 构建文件来生成它。
扩展的缺点
这个扩展有一些限制 — 主要以我自己有限的实现 Firefox 的扩展知识为根据:
- 复杂的 JavaScript 代码:如果需要大量 JavaScript 编码工作,则它可能无法剪切您所需的内容。举例来说,如果您尝试复制 JavaScript 应用程序(如 Google 的 Gmail)中的链接,则扩展没有确定链接的逻辑。在这些情况中,可能无法获取正确的内容。
- 图像:AtomClip 没有选择带链接图像上的适当内容的逻辑。另外,图像本身并未保存 — 仅
<img> HTML 标记包含 <src> 属性。
- 原始上下文:当您右键单击内容时,最好是只看到有用的选项。举例来说,如果您单击某个图像,AtomClip 仅显示 Copy image to AtomClip 选项。
AtomClip 实际应用
要使用 AtomClip,启动 eXist 并确保它根据 README 安装指令配置了它。在 Firefox 中安装 atomclip.xpi,然后导航到任意 Web 页面。
接下来,选择一些文本并单击鼠标右键。您应该能看到一个包含新 AtomClip 操作的上下文菜单。选择 Copy text to AtomClip 将文本片段发送给 Atom 提要。要粘贴文本剪切,再次单击鼠标右键(在任何元素上),然后选择 Copy from AtomClip。您应该能看到一个包含 Atom XML 提要的所有之前剪切的列表。选择文本条目并粘贴 AtomClip 文本。
结束语
现在已经部署了一个使用开放标准和技术的 Web 剪贴板,它具备良好的实用特性。要让 Web 剪贴板提供更加复杂的功能,首先必须解决一个简单的场景。通过结合 XUL、Atom XML 提要和 AtomPub,您将拥有当前 Web 上最流行的一组强大技术。
AtomClip 的构建非常有趣,希望您能从中受益。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| AtomClip 源代码 | atomclip.zip | 12KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者
对本文的评价
|