How does Trello access the user's clipboard?
Disclosure: I wrote the code that Trello uses; the code below is the actual source code Trello uses to accomplish the clipboard trick.
We don't actually "access the user's clipboard", instead we help the user out a bit by selecting something useful when they press Ctrl+C.
Sounds like you've figured it out; we take advantage of the fact that when you want to hit Ctrl+C, you have to hit the Ctrl key first. When the Ctrl key is pressed, we pop in a textarea that contains the text we want to end up on the clipboard, and select all the text in it, so the selection is all set when the C key is hit. (Then we hide the textarea when the Ctrl key comes up.)
Specifically, Trello does this:
TrelloClipboard = new class constructor: -> @value = "" $(document).keydown (e) => # Only do this if there's something to be put on the clipboard, and it # looks like they're starting a copy shortcut if !@value || !(e.ctrlKey || e.metaKey) return if $(e.target).is("input:visible,textarea:visible") return # Abort if it looks like they've selected some text (maybe they're trying # to copy out a bit of the description or something) if window.getSelection?()?.toString() return if document.selection?.createRange().text return _.defer => $clipboardContainer = $("#clipboard-container") $clipboardContainer.empty().show() $("<textarea id='clipboard'></textarea>") .val(@value) .appendTo($clipboardContainer) .focus() .select() $(document).keyup (e) -> if $(e.target).is("#clipboard") $("#clipboard-container").empty().hide() set: (@value) ->
In the DOM we've got:
<div id="clipboard-container"><textarea id="clipboard"></textarea></div>
CSS for the clipboard stuff:
#clipboard-container { position: fixed; left: 0px; top: 0px; width: 0px; height: 0px; z-index: 100; display: none; opacity: 0;}#clipboard { width: 1px; height: 1px; padding: 0px;}
... and the CSS makes it so you can't actually see the textarea when it pops in ... but it's "visible" enough to copy from.
When you hover over a card, it calls
TrelloClipboard.set(cardUrl)
... so then the clipboard helper knows what to select when the Ctrl key is pressed.
I actually built a Chrome extension that does exactly this, and for all web pages. The source code is on GitHub.
I find three bugs with Trello's approach, which I know because I've faced them myself :)
The copy doesn't work in these scenarios:
- If you already have Ctrl pressed and then hover a link and hit C, the copy doesn't work.
- If your cursor is in some other text field in the page, the copy doesn't work.
- If your cursor is in the address bar, the copy doesn't work.
I solved #1 by always having a hidden span, rather than creating one when user hits Ctrl/Cmd.
I solved #2 by temporarily clearing the zero-length selection, saving the caret position, doing the copy and restoring the caret position.
I haven't found a fix for #3 yet :) (For information, check the open issue in my GitHub project).
With the help of raincoat's code on GitHub, I managed to get a running version accessing the clipboard with plain JavaScript.
function TrelloClipboard() { var me = this; var utils = { nodeName: function (node, name) { return !!(node.nodeName.toLowerCase() === name) } } var textareaId = 'simulate-trello-clipboard', containerId = textareaId + '-container', container, textarea var createTextarea = function () { container = document.querySelector('#' + containerId) if (!container) { container = document.createElement('div') container.id = containerId container.setAttribute('style', [, 'position: fixed;', 'left: 0px;', 'top: 0px;', 'width: 0px;', 'height: 0px;', 'z-index: 100;', 'opacity: 0;'].join('')) document.body.appendChild(container) } container.style.display = 'block' textarea = document.createElement('textarea') textarea.setAttribute('style', [, 'width: 1px;', 'height: 1px;', 'padding: 0px;'].join('')) textarea.id = textareaId container.innerHTML = '' container.appendChild(textarea) textarea.appendChild(document.createTextNode(me.value)) textarea.focus() textarea.select() } var keyDownMonitor = function (e) { var code = e.keyCode || e.which; if (!(e.ctrlKey || e.metaKey)) { return } var target = e.target if (utils.nodeName(target, 'textarea') || utils.nodeName(target, 'input')) { return } if (window.getSelection && window.getSelection() && window.getSelection().toString()) { return } if (document.selection && document.selection.createRange().text) { return } setTimeout(createTextarea, 0) } var keyUpMonitor = function (e) { var code = e.keyCode || e.which; if (e.target.id !== textareaId || code !== 67) { return } container.style.display = 'none' } document.addEventListener('keydown', keyDownMonitor) document.addEventListener('keyup', keyUpMonitor)}TrelloClipboard.prototype.setValue = function (value) { this.value = value;}var clip = new TrelloClipboard();clip.setValue("test");
See a working example:http://jsfiddle.net/AGEf7/