Unable to input spaces into a span, inside a button in Firefox - javascript

I have a button element, which needs to have text which can be modified dynamically. I was having trouble with adding spaces which is when I refered this answer to have a span, which is contenteditable. During this time, I was testing on Chrome and it was working.
However, when I was testing this feature in Firefox, I was not able to insert spaces in the span element. I do not see anything about this online either. Is there anyway I can have this supported on Firefox too?
I should also note that, I am doing this for an Extension that I am building for Chrome and Firefox.
This is what my code looks like:
<!-- HTML -->
<button id="editableBtn">
<span id="titleText" contenteditable="false" style="border: solid; border-color: green;">Button</span>
</button>
<button id="editorBtn">Click me to edit</button>
<!-- JS -->
<script>
var spanText = document.getElementById("titleText");
var editableBtn = document.getElementById("editableBtn");
var editorBtn = document.getElementById("editorBtn");
spanText.addEventListener('keydown', (event) => {
console.log("Span keydown = ", event)
})
editorBtn.addEventListener('click', (event) => {
spanText.contentEditable = "true"
})
</script>
Firefox Version: 110.0
Google Chrome Verion: 110.0

You could add an event listener to the span element for the "keydown" event, so when the space bar is pressed it manually inserts a non-breaking space character (which shows up as a space) at the current cursor position using the document.execCommand function.
var spanText = document.getElementById("titleText");
var editableBtn = document.getElementById("editableBtn");
var editorBtn = document.getElementById("editorBtn");
spanText.addEventListener('keydown', (event) => {
if (event.keyCode === 32) { // 32 is the keycode for the spacebar
// prevent default behavior, since we're handling it ourselves
event.preventDefault();
// insert the space character at the current cursor position
document.execCommand('insertHTML', false, ' ');
}
});
editorBtn.addEventListener('click', (event) => {
spanText.contentEditable = "true"
});
<button id="editableBtn">
<span id="titleText" contenteditable="false" style="border: solid; border-color: green;">Button</span>
</button>
<button id="editorBtn">Click me to edit</button>

Related

Working clipboard script not compatible with Chrome. Works with Edge/IE

I have a working clipboard script that I have to use on our portal page to utilize clipboard functionality. We are moving from IE/Edge to Chrome, and it seems this script will not function in Google Chrome. I would love it if we can find a way to make the code chrome/multi browser compatible without having to make a new script for Chrome-only.
While I do have a working script for Chrome, it would mean i would have to re-build hundreds of pages using clipboard, and I would rather make the script already embedded in all these pages chrome compatible. Below is the script i am using:
(function() {
'use strict';
// click events
document.body.addEventListener('click', copy, true);
// event handler
function copy(e) {
// find target element
var
t = e.target,
c = t.dataset.copytarget,
inp = (c ? document.querySelector(c) : null);
// is element selectable?
if (inp && inp.select) {
// select text
inp.select();
try {
// copy text
document.execCommand('copy');
inp.blur();
// copied animation
t.classList.add('copied');
setTimeout(function() { t.classList.remove('copied'); }, 1500);
}
catch (err) {
alert('please press Ctrl/Cmd+C to copy');
}
}
}
})();
// Button must include data-copytarget="#website" with the #xxx matching the element id
Results: In IE/Edge, you click on the button and the assigned text to that button is added to the clipboard for pasting. In Chrome however, clicking on the button and nothing happens.
Your code works fine in Chrome as long as the input is visible.
Chrome does not allow copying from a hidden input. There are multiple workarounds. In the example below, I've moved the input of screen using absolute positioning.
(function() {
"use strict";
// click events
document.body.addEventListener("click", copy, true);
// event handler
function copy(e) {
// find target element
var t = e.target,
c = t.dataset.copytarget,
inp = c ? document.querySelector(c) : null;
// is element selectable?
if (inp && inp.select) {
// select text
inp.select();
try {
// copy text
document.execCommand("copy");
inp.blur();
// copied animation
t.classList.add("copied");
setTimeout(function() {
t.classList.remove("copied");
}, 1500);
} catch (err) {
alert("please press Ctrl/Cmd+C to copy");
}
}
}
})();
#vishidden {
position: absolute;
top: -9999px;
left: -9999px;
}
<div>
<input type="text" id="text" value="visible test" readonly="true">
<button type="button" data-copytarget="#text">copy</button>
</div>
<div>
<input type="hidden" id="hidden" value="hidden test" readonly="true">
<button type="button" data-copytarget="#hidden">copy hidden</button>
</div>
<div>
<input type="text" id="vishidden" value="visually hidden test" readonly="true">
<button type="button" data-copytarget="#vishidden">copy visually hidden</button>
</div>
<div>
<textarea cols="30" rows="10" placeholder="paste here to test"></textarea>
</div>
Another example: Using execCommand (Javascript) to copy hidden text to clipboard
Clipboard.js is a useful library to do this. It also uses a visually hidden input behind the scenes (in a similar but more robust way than in my example).

