Bespin now learning some art in the Dojo Integrating info from Google Doctype into Bespin
Mar 04

Supporting the system clipboard in your Web Applications: Part Two

Bespin, JavaScript, Tech with tags: Add comments

paste
Courtesy SierraBlair

I had a rather long post on the pain of dealing with the system clipboard in Web applications now that Flash went and fixed some security hole :/

The solution in place at that time worked well in Safari, with the on[cut|copy|paste] and unfortunately not great with Firefox due to one crucial big of data being omitted.

Then Tom Robinson and a couple of others made the great point that I could get ALMOST everything I wanted with just the hidden textaraea trick. Instead of tying the trick to the on[cut|copy|paste] events, I can just manually grab the Cmd/Ctrl-C X V commands. The only downside to this is that if the user goes to the Edit menu and chooses something, it won’t work. Annoying, but that’s the world of the hacky Web sometimes.

I added in this new tactic, and copy and paste works OK for Firefox in the latest version of Bespin in code (not deployed to bespin.mozilla.com at the time of this writing):

dojo.declare("bespin.util.clipboard.HiddenWorld", null, {
    install: function() {
        // * Configure the hidden copynpaster element
        var copynpaster = dojo.create("textarea", {
            tabIndex: '-1',
            autocomplete: 'off',
            id: 'copynpaster',
            style: "position: absolute; z-index: -400; top: -100px; left: -100px; width: 0; height: 0; border: none;"
        }, dojo.body());
 
        var grabAndGo = function(text) {
            copynpaster.value = text;
            focusSelectAndGo();
        };
 
        var focusSelectAndGo = function() {
            copynpaster.focus();
            copynpaster.select();
            setTimeout(function() {
                dojo.byId('canvas').focus();
            }, 0);
        };
 
        this.keyDown = dojo.connect(document, "keydown", function(e) {
            if ((bespin.util.isMac() && e.metaKey) || e.ctrlKey) {
                // Copy
                if (e.keyCode == 67 /*c*/) {
                    // place the selection into the textarea
                    var selectionText = _editor.getSelectionAsText();
 
                    if (selectionText && selectionText != '') {
                        grabAndGo(selectionText);
                    }
 
                // Cut
                } else if (e.keyCode == 88 /*x*/) {
                    // place the selection into the textarea
                    var selectionObject = _editor.getSelection();
 
                    if (selectionObject) {
                        var selectionText = _editor.model.getChunk(selectionObject);
 
                        if (selectionText && selectionText != '') {
                            grabAndGo(selectionText);
                            _editor.ui.actions.deleteSelection(selectionObject);
                        }
                    }
 
                // Paste
                } else if (e.keyCode == 86 /*v*/) {
                    focusSelectAndGo();
 
                    setTimeout(function() { // wait just a TOUCH to make sure that it is selected
                        var args = bespin.editor.utils.buildArgs();    
                        args.chunk = copynpaster.value;
                        if (args.chunk) _editor.ui.actions.insertChunk(args);
                    }, 1);
                }
            }
        });
    },
 
    uninstall: function() {
        dojo.disconnect(this.keyDown);
    }
});

Because of the issues, I took out the UI buttons for cut/copy/paste, and am in fact wondering if the editor needs that row at all. I wonder if we can consolidate the header to one line, giving us more vertical space. A code editor for developers is not like Google Docs for average Joe users, so having the visual cues probably doesn’t matter in the same way for items like copy.

There are a few subtle annoyances such as running an action like killLine (Ctrl-K) which cuts the selection but has to do so in a non-work with the clipboard way.

End result: getting there, but still need to work on making this generally viable for any application on the Open Web Platform. What do you think?

UPDATE: Used the copynpaster variable throughout, and added a setTimeout around the paste operation as I found some people needed to hit the paste key twice as the focus()/select() was taking a little too long.

