Implement Web cut-and-paste using Atom XML and Firefox XUL

Create AtomClip: A Web clipboard

Even after 20 years, the Web continues to redefine itself. The Internet is transforming from a hypertext document system to something that resembles a full-blown operating system. In this article, focus on a critical functionality missing in the emerging cloud-based operating system: The existence of a standards-based Web clipboard. Discover what a Web clipboard might look like using AtomPub and the AtomClip XUL Firefox extension.

James R. Fuller (jim.fuller@webcomposite.com), Technical Director, FlameDigital Limited & Webcomposite s.r.o.

Photo of Jim FullerJim 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.



02 June 2009

Also available in Japanese

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.

Frequently used acronyms

  • 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 User Interface Language

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.


Installing 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.


AtomClip

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
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:

  • image contains an image tag. For example: <img src="" alt="" height="" width=""/>
  • link contains an anchor link tag. For example: <a href=""></a>
  • text contains raw text in XHTML. For example: <div/>

I also reuse each of Atom's basic elements to provide some basic metadata:

  • <title/> contains either image, text, or link and 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 GET on 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 GET operation 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 POST to 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.


Drawbacks to the extension

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.

AtomClip in action

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.


Conclusion

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.


Download

DescriptionNameSize
AtomClip source codeatomclip.zip12KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=392676
ArticleTitle=Implement Web cut-and-paste using Atom XML and Firefox XUL
publish-date=06022009