Is it possible to select text (as if the user dragged his mouse across an HTML word in the browser) with the help of Javascript?
I have some JS code that searches an HTML table for a user's string input. I would then like for this code to select/focus on this word if it is found.
var targetTable = document.getElementById("accountTable");
for (var rowIndex = 1 + searchStart; rowIndex < targetTable.rows.length; rowIndex++)
{
var rowData = '';
rowData = targetTable.rows.item(rowIndex).cells.item(0).textContent;
if (rowData.indexOf(str) != -1)
{
//select word and focus on it in user's browser?
}
}
You could replace the found string with the same string but inside of a span with a specific class.
For Example if the search term was "alpha":
<td>The alphabet is long.</td>
<td>The <span class="highlighted">alpha</span>bet is long.</td>
An when the search changes you would have to revert all highlighted spans back to plain text.
If you want to actually select using the browsers selection there is a limited way discussed here: Programmatically select text in a contenteditable HTML element?
It looks as if you want to use the Selection object. At the basic level you can just set its anchor and focus node and offset, or you can also create a multi-range selection.
In order to highlight an element by hovering, the easiest way to do this would be to use css.
Example:
You have html code:
<p id="test">Word</p>
CSS File:
#word:hover{ color: #FFFF00; }
That will color the text by hovering the element.
Edit, if you had multiple words... You would want to separate them in an element per word. For example:
<text class="word">each</text><text class="word">word</text><text class="word">is</text><text class="word">selectable</text>
.word:hover{ color: #FFFF00; }
Here's the JS Fiddle: http://jsfiddle.net/braTe/
Related
I've been working on how to allow visitors to select multiple sections of text wrapped in <p> tags to highlight them, and then click a button to remove all highlights.
Selected text highlights fine with added classList to created <span> elements.
If user selects some text that overlaps highlighted text,
Uncaught DOMException: Failed to execute 'surroundContents' on 'Range': The Range has partially selected a non-Text node.
Removing the classList produces fragmented text with empty spans.
I have tried to remove the child elements from the parent, but that removes the original text as well as the tag element.
I think the DOMException is because of the created span tag, but I'm not sure how to remove them when a new selection overlaps.
I have looked at many SO articles, but they seem to focus on JQuery, which I am not using. There is still a lot I do not understand about JavaScript, so MDN has helped a bit, but I sometimes struggle to apply the concepts.
// HIGHLIGHT SELECTIONS
const elementToHighlight = document.getElementById('higlight-this');
elementToHighlight.addEventListener('mouseup', selection);
function selection() {
let element = document.createElement('span');
element.classList.add('hl');
window.getSelection().getRangeAt(0).surroundContents(element);
}
// REMOVE hl CLASS
const removeClassListFromAll = document.getElementById('remove');
removeClassListFromAll.addEventListener('click', () => {
let grabHighlighted = document.getElementsByClassName('hl');
while (grabHighlighted.length) {
grabHighlighted[0].classList.remove('hl');
// grabHighlighted[0].parentElement.removeChild.(grabHighlighted[0]);
}
});
.hl {
background-color: yellow;
}
<section id="higlight-this">
<p>Some text to test with. Make it look good with highlights!.<br>
If you don't, It won't be useful for you.</p>
</section>
<button id="remove">remove</button>
the main problem is your selection container is not right try to identify your selection using console.log() and add if else or switch like :
if(window.getSelection().getRangeAt(0).commonAncestorContainer.nodeName == "P")
window.getSelection().getRangeAt(0).surroundContents(element);
else { //this case when i select from top to bottom with the button
window.getSelection().getRangeAt(0).commonAncestorContainer.querySelector("#higlight-this").querySelector("p").getRangeAt(0).surroundContents(element);
}
I'm trying to write a basic text editor using contenteditable. In this MCVE, it only has one function, which is that selected text is given a red highlight.†
The code I'm using is here:
function createSpan() {
let selection = document.getSelection();
let range = selection.getRangeAt(0);
let element = document.createElement("span");
element.className = "inline-equation";
range.surroundContents(element);
let newRange = new Range();
newRange.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(newRange);
}
$("button").click(createSpan)
.inline-equation {
background-color: red;
display: inline-block;
}
#editor {
width: 100%;
height: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>
Create Span
</button>
<div id="editor" contenteditable="true">
This is a contenteditable area.
</div>
I'm having trouble with the idea that the user may move out of the highlight area and continue typing in unformatted text. To experience this issue:
Run the Stack Snippet above
Select the text from somewhere in the middle til the end, then click Create Span
Type some new text at the end of the line
This new text has the red highlight too, even if you attempt to move out of the inserted span by pressing the right arrow key.
I'd still like to give the user the option to append new text which is formatted, but then also allow the user to navigate out of the span so that they may continue to type normal text.
In other words, the span should act as a completely separate editable object which may be moved into or out of. This includes the ability to move out of the span even if it's at the end of the document, so that the user can continue typing in non-formatted text.
The best example I am able to give of what I'd like is Microsoft Word's inline equations. Notice how, in the GIF below, the equation acts as a separate object, which I may navigate out of so that I can type normal text to the right of it. The is how I'd like my span to act.
I've tried replacing the span with a div with inline-block formatting to see if that affected the behaviour, but it didn't. How should I achieve the effect I'm looking for?
† In the actual use case, the 'highlight' actually denotes LaTeX-formatted mathematics which are rendered later. I'm writing what is essentially an editor for a proprietary markup language which supports inline LaTeX.
The issue is that you need something editable at end for this to work. There are lot of existing SO thread for the same. You can see below
Why Is My Contenteditable Cursor Jumping to the End in Chrome?
contenteditable put caret outside inserted span
contenteditable with nested span. Who has the focus?
Focusing on nested contenteditable element
Combining knowledge from above thread the simplest thing I could think of was adding below keyup handler
$("#editor").on('keyup',(e) => {
var editor = $("#editor").get(0)
var cn = editor.childNodes;
if (cn[cn.length - 1].nodeType !== Node.TEXT_NODE)
{
empty = document.createTextNode( '\uFEFF' );
editor.appendChild(empty);
}
if (cn[0].nodeType !== Node.TEXT_NODE)
{
empty = document.createTextNode( '\uFEFF' );
editor.prepend(empty);
}
})
Which makes sure there is one text node to step out of the div. You can do the same thing for the starting div if you want. Below is JSFiddle for the same
https://jsfiddle.net/4tcLr0qa/1/
I'm developing an Extension for Google Chrome. It has a text highlighting facility when user select a certain text area.
When it comes to a single tag I can just use a <span> tag inside that particular tag.
so far what i have done is below.
var divs = document.getElementsByTagName("p");
for(var d in divs) {
// highlight a part of a <p> tag
divs[d].addEventListener('mouseup',function(){var str= document.getSelection() ;alert(str);
this.innerHTML =this.innerHTML.replace(str,'<span style="background-color: '+'yellow'+' ">'+str+'</span>');
});
}
But if user select several tag areas (contiguous tags) how can I do it. I can't put a single <span> tag there. I have to use <span> tag for each of those selected tags.
1) how to detect and get user selected tags. And the starting points and end points.?
2) how to change back color of them at once.?
3) do i want to add listeners , If so how should i do that ?
Any help will be appreciate .
I have understood the following:
Your extension changes the background color of the text (of the underlying paragraph-nodes) the user has selected in the webpage.
Currently your implementation adds an EventListener to inject a span tag inside a paragraph-tag if the Event mouseup fires.
You would like to know how to highlight the selected area (the nodes) if the user has selected more than a single leaf-node?
Have i understood you correctly?
As far as i have understood your question you could do the following.
If multiple nodes are selected you could set the background-color on each node within the selected text:
Based on this html:
<h2>How to set background color of selected text</h2>
<div>
<h3 id="h">This is a headline</h3>
<p id="p1">This is the first paragraph</p>
<p id="p2">This is the second paragraph</p>
<button id="b1" onclick="colorSelectedNodes()">color selected nodes</button>
</div>
You could use the following Javascript:
function colorSelectedNodes(){
var ds= document.getSelection();
var elements = []
// only start node if selection occured from start to end
// (from top left to right bottom)
var anchorNode = ds.anchorNode.parentNode;
var elementSibling = anchorNode.nextElementSibling;
var focusNode = ds.focusNode.parentNode; // endnode
while(elementSibling.nextElementSibling){
console.log("elementSibling", elementSibling);
elementSibling.style.backgroundColor = "#00FFFF";
elementSibling.style.fontWeight = "bold";
elementSibling.style.color = "blue";
elementSibling = elementSibling.nextElementSibling;
}
// just to show focus and end node
anchorNode.style.backgroundColor = "lightgreen";
focusNode.style.backgroundColor = "yellow";
console.log("anchorNode", anchorNode);
console.log("focusNode", focusNode);
}
There are a few thinks to take care of
if the user selects from end to start anchorNode and focusNode are switched,
above i do not check if the selection is within a single node (for example inside a span) or if the selection contains many nodes
according to another question chrome (webkit) changed the api of document.getSelection();
cross browser is a bumby ride; but you are looking for a chrome only solution this should not be to relevant for you
I hope my answer gets you going in the right direction. If you have more questions feel free to ask.
Hi here is my total work to search a string in HTML and highlight it if it is found in document:
The problem is here
var SearchItems = text.split(/\r\n|\r|\n/);
var replaced = body.html();
for(var i=0;i<SearchItems.length;i++)
{
var tempRep= '<span class="highlight" style="background-color: yellow">';
tempRep = tempRep + SearchItems[i];
tempRep = tempRep + '</span>';
replaced = replaced.replace(SearchItems[i],tempRep); // It is trying to match along with html tags...
// As the <b> tags will not be there in search text, it is not matching...
}
$("body").html(replaced);
The HTML I'm using is as follows;
<div>
The clipboardData object is reserved for editing actions performed through the Edit menu, shortcut menus, and shortcut keys. It transfers information using the system clipboard, and retains it until data from the next editing operation replace s it. This form of data transfer is particularly suited to multiple pastes of the same data.
<br><br>
This object is available in script as of <b>Microsoft Internet Explorer 5.</b>
</div>
<div class='b'></div>
If I search for a page which is pure or without any html tags it will match. However, if I have any tags in HTML this will not work.. Because I am taking body html() text as the target text. It is exactly trying to match along with html tags..
In fiddle second paragraph will not match.
First of all, to ignore the HTML tags of the element to look within, use the .text() method.
Secondly, in your fiddle, it wasn't working because you weren't calling the SearchQueue function on load.
Try this amended fiddle
Is it possible to select the text (i.e. have it highlighted so that it can be copy+pasted) of every cell in one vertical column of an HTML table.
Is there a JavaScript method, or perhaps an equivalent in some browsers to the Alt-Click-Drag shortcut used in many text editors?
Or is this impossible?
What you're looking for is called Range object (TextRange in IE).
Update:
Here's a working code to do what you're suggesting: http://jsfiddle.net/4BwGG/3/
While capturing cell contents, you can format them in any manner you wish. I'm simply appending a new line every time.
Note:
Works fine in FF 3 and above
IE (before 9) and Chrome do not support multiple selection.
Chrome doesn't highlight all cells (but captures all content). Same goes for IE9
IE 7 & 8 will throw an error.
An alternative is apply a CSS style that simulates highlighting on click of column header and loop through all cells to capture their content. Look and feel of this approach may differ from native selection's look (unless you somehow capture select event and alter the appearnce).
Then use jQuery copy plugin to copy them to clipboard.
Some code review tools implement this to allow copying & pasting code from one side of a side-by-side diff. I looked into how ReviewBoard pulls it off.
The gist is:
When a column selection begins, style the cells in all other columns with user-select: none (and its prefixed variants, if necessary). This creates the appearance of a column selection. The other columns are still secretly selected, so you have to...
Intercept the copy event and change its payload to reflect the contents of the selected column.
The ReviewBoard code to do this consists of this CSS and this JavaScript.
I pulled it out into a fairly minimal jsbin demo.
Here's the CSS to create the appearance of a single-column selection (you add the selecting-left class to the table when the left column is being selected, or selecting-right for the right):
.selecting-left td.right,
.selecting-left td.right *,
.selecting-right td.left,
.selecting-right td.left *,
user-select: none;
}
.selecting-left td.right::selection,
.selecting-left td.right *::selection,
.selecting-right td.left::selection,
.selecting-right td.left *::selection,
background: transparent;
}
Here's the JavaScript to intercept the copy event and plug in a single column's worth of data:
tableEl.addEventListener('copy', function(e) {
var clipboardData = e.clipboardData;
var text = getSelectedText();
clipboardData.setData('text', text);
e.preventDefault();
});
function getSelectedText() {
var sel = window.getSelection(),
range = sel.getRangeAt(0),
doc = range.cloneContents(),
nodes = doc.querySelectorAll('tr'),
text = '';
var idx = selectedColumnIdx; // 0 for left, 1 for right
if (nodes.length === 0) {
text = doc.textContent;
} else {
[].forEach.call(nodes, function(tr, i) {
var td = tr.cells[tr.cells.length == 1 ? 0 : idx];
text += (i ? '\n' : '') + td.textContent;
});
}
return text;
}
There's also some less interesting code to add the selecting-left and selecting-right classes at the start of a selection. This would require a bit more work to generalize to n-column tables.
This seems to work well in practice, but it's surprising how hard it is!
Here is a hack that doesn't involve javascript at all:
Step 1: open the inspector
For Chrome on mac, press command + option + J.
Step 2: select a random cell using the selector tool
For Chrome on mac, click the selector icon on the top left corner of the inspector to enter the selector mode.
Then click a random cell in the table.
Step 3: hide all cells by editing CSS
Click the New Style Rule button (see image below)
then enter this rule (you may want to modify it a little bit depending on your HTML)
tr td {
display: none; # hide all cells
}
Now all cells should have disappeared.
Step 4: display only the column that you want by editing CSS
Go ahead and add another rule above that one:
tr td:nth-child(2) { # replace 2 with the index of the column you want to copy. 2 means the second column
display: table-cell; # display that column
}
Now the column you want to copy from should have reappeared.
All the other columns should be invisible and can't be selected.
Step 5: just copy that column!
Note
You can restore the page by refreshing.
I find this work perfectly if you just want to select one column or two.
You could have a div which gets populated with the column data on click and apply a css class to give the columns the appearence of being selected
something like this:
var $mytable = $("#mytable"),
$copydiv = $("#copy_div");
$mytable.find("td").click(function(){
//get the column index
var $this = $(this),
index = $this.parent().children().index($this);
//find all cells in the same column
$mytable.find("tr:nth-child(" + index + ")").removeClass("selected").each(function () {
var $this = $(this);
$this.addClass("selected");
$copydiv.html($this.html() + "<br />");
});
});
or you could have a separate table for each column, but I don't think that would be worth it.
WIP: CSS only solution using :has() selector
The new :has() selector gave me hope in solving this issue without JS. The idea was to disable text selection for all cells, and only activate it for cells of a column that is hovered.
So you would have rule like this:
table:has(tr td:nth-child(1):hover) tr td:nth-child(1) {
-webkit-user-select: auto;
user-select: auto;
}
A complete sample can be found here: https://codepen.io/catlan/pen/XWELegW
This is work in progress, because in the current version of Safari (15.6.1), the display of the text range disappears after the selection is done, only to reappear after moving the cursor for a few pixel. See https://bugs.webkit.org/show_bug.cgi?id=244445
It seems to work fine in Chrome starting with Version 105.