vue - copy password input field to clipboard - javascript

I have a form input in my vue app that is used to create a password. I've successfully added a button to show/hide the password. I want to add a copy to clipboard function to let the user copy and paste the password in a safe place, but it's not working. What am I doing wrong here?
Template code
<small class="font-wieght-bold text-success mb-0" v-if="isCopied">Copied</small>
<div class="input-group">
<input :type="showPassword ? 'text' : 'password'" class="form-control" ref="password" required v-model="password">
<div class="input-group-append">
<button class="btn btn-secondary" #click.prevent="copyToClipboard()"><i class="fas fa-clipboard"></i></button>
</div>
</div>
Vue code
viewPassword() {
this.showPassword = !this.showPassword;
},
copyToClipboard() {
this.$refs.password.select();
document.execCommand('copy');
this.isCopied = true;
setTimeout( () => { this.isCopied = !this.isCopied },3000);
}

You need to copy the contents of your v-model, not the input itself.
You can use a function like this to copy from a variable.
It makes a new text box, uses the copy command, then immediately removes it. All in a single event loop so it never even renders.
const copyTextToClipboard = (text) => {
const textArea = document.createElement('textarea')
//
// *** This styling is an extra step which is likely not required. ***
//
// Why is it here? To ensure:
// 1. the element is able to have focus and selection.
// 2. if element was to flash render it has minimal visual impact.
// 3. less flakyness with selection and copying which **might** occur if
// the textarea element is not visible.
//
// The likelihood is the element won't even render, not even a flash,
// so some of these are just precautions. However in IE the element
// is visible whilst the popup box asking the user for permission for
// the web page to copy to the clipboard.
//
// Place in top-left corner of screen regardless of scroll position.
textArea.style.position = 'fixed'
textArea.style.top = '0'
textArea.style.left = '0'
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textArea.style.width = '2em'
textArea.style.height = '2em'
// We don't need padding, reducing the size if it does flash render.
textArea.style.padding = 0
// Clean up any borders.
textArea.style.border = 'none'
textArea.style.outline = 'none'
textArea.style.boxShadow = 'none'
// Avoid flash of white box if rendered for any reason.
textArea.style.background = 'transparent'
textArea.value = text
document.body.appendChild(textArea)
textArea.select()
try {
const successful = document.execCommand('copy')
const msg = successful ? 'successful' : 'unsuccessful'
console.log('Copying text command was ' + msg)
} catch (err) {
console.log('Oops, unable to copy')
}
document.body.removeChild(textArea)
}

As an alternative solution to the one of the accepted answer I've tried with the clipboard API. In particular I've used the clipboard.writeText() method to copy the v-modeled data directly into the clipboard and it worked also with <input type="password">
Here is the refactored code of copyToClipboard() method:
copyToClipboard() {
navigator.clipboard.writeText(this.password);
this.isCopied = true;
setTimeout( () => { this.isCopied = !this.isCopied },3000);
}

Related

Does html textarea support cyclic display of text?

What I am trying to get is a text field of a fixed size (with a height of exactly one row) where you can type some text. If the text is short enough to fit in the text field it should just be displayed. But if the text is too long it should be displayed in a cycle, e.g. it constantly moves across the field and starts over. Like a continues sideways scroll of an infinite cyclic string, like you may have seen on news television. This should happen immediately after the user stops typing.
My question:
Is it possible to achieve this with just using html-textarea/css/js?
Maybe there is a way to smoothly scroll sideways through the typed text and seemlessly jump back to the beginning?
I can create a html textarea with all of properties that I need (like hidden scrollbar, fixed size, only horizontal scrolling) but I do not know how to make the text move in the above described way.
I am not limited to using the bulit-in textarea from html so if you have any other implementation ideas that can be done in html/css/js - they are welcomed.
Thank you!
No there isn't something native for that. This is a hacky solution. First detecting the overflow, but it's possible to hack it. For simplicity I'll assume length of string. Then when it "overflows" I shall rotate the string. It's important to reset when keydown again.
All in all, Maybe a more robust solution would be to rotate on blur and stop on focus.
var input = document.querySelector("input");
var int_id;
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
}
function is_overflow(input) {
// for now:
return input.value.length >= 14
}
input.addEventListener('keydown', function() {
stop(input)
})
function stop(input) {
if (int_id) {
clearInterval(int_id)
input.value = input.getAttribute("data-value") || input.value
int_id = null;
}
}
input.addEventListener('input', debounce(function(ev) {
if (is_overflow(input)) {
input.setAttribute("data-value", input.value);
int_id = setInterval(function() {
rotate(input)
}, 100);
} else {
stop(input)
}
}))
function rotate(input) {
var str = input.value
var num = 1;
input.value = str.substr(num) + str.substr(0, num);
}
<input style="width:100px">

