Today's modern operating systems consist of a few well-recognized components, one of which is the GUI. GUIs provide windowing, drag-and-drop functionality, menus, and mouse-based interaction. Most of these GUI innovations were developed in the 1980s, with few radical new features added since.
Now GUIs are undergoing a transformation. Certainly, new ways to interact with computers—brought about in part by the advent of smart phones and similar devices—are leading to exciting possibilities. But perhaps the greatest, most sweeping change is in the foundations of the present-day operating system, as many of the services you expect operating systems to provide are now supplied by Web services. Indeed, as applications and operating systems alike use Web services directly, you might argue that this is the start of a Web-based operating system.
The missing feature: Web clipboards
One feature that seems missing from the Web operating system collective tool set is the notion of a Web-based clipboard. There has been some work on such a tool in the past.
For example, in March 2006, Microsoft® CTO Ray Ozzie made quite a splash by demonstrating the concept of a Live Clipboard (see Resources for links to more information). The idea was that Microsoft would enable all its software to plug into a Live Clipboard, to allow the cut and paste of objects and data types among Web applications. Live Clipboard was an illustration of what a Web clipboard might look like, but it seems to have vanished as an initiative.
Now, a number of disparate, commercial online services emulate a Web clipboard. A few of the more popular offerings include Clipmarks.com, Friendspaste.com, and Pastebin. The lack of any standardized approach means that a fragmented set of systems lean toward vendor lock-in scenarios. To achieve something with good adoption characteristics, you should strive to employ existing tools and technologies to get the job done. In this article, I describe one such experiment which I refer to as AtomClip.
Before I show how to build the Web clipboard application, I recommend that you download the source files (see Download). Extract the .zip file, and follow the included README file instructions. (If you don't have Apache Ant, you can just use the pre-built atomclip.xpi extension for Mozilla Firefox.)
When you have everything installed, you should be able to see new options in your Firefox browser by right-clicking elements in any Web page. The context menu that appears contains three options—Image to AtomClip, Copy text to AtomClip, and Copy link to AtomClip—each of which copies resources to an eXist XML database. A fourth option—Copy from AtomClip—enumerates the previously clipped items and makes the item you select available for pasting from your local clipboard.
My goals for AtomClip are modest: implement a Web clipboard using open standards and existing technologies to clip simple content and provide a foundation for more sophisticated data types and objects. I use a Firefox extension as the client application and an Atom XML feed, provided by eXist XML database (see Resources), as Web storage. Figure 1 shows the entire AtomClip Web clipboard application.
Figure 1. The AtomClip server and client component architecture
Here you see a multi-user scenario in which many browsers are clipping, potentially allowing users to remix Web clipboards and cut and paste from each other. Copying occurs only within Firefox through an extension, with data saved to an Atom XML feed. Being able to paste a previously clipped item is thus just a matter of consuming the Atom feed and placing items onto the local clipboard.
I developed the AtomClip client as an XUL extension (see Resources) built in Firefox, which implements all user-centric functionality. The server component comes in the form of an eXist XML database. I chose eXist because it bundles an Atom Publication Standard (AtomPub) implementation. A Copy operation from the Firefox browser allows AtomClip to build and send an Atom entry to the eXist database. AtomClip uses basic HTTP messages with standard URL construction to add an entry to the Atom feed as defined by the AtomPub.
The payload of the Atom entry element contains one of
three possible content types: an image, a link, or text. Each content type inserts a
corresponding HTML element into the <content/>
element:
imagecontains an image tag. For example:<img src="" alt="" height="" width=""/>linkcontains an anchor link tag. For example:<a href=""></a>textcontains raw text in XHTML. For example:<div/>
I also reuse each of Atom's basic elements to provide some basic metadata:
<title/>contains eitherimage, text,orlinkand is used to identify the clip type.<link/>contains the URL of the source Web page from which the data was clipped.<content/>contains the clipped data.
Atom allows for insertion of foreign namespace elements, so why not define a
special-purpose XML format to represent a clipping, and embed this in the
<content/> element:
<clip type="image|text|link" srcURL="http://www.example.com">
<content>
<img src="" alt="" height="" width=""/>
</content>
</clip>
|
The server: an Atom store using the eXist database
The server-side component needs to be able to communicate using AtomPub and generate an Atom feed. It's also useful to have an XML database with XQuery so you can select and aggregate feeds. We will use eXist, which supports AtomPub.
AtomPub is an HTTP-based protocol for creating and updating Web resources. You need to invoke the proper HTTP operation to the proper URL to use AtomPub. You will use three AtomPub services, each with its own URL:
- http://localhost:8080/exist/atom/introspect/clipboard: An HTTP
GETon this URL returns an Atom service document. The service document contains URL links for all of the Atom feeds. - http://localhost:8080/exist/atom/content/clipboard: An HTTP
GEToperation on this URL returns an Atom feed document. The feed document contains an entry for each clip currently stored in the Atom collection. - http://localhost:8080/exist/atom/edit/clipboard: You create an XML
document containing the metadata and content of the clip, and invoke an
HTTP
POSTto this URL, which returns an HTTP status code 201 ("Created") response code and a response document that represents the new Atom entry (the clip).
In eXist, the Atom XML feed is attached to a collection (I use a top-level collection
called clipboard). Getting an Atom XML feed from eXist is a matter of
using an HTTP GET operation to the proper URL. The Atom
feed will contain an entry for each clipping made, and that's really all
you need eXist to do.
The client: the AtomClip Firefox extension
The client side of this solution is a Firefox XUL extension, which adds AtomClip context menu options for copying to and from the Atom feed. You want to be able to select items on a Web page for copying to the Atom feed, so you need to augment the context menu (accessed whenever you right-click a Web page).
Developing for Firefox for the first time can be daunting. Here are some pointers to help make Firefox extension development smoother:
- Firefox can use multiple profiles, so, you can retain your
preferred browsing setup as well as create a profile suitable for
development. To create a new profile, invoke Firefox from the
command line as follows:
./firefox -ProfileManager
Then, create a profile called dev. You can now run Firefox, and it will ask you to choose which profile to use. An easier method is to create a script that launches Firefox with this profile:
./firefox-bin -no-remote -P dev &
- Use the Firefox Extension Developer Extension. The real pain in developing for Firefox is that you need to restart the browser between code edits. An extension was developed to address this called (appropriately) the Extension Developer Extension (see Resources to download this extension).
- Link the extension source directory. To avoid having to reinstall the
.xpi file every time, link the development source directory to your
development profile extension directory. You do this by creating a
file named for the ID of the extension (defined in install.rdf) under the
Firefox applications/extension directory:
/Applications/Firefox/Content/MacOS/extensions/atomclip@webcomposite.com
This file must contain the full directory path to wherever this source directory exists (with hanging slash but all spaces trimmed). Firefox then loads the latest version of your extension under development.
Now that you know the basics of developing a Firefox extension, look at what makes up the AtomClip extension. Listing 1 shows the XUL that tells Firefox what chrome (that is, UI elements) to display.
Listing 1. Context menu 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>
|
The three <menuitem/> elements define
the copy-to-AtomClip operations and link up with their JavaScript
behaviors through the oncommand attribute. The last
<menu/> element is for the copy-from-AtomClip
operation and is a nested menu which lists previous clippings.
The AtomClip extension in its most basic guise is just an AtomPub client, so it must be able to send AtomPub-formatted requests to the AtomPub server. XUL uses JavaScript code. Listing 2 shows how you implement functions that get the Atom feed and send a new Atom entry.
Listing 2. Get the Atom XML feed
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
|
Some versions of some Mozilla browsers won't work properly if the response from the server doesn't have an
XML mime-type header. For that case, the statement
httpRequest.overrideMimeType('text/xml');
will override the header sent to the server to force text/xml as the
mime-type.
Notice that you use a callback function to handle the response,
alertGetContents(httpRequest). I will discuss it
later, as it manages the Copy from AtomClip menu listing behavior.
The other creation function, postAtomRequest, creates a
new Atom entry using HTTP POST, providing an Atom XML
document representing the clipping, as in Listing 3.
Listing 3. Creating a new entry
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
|
The eXist engine needs to have the Content type
set to application/atom+xml; otherwise, it won't do
anything with the XML document. The only other thing to note about these two
JavaScript functions is that, to use Basic authentication, you need to craft the
HTTP authorization header.
Listing 4 shows the callback function for the HTTP
GET version function. Its job is to list all the
previous clippings and supply them as a nested menu from the Firefox context
menu.
Listing 4. Copying from 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
|
This function first erases all formerly created menu items to ensure that you have the most current data set. Then, through the miracle of DOM, it enumerates through the Atom XML document, and generates a specific menu option based on whether the content is an image, link, or text. Images, for example, generate a small image to place in the context menu itself to give a hint of what it is about.
Each content type also has its own function from which to create an Atom entry. Listing 5 illustrates the text version.
Listing 5. Atom entry creation
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
|
The title element defines the content type
(image, link, or text); the link element contains
the page from which the item was clipped and uses
document.commandDispatcher.focusedWindow.location.href
to determine it. The value is the payload content itself.
Two functions (in Listing 6) facilitate copying to and from the system clipboard so when you perform a copy-to operation, you take the content from the clipboard. When you copy from the Atom feed, you must take the data gleaned from the feed and place it onto the clipboard so it is available for pasting.
Listing 6. Utility functions
function copyFromAtomClip(data){}
function CopyToClipboard(element){}
|
Note: To create the AtomClip extension, you can use the included Ant build file to generate it.
This extension has some limitations—mostly from my own limited knowledge of implementing Firefox extensions:
- Complicated JavaScript code: If there is a lot of JavaScript coding going on, it might not clip what you want. For example, if you try to copy a link within a JavaScript-heavy application like Google's Gmail, the extension does not have the logic to determine the link. In these circumstances, it might be inconsistent with obtaining the correct content.
- Images: AtomClip does not have the logic to choose appropriate
content on an image with a link. Also, images themselves are not
saved—only the
<img>HTML tag containing<src>attribute. - Primitive context: It is better if, when you right-click content, you see only the options that apply. For example, if you click an image, AtomClip only shows the Copy image to AtomClip option.
To use AtomClip, start eXist and ensure that it is configured according to the README installation instructions. Install atomclip.xpi in Firefox, then navigate to any Web page.
Next, select some text and right-click. You should see a context menu with new AtomClip actions. Select Copy text to AtomClip to send the text snippet to the Atom feed. To paste the text clipping, right-click again (on any element), then select Copy from AtomClip. You should see a list of all the previous clippings for the Atom XML feed. Choose the text entry; and paste your AtomClip-ped text.
A Web clipboard using open standards and technologies that are currently deployed has good adoption characteristics. For a Web clipboard to provide more sophisticated functionality, a simple scenario must first be addressed. With a combination of XUL, Atom XML feeds, and AtomPub, you have a powerful set of technologies based on what is popular on the Web right now.
AtomClip was fun to build and I hope will inspire your own experiments.
| Description | Name | Size | Download method |
|---|---|---|---|
| AtomClip source code | atomclip.zip | 12KB | HTTP |
Information about download methods
Learn
- History of the GUI: See Wikipedia's overview of
the GUI in computing.
- Live Clipboard: Read
Ray Ozzie's announcement about Microsoft's Live Clipboard.
- Web
clipboard: Read Bill Burcham's thoughts on the Web clipboard.
- AtomPub in eXist: Learn how to set
up an eXist XML database to work with AtomPub.
- AtomPub Publishing Protocol: Read the
Internet Engineering Task Force (IETF) Atom specification.
- Building
Firefox extensions: Read the Mozilla developer documentation for building a Firefox extension.
- Extension Developer
Extension: Try this Firefox extension that helps developers build XUL extensions.
- Atom Syndication Format: Read the IETF
specification.
- An introduction to XML
User Interface Language (XUL)
development Creating a XUL-based blog editor (Michael Galpin, developerWorks, November 2008): Build desktop apps with a XUL
runtime environment from Firefox 3.0, some tools, and existing Web development skills. In this refreshed tutorial, create a XUL
-based blog editor.
- Getting to know the Atom Publishing Protocol (James Snell, developerWorks, October-
December 2006): In this three-part series, explore an overview of the Atom Publishing Protocol and how to use it in real world
apps. Also, begin to implement Atom-enabled apps with a new open-source project, called Abdera, currently under incubation at
the Apache Software Foundation.
- IBM XML certification: Find
out how you can become an IBM-Certified Developer in XML and related technologies.
- XML technical
library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM
Redbooks.
- developerWorks technical
events and webcasts: Stay current with technology in these sessions.
- developerWorks
podcasts: Listen to interesting interviews and discussions for software developers.
Get products and technologies
- AtomServer: Read about
and download AtomServer.
- eXist XML database: Download the
eXist database, an open source database-management system that stores XML data according to the XML data model and features
efficient, index-based XQuery processing.
- IBM product evaluation versions: Download or explore the online trials in
the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®,
Rational®, Tivoli®, and WebSphere®.
Discuss
- XML zone discussion
forums: Participate in any of several XML-related discussions.
- developerWorks blogs: Check out these blogs and get involved in the developerWorks community.

Jim Fuller has been a professional developer for 15 years, working with several blue-chip software companies in both his native USA and the UK. He has co-written a few technology-related books and regularly speaks and writes articles focusing on XML technologies. He is a founding committee member for XML Prague and was in the gang responsible for EXSLT. He spends his free time playing with XML databases and XQuery. Jim is technical director for a few companies (FlameDigital, Webcomposite s.r.o.) and can be reached at jim.fuller@webcomposite.com.
Comments (Undergoing maintenance)





