HTML5 Zone is brought to you in partnership with:

Avid web designer and developer striving to make the web awesome. Obsessed with UI and graphic design. Joshua is a DZone MVB and is not an employee of DZone and has posted 7 posts at DZone. You can read more from them at their website. View Full User Profile

The Paste Wasteland (or, why the onPaste event is a mess)

09.24.2012
| 5760 views |
  • submit to reddit

When you work on the bleeding-edge, sometimes you’re going to get cut. The tech isn’t stable, things are buggy and may not conform to [a/the] standard—but the onPaste event isn’t a bleeding-edge technology. In fact, it’s been around since IE5. So why is it such a mess?

I spent a lot of time working with the paste event, recently, as I’ve been working on Hopper. What I found was a stunning lack of uniformity. Every single browser seems to have it’s own implementation of the event (and all of them aren’t great). I wanted to go over a few of the implementations and give you an idea of just what sort of things to look out for when using this non-standard event.

The simplest and most complete implementation is Google Chrome’s (all examples assume we’re using jQuery, to reduce verbosity):

$( 'body' ).bind( 'paste', function( evt ) {
    var items = evt.originalEvent.clipboardData.items
      , paste;

    // Items have a "kind" (string, file) and a MIME type
    console.log( 'First item "kind":', items[0].kind );
    console.log( 'First item MIME type:', items[0].type );

    // If a user pastes image data, there are 2 items: the file name (at index 0) and the file (at index 1)
    // but if the user pastes plain text or HTML, index 0 is the data with markup and index 1 is the plain, unadorned text.

    if( items[0].kind === 'string' && items[1].kind === 'file' && items[1].type.match( /^image/ ) ) {
        // If the user copied a file from Finder (OS X) and pasted it in the window, this is the result. This is also the result if a user takes a screenshot and pastes it.
        // Unfortunately, you can't copy & paste a file from the desktop. It just returns the file's icon image data & filename (on OS X).
        item = items[0];
    } else if( items[0].kind === 'string' && items[1].kind === 'string' ) {
        // Get the plain text
        item = items[1];
    }

    // From here, you can use a FileReader object to read the item with item.getAsFile(), or evt.originalEvent.clipboardData.getData(item.type) to get the plain text. Confused yet?
} );

Whoa. And the above isn’t even comprehensive; I’m still finding plenty of edge cases that return non-plain-text results when we want the plain text.

Firefox, on the other hand, has a thoroughly disappointing implementation. You cannot retrieve the pasted content from the event, and—unlike Chrome, where you can bind to anything—the event only seems to work when bound to a field that would “normally” accept a paste event, like an input field or textarea.

var $pasteField = $( '#pasteField' );

function onPaste( evt, retries ) {
    // No clipboardData object, so we fake it and just return the field's content, when we receive it.
    var text = $pasteField.val();
    $pasteField.val( '' ); // reset the field
    clipboardObj = { getData: function() { return text; } };
    items = [ { kind: 'string', type: 'text' } ];

    // If we didn't find any content on this attempt, and we haven't tried > 3 times,
    // try again in 100 ms to find content in the textarea.
    if( !text.length && ( retries < 3 || typeof retries === 'undefined' ) ) {
        if( typeof retries === 'undefined' )
             retries = 0;

         setTimeout( function() {
             onPaste( evt, ++retries );
         }, 100 );
         return true;
    }

    // Now you finally have the item's content, with an API similar to that of Chrome -- at least, for plain text.
}

$pasteField.bind( 'paste', onPaste );

You’ll notice that we use a timer and repeatedly attempt to get the textarea’s content. This is because the paste event doesn’t seem to fire in sync with the content actually appearing in the textarea and it may take a couple of attempts until this becomes the case.

What about IE?

// We need a textarea for IE, too: <textarea id="pasteField"></textarea>

$( '#pasteField' ).bind( 'paste', function( evt ) {
    // In true Microsoft form, the type to get plain text is Text with a captial T.
    console.log( window.clipboardData.getData( 'Text' ) );
} );

Pretty basic. Around since IE5. Not very versatile. But at least we can get the data.

And last but not least, Safari, which has the strangest implementation of all:

// Safe to say...textarea is a good idea: <textarea id="pasteField"></textarea>

$( '#pasteField' ).on( 'paste', function( evt ) {
    // Safari has a weird "types" list that we need to loop through and no "items" array.
    var i = 0, items = [], item, key = 'text/plain', kind = 'string';
    while (i < evt.originalEvent.clipboardData.types.length) {
        var key = evt.originalEvent.clipboardData.types[i];
        if( !key.match( /(text\/)|(plain-text)/i ) ) {
            kind = 'file';
        }
        items.push( { kind: kind, type: key } );
        i++;
    }

    // Let's just get content of the first item.
    item = items[0];
    console.log( evt.originalEvent.clipboardData.getData( item.type ) );
} );

This isn’t to even mention the differences between OSes (OS X and Windows) and how they each handle metadata in paste content.

So how do we reconcile all of this information and variety of implementations? It seems the safest solution is to have an off-screen, or hidden, text field that you can put the user’s focus into, as necessary, and bind all of your paste events accordingly. Then, combine the above outlined methods (I’ll leave as an exercise to the reader to combine all of them into a cross-browser implementation).

You’ll also need to test a variety of paste cases—items pasted from Word, the browser, and other rich text editors all react differently and may return different metadata/markup.

The greatest thing is that we can capture paste content from users and find new, creative ways to improve user experience. The worst part is the countless implementations of the paste event and the data it returns.

Postscript: I was informed recently by web developer Joel Besada that, despite the lack of the clipboardData object in Firefox, you can still retrieve images from the clipboard. Thanks Joel! I found that a variation of this technique also works in IE, interestingly enough.

 

Published at DZone with permission of Joshua Gross, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)