The coder's fantasy
I created a simple user script to act very quickly on the text I selected. It goes like this:
I select a word, a website (doesn't have to be a link), or a phrase from, let's say, a p element
When I press the trigger key, the algorithm will try to figure out if it's a website or text. It will open a tab: if it's a website, that's what it'll load; if it's text, it will google it.
The problem shows its ugly head
It works great except when I'm editing text. If I'm editing something I've written in a textarea/input it will fire, potentially losing what I wrote. Fortunately, there's usually cache, or even the site will warn me for having unsaved changes, which saves me from losing whatever I wrote. But it's something to fix.
The challenge
The userscript should only run on text that can't be edited. You'd think it is as easy as not calling the function if the selected text is within a textarea. But there are many ways to display editable content without using classical elements. The "best" filter I've found is to check for document.activeElement.isContentEditable. However, in this very box, that returns false. This is a textarea element, so I can add it to the filter, and I can do so with a few more I can think of. But apart from being an ugly solution, it is not foolproof.
Besides adding a "did you run me by accident?" prompt, is there a better way to do this?
Edit: my current code
If I understand correctly .... here is an example of how to go about it.
if (['TEXTAREA', 'INPUT'].includes(document.activeElement.nodeName)) {
// it is in textarea, input
}
else if (document.activeElement.isContentEditable) {
// it is in contentEditable element
}
else {
// not above
}
Above is not the only method, e.g. the following using window.getSelection():
const sel = window.getSelection();
const text = sel.toString();
if (!text.trim()) {
// there is no selection
// or selection is white-space
// or selection is in textarea, input
}
else if (document.activeElement.isContentEditable) {
// it is in contentEditable element
}
else {
// not above
}
Related
I'm trying to create a bookmarklet which when clicked inserts a specific text into the currently active text control. Don't judge.
So this is my code so far:
(function()
{
var t="<<< VALUE >>>";
var a=document.activeElement;
if(!a) alert("No active element found.");
if(!!a)
if(a.nodeName=="TEXTAREA"||a.nodeName=="INPUT")
if(!a.value) a.value=t;
else a.value+=t;
else if(a.nodeName=="DIV"&&a.isContentEditable==true)
if(!a.textContent) a.textContent=t;
else a.textContent+=t;
}
)();
It works for textareas and inputs, but not for editable divs. It does change the text, but the control is broken afterwards in a way that I can't use backspace or delete anymore.
How do I set the content of such a div without braking anything?
The best place to test this is supposedly facebook. Put the focus on the text control for a new post, then execute the function. You'll notice that you can't use backspace and delete anymore.
// EDIT
After some comments and jsfiddle experiments it looks like this is indeed a Facebook related problem. I've updated the title accordingly. Still baffles me why this is the behavior and how to overcome it.
I have created a Google Chrome extension to allow users to select text in a component. This works great for most sites. However, Facebook handles its status updates differently. It seems that even though you are filling in what seems to be a single text box, it is actually using a div > div > span > span construct for every single line in this text box. I have no idea why they chose to do this but it makes replacing multiple lines of text much more complex.
Is there a way to select multiple lines (or even contiguous portions of multiple lines) of text in a Facebook status update and replace the data?
The relevant portion of my code looks like this:
function replace_text(language){
let selection = window.getSelection();
string = selection.toString();
/* This section contains code that uses string.replace to replace characters in the string. */
document.execCommand("insertText", false, string);
}
Based on the way my code works now, if I replace text on a single line I have no problems. But, if I replace text that spans multiple lines I end up with a blank unusable input box. Undoubtedly it is because it is removing portions of the html code. How can I fix my code so that the replacement process works properly not only for other sites but also for Facebook?
As of this moment, the one common theme among all status updates (and comments) are that their texts reside within a single or set of span elements with the attribute data-text set to true. So let's target those:
document.querySelectorAll("span[data-text='true']");
For me, I've typed into the status field 3 lines and comment field 1 line of dummy text. So when I execute the above code into the console it returns an array of those four cumulative lines:
>>> (4) [span, span, span, span]
With that array, I can use the Array.prototype.forEach() method to iterate through the spans and replace the innerText:
document.querySelectorAll("span[data-text='true']").forEach(function(element) {
element.innerText = element.innerText.replace('Lorem ipsum','Hello world');
});
However, it is important to note that these changes are being made in the HTML itself and Facebook doesn't store all of its data directly in the HTML. Therefore it can cause undesirable events to occur when you type text into a field, unfocus, change the text in the field, and refocus that field. When you refocus I believe it grabs data of what the text was, before you unfocused that field, from an ulterior source like React's Virtual DOM. To deter it from doing that, the changes either need to be made after clicking the field (real or simulate) or as the user is typing using some sort of MutationObserver (src).
I've been struggling with getting a field working properly. This field displays a lot of data, and the user wants to select and copy a large portion of it. The data is basically a big list and the user wants to select all entries below a certain point. The way that they achieve the selection is by highlighting a word or two in the first entry they want then pressing ctrl+shft+end to select everything to the bottom. This was working until a new feature on the page was added below the contents of the list. Now the hot key select also selects the contents of the rest of the page.
The current implementation is simply :
<div id='diff-contents'>[content here]</div>
<div id='trailing-content'>blah blah blah...</div>
I have tried a read-only input field:
<input id='diff-contents' value='[content here]' readonly/>
This works in Firefox to some extent however the contents contains HTML, and the input field show html literally, not rendered. In addition to that Chrome doesn't show a blinking caret and the hot keys do nothing, so the input field is sadly not viable for me in this situation.
How can I make a selectable field that maintains focus for the cursor and shows a blinking caret but is not editable using javascript, CSS, HTML, or JQuery?
Edit: jsfiddle example that should clarify a bit.
Look at these questions how to determine the current selection: Getting selected text in a browser, cross-platform
The next step is to create a new range which starts at the end tag of #diff-contents. With this information, you should be able to extend/modify the existing selection.
I suggest to either add a button to the UI or use JavaScript with a key-press handler to trigger this code.
With that, the correct amount of HTML should be selected. Users can then copy that into the clipboard with Ctrl+C.
#Aaron Digulla mentioned key listeners, and that got me thinking about simply stopping the events.
The diff-content element is still a div but it is set to editable. This gives both HTML rendering and a blinking caret.
$(this).keydown(function (event) {
if (document.activeElement.id == 'diff-content') {
if (!allowedKeys(event.keyCode)) {
//The only other key presses that should be processed are ctrl+c (keycode 67) and ctrl+a (65)
if (!event.ctrlKey || !(event.keyCode == 67 || event.keyCode == 65)) {
event.preventDefault();
}
}
}
});
The javascript adds a keydown event listener to the entire page. This is necessary since if you just add it to the element, the event has already propagated through the rest of the page and will still be processed, and this was causing funny issues for me. Next we check if it's the diff-content that is active since we want other input elements to still operate normally. Then we check if the key event is an allowed key (tab, home, end, arrows). And finally, check for ctrl+c and ctrl+a and allow those too. I tried event.stopPropogation() and event.stopImmediatePropogation(), and neither of those worked, but preventDefault did.
Lastly, I added style="outline-style:none" to the element so that the blue border would not appear when the element has focus.
The only issue that I have yet to resolve is that since it is editable, the browser still allows you to select and then right click to either cut or paste, which will allow you to alter the text.
Here is the final jsfiddle for what I am using: http://jsfiddle.net/wh3nzmj8/12/
How to hide cursor in asp.net textbox using JavaScript? I don't want see blink thing in textbox.
Please don't do this, you're breaking the user's expectations, the cursor is there for a reason, when the user types or hits delete, backspace, etc...they want to know where it's going to happen at.
If you want to edit a textbox and then cause focus to leave, that's a different matter, just focus another element:
document.getElementById("otherElement").focus();
Here's something you can try.
disclaimer -- as others have mentioned, it sounds like you're headed for an accessibility nightmare. You (or your client) still might have their reasons for wanting this behavior, though. This is a terrible hack, but it might give the results you want.
Hack
Have two text boxes, a real textbox that the user never sees but enters the text into and a dummy text box that displays the text. When the user clicks the dummy textbox, the real textbox should be focused. When the user edits the contents of the real textbox, the dummy textbox should be updated.
Example
Test it out here - http://jsbin.com/ihobe4/edit
function makeCaretInvisible(textboxId) {
var inputBox = document.getElementById(textboxId);
var outputBox = inputBox.cloneNode(true);
outputBox.id=outputBox.name='';
outputBox.onclick=function(){
inputBox.setSelectionRange(outputBox.selectionStart, outputBox.selectionEnd);
};
inputBox.onkeyup=function(){
outputBox.value=inputBox.value;
};
inputBox.style.position='absolute';
inputBox.style.top='-10000px';
inputBox.parentElement.insertBefore(outputBox, inputBox);
}
I recall seeing a web site that when you highlighted/selected text on their page, it produced a small balloon just to the upper right, which was clickable and would perform some action when clicked. I have an application where this type of interface would be appropriate for my users. But... I haven't any idea what to call this widget nor where to start from scratch.
I think you may be referring to a tooltip. These are easily done with javascript; here are just a couple of options:
http://www.nickstakenburg.com/projects/prototip2/
http://craigsworks.com/projects/qtip/
You would use a javascript event to trigger the popup when the user selects some text. jQuery comes with some pre-rolled event handlers that will probably accomplish what you are looking for:
http://docs.jquery.com/Events/select
Noah
(Quick & Dirty) - Use this as a starting point. I'm going to assume that you're using jQuery to provide a cool tooltip to the user upon text selection, instead of the alert the code does. :p
function getSelection()
{
if(document.selection)
{
return document.selection.createRange().text;
}
else
{
return window.getSelection();
}
}
$(document).mouseup(function() { alert(getSelection()); });
This subscribes to the mouseup function and will alert whatever the user has selected, if anything. Naturally you'd have to flesh this out so that you check if the text is empty, and if not spawn a tooltip or do whatever you'd like with the text.