Copy to clipboard code in JavaScript doesn't work

I'm trying to write in javascript and I want to understand why it doesn't work?
sample order code that I want to copy: 593004485164756431
when I copy to clipboard : 593004485164756500
I checked for an error in the console, there was no error, does the browser affect the error? i use firefox
this my code
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text)
} else {
// text area method
let textArea = document.createElement("textarea")
textArea.value = text
// make the textarea out of viewport
textArea.style.position = "fixed"
textArea.style.left = "-999999px"
textArea.style.top = "-999999px"
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
return new Promise((res, rej) => {
// here the magic happens
document.execCommand('copy') ? res() : rej()
textArea.remove()
})
}
}
on button
<img onclick="copyToClipboard({{ $result['code_order'] }})" style="width:20px;cursor:pointer;" src="{{ url('img/copy-solid.svg') }}"/>
I hope I can find the solution to this problem. Thank you

Why document.execCommand("copy") doesn't work?

I have the following Page
When I click "Click Here To Copy", it calls document.execCommand("copy") to copy some text to the clipboard and it works.
However, when I hit the button "Open popup", it opens a div in the same page (no iframe), then when clicking on "Click Here To Copy", document.execCommand("copy") doesn't work.
Steps to reproduce :
document.execCommand("copy") works :
However if I open the popup, document.execCommand("copy") doesn't work
Does anyone know the reason for that please ?
Thanks
cheers,
Here is my entire code :
function CopyToClipBoard(d){
var c=document.createElement("textarea");
c.innerText=d;
document.body.appendChild(c);
c.select();
document.execCommand("copy");
document.body.removeChild(c);
}
<div onclick="CopyToClipBoard('text to be copied')">Click Here To copy</div>
Here is the cause of the problem :
The following code doesn't seem to work when there is an overlay of a document. For example when the caller is on a div with higher z-index or something... I'm not sure on what exactly causes this code to fail. But it seems related to overlayers, focusable elements or something... The fact is that, when the body of the document is hidden, the created text area is unable to focus and it doesn't work.
function CopyToClipBoard(d){
var c=document.createElement("textarea");
c.innerText=d;
document.body.appendChild(c);
c.select();
document.execCommand("copy");
document.body.removeChild(c);
}
<div onclick="CopyToClipBoard('text to be copied')">Click Here To copy</div>
Solution :
Instead of adding the text area to the body of the document, it can be added to the caller itself... Hence it will always be in the foreground. This assumes the text to copy is short such that the execution is fast enough for the user not to notice the creation and the removal of the text area...
function CopyToClipBoard(item, d){
var c=document.createElement("textarea");
c.value=d;
c.style.maxWidth = '0px';
c.style.maxHeight = '0px';
item.appendChild(c);
c.focus();
c.select();
document.execCommand("copy");
item.removeChild(c);
}
<div onclick="CopyToClipBoard(this,'text to be copied')">Click Here To copy</div>
Here is my working example of copyTextToClipboard function. Please try to add textArea.focus call.
const fallbackCopyTextToClipboard = text => {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
const copyTextToClipboard = text => {
if (navigator.clipboard) {
return navigator.clipboard.writeText(text);
}
return new Promise((resolve, reject) => {
fallbackCopyTextToClipboard(text);
resolve();
});
};

Detect weather user clicked on text or white space inside textarea

I have a textarea that can be draged around in which text should still be selectable by draging over it. In order to distinguish a "over text drag" from a "move drag" I have to know weather the point where the user started its drag (i.e. mousedown position) was white space or text.
The only thing I can come up with to figure this out is calculating using character width and line height, which I would like to avoid.
Not really sure what you're asking, but here's my attempt to translate the insanity into working code:
let doc, htm, bod, I; // for use on other loads
addEventListener('load', ()=>{
doc = document; htm = doc.documentElement; bod = doc.body; I = id=>doc.getElementById(id);
// magic under here
const test = I('test');
function whiteStart(string){
if(string.match(/^\s+/)){
return true;
}
return false;
}
test.oncopy = test.ondragstart = function(){
let s = getSelection().toString();
if(whiteStart(s)){
console.log('"'+s+'" starts with one or more white spaces');
}
else{
console.log('"'+s+'" starts without a white space');
}
}
}); // end load
<div id='test'>I&apos;m just going to put some text in here so we can run a very basic test. I hope this helps!</div>

How to detect number of characters that can exactly fit on a pre defined length of text box?

Lets say I add a text box of length of 50px, And I want to count the exact number of characters (including whitespace) that perfectly fits inside that text box, I mean no character should be allowed to be typed inside the textbox that require the sliding of whole line toward left; I mean, in another other-words, we need to disallow the typist to further insert any letter as the line reaches up to the length of the text box. Can we anyhow solve this by JavaScrip? Thanks for the help in advance, any help would be appreciated.
The whole logic is flawed as it would depend also on the size of the text inside the input. I'd put instead a limit of chars to be entered that don't go beyond. Using maxlength input attribute.
Anyways if you really wanna go this route, which I think is an overkill and not needed, then you can:
Make use of CanvasRenderingContext2D.measureText, docs here
In order to do that you'd have to create a hidden canvas element where to mimic your input text.
After that you will need to check on input if the text goes beyond the input width and avoid any further keystrokes but still allow for deletion.
Find attached an example snippet, not optimised, of what I am talking about.
const form = document.querySelector('#form'),
input = form.querySelector('input')
const createAppendCanvas = form => {
const canvas = document.createElement('Canvas')
form.appendChild(canvas)
}
createAppendCanvas(form)
const getTextMetrics = inputText => {
const canvas = document.querySelector('canvas'),
textWidth = Math.ceil(canvas.getContext('2d').measureText(inputText).width) + 10
return textWidth
}
const disableTyping = (event, input) => {
const inputText = event.target.value,
inputWidth = input.clientWidth
if (getTextMetrics(inputText) >= inputWidth) {
event.preventDefault()
return false
}
}
input.addEventListener('keypress', event => disableTyping(event, input))
input {
width: 50px;
}
canvas {
display: none;
}
<form id="form">
<input type="text" />
</form>
As #mel-macaluso rightly points out, this is a very big rabbit hole to go down, and the standard practice is to use the maxlength attribute to limit the number of characters.
*Edit: You can also set the width of the input using em, which is proportional to the font size. (The name em was originally a reference to the width of the capital M in the typeface and size being used, which was often the same as the point size ref) A combination of width in em and maxlength will give a very rough approximation of what you may be trying to achieve.
However if you really want to be able to limit input based text length, this would serve as a very simplistic example of how you might get started.
Edit: I recommend #mel-macaluso's answer: he added an example using CanvasRenderingContext2D.measureText(), which I suspect is much more efficient than getBoundingClientRect.
First some disclaimers:
This example doesn't take into account clipboard actions. That's a pretty big problem, and you'd be talking a lot more code to try to account for it (way beyond the scope of what can reasonably be done here).
It's also rather resource intensive. The process doing a getBoundingClientRect, forces the browser to reflow the document contents an extra time. Depending on the size of the page this can be a big deal, and it's not something to be done lightly.
var inp = document.getElementById('test');
// get font for input
var style = getComputedStyle(inp);
var maxWidth = inp.getBoundingClientRect().width;
var sizeTest = document.createElement('span');
// set font for span to match input
sizeTest.style.font = style.font;
inp.addEventListener('keydown', function(e){
if(e.ctrlKey || e.altKey) return;
if(e.key && e.key.length===1) {
sizeTest.textContent = inp.value;
document.body.append(sizeTest);
var w = sizeTest.getBoundingClientRect().width;
sizeTest.remove();
console.log(maxWidth, w, e.key, e.code);
if(w>maxWidth) e.preventDefault();
}
})
<input id='test'/>
So why is it so complex to do something like this? Fonts are tricky things. You have variable width (proportional) fonts, kerning, ligatures, etc. It's very complex, and browsers don't provide access to most of this information.
So if you want to know how long a segment of text is, you generally have to put it in a span with the same font settings and then request the bounding dimensions.
Here's neat solution using nested spans (with a contenteditable inner span) as a proxy input.
// Identifiers and dynamic styling
const innerSpan = document.querySelector("span.inner"),
outerSpan = document.querySelector("span.outer");
/* Threshold should be at least one character-width less than outerSpan.
(This formula was pretty close for my few tests;
for more precision and less flexibility, you can hard-code a value.) */
const estMaxCharWidth = innerSpan.offsetHeight / 1.7,
thresholdWidth = outerSpan.offsetWidth - estMaxCharWidth;
innerSpan.style.minWidth = `${Math.floor(thresholdWidth)-3}px`; // defaults to 0
innerSpan.style.minHeight = `${Math.floor(outerSpan.offsetHeight)-2}px`
// Listeners
innerSpan.addEventListener("focus", customOutline);
innerSpan.addEventListener("keydown", checkKeyAndWidth);
innerSpan.addEventListener("blur", removeOutlineAndHandleText);
// Functions
function checkKeyAndWidth(e){
// Runs when user presses a key, Conditionally prevents input
if(e.code == "Enter" || e.keyCode == 13){
e.preventDefault(); // Don't insert a new line
e.target.blur(); // (In production, set the focus to another element)
}
else{
// Some keys besides Enter are important, More could be added
const whitelistCodes = ["Backspace", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Insert", "Delete"];
const whitelistKeyCodes = [8,9,27,37,39,45,46];
// If the inner span is wide enough, stop accepting characters
let acceptingCharacters = e.target.offsetWidth <= thresholdWidth;
if(!acceptingCharacters && !whitelistCodes.includes(e.code) && !whitelistKeyCodes.includes(e.keyCode) && !whitelistKeyCodes.includes(e.which)){
// Unauthorized incoming keystroke
e.preventDefault();
}
}
}
function customOutline(){
// Runs when span gets focus, Needed for accessibility due to CSS settings
outerSpan.style.borderColor = "DeepSkyBlue";
}
function removeOutlineAndHandleText(){
// Runs when focus is lost
outerSpan.style.borderColor = "Gray";
if(innerSpan.length < 1){ innerSpan.innerHTML = " "; } // force content
/* Since this is not a real input element, now might be the time to do something with the entered text */
}
.outer{
display: inline-block;
position: relative;
width: 100px; /* Defaults to 0 */
padding: 0;
border: 1px solid gray;
}
.inner{
display: inline-block;
position: relative;
top: 0;
height: 100%;
margin: 0;
outline: none; /* Don't do this without calling customOutline on focus */
}
<!-- requires that browser supports `contenteditable` -->
<span class="outer">
<!-- space character in innerSpan may improve cross-browser rendering -->
<span class="inner" contenteditable="true"> </span>
</span>

Categories