21

I'm trying to write a little bookmarklet that can extract some text from the active page and load that into the clipboard.

The extraction is easy enough, but I'm really stuck doing the clipboard-copying part. Currently, I'm just alerting the text and hitting Ctrl+C to copy the text from the message-box, which isn't ideal.

I've read How to Copy to Clipboard in JavaScript and other questions that suggest I use zeroclipboard, but I have no idea how one would make that work from a bookmarklet, considering I have to load external flash and javascript resources to be able to use the library.

I have no issues with messing up the page's DOM to accomplish this or having to enable some permissions on my browser (Google Chrome), considering this is just a private bookmarklet.

Any pointers would be appreciated.

Community
  • 1
  • 1
Ani
  • 103,292
  • 21
  • 241
  • 294

6 Answers6

16

There is a nice little bookmarket in Github Gist that does the core of what you want -- copying to the clipboard. It does not use any external libraries, which I think of as an advantage.

As written, it copies some static text, but toward the bottom it talks about adapting it to other uses, such as copying the page title.

Since you've stated that 'The extraction is easy enough ...', you should be easily be able to adapt that gist to what you want to do.

I tried the plain vanilla version of the bookmarklet, because I have some static text that I often need to transfer to my clipboard. It works very well in Chrome 61 with no modifications. But make sure you read the comments; some people have suggestions on getting it to work in other browsers and scenarios.

Here is the code that I tested, already minified and ready to turn into a bookmarklet:

javascript:!function(a){var b=document.createElement("textarea"),c=document.getSelection();b.textContent=a,document.body.appendChild(b),c.removeAllRanges(),b.select(),document.execCommand("copy"),c.removeAllRanges(),document.body.removeChild(b)}("Text To Copy");

Gist has the pre-minified code as well.

Gundark
  • 371
  • 2
  • 9
  • 1
    Apparently, this **does not work in Firefox**: `document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.` – Perseids Aug 03 '20 at 05:06
10

Since the new Clipboard API, this is easy in browsers that support it:

javascript: navigator.clipboard.writeText('some text from somewhere');null;

Caveat: Any alerts or prompts in your bookmarklet will cause the document to lose focus, and the clipboard API will become unavailable. In that case you need to move focus back into the document before any subsequent clipboard operations will work:

const oldFocus = document.activeElement; 
/* do popup stuff */
oldFocus.focus(); 
/* do clipboard stuff */
Community
  • 1
  • 1
Joachim Lous
  • 856
  • 1
  • 9
  • 19
  • 1
    'some text from somewhere' is so much more serious that the otherwise usual "bla blah bal" which would be used in an example. – dotbit May 20 '20 at 19:21
  • 2
    What is the purpose of `null;` in `;null;` ? – Kevin Fegan Oct 24 '20 at 00:16
  • 1
    Also, since this is for a bookmarklet, everything is "compressed" (joined) to one line, so using `//` in a bookmarklet is a problem. I think `/* comment */` should work fine. – Kevin Fegan Oct 24 '20 at 00:20
  • @kevin-fegan Bookmarklets need to return something falsy, unless you want the browser to replace the current page with the return value. This particular code already does, but it is standard practice to always explicitly null the return value so you are safe from any changes triggering that trap. There are many ways to ensure this - I prefer returning an explicit null or false. – Joachim Lous Oct 31 '20 at 11:18
  • it works for firefox which doesn't support document.execCommand("copy") , but how it lost focus. what is the meaning do popup stuff? do you have a full example to copy webpage title to clipboard without lost focus. @JoachimLous – netawater Dec 26 '20 at 07:34
  • 2
    @netawater - I'm pretty sure that JoachimLous just meant that you should be careful if your bookmarklet code opens any alerts or "Popups" (like: `window.alert("sometext");`, `alert("sometext");`, `window.confirm("sometext");`, `confirm("sometext");`, `window.prompt("sometext","defaultText");` or `prompt("sometext","defaultText");`), ***Before*** your code that writes to the clipboard. If you don't use alerts or prompts, or if you use them ***After*** your code that writes to the clipboard, you don't (shouldn't) have to worry about restoring the focus, you can ignore the `....focus();` stuff. – Kevin Fegan Jan 05 '21 at 23:00
  • @Kevin Fegan got it, thanks! – netawater Jan 20 '21 at 03:17
9

With recent versions of Firefox, interacting with the clipboard via a bookmarklet in general won't work due to missing permissions (see this information for more details). There may be a way, however, to have the bookmarklet display a button, and perform the clipboard interaction in the context of a button-click event handler.

A possibly more straightforward solution is to use a user-script manager, and define your bookmarklet in the form of a user-script that you can activate via a keyboard combination. See, for example this user script, reproduced here for completeness:

// Add the following as a user-script (via an extension like https://github.com/violentmonkey/violentmonkey) in order to copy the
// current webpage and selected text to the clipboard in a format suitable for pasting into an org-mode document.
// To execute the action, you need to press Alt-C on a webpage, though this can be modified by changing the keycode
// used in the onkeyup function.

