I generate one-shot passwords (think app passwords on Facebook) in my Web app. These are 32 characters long hexadecimal strings.
I want to display them in a way so it is easy to copy using pen and paper (ie. group by four characters like 600d deed), but I donʼt want these “grouping spaces” to appear in the copied text if the user selects it in the browser to copy/paste it to another app.
Is there a CSS or HTML trick for this?
Why would you care? Wouldn't the simplest solution be to just strip whitespace in whatever function that receives the password when used? If you know that your passwords are only [A-Za-z0-9], strip everything else. This way you can present the code to the user grouped however you like; dots, dashes, spaces, exclamation marks, poop-emojis...
Good question. I came up with a 'cave man' solution. Now apparently evergreen browsers will let you get access to the event.clipboardData data within a "copy" event attached to the document (this event is not supported on ie).
This means once you select the contents of an input via HTMLinputElement.select method, a "copy" event will be fired (provided that you attached it beforehand) which you can get access to event.clipboardData AND the setData method of it.
In the below example, watch out that the random number generation (I used 16 digits) might look wrong if the number notation defaults to scientific, you are better off with quartets. Also the example can be written much shorter if you have DOM manipulation library.
That being said, I will drop my cave man solution which I guess would work in older browsers (you can easily extend this to non input elements. For instance, attach a click event on a div with spans etc. Then get the textContent and modify and then proceed like below with the mockInput) :
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<input id="someInput" type="text"></input>
<script id="mainScript" type="text/javascript">
!function(){
var someInput = document.getElementById("someInput"); //don't need to do this in chrome
/*generate an input with random hex*/
someInput.value = Number(Array.apply(null,Array(20))
.map(function(d,i){return ~~(Math.random()*16)})
.join(""))
.toString(16)
.slice(0,16)
.replace(
/(?:\w{4}(?=\w))/gi,
function(m,o,s){return m+"-"}
);
/*attach event handler*/
someInput.addEventListener("click",function(e){
/*if emtpy do nothing*/
if(!this.value){return}
/*create a dummy input element that will hold data
it needs to be part of the document so that we can
use copy command*/
var mockInput = document.createElement("input");
document.body.appendChild(mockInput);
/*making it 0 width/height or putting it inside
an invisible iframe or setting display none alerts
the browser to not execute copy, so best is to
copy and remove*/
mockInput.type = "text";
mockInput.value = this.value.replace(/\s|-/gi,"");
/*we will overlap it with the original element
and set the opacity to 0, at least the browser allows that*/
mockInput.style.opacity = 0;
mockInput.style.position = "absolute";
mockInput.style.top = someInput.offsetTop+"px";
mockInput.style.left = someInput.offsetLeft+"px";
mockInput.select();
document.execCommand("Copy");
alert("Copied to Clipboard");
/*remove once we are done*/
mockInput.parentNode.removeChild(mockInput);
},false)
}()
</script>
</body>
</html>
And here is a little function to generalize the use case:
node is the node to operate on, replacer is a function to be called and transform the contents of the node and shouldAlert will alert the user if set to true.
function copyContents(node,replacer,shouldAlert){
var contents = replacer(node.textContent || ("value" in node ? node.value : "")),
mockInput = node
.parentElement
.insertBefore(document.createElement("input"),node);
if(!contents){return}
copyContents._style = mockInput.style;
mockInput.type = "text";
mockInput.value = contents;
copyContents
._set("opacity",0)
._set("position","absolute")
._set("top",node.offsetTop+"px")
._set("left",node.offsetLeft+"px");
mockInput.select();
document.execCommand("Copy");
shouldAlert ? alert("Copied to Clipboard") : void(0);
mockInput.parentNode.removeChild(mockInput);
}
copyContents._set = function(prop,value){
this._style[prop] = value;
return this;
}
And the usage, try on google input :))
var input = document.getElementById("lst-ib")
input.addEventListener(
"mouseup",
function(e){
copyContents(
this,
function(x){return x.replace(/\s/gi,"")},
true
)
},
false
)
There are many masked inputs so I'm sure you can pull out some information from those, unfortunately I don't have experience in making such
Daniel J Dominguez
You have to believe in things that are not true. How else would they become?
When I'm generating codes and things I expect the user to copy/paste, I often output it inside an
<input>or<textarea>to make it simpler to focus the element, select all, and copy—rather than making the user carefully select the start and end points of the selection with the mouse (or their finger on mobile). But, I'm not usually adding visual spaces in.If I needed to display 'chunks' of a generated code, but wanted the spaces to be gone when copied there are 2 routes you could explore:
I haven't done much with the JS clipboard API, so I'm not sure what that demo would look like, but I do have an idea for the second one:
<div id=output> <span>0123</span><span>4567</span><span>89ab</span><span>cdef</span> </div> <style> #output { padding: 1em; border: 1px solid black; } #output span + span { margin-left: .5em; } </style>Here we have a
<div>element that is filled with text, wrapped in chunks using<span>tags. For CSS all I've done is put some spacing and border around the containing element so you can see where it is, and any time there is a<span>tag followed by another<span>tag inside our<div id=output>it will add a left margin to add some space between them. Hopefully this makes sense <3