17 Responses to “Supporting the system clipboard in your Web Applications: Part Two”

  1. RichB Says:

    > visual queues

    cues?

  2. Roberto Says:

    Oh yes, consolidating the two headers to one line sounds great.

  3. dion Says:

    RichB,

    Thanks :) Fixed

  4. Andrew Valums Says:

    Thanks for sharing, I needed some clipboard filtering solution for my in place editor, and this fits perfectly. As i understand, it should work on all browsers, not just firefox?

  5. monk.e.boy Says:

    I find typing one handed and copy/pates one handed is easier with the mouse.

    Click and highlight the text I want, right click copy, go to my next window, click to place cursor then click on the paste button. I dislike right click to paste because the cursor usually moves as I right click.

    Food for thought. I do have two hands but sometimes I am coding and holding a sleeping baby at the same time.

    monk.e.boy

  6. Ben Says:

    Sorry, this is a bit OT

    Concerning the Mozilla Labs event on Tuesday March 10, 2009, I came to the one the last time, maybe a bit late (around 7pm) but I could not find the floor where the meeting was. I guess it was probably the last floor of the Waterstone, but as I did not see anybody with a T-shirt mozilla, I left…

    Would it be possible to have a sign or something to indicate where is the mozilla lab meeting?

    thanks in advance,

  7. unscriptable Says:

    Hey Dion,

    Excellent! It even compensates for us Mac users who have to switch back and forth from windows VMs! Ctrl-C/X/V works on all platforms according to your code. :-)

    You sure got comfy with dojo fast! You make it look so easy!

    One suggestion: dojo.publish/subscribe might be a simple way to pass data to/from the editor. (How’s that _editor var getting into the scope here, anyways?)

    Oh, #2: instead of using dojo.byId(’copynpaster’) to look up the hidden textarea every time, you could use the reference you already have as var copynpaster = …

    Great work (and beautiful code: I wish more peeps could take the time to write good code like this.)

    – John

  8. James MacFarlane Says:

    Hi Dion,

    Being left-handed, I never use Cmd/Ctrl-C X V. I would have to take my hand off the mouse to do that. I use CTRL-Insert (copy) and SHIFT-Insert (paste).

    This works in almost every Windows app, except for those by Adobe for some reason. It would be nice if it could work here too.

  9. Dion Almaer Says:

    @James,

    Ah, good point. I will have to test to see if CTRL-Insert/SHIFT-Insert fire the on* events for WebKit. On Firefox, if they cause a copy/paste then I should be able to just wire them up and be good to go.

    Thanks! Filed this as a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=481801

  10. Dion Almaer Says:

    @unscriptable,

    Great points. We do use pub/sub as a way to event into the right scope. _editor is a cheat for this example. I *love* pub/sub (and will do a post on our usage of that shortly).

    I changed the code to use the copynpaster too, great idea. Thanks!

  11. Dion Almaer Says:

    @Ben,

    Ah yes, I *really* don’t want this to happen to you again (and anyone else). Also, just in case, I will be the large chap with glasses, and Ben is the slightly taller and smaller chap.

    Some bad photos: http://www.flickr.com/search/?w=all&q=Ben+Galbraith+Dion+Almaer&m=text

    Looking forward to meeting you there!

  12. Dave Johnson Says:

    One thing that we ran into with the hidden textarea approach is placement of the textarea element. When the textarea gets focus it will try to scroll to that position on the page (-100px in this case). Not a problem for Bespin since the browser scroll bar is not used I guess :) but can be annoying in a general case.

  13. unscriptable Says:

    @Dave,

    If you notice, Dion is using tabIndex=-1. This prevents the text area from getting focus. If for some reason you can’t use this, then you could just use right: 101% to position the text area. This won’t scroll the page if it gets focus.

    – John

  14. liucougar Says:

    IMO, a better way of doing this is listening to onpaste/oncut/oncopy, instead of listening to onkeydown of ctrl+v/ctrl+x/ctrl+c

    keyboard shortcut will trigger onpaste/oncut/oncopy (right after onkeydown), so for keyboard events, it works the same, while this also works if user choose copy/paste/cut from the brower Edit menu or from the native browser context menu (if the native context menu is used there)

  15. Wired Earp Says:

    @liucougar: In Firefox, the onpaste/oncut/oncopy events fire too late, or too weird, for this to work. Key combo listeners will do the trick, but the odd fact is that paste combo Shift+Insert is mangled: The textarea receives the focus alright, but nothing gets pasted into it.

  16. Sam Goody Says:

    Having read both articles and looked at the code –

    1)
    Are you able to have a ‘copy’ button/icon and a ‘paste’ button/icon that lets you copy/paste from an external editor into the browser?
    If so, how? Can you give a more generic (non-Dojo) description?

    2)
    If not, why is Ctrl-XCV not working – isn’t this supported by all browsers? (IE. I just copied and pasted this post from Notepad++)

  17. Brook Novak Says:

    Awesome articles, very helpful. I also used the textarea technique in my web app to get at the system clipboard via keystrokes – but i never thought of using tiny floats… thanks!

    I have also written a similar article at http://brooknovak.wordpress.com/2009/07/28/accessing-the-system-clipboard-with-javascript/ which explores some other approaches, check it out

    Cheers

Leave a Reply

Spam is a pain, I am sorry to have to do this to you, but can you answer the question below?

Q: Type in the word 'ajax'