// ==UserScript==
// @name Copy Org-mode Link
// @namespace Violentmonkey Scripts
// @match *://*/*
// @grant clipboardWrite
// ==/UserScript==

function main() {
    function copyTextToClipboard(text) { var textArea = document.createElement("textarea"); textArea.style.position = 'fixed'; textArea.style.top = 0; textArea.style.left = 0; textArea.style.width = '2em'; textArea.style.height = '2em'; textArea.style.padding = 0; textArea.style.border = 'none'; textArea.style.outline = 'none'; textArea.style.boxShadow = 'none'; textArea.style.background = 'transparent'; textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { var successful = document.execCommand('copy'); var msg = successful ? 'successful' : 'unsuccessful'; console.log('Copying text command was ' + msg); } catch (err) { console.log('Oops, unable to copy'); } document.body.removeChild(textArea); }; var url = encodeURIComponent(location.href); url = url.replace(/%253A/g, ':').replace(/%252F/g, '/'); var title = document.title; title = title.replace(/\[/g, '{'); title = title.replace(/\]/g, '}'); var sel_text = window.getSelection(); copyTextToClipboard('[['+url+']['+title+']]'+'\n\n'+sel_text);
}

// listen for Alt-C key-combination, and then execute
document.onkeyup=function(e){
    var e = e || window.event; // for IE to cover IEs window object
    if(e.altKey && e.which == 67) {
         main();
         return false;
    }
}
Mark
  • 2,829
  • 25
  • 27
5

An answer that's a bit unusual: open a blank page from which the user will copy the text:

<a href="javascript:window.open('data:text/html, <html contenteditable>sup<script>document.execCommand(\'selectAll\')</script></html>')">
  Copy the text “sup”
</a>

Just replace sup by the text you want the user to copy.

JS Bin example

Christophe Marois
  • 5,513
  • 1
  • 23
  • 23
4

Here's how I solved it, using the technique @zzzzBov mentioned in his answer, to import zeroclipboard into the page via a bookmarklet.

When the bookmarket runs, a hand cursor appears on hovering over anywhere on the body. Clicking will copy (for example) the document's title to the clipboard.

(Links to zeroclipboard resources have been replaced with placeholders, and multi-line comments have been used since Chrome appears to be removing all line-breaks from bookmarklets (or something))

javascript:

var s = document.createElement('script');
s.setAttribute('src', 'http://example.com/ZeroClipboard.js');

s.onload = s.onreadystatechange = 
  function()
  { 
     ZeroClipboard.setMoviePath( 'http://example.com/ZeroClipboard.swf');
     var clip = new ZeroClipboard.Client();   

     /* glue to the body: sample only, in reality  we should
        probably create a new visible element and glue to that. */
     clip.glue(document.body);   

     clip.setHandCursor( true );

     /* copy to clipboard on mouse-up */
     clip.addEventListener('onMouseUp', 
      function (client) 
      {      
         /* example */
         var toCopy = document.title;        
         clip.setText(toCopy);    

         alert(toCopy + ' copied.');
         clip.hide();
      });  
   };

document.body.appendChild(s);
Community
  • 1
  • 1
Ani
  • 103,292
  • 21
  • 241
  • 294
3

A couple disclaimers:

  1. I'm not trying to spam you
  2. I gain nothing if you choose to use this

I made a bookmarklet generator a while back to make it easier for me to create bookmarklets.

It's jQuery enabled, but that doesn't mean you have to use jQuery.

You can check out the source to see how to import another script/library into a page via a bookmarklet.

In particular, the lines that import jQuery:

if (!window.zbooks)
  {
    //if zbooks hasn't been set, initialize it

    //s used for the Script element
    var s = document.createElement('script');
    //r used for the Ready state
    var r = false;
    //set the script to the latest version of jQuery
    s.setAttribute('src', 'http://code.jquery.com/jquery-latest.min.js');
    //set the load/readystate events
    s.onload = s.onreadystatechange = function()
    {
/**
 * LOAD/READYSTATE LOGIC
 * execute if the script hasn't been ready yet and:
 * - the ready state isn't set
 * - the ready state is complete
 *   - note: readyState == 'loaded' executes before the script gets called so
 *     we skip this event because it wouldn't have loaded the init event yet.
 */
      if ( !r && (!this.readyState || this.readyState == 'complete' ) )
      {
        //set the ready flag to true to keep the event from initializing again
        r = true;
        //prevent jQuery conflicts by placing jQuery in the zbooks object
        window.zbooks = {'jQuery':jQuery.noConflict()};
        //make a new zbook
        window.zbooks[n] = new zbooks(c);
      }
    };
    //append the jQuery script to the body
    b.appendChild(s);
  }

I hope that helps.

zzzzBov
  • 157,699
  • 47
  • 307
  • 349
  • Looks useful; will try it out and let you know. Will require a lot of trial and error on my part; I don't know a thing about js. – Ani Feb 18 '11 at 22:00
  • 1
    Thanks; that import technique was good enough to set me on my way. – Ani Feb 21 '11 at 14:16