Click button using Javascript / Speech recognition /tampermonkey

Currently, I can click any of the 4 buttons in a HTML, using Javascript running in tampermonkey, to select the ID of the button DIV to click. However I want to use speech recognition to click any of the 4 buttons by speaking any of the following words, NONE, ONE, TWO, THREE. I am guessing that the speech script will change the word I speak to text which will be added to a javascript array which will be matched to a DIV ID to be clicked. How to achieve this using javascript. thanks
document.getElementById('radio0').click();
<div class="radio-container">
<div class="col-6">
<button id="radio0">None</button>
</div>
<div class="col-6">
<button id="radio1">One</button>
</div>
<div class="col-6">
<button id="radio2">Two</button>
</div>
<div class="col-6">
<button id="radio3">Three +</button>
</div>
</div>
Come up with an array of button names. Because SpeechRecognition recognizes numbers as the actual numbers (eg 1, not one), use the numeric values rather than their word representations.
var buttonNames = [ 'None', '1', '2', '3'];
I had trouble giving an embedded StackSnippet permission to access the microphone (probably has to do with cross-domain and sandboxing rules), so I put all the code in a userscript. It replaces the page's HTML with your HTML. Click on the document body and the recognition will start. (Open your browser's console to see what it's doing) Then, speak one of the button names. (Make sure Stack Overflow - or whatever domain you run the userscript on - has permission to listen to your microphone)
When the onresult handler is triggered (when you stop speaking), identify the last word in the transcript, and see if it matches any of the buttonNames. If so, querySelectorAll the buttons in the document, and .click() the appropriate button index.
// ==UserScript==
// #name Userscript Speech Recognition
// #namespace CertainPerformance
// #version 1
// #match https://stackoverflow.com/questions/51702275/click-button-using-javascript-speech-recognition-tampermonkey
// #grant none
// ==/UserScript==
document.head.innerHTML = '';
document.body.innerHTML = `
<div class="radio-container" style="height:1000px">
<div class="col-6">
<button id="radio0">None</button>
</div>
<div class="col-6">
<button id="radio1">One</button>
</div>
<div class="col-6">
<button id="radio2">Two</button>
</div>
<div class="col-6">
<button id="radio3">Three +</button>
</div>
</div>
`;
document.addEventListener('click', ({ target }) => {
if (!target.matches('button')) return;
console.log('Click detected: ' + target.outerHTML);
});
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition
var SpeechGrammarList = SpeechGrammarList || webkitSpeechGrammarList
var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent
var buttonNames = [ 'None', '1', '2', '3'];
var recognition = new SpeechRecognition();
document.body.onclick = function(e) {
if (e.target.matches('button')) return;
recognition.start();
console.log('Listening');
}
recognition.onresult = function(event) {
var last = event.results.length - 1;
var speechText = event.results[last][0].transcript;
console.log('Heard ' + speechText);
const foundButtonIndex = buttonNames.findIndex(buttonName => buttonName === speechText);
console.log(foundButtonIndex);
if (foundButtonIndex !== -1) document.querySelectorAll('button')[foundButtonIndex].click();
}
recognition.onspeechend = function() {
recognition.stop();
}
recognition.onnomatch = function(event) {
console.log('Not recognized')
}
recognition.onerror = function(event) {
console.log('Error ' + event.error);
}
For a more generic solution when the buttons can have any text inside them, and you want to be able to speak the button text and have the appropriate button clicked, you might querySelectorAll all buttons on pageload, map them to an object with keys corresponding to their text content, and then click buttonObj[speechText] if it exists.
You could select the div by checking the innerHTML of the div with the input you get from speech to text. To match the element you could use the answers from this link Javascript .querySelector find <div> by innerTEXT

