Why isn't document.execCommand causing the clipboard to populate? - javascript

I have the following HTML:
<td class="pn">
<span class="copyable">410-555-1234</span>
<span title="Click to copy" class="clipboard">📋</span>
<form class="pn-copy-form">
<input class="pn-copy" type="text" value="+14105551234" />
</form>
</td>
When the user clicks on the clipboard icon, I would like to populate the contents of the user's system clipboard with "+14105551234". However, I notice that nothing populates. Here's the Javascript I'm using, placed at the bottom of the same page:
var evHandler = function(clipboardElem) {
return function() {
var pnCopy = clipboardElem.parentNode.querySelector('.pn-copy');
if (pnCopy === null) {
return;
}
pnCopy.select();
try {
result = document.execCommand('copy');
if (result === false) {
throw new Error("Could not copy value: " + pnCopy.value);
}
} catch (e) {
console.error(e);
alert("Couldn't copy text, sorry. Here it is: " + pnCopy.value);
}
console.log("Copied "+ pnCopy.value + " to the clipboard");
pnCopy.blur();
};
}
var clipboards = document.querySelectorAll('.clipboard');
for (var i = 0; i < clipboards.length; i++) {
var clipboard = clipboards[i];
clipboard.addEventListener('click', evHandler(clipboard));
}
I'm hiding the form, since I don't want it to appear on the page.
.pn-copy {
display: none;
}
Why isn't the clipboard populating? Does click-to-copy not work on localhost, or not work on unencrypted HTTP?

The form needs to be "visible," though that definition is loose, and I think is roughly "not display:none". Without that in place, .select() selects no text. I had success with the following CSS, which leaves the input "visible" but placed 3000 pixels off screen.
/* the input has to be "visible", if you use display: none copy won't work. */
.pn-copy-form {
position: absolute;
height: 1px;
left: -3000px;
}

Related

How to draw connecting lines between web elements on a page

I want to find the simplest barebones (that is, no libraries if possible; this is a learning exercise) way to draw a simple line between components. The elements are divs representing cards always stacked vertically potentially forever. Cards can be different heights. The line will exit the left hand side of any given element (card a), turn 90 degrees and go up, turning 90 degrees back into another (card b).
I've tried a few things. I haven't got any fully working yet and they're looking like they all need some serious time dedicated to figuring them out. What I want to know is what's the right/preferred way to do this so that I spend time on the right thing and it's future proof with the view:
I can add as many connecting lines as I need between any two boxes, not just consecutive ones
These lines obey resizing and scrolling down and up the cards
Some cards may not have an end point and will instead terminate top left of page, waiting for their card to scroll into view or be created.
Attempts
My first thought was a <canvas> in a full column component on the left but aligning canvas' and the drawings in them to my divs was a pain, as well as having an infinite scrolling canvas. Couldn't make it work.
Next I tried <div>s. Like McBrackets has done here. Colouring the top, bottom and outer edge of the div and aligning it with the two cards in question but while I can position it relative to card a, I can't figure out how to then stop it at card b.
Lastly I tried <SVG>s. Just .getElementById() then add an SVG path that follows the instructions above. i.e.
const connectingPath =
"M " + aRect.left + " " + aRect.top + " " +
"H " + (aRect.left - 50) +
"V " + (bRect.top) +
"H " + (bRect.left);
Nothing seems to line up, it's proving pretty difficult to debug and it's looking like a much more complex solution as I need to take into account resizing and whatnot.
You might be able to apply something like this by taking a few measurements from the boxes you want to connect; offsetTop and clientHeight.
Update Added some logic for undrawn cards requirement.
While this doesn't fully simulate dynamic populating of cards, I made an update to show how to handle a scenario where only one card is drawn.
Click connect using the default values (1 and 5). This will show an open connector starting from box 1.
Click "Add box 5". This will add the missing box and update the connector.
The remaining work here is to create an event listener on scroll to check the list of connectors. From there you can check if both boxes appear or not in the DOM (see checkConnectors function). If they appear, then pass values to addConnector which will connect them fully.
class Container {
constructor(element) {
this.connectors = new Map();
this.element = element;
}
addConnector(topBox, bottomBox, displayHalf = false) {
if (!topBox && !bottomBox) throw new Error("Invalid params");
const connector = new Connector(topBox, bottomBox, displayHalf);
const connectorId = `${topBox.id}:${bottomBox.id}`;
this.element.appendChild(connector.element);
if (this.connectors.has(connectorId)) {
connector.element.style.borderColor = this.connectors.get(connectorId).element.style.borderColor;
} else {
connector.element.style.borderColor = "#" + Math.floor(Math.random() * 16777215).toString(16);
}
this.connectors.set(connectorId, connector);
}
checkConnectors() {
this.connectors.forEach((connector) => {
if (connector.displayHalf) {
connector.firstBox.updateElement();
connector.secondBox.updateElement();
if (connector.firstBox.element && connector.secondBox.element) {
this.addConnector(connector.firstBox, connector.secondBox);
}
}
});
}
}
class Box {
constructor(id) {
this.id = id;
this.updateElement();
}
getMidpoint() {
return this.element.offsetTop + this.element.clientHeight / 2;
}
updateElement() {
this.element ??= document.getElementById(`box${this.id}`);
}
static sortTopDown(firstBox, secondBox) {
return [firstBox, secondBox].sort((a,b) => a.element.offsetTop - b.element.offsetTop);
}
}
class Connector {
constructor(firstBox, secondBox, displayHalf) {
this.firstBox = firstBox;
this.secondBox = secondBox;
this.displayHalf = displayHalf;
const firstBoxHeight = this.firstBox.getMidpoint();
this.element = document.createElement("div");
this.element.classList.add("connector");
this.element.style.top = firstBoxHeight + "px";
let secondBoxHeight;
if (this.displayHalf) {
secondBoxHeight = this.firstBox.element.parentElement.clientHeight;
this.element.style.borderBottom = "unset";
} else {
secondBoxHeight = this.secondBox.getMidpoint();
}
this.element.style.height = Math.abs(secondBoxHeight - firstBoxHeight) + "px";
}
}
const connectButton = document.getElementById("connect");
const error = document.getElementById("error");
const addBoxButton = document.getElementById("addBox");
const container = new Container(document.getElementById("container"));
connectButton.addEventListener("click", () => {
const firstBoxId = document.getElementById("selectFirstBox").value;
const secondBoxId = document.getElementById("selectSecondBox").value;
if (firstBoxId === "" || secondBoxId === "") return;
error.style.display = firstBoxId === secondBoxId ? "block" : "none";
const firstBox = new Box(firstBoxId);
const secondBox = new Box(secondBoxId);
// Check for undrawn cards
if (!!firstBox.element ^ !!secondBox.element) {
return container.addConnector(firstBox, secondBox, true);
}
const [topBox, bottomBox] = Box.sortTopDown(firstBox, secondBox);
container.addConnector(topBox, bottomBox);
});
window.addEventListener("resize", () => container.checkConnectors());
addBoxButton.addEventListener("click", () => {
const box = document.createElement("div");
box.innerText = 5;
box.id = "box5";
box.classList.add("box");
container.element.appendChild(box);
addBoxButton.style.display = 'none';
container.checkConnectors();
});
.box {
border: solid 1px;
width: 60px;
margin-left: 30px;
margin-bottom: 5px;
text-align: center;
}
#inputs {
margin-top: 20px;
}
#inputs input {
width: 150px;
}
.connector {
position: absolute;
border-top: solid 1px;
border-left: solid 1px;
border-bottom: solid 1px;
width: 29px;
}
#error {
display: none;
color: red;
}
<div id="container">
<div id="box1" class="box">1</div>
<div id="box2" class="box">2</div>
<div id="box3" class="box">3</div>
<div id="box4" class="box">4</div>
</div>
<div id="inputs">
<input id="selectFirstBox" type="number" placeholder="Provide first box id" min="1" value="1" max="5" />
<input id="selectSecondBox" type="number" placeholder="Provide second box id" min="1" value="5" max="5" />
<div id="error">Please select different boxes to connect.</div>
</div>
<button id="connect">Connect</button>
<button id="addBox">Add box 5</button>

Open a image of a Google Doc in in another window