Live HTML/CSS preview from a div tag and not a text area tag

I want to create a live HTML/CSS preview on a page.
But the code will not be given using textareas. The code is going to be fixed in the page (div).
I want the user to be able to alter the code and that will reflect on the live preview box.I have created the page where you can change parts of the script text (for amateurs). You can preview that here :
http://apolosiskos.co.uk/HTML-CSS-EDITOR/index3.html
01) The live preview does not work if I replace the textarea with a div.
02) Even if I use the textareas, the live preview does not work because in my HTML script I am using the codeand the xmp tags.
--> Snippet that works with a textarea but not with a div :
var wpcomment = document.getElementById('WPComment');
wpcomment.blur = wpcomment.onkeypress = function(){
document.getElementById('prevCom').innerHTML = this.value;
}
#prevCom
{
background:#124;
color:#fff;
min-width:20px;
min-height:50px;
font-size:25pt;
}
<textarea name="WPcomment" id="WPComment" placeholder="Add comments:">aaaaa</textarea>
<div id="prevCom"></div>
with no success. Is there any other addEventListener() method I can replace keyup with?
Yes, blur
If you would like to add keydown events on a <div> element, you can do the following:
First, you need to set the tabindex attribute:
<div id="a-div" tabindex="1" />
Then,
(2) Bind to keydown:
$('#mydiv').bind('keydown', function(event) {
//console.log(event.keyCode);
});
If you would like your div to be "focused" from the start:
$(function() {
$('#mydiv').focus();
});
You should place your preview code it within a function, then you can simply call it once the document has loaded.
https://jsfiddle.net/michaelvinall/4053oL1x/1/
The separate issue of your preview only rendering when you press the enter key, is because of the following if statement:
if(e.which == 13 && $(this).val().length > 0)
The e.which == 13 within your if is specifying that the code within the block should only be ran if the key pressed by the user was the enter key (code 13). By removing this portion of each if statement, any key pressed will execute the code within the block:
if($(this).val().length > 0)
Your function is call when keyup is trigger, but no after page load.
You must do it : Define function to call them when 2 different event are fired.
$(function() {
function GetHtml(){
var html = $('.html').val();
return html;
}
function GetCss(){
var Css = $('.css').val();
return Css;
}
var previewRendering = function(){
console.log('kikou');
var targetp = $('#previewTarget')[0].contentWindow.document;
targetp.open();
targetp.close();
var html = GetHtml();
var css = GetCss();
$('body',targetp).append(html);
$('head', targetp).append('<style>' + css + '</style>');
};
$('.innerbox').on("keyup",function(){
previewRendering();
});
$(document).ready(function() {
previewRendering();
});
});
This code can not work because load event is only compatible with this list of HTML tags: body, frame, iframe, img, input type="image", link, script, style
$('.innerbox').load(function()

keyboard events on [non-] contenteditable HTML5 elements

I'm coding the MELT monitor (free software, alpha stage, related to the GCC MELT domain specific language to customize GCC). It is using libonion to behave as a specialized web server, and I want it to become a syntax directed editor of some DSL I am designing. I'm speaking of commit 97d60053 if that matters. You could run it as ./monimelt -Dweb,run -W localhost.localdomain:8086 then open http://localhost.localdomain:8086/microedit.html in your browser.
I am emitting (thru file webroot/microedit.html)
<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>*</div>
<hr/>
then some AJAX trickery is filling that #micredit_id element with something containing stuff similar to:
<dd class='statval_cl' data-forattr='notice'> ▵
<span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>comment</span></span>
(“<span class='momstring_cl'>some simple notice</span>”
<span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>web_state</span></span>
(<span class='momnumber_cl'>2</span>)</span>
<span class='momitemval_cl'>hashset</span>
<span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
<span class='momitemref_cl'>the_agenda</span>}</span>
<span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
<span class='momitemref_cl empty_cl'>~</span>
<span class='momitemref_cl'>the_system</span>]</span>)</span> ;</dd>
Now, I want every <span> of class momitemref_cl to be sensitive to some keyboard (and perhaps mouse) events. However, the contenteditable elements can be edited by many user actions (I don't even understand what is the entire list of such user actions....) and I only want these span elements to be responsive to a defined and restricted set of key presses (alphanumerical & space) and not be able to be user-changed otherwise (e.g. no punctuation characters, no "cut", no "paste", no backspace, no tab, etc...).
Is there a complete list of events (or user actions) that a contenteditable='true' element can get and is reacting to?
How to disable most of these events or user actions (on keyboard & mouse) and react only to some (well defined) keyboard events?
Apparently, a <span> element in a non-contenteditable element cannot get any keyboard user action (because it cannot get the focus)...
I am targeting only recent HTML5 browsers, such as Firefox 38 or 42, or Chrome 47 etc... on Debian/Linux/x86-64 if that matters (so I really don't care about IE9)
PS. this is a related question, but not the same one.
PS2: Found the why contenteditable is terrible blog page. Makes me almost cry... Also read about faking an editable control in browser Javascript (for CodeMirror). See also W3C draft internal document on Editing Explainer and edit events draft. Both W3C things are work in progress. W3C TR on UI events is still (nov.2015) a working draft. See also http://jsfiddle.net/8j6jea6p/ (which behaves differently in Chrome 46 and in Firefox 42 or 43 beta)
PS3: perhaps a contenteditable is after all a bad idea. I am (sadly) considering using a canvas (à la carota) and doing all the editing & drawing by hand-written javascript...
addenda:
(November 26th 2015)
By discussing privately with some Mozilla persons, I understood that:
contenteditable is messy (so I rather avoid it), and is not anymore worked much in Firefox (for instance, even recent beta Firefox don't know about contenteditable='events', see nsGenericHTMLElement.h file)
event bubbling and capturing matters a big lot
a normal <div> (or <span>) can be made focusable by giving it a tabindex property
text selection API could be useful (but has some recent bugs)
So I probably don't need contenteditable
You can do as such:
function validateInput(usrAct){
swich(usrAct){
case "paste":
// do something when pasted
break;
case "keydown":
// dosomething on keydown
break;
default:
//do something on default
break;
}
}
document.querySelectorAll('.momitemref_cl').addEventListener('input', function(e){
validateInput(e.type)
}, false);
This snippet could be what you are looking for, making span.momitemref_cl elements focusable but not tabbable and setting has contenteditable. But as i'm testing it on chrome, contenteditable inside any container with attribute contenteditable set to true, don't fire any keyboard event. So the trick could be on focus to set any container to not editable (and switch back on blur).
See e.g: (keypress and keydown events are both binded to handle some specific cases where keypress or keydown wouldn't be fired on specifc keys)
NOTE: has you seem to populate DIV with content dynamically, you could delegate it or bind event (& set tabindex attribute if changing it in HTML markup not a solution) once ajax request has completed.
$('#microedit_id .momitemref_cl').attr('tabindex', -1).prop('contenteditable', true).on('focusin focusout', function(e) {
$(this).parents('[contenteditable]').prop('contenteditable', e.type === "focusout");
}).on('keypress keydown paste cut', function(e) {
if (/[a-zA-Z0-9 ]/.test(String.fromCharCode(e.which))) return;
return false;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>
<dd class='statval_cl' data-forattr='notice'>▵ <span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>comment</span></span>(“<span class='momstring_cl'>some simple notice</span>” <span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>web_state</span></span>(<span class='momnumber_cl'>2</span>)</span> <span class='momitemval_cl'>hashset</span>
<span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
<span class='momitemref_cl'>the_agenda</span>}</span> <span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
<span class='momitemref_cl empty_cl'>~</span>
<span class='momitemref_cl'>the_system</span>]</span>)</span>;</dd>
</div>
<hr/>
First, HTMLElements become contentEditableElements when you set their contentEditable attribute to true.
Now, the best way to do your parsing IMO is to listen to the inputEvent and check your element's textContent:
s.addEventListener('input', validate, false);
function validate(evt) {
var badValues = ['bad', 'content'];
var span = this;
badValues.forEach(function(v) {
if (span.textContent.indexOf(v) > -1) {
// that's bad m..key
span.textContent = span.textContent.split(v).join('');
}
});
};
<span id="s" contentEditable="true">Hello</span>
Unfortunately, the input event isn't widely supported
so you may need to add onkeydown and onpasteand maybe onclick event handlers to catch non-supporting browsers (a.k.a IE).
Edit:
(Handles only the spans with the said class. Also handles the case, where you could go back from another span into a previous one and could delete it. Incorporates the idea of #AWolff for switching the contenteditable attribute on focus)
The overall idea remains the same as that of the previous version.
Fiddle: http://jsfiddle.net/abhitalks/gb0mbwLu/
Snippet:
var div = document.getElementById('microedit_id'),
spans = document.querySelectorAll('#microedit_id .momitemref_cl'),
commands = ['paste', 'cut'],
// whitelist is the keycodes for keypress event
whitelist = [{'range': true, 'start': '97', 'end': '122'}, // lower-case
{'range': true, 'start': '65', 'end': '90'}, // upper-case
{'range': true, 'start': '48', 'end': '57' } // numbers
],
// specialkeys is the keycodes for keydown event
specialKeys = [8, 9, 13, 46] // backspace, tab, enter, delete
;
div.addEventListener('keydown', handleFromOutside, false);
[].forEach.call(spans, function(span) {
span.setAttribute('contenteditable', true);
span.setAttribute('tabindex', '-1');
span.addEventListener('focus', handleFocus, false);
span.addEventListener('blur', handleBlur, false);
commands.forEach(function(cmd) {
span.addEventListener(cmd, function(e) {
e.preventDefault(); return false;
});
});
span.addEventListener('keypress', handlePress, false);
span.addEventListener('keydown', handleDown, false);
});
function handleFocus(e) { div.setAttribute('contenteditable', false); }
function handleBlur(e) { div.setAttribute('contenteditable', true); }
function handlePress(e) {
var allowed = false, key = e.keyCode;
whitelist.forEach(function(range) {
if (key && (key != '') && (range.start <= key) && (key <= range.end)) {
allowed = true;
}
});
if (! allowed) { e.preventDefault(); return false; }
}
function handleDown(e) {
var allowed = false, key = e.keyCode;
specialKeys.forEach(function(spl) {
if (key && (spl == key)) { e.preventDefault(); return false; }
});
}
function handleFromOutside(e) {
var key = e.keyCode, node = window.getSelection().anchorNode, prev, next;
node = (node.nodeType == 3 ? node.parentNode : node)
prev = node.previousSibling; next = node.nextSibling;
if (prev || next) {
if (node.className == 'momitemref_cl') {
if (specialKeys.indexOf(key) >= 0) {
e.preventDefault(); return false;
}
}
}
}
<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>
<dd class='statval_cl' data-forattr='notice'> ▵
<span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>comment</span></span>
(“<span class='momstring_cl'>some simple notice</span>”
<span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>web_state</span></span>
(<span class='momnumber_cl'>2</span>)</span>
<span class='momitemval_cl'>hashset</span>
<span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
<span class='momitemref_cl'>the_agenda</span>}</span>
<span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
<span class='momitemref_cl empty_cl'>~</span>
<span class='momitemref_cl'>the_system</span>]</span>)</span> ;</dd>
</div>
<hr/>
Apart from the usual handling of events on the spans and preventing / allowing the keys and/or commands from the white-lists and balck-lists; what this code does is to also check if the cursor or editing is currently being done on other spans which are not constrained. When selecting or moving using arrow keys from there into the target spans, we dis-allow special keys to prevent deletion etc.
function handleFromOutside(e) {
var key = e.keyCode, node = window.getSelection().anchorNode, prev, next;
node = (node.nodeType == 3 ? node.parentNode : node)
prev = node.previousSibling; next = node.nextSibling;
if (prev || next) {
if (node.className == 'momitemref_cl') {
if (specialKeys.indexOf(key) >= 0) {
e.preventDefault(); return false;
}
}
}
}
I could not get much time, and thus one problem still remains. And, that is to disallow commands as well like cut and paste while moving into the target spans from outside.
Older version for reference only:
You could maintain a white-list (or blacklist if number of commands allowed are higher) of all keystrokes that you want to allow. Similarly, also maintain a dictionary of all events that you want to block.
Then wire up the commands on your div and use event.preventDefault() to reject that action. Next up, wire up the keydown event and use the whitelist to allow all keystrokes that are in the permissible ranges as defined above:
In the example below only numbers and alphabets will be allowed as per the first range and arrow keys (along with pageup/down and space) will be allowed as per the second range. Rest all actions are blocked / rejected.
You can then extend it further to your use-case. Try it out in the demo below.
Fiddle: http://jsfiddle.net/abhitalks/re7ucgra/
Snippet:
var div = document.getElementById('microedit_id'),
spans = document.querySelectorAll('#microedit_id span'),
commands = ['paste'],
whitelist = [ {'start': 48, 'end': 90}, {'start': 32, 'end': 40 }, ]
;
commands.forEach(function(cmd) {
div.addEventListener(cmd, function(e) {
e.preventDefault(); return false;
});
});
div.addEventListener('keydown', handleKeys, false);
function handleKeys(e) {
var allowed = false;
whitelist.forEach(function(range) {
if ((range.start <= e.keyCode) && (e.keyCode <= range.end)) {
allowed = true;
}
});
if (! allowed) { e.preventDefault(); return false; }
};
<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>
<dd class='statval_cl' data-forattr='notice'> ▵
<span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>comment</span></span>
(“<span class='momstring_cl'>some simple notice</span>”
<span class='momnode_cl'>*<span class='momconn_cl'>
<span class='momitemref_cl'>web_state</span></span>
(<span class='momnumber_cl'>2</span>)</span>
<span class='momitemval_cl'>hashset</span>
<span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
<span class='momitemref_cl'>the_agenda</span>}</span>
<span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
<span class='momitemref_cl empty_cl'>~</span>
<span class='momitemref_cl'>the_system</span>]</span>)</span> ;</dd>
</div>
<hr/>
Edited, to fix the problem of not capturing special keys especially when shift was pressed and the same keyCode is generated for keypress. Added, keydown for handling special keys.
Note: This is assuming that to happen on the entire div. As I see in the question, there are only spans and that too nested ones. There are no other elements. If there are other elements involved and you want to exempt those, then you will need to bind the event to those elements only. This is because, the events on children are captured by the parent when parent is contenteditable and not fired on the children.
A straightforward solution to your problem would be to listen on the keydown event fired by the inner-most element and act accordingly. An exemplary code snippet can be found below:
HTML:
<div class="momitemref_cl" contenteditable="true">Foo Bar</div>
<input class="not-momitemref_cl"/>
<input class="momitemref_cl"/>
JS:
document.querySelectorAll('.momitemref_cl').forEach((el) => {
el.addEventListener('keydown', validateInput);
el.addEventListener('cut', e => e.preventDefault());
el.addEventListener('copy', e => e.preventDefault());
el.addEventListener('paste', e => e.preventDefault());
});
function validateInput(userAction) {
console.log(userAction);
if (userAction.ctrlKey) {
userAction.preventDefault();
return false;
}
let code = (userAction.keyCode ? userAction.keyCode : userAction.which);
if ((48 <= code && code <= 57 && !userAction.shiftKey) || (65 <= code && code <= 90) || (97 <= code && code <= 122) || code === 32) {
console.log(`Allowing keypress with code: ${code}`);
return true;
}
console.log(`Preventing keypress with code: ${code}`);
userAction.preventDefault();
return false;
}
This works for both <input> elements as well as elements with the contenteditable attribute set to true.
JS Fiddle: https://jsfiddle.net/rsjw3c87/22/
EDIT: Also added additional checks to prevent right-click & copy/cut/paste. Disabling right-click directly via the contextmenu event will not work as certain browsers & OSes disallow you from disabling that specific event.

jQuery highlight plugin cancels text selection, making copy impossible while also rendering links unclickable

I'm using the jQuery Highlight plugin to select some text on a web page.
I've hooked up selecting and deselecting with mouse events:
document.addEventListener('mouseup', doSelect);
document.addEventListener('mousedown', doDeselect);
The functions are:
function doSelect() {
var selectionRange = window.getSelection();
var selection = selectionRange.toString();
if (selection.trim().length > 0) {
$('body').highlight(selection);
}
}
function doDeselect() {
$('body').unhighlight();
}
Short and easy. The library searches for the selected text and wraps each occurrence in a <span> and so the text stands out.
It's working great, but I have two issues with how it behaves.
The problem is that once the span elements are applied, I cannot click hyperlinks (the ones that were found/selected), they don't react to clicks (I have to deselect the text first).
Once the span elements are added, the original selection is somehow lost, i.e. I cannot copy what I selected with CTRL+C.
These issues can be seen in this jsfiddle.
Why is this happening?
The code
The working demo is available here: jsfiddle
JavaScript
var $body = $('body');
var $copyArea = $('#copyArea');
document.addEventListener('mouseup', doSelect);
document.addEventListener('mousedown', doDeselect);
document.addEventListener('keydown', keyPressHandler);
function keyPressHandler(e) {
if(e.ctrlKey && e.keyCode == 67) {
$copyArea.focus().select();
}
}
function doSelect() {
var selectionRange = window.getSelection();
var selection = selectionRange.toString();
if (selection.trim().length > 0) {
$copyArea.val(selection);
$body.highlight(selection);
}
}
function doDeselect(e) {
var elem = $(e.target).parents('a');
if(elem.length == 0) {
$copyArea.val('');
$body.unhighlight();
}
}
HTML
Sample text to select.
<br/>Sample text to select.
<br/>Sample text to select.
<br/>google.com
google.com
<a href="http://google.com" target="_blank">
<span>
<span>google.com</span>
</span>
</a>
<textarea id="copyArea"></textarea>
CSS
.highlight {
background-color: #FFFF88;
}
#copyArea {
position:fixed;
top:-999px;
height:0px;
}
Part 1 - Clicking through the selection
Presumably, the reason clicking on a highlighted link doesn't work is because the process that disables the highlighting kicks in first and cancels the click.
To bypass that, we implement a condition that checks if the target element of the mousedown event has an a element as ancestor. If that is true, we simply do not execute $body.unhighlight();, allowing the click to pass through and open the link.
function doDeselect(e) {
var elem = $(e.target).parents('a');
if(elem.length == 0) {
$copyArea.val('');
$body.unhighlight();
}
}
Part 2 - Copying the selection
Presumably, again, the reason the selection is lost is because the document is modified by the highlighting, which introduces elements into the DOM.
My first idea was to reapply the selection after the modification was done. This became annoying and I went in a different direction, which allowed me to stumble upon this:
The Definitive Guide to Copying and Pasting in JavaScript
This offered a simple and efficient idea: using an hidden element that could contain selectable text.
Therefore, to allow copying the selected text that we highlighted despite having lost the original selection:
We add a hidden textarea element to our document.
<textarea id="copyArea"></textarea>
We get a reference to that element.
var $copyArea = $('#copyArea');
We add an event handler for the keydown event.
document.addEventListener('keydown', keyPressHandler);
We add the event handler.
function keyPressHandler(e) {
if(e.ctrlKey && e.keyCode == 67) {
$copyArea.focus().select();
}
}
We modify doSelect() to add some logic that will set the selection as the value of the textarea element, in the form of $copyArea.val(selection);.
function doSelect() {
var selectionRange = window.getSelection();
var selection = selectionRange.toString();
if (selection.trim().length > 0) {
$copyArea.val(selection);
$body.highlight(selection);
}
}
What does the handler do ? it captures the combination CTRL+C and focuses on the text in the hidden textarea, which ends up being copied by the keyboard command we just issued.

Categories

Resources