I have a Google Doc with images. I would like to open a selected image in a page in another window (the google doc is a role playing game scenario and I want to show the image to my players on a second screen).
I have created a sidebar with a google script and I am able to show the selected image in this sidebar.
Now, I don't know how to open a new window (or connect a existing window) and send the image data to this window.
I start by trying to use the "PresentationRequest", but I have the error "PresentationRequest is not defined" on the init...
presentationRequest = new PresentationRequest('receiver.html');
My source :
https://developers.google.com/web/updates/2018/04/present-web-pages-to-secondary-attached-displays
For information (and if it helps someone) how I send the image to the sidebar page:
var doc = DocumentApp.getActiveDocument();
var selection = doc.getSelection();
if (selection) {
var elements = selection.getRangeElements();
var e = elements[0].getElement();
if (e.getType() == DocumentApp.ElementType.INLINE_IMAGE) {
var blobImg = e.asInlineImage().getBlob();
return 'data:' + blobImg.getContentType() + ';base64,' + Utilities.base64Encode(blobImg.getBytes());
}
}
The HTML code :
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style type="text/css">
.tailMax {
max-width: 260px;
max-height: 260px;
}
.centre {
display: block;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<form id="formJdr">
<div style="padding-bottom: 10px;">
<button type="button" id="btnAffImg" onclick="google.script.run.withSuccessHandler(afficheImg).selectImg()">Afficher</button>
<label id="lblImg">Sélectionnez une image</label>
</div>
<img id="img" class="tailMax centre"/>
</form>
<script>
function afficheImg(valeur) {
if (typeof value === "string"){
// Message
afficheMessage(valeur);
}
else {
try {
// Image to show
afficheMessage("");
document.getElementById("img").src = valeur;
}
catch(error) {
afficheMessage(error);
}
}
}
function afficheMessage(message) {
document.getElementById("lblImg").innerHTML = message;
}
</script>
</body>
</html>
I use a Chrome browser.
Do you think it is possible?
Modify your try statement as following:
try {
// Image to show
afficheMessage("");
var image=document.getElementById("img");
image.src = valeur;
var w = window.open("", '_blank');
w.document.write(image.outerHTML);
}
var w = window.open("", '_blank'); w.document.write(image.outerHTML); allows you to open a new window and then write the image as bytearray into it.
Ok, with the help of Ziganotschka, I update my javascript code for this.
Now I can change the image in the new window.
Just some improvements to make on the opening of this window and it will be good.
<script>
var affichage;
function afficheImg(valeur) {
if (typeof value === "string"){
afficheMessage(valeur);
}
else {
try {
afficheMessage("");
var image = document.getElementById("img");
image.src = valeur;
affichage.document.body.innerHTML = "";
affichage.document.write(image.outerHTML);
}
catch(error) {
afficheMessage(error);
}
}
}
function afficheMessage(message) {
document.getElementById("lblImg").innerHTML = message;
}
window.onload = function() {
affichage = window.open("", '_blank');
</script>

Prevent link dragging, but still allow text highlighting

I have some data in a table where clicking it will navigate you elsewhere, but people are requesting the ability to highlight the text to be able to copy/paste it elsewhere. Since they are links, the default behavior in HTML is to drag the link... I don't know why or how that is useful, but I want to disable that on certain links.
TL;DR: I want to be able to highlight the text of a link and not drag it.
The gif below should help explain my issue.
The following methods are NOT what I want:
I have seen examples that prevent both highlighting & dragging using something like this
<a draggable="false" href="#">
or this
.no-drag {
user-drag: none;
}
Or this
myElement.ondragstart = function () {
return false;
};
But obviously that is not what I need here.Is what I want possible to do?
In Google Chrome this works
user-select: none;
-webkit-user-drag: none;
#Julien Grégoire's answer above put me on the right track for this, but the below code is the basics of what I ended up using.
var clickedEl = document.getElementById("test");
var limit = 5;
var mouseMoved = false;
function resetEvents() {
clickedEl.onmousemove = null;
clickedEl.ondragstart = null;
clickedEl.onmouseleave = null;
mouseMoved = false;
}
clickedEl.onmousedown = function (downEvent) {
if (clickedEl.attributes.href) {
clickedEl.onclick = function (clickEvent) {
if (mouseMoved) {
clickEvent.preventDefault();
}
resetEvents();
};
}
clickedEl.onmouseleave = function () {
resetEvents();
};
clickedEl.onmousemove = function (moveEvent) {
// This prevents the text selection being dragged
clickedEl.ondragstart = function (dragEvent) {
dragEvent.preventDefault();
};
if (Math.abs(moveEvent.x - downEvent.x) >= limit || Math.abs(moveEvent.y - downEvent.y) >= limit) {
// If user clicks then moves the mouse within a certain limit, select the text inside
window.getSelection().selectAllChildren(clickedEl);
mouseMoved = true;
}
};
};
<a id="test" href="http://stackoverflow.com">Click or select</a>
I'm super late to answer but I'm just gonna leave it here:
Just put draggable="false" inside <a> tag,
<a draggable="false" href="./"></a>
then in CSS you put:
body {
-webkit-user-drag: none;
}
You could detect if user moves the mouse after the click and if so manage selection using window.getSelection. Something like this for example:
var linkEl = document.getElementById('test')
linkEl.onmousedown = function(downEvent) {
var clickedEl = downEvent.target;
var mouseMoved = false;
clickedEl.onmousemove = function() {
// If user clicks then moves, select the whole link
window.getSelection().selectAllChildren(clickedEl);
// Set a flag to prevent opening the link
mouseMoved = true;
// Reset mousemove, else it'll run constantly
clickedEl.onmousemove = null;
// This is only to prevent having the text selection being dragged
clickedEl.ondragstart = function(dragEvent) {
dragEvent.preventDefault();
}
}
if (mouseMoved) {
// If mouse has moved, prevent default
downEvent.preventDefault();
}
}
<a draggable="false" id="test" href="http://stackoverflow.com">Click or select</a>
This is the simplest solution that worked for me. You can change '*' to 'a'.
*, *::after, *::before {
-webkit-user-select: none;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
}

Element following user message

I have created a unique web chat using websockets and python for the server. It's very basic at the moment however there's a feature I'd like to implement but I'm not sure as to how I'd go about it.
I made a div which is basically a small red square which is located beside the chat input, when a user types a message and clicks enter/submit I'd like for that red square to be at the end of the message which is displayed in the chat log.
The only clue I can think of is that it would involve an appendTo in js after an event has occured (the sending of the message) using a listener but I'm unsure as to how it would detect the end of a message. Any ideas? Hopefully it's a possible task.
HTML
<input type="text" id="send">
<div id="box"></div>
<!-- Message appears here -->
<fieldset id="chatlog"></fieldset>
CSS
div#box {
background:url(http://i.imgur.com/iJipdb6.png) no-repeat;
width: 10px;
height: 20px;
position: absolute;
top: 9px;
left: 110px;
SERVER
var messages;
var form;
var inputBox;
function log_msg(msg) {
var p = document.createElement("p");
p.innerHTML = msg;
messages.appendChild(p);
}
function doInit() {
inputBox = document.getElementById("message");
messages = document.getElementById("messages");
form = document.getElementById("message-form");
var s;
try {
var host = "ws://localhost:4545/";
if(window.location.hostname) {
host = "ws://" + window.location.hostname + ":4545/";
}
s = new WebSocket(host);
s.onopen = function (e) { log_msg("connected..."); };
s.onclose = function (e) { log_msg("connection closed."); };
s.onerror = function (e) { log_msg("connection error."); };
s.onmessage = function (e) { log_msg("message: " + e.data); };
} catch (ex) {
log_msg("connection exception:" + ex);
}
form.addEventListener("submit", function (e) {
e.preventDefault();
s.send(inputBox.value);
inputBox.value = "";
}, false);
}
IMAGE for further clarification: http://i.imgur.com/RkBpAMI.png
beTo my knowledge, it is not possible. A textarea only accepts raw text. To be able to do that, you will need to format your chat log in HTML.
On the top of my head and just for the sake of scratching it a little, you could imagine calculating the grid of your textarea (chars on x axis * chars on y axis), then calculate where on this grid the square would end up based on the length of its value, and absolutely position the box to simulate it being in the textarea. It could manageable since textarea are by default in monospace fonts, but this is a very bad idea. I would just make it HTML, it will be easier.

Image upload on file chosen not on submit form , [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have a HTML form where the user can upload an image in the input field and it works fine but the image is uploaded when the form is submitted.
Is there a way where I can get the image to upload to the TMP directory when the file is chosen and the user is still filling out the form then when the user submits the form it can be moved to the actual file directory. This would make for a better user experience and especially people with slow internet connections would benefit from this as it would utilise the users time for effectively and efficiently.
I am not sure exactly but I think this would need some sort of jQuery/Ajax solution to upload the image mid form entry then use PHP to transfer the file from the TMP to the actual directory.
As Diodeus suggested, putting the form in an iframe would prevent it from posting in the current page frame and allow users to work on other form items. A solution more to what you were expecting would be using an AJAX request. You could look into the HTML5 API, there are many different already-built solutions and many tutorials.
Here's a simple example taken from this demo at html5demos.com
<title>Drag and drop, automatic upload</title>
<style>
#holder { border: 10px dashed #ccc; width: 300px; min-height: 300px; margin: 20px auto;}
#holder.hover { border: 10px dashed #0c0; }
#holder img { display: block; margin: 10px auto; }
#holder p { margin: 10px; font-size: 14px; }
progress { width: 100%; }
progress:after { content: '%'; }
.fail { background: #c00; padding: 2px; color: #fff; }
.hidden { display: none !important;}
</style>
<article>
<div id="holder">
</div>
<p id="upload" class="hidden"><label>Drag & drop not supported, but you can still upload via this input field:<br><input type="file"></label></p>
<p id="filereader">File API & FileReader API not supported</p>
<p id="formdata">XHR2's FormData is not supported</p>
<p id="progress">XHR2's upload progress isn't supported</p>
<p>Upload progress: <progress id="uploadprogress" min="0" max="100" value="0">0</progress></p>
<p>Drag an image from your desktop on to the drop zone above to see the browser both render the preview, but also upload automatically to this server.</p>
</article>
<script>
var holder = document.getElementById('holder'),
tests = {
filereader: typeof FileReader != 'undefined',
dnd: 'draggable' in document.createElement('span'),
formdata: !!window.FormData,
progress: "upload" in new XMLHttpRequest
},
support = {
filereader: document.getElementById('filereader'),
formdata: document.getElementById('formdata'),
progress: document.getElementById('progress')
},
acceptedTypes = {
'image/png': true,
'image/jpeg': true,
'image/gif': true
},
progress = document.getElementById('uploadprogress'),
fileupload = document.getElementById('upload');
"filereader formdata progress".split(' ').forEach(function (api) {
if (tests[api] === false) {
support[api].className = 'fail';
} else {
// FFS. I could have done el.hidden = true, but IE doesn't support
// hidden, so I tried to create a polyfill that would extend the
// Element.prototype, but then IE10 doesn't even give me access
// to the Element object. Brilliant.
support[api].className = 'hidden';
}
});
function previewfile(file) {
if (tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader();
reader.onload = function (event) {
var image = new Image();
image.src = event.target.result;
image.width = 250; // a fake resize
holder.appendChild(image);
};
reader.readAsDataURL(file);
} else {
holder.innerHTML += '<p>Uploaded ' + file.name + ' ' + (file.size ? (file.size/1024|0) + 'K' : '');
console.log(file);
}
}
function readfiles(files) {
debugger;
var formData = tests.formdata ? new FormData() : null;
for (var i = 0; i < files.length; i++) {
if (tests.formdata) formData.append('file', files[i]);
previewfile(files[i]);
}
// now post a new XHR request
if (tests.formdata) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/devnull.php');
xhr.onload = function() {
progress.value = progress.innerHTML = 100;
};
if (tests.progress) {
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
var complete = (event.loaded / event.total * 100 | 0);
progress.value = progress.innerHTML = complete;
}
}
}
xhr.send(formData);
}
}
if (tests.dnd) {
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragend = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
this.className = '';
e.preventDefault();
readfiles(e.dataTransfer.files);
}
} else {
fileupload.className = 'hidden';
fileupload.querySelector('input').onchange = function () {
readfiles(this.files);
};
}
</script>
This creates a zone to drop a file (instead of a browse button) and initiate the file upload when the drag and drop event occurs. It will do it asynchronously and allow the page contents to be interacted with as normal while the transfer proceeds in the background. There is an important thing in this example to change, however. This line:
xhr.open('POST', '/devnull.php');
Should be changed to a code file in your environment/server that will process the file upload data and save or process the file however you need. This script merely acts as a front-end to that script. Another thing to remember is the HTML5 File API is still a modern-browser-only type of thing; it's well supported in current browsers, but older ones are out of luck. If you need to have them supported, you should look for another solution.

Categories