I need to find the H2 tag that matches the text content and add focus to it.
For example,
matchText = 'Hello, India';
<h2 tabIndex={-1}>Hello, India</h2>
<h2 tabIndex={-1}>Hello, USA</h2>
<h2 tabIndex={-1}>Hello, China</h2>
In the above example, I need to add focus to Hello, India;
In the below code, I got all the headings but how do I add focus to the heading tag that matches my text ?
const h = ['h1'];
const headings = [];
for (let i = 0; i < h.length; i++) {
if (document.getElementsByTagName(h[i])) {
headings[i] = document.querySelector(h[i]);
if (headings[i]) {
console.log(headings[i].textContent);
}
} else {
console.log(`${h[i]}doesn't exist`);
}
}
Related
As the title suggests I am trying to find the index of a placeholder text element in a google doc table so that I can replace it with text stored in a different doc.
I can get the pre-formatted text to be added to the document - not the place in the table required - using other examples I have found on stackoverflow.
However I am unclear as how to find the index of the placeholder element within one of many tables in the template document.
I need the index of the placeholder text to be able to use the insertParagraph function.
To add slightly more detail: I am able to find the placeholder text and insert an image using the below code.
function replaceTextToImage(body, searchText, image, width) {
var next = body.findText(searchText);
if (!next) return;
var r = next.getElement();
r.asText().setText("");
var img = r.getParent().asParagraph().insertInlineImage(0, image);
if (width && typeof width == "number") {
var w = img.getWidth();
var h = img.getHeight();
img.setWidth(width);
img.setHeight(width * h / w);
}
return next;
};
However, I need to preserve the formatting of the doc I want to import. So I open the document with the formatted text and then loop through the different element types with a conditional to insert the text/image if the element type is matched. This is why I need the index of the placeholder text. See for function below:
function replaceTextWithDoc(body, searchText, id) {
let doc = DocumentApp.openById(id)
var numElements = body.getNumChildren();
var index = numElements;
for (var i = 0; i < numElements; i++) {
var child = body.getChild(i);
if (child.asText().getText() == searchText){
index = i;
body.removeChild(child);
break;
}
}
var totalElements = doc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = doc.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.insertParagraph(index, element);
else if( type == DocumentApp.ElementType.TABLE )
body.insertTable(index, element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.insertParagraph(index, element);
else
throw new Error("According to the doc this type couldn't appear in the body: "+type);
}
}
Here is an example of the placeholder text ( {iText} ) in a table: https://docs.google.com/document/d/1mZWpQqk4gYAF6UCRALrT8S99-01RYNfwni_kqDzOg7E/edit?usp=sharing
Here is an example of text and images that I need to replace the placeholder text with - maintaining all/any formatting. https://docs.google.com/document/d/1wuX0g5W2GL0YJ7admiv3TNEepb_zVwQleKwazCiAMBU/edit?usp=sharing
Issue:
If I understand you correctly, you want to copy the contents of one document (made up of text and inline images) to certain table cells that contain a certain placeholder text.
Solution:
In that case, I'd suggest the following:
Iterate through all tables in the target document, using Body.getTables().
For each table, iterate through all its cells.
For each cell, check whether its text contains the placeholder.
If the placeholder is included, clear the current cell content via TableCell.clear().
Iterate through all source document elements (see for example, this answer).
For each element, check its type (Element.getType()).
If the element is a paragraph, append the paragraph to the cell via TableCell.appendParagraph.
If the element is an image, append it via TableCell.appendImage.
Code sample:
const PLACEHOLDER = "{iText}";
function myFunction() {
const doc = DocumentApp.openById(TARGET_ID);
const sourceDoc = DocumentApp.openById(SOURCE_ID);
const body = doc.getBody();
const tables = body.getTables();
tables.forEach(table => {
const numRows = table.getNumRows();
for (let i = 0; i < numRows; i++) {
const row = table.getRow(i);
const numCells = row.getNumCells();
for (let j = 0; j < numCells; j++) {
const cell = row.getCell(j);
const cellText = cell.editAsText();
const text = cellText.getText();
if (text.includes(PLACEHOLDER)) {
cell.clear();
appendSourceContent(sourceDoc, cell);
}
}
}
});
}
function appendSourceContent(doc, cell) {
const numChildren = doc.getNumChildren();
for (let j = 0; j < numChildren; j++) {
const element = doc.getChild(j).copy();
const type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
cell.appendParagraph(element);
} else if (type == DocumentApp.ElementType.INLINE_IMAGE) {
cell.appendImage(element);
}
}
}
Note:
Add additional else if blocks if the source content can have different ElementTypes than paragraphs and inline images.
I assumed that you want to preserve none of the current content in the table cell.
I'm trying to make the text in the results bold but I can't manage to do so
Here is the HTML:
<input type="text" id="searchbar" onkeyup="search()" placeholder="Search here...">
<div class = "sometext">
Sentence going here
</div>
<div class = "sometext">
Sentence going there
</div>
<div class = "sometext">
Sentence going somewhere
</div>
And here is the JavaScript:
function search() {
let input = document.getElementById('searchbar').value;
input=input.toLowerCase();
let x = document.getElementsByClassName('sometext');
var regex = new RegExp(input, 'gi');
sometextvalues = [];
for (j = 0; j < x.length; j++){
sometextvalues[j] = x[j].innerHTML;
}
for (i = 0; i < x.length; i++) {
str = sometextvalues[i];
if (!x[i].innerHTML.toLowerCase().includes(input)) {
x[i].style.display="none";
}
else {
if (input){
x[i].style.display="flex";
var result = str.replace(regex, '<b>' + input + '</b>');
x[i].innerHTML = result;
}
else{
x[i].innerHTML = sometextvalues[i];
}
}
}
}
So the problem is that right now, the bold text only appears for one letter and then the results don't appear anymore. What did I do wrong?
The issue is you are highlighting the innerText of elements with class sometext if the text in the serach input field at the first keyUp event is matching by adding <b></b> tag and then matching the modified inner text against new search string on your next keyup event. Lets say you enter 's' in serach field and then the texts in sometext would be changed to
<b>S</b>entence going here
<b>S</b>entence going there
<b>S</b>entence going somewhere
then you are typing 'e' next to 's' in the serach field, then you are trying to match the text '<b>S</b>entence going here' against 'se' which will return false always so there onwards the highlighting won't work.
to fix the issue, always remove <b> and </b> tags from the innterTexts of elements with class sometext before matching it with the input text.
function search() {
let input = document.getElementById('searchbar').value;
input=input.toLowerCase();
let x = document.getElementsByClassName('sometext');
var regex = new RegExp(input, 'gi');
sometextvalues = [];
for (j = 0; j < x.length; j++){
sometextvalues[j] = x[j].innerHTML.replaceAll('<b>','').replaceAll('</b>','');
}
for (i = 0; i < x.length; i++) {
str = sometextvalues[i].replaceAll('<b>','').replaceAll('</b>','');
if (!x[i].innerHTML.replaceAll('<b>','').replaceAll('</b>','').toLowerCase().includes(input)) {
x[i].style.display="none";
}
else {
if (input){
x[i].style.display="flex";
var result = str.replace(regex, '<b>' + input + '</b>');
x[i].innerHTML = result;
}
else {
x[i].innerHTML = sometextvalues[i];
}
}
}
}
<input type="text" id="searchbar" onkeyup="search()" placeholder="Search here...">
<div class = "sometext">
Sentence going here
</div>
<div class = "sometext">
Sentence going there
</div>
<div class = "sometext">
Sentence going somewhere
</div>
I have only corrected the highlighting part since that is the main focus of your question. You can put some effort yourself to correct some other issues like the formatted lower cased text is being set with sometext elements, dropping space between words when a whole word is searched, etc...
My goal:
Let users highlight different substring in a single long string.
However, once I've highlighted one substring with range.surroundContents(newNode) (newNode is a span with yellow background), the innerHTML of the whole long string changed-- it started to contain the span element; consequently, if the user wants to highlight a substring after the previous highlighted substring in the same long string, the anchorOffset will return the index starting after the previous span.
For example, in this long string:
"Mr. and Mrs. Dursley, of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much."
this long sentence is wrapped by a p whose class name is noting. If the range.surroundContents() method the substring "Privet Drive", then, when I want to get the window.getSelection().anchorOffset of the substring "thank", the answer wrongly is 53 while the correct answer should be 102.
How should I do? Thank you!!
P.S. I don't want to use substring method to find the position, thank you!
$(".noting").mouseup(function(e){
$("#noteContent").val("");/*flushing*/
curSentNum = $(this).attr("id").split("-")[1];
$('#curSentNum').val(curSentNum);
highlightLangName = $(this).attr("id").split("-")[2];
$('#highlightLangName').val(highlightLangName);
//console.log(".noting $(this).html()"+$(this).html()+" "+$(this).attr("id"));//id, for example: p-2-French
if (window.getSelection) {
highlightedText = window.getSelection().toString();
curAnchorOffset = window.getSelection().anchorOffset;
$('#anchorAt').val(curAnchorOffset);
$('#highlightLen').val(highlightedText.length);
}
else if (document.selection && document.selection.type != "Control") {
highlightedText = document.selection.createRange().text;
}
});
And then I'll save the anchorAt information to db; after the db operation, I'll immediately call this function using the previous variables remained:
function highlightNoteJustSaved(){
var curI = noteCounter;
var anchorAt = parseInt($("#anchorAt").val());
var highlightLen = parseInt($("#highlightLen").val());
/*p to find, for example: p-2-French*/
var curP = document.getElementById('p-'+curSentNum.toString()+"-"+$("#highlightLangName").val());
var range = document.createRange();
root_node = curP;
range.setStart(root_node.childNodes[0], anchorAt);
range.setEnd(root_node.childNodes[0], anchorAt+highlightLen);
var newNode = document.createElement("span");
newNode.style.cssText="background-color:#ceff99";//yellow
newNode.className = alreadyNoteStr;
newNode.setAttribute('id','already-note-'+curI.toString());
range.surroundContents(newNode);
}
for HTML tree node structure, please take a look at the comment below( I didn't figure out how to copy-paste the code at this asking area).
I replaced your method to highlight text with 2 methods. highlightTextNodes finds the word in the content of the node. Searching each child. Also I implemented a highlight remover to show how it works. I replaced the span with a mark tag.
let alreadyNoteStr = 'already';
let noteCounter = 0;
let elementId;
$('p.noting').mouseup(function(e) {
elementId = $(this).attr('id');
$('#noteContent').val(''); /*flushing*/
curSentNum = elementId.split('-')[1];
$('#curSentNum').val(curSentNum);
highlightLangName = elementId.split('-')[2];
$('#highlightLangName').val(highlightLangName);
//console.log(".noting $(this).html()"+$(this).html()+" "+$(this).attr("id"));//id, for example: p-2-French
if (window.getSelection) {
highlightedText = window.getSelection().toString();
curAnchorOffset = window.getSelection().anchorOffset;
$("#noteContent").val(highlightedText);
$('#anchorAt').val(curAnchorOffset);
$('#highlightLen').val(highlightedText.length);
highlight(elementId, highlightedText);
} else if (document.selection && document.selection.type != "Control") {
highlightedText = document.selection.createRange().text;
}
});
function highlightNoteJustSaved() {
let curI = noteCounter;
let anchorAt = parseInt($("#anchorAt").val());
let highlightLen = parseInt($("#highlightLen").val());
/*p to find, for example: p-2-French*/
let curP = document.getElementById('p-' + curSentNum.toString() + "-" + $("#highlightLangName").val());
let range = document.createRange();
rootNode = curP;
let childNode = rootNode.childNodes[0];
range.setStart(rootNode.childNodes[0], anchorAt);
range.setEnd(rootNode.childNodes[0], anchorAt + highlightLen);
var newNode = document.createElement("span");
newNode.style.cssText = "background-color:#ceff99"; //yellow
newNode.className = alreadyNoteStr;
newNode.setAttribute('id', 'already-note-' + curI.toString());
range.surroundContents(newNode);
}
/*
* Takes in an array of consecutive TextNodes and returns a document fragment with `word` highlighted
*/
function highlightTextNodes(nodes, word) {
if (!nodes.length) {
return;
}
let text = '';
// Concatenate the consecutive nodes to get the actual text
for (var i = 0; i < nodes.length; i++) {
text += nodes[i].textContent;
}
let fragment = document.createDocumentFragment();
while (true) {
// Tweak this if you want to change the highlighting behavior
var index = text.toLowerCase().indexOf(word.toLowerCase());
if (index === -1) {
break;
}
// Split the text into [before, match, after]
var before = text.slice(0, index);
var match = text.slice(index, index + word.length);
text = text.slice(index + word.length);
// Create the <mark>
let mark = document.createElement('mark');
mark.className = 'found';
mark.appendChild(document.createTextNode(match));
// Append it to the fragment
fragment.appendChild(document.createTextNode(before));
fragment.appendChild(mark);
}
// If we have leftover text, just append it to the end
if (text.length) {
fragment.appendChild(document.createTextNode(text));
}
// Replace the nodes with the fragment
nodes[0].parentNode.insertBefore(fragment, nodes[0]);
for (var i = 0; i < nodes.length; i++) {
let node = nodes[nodes.length - i - 1];
node.parentNode.removeChild(node);
}
}
/*
* Highlights all instances of `word` in `$node` and its children
*/
function highlight(id, word) {
let node = document.getElementById(id);
let children = node.childNodes;
let currentRun = [];
for (var i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.TEXT_NODE) {
// Keep track of consecutive text nodes
currentRun.push(child);
} else {
// If we hit a regular element, highlight what we have and start over
highlightTextNodes(currentRun, word);
currentRun = [];
// Ignore text inside of our <mark>s
if (child.nodeType === Node.ELEMENT_NODE && child.className !== 'found') {
highlight(child, word);
}
}
}
// Just in case we have only text nodes as children
if (currentRun.length) {
highlightTextNodes(currentRun, word);
}
}
/*
* Removes all highlighted <mark>s from the given node
*/
function unhighlight(id) {
let node = document.getElementById(id);
let marks = [].slice.call(node.querySelectorAll('mark.found'));
for (var i = 0; i < marks.length; i++) {
let mark = marks[i];
// Replace each <mark> with just a text node of its contents
mark.parentNode.replaceChild(document.createTextNode(mark.childNodes[0].textContent), mark);
}
}
label {
display: block;
position: relative;
padding-left: 100px;
}
button {
margin-top: 20px;
margin-bottom: 20px;
padding: 10px;
}
label>span {
position: absolute;
left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button" onclick="unhighlight(elementId);">Unhighlight</button>
<div id="div-0" class="only-left-border">
<p class="lan-English noting" id="p-1-English">Mr. and Mrs. Dursley, of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much.</p>
</div>
<label><span>Content:</span><input type="text" id="noteContent"></input></label>
<label><span>Numer:</span><input type="text" id="curSentNum"></input></label>
<label><span>Language:</span><input type="text" id="highlightLangName"></input></label>
<label><span>Anchor:</span><input type="text" id="anchorAt"></input></label>
<label><span>Length:</span><input type="text" id="highlightLen"></input></label>
I am creating a mock blog where I will be appending the posts via the JavaScript file. I currently have it set up so that a search button will work to only show posts with the same text in the search bar. Now, I want the posts to have the found word highlighted.
HTML:
<div id= "custom_blog_div"></div>
JS:
if (document.getElementById("custom_blog_div")) {
//Blog Post 2
var post_2_div = document.createElement("div");
post_2_div.setAttribute("id", "post_2_div");
post_2_div.setAttribute("class", "post_div");
custom_blog_div.appendChild(post_2_div);
// Header
var post_2_Header = document.createElement("h2");
var post_2_Header_Text = document.createTextNode("Welcome, and Pardon the Construction!");
post_2_Header.setAttribute("class", "blog_post_header");
post_2_Header.appendChild(post_2_Header_Text);
post_2_div.appendChild(post_2_Header);
// Date
var post_2_Date = document.createElement("p");
var post_2_Date_Text = document.createTextNode("January 2, 2018 12:00 am");
post_2_Date.setAttribute("class", "blog_post_date");
post_2_Date.appendChild(post_2_Date_Text);
post_2_div.appendChild(post_2_Date);
// Blog
var post_2_Blog = document.createElement("p");
var post_2_Blog_Text = document.createTextNode("This is a Left Image:");
var post_2_Blog_Image_1 = document.createElement("img");
post_2_Blog.setAttribute("class", "blog_post_text");
post_2_Blog_Image_1.setAttribute("class", "Left_Image");
post_2_Blog_Image_1.setAttribute("width", "100px");
post_2_Blog_Image_1.setAttribute("src", "./series images/main series/spirit legends issue 5/Spirit Legends 5 - Cover.jpg")
post_2_Blog.appendChild(post_2_Blog_Text);
post_2_Blog.appendChild(post_2_Blog_Image_1);
post_2_div.appendChild(post_2_Blog);
// Blog Post 1
var post_1_div = document.createElement("div");
post_1_div.setAttribute("id", "post_1_div");
post_1_div.setAttribute("class", "post_div");
custom_blog_div.appendChild(post_1_div);
// Header
var post_1_Header = document.createElement("h2");
var post_1_Header_Text = document.createTextNode("Welcome, and Pardon the Construction!");
post_1_Header.setAttribute("class", "blog_post_header");
post_1_Header.appendChild(post_1_Header_Text);
post_1_div.appendChild(post_1_Header);
// Date
var post_1_Date = document.createElement("p");
var post_1_Date_Text = document.createTextNode("January 2, 2018 12:00 am");
post_1_Date.setAttribute("class", "blog_post_date");
post_1_Date.appendChild(post_1_Date_Text);
post_1_div.appendChild(post_1_Date);
// Blog
var post_1_Blog = document.createElement("p");
var post_1_Blog_Text = document.createTextNode("Hi, and welcome to the official Spirit Legends website! The site is live in order to test out certain things, but as you can see, it is very much incomplete. Please look forward to the complete site in the future!");
post_1_Blog.setAttribute("class", "blog_post_text");
post_1_Blog.appendChild(post_1_Blog_Text);
post_1_div.appendChild(post_1_Blog);
}
// Search Bar button
document.getElementById("search_news_button").onclick = function() {
var all_blogs = document.getElementById("custom_blog_div").querySelectorAll(".post_div");
var text_field = document.getElementById("search_news_button_text").value.toLowerCase();
var custom_blog = document.getElementById("custom_blog_div");
// Restore all Blog Posts before searching
for (i = 0; i < all_blogs.length; i++) {
if (all_blogs[i].style.display === "none") {
all_blogs[i].style.display = "inline";
}
}
// Loop through all Blog posts
for (i = 0; i < all_blogs.length; i++) {
// Display all Blog posts containing the text in the Search Bar
if (all_blogs[i].innerText.toLowerCase().includes(text_field) === true) {
all_blogs[i].style.display = "inline";
var x = "";
for (x = 0; x < custom_blog.innerText.length; x++) {
if (custom_blog[x].innerText.toLowerCase().includes(text_field) === true) {
x = custom_blog[x].innerText.toLowerCase();
x.style.backgroundColor = "yellow";
}
}
// Highlight the found text in each blog post
var x = "";
for (x = 0; x < custom_blog.innerText.length; x++) {
if (custom_blog[x].innerText.toLowerCase().includes(text_field) === true) {
x = custom_blog[x].innerText.toLowerCase();
x.style.backgroundColor = "yellow";
}
}
// Otherwise, if no Blog posts contain the text in the Search Bar or if Search Bar is empty, display the default
} else {
all_blogs[i].style.display = "none";
}
}
}
So, like I said, the blog is working, and the search button is working, but I cannot figure out how to get the searched word highlighted. Currently, this code results in the console telling me "TypeError: custom_blog[x] is undefined".
And, if it helps, the website is http://spiritlegendsofficial.com/ but this feature hasn't been added yet. Although you can look at the rest of the site's code on there and get some context for this mock blog.
Thanks!
Assuming custom_blog is an HTMLElement type, custom_blog[0] would represent the first child node of that HTMLElement. If you check that your variable custom_blog is defined in a javascript console, but custom_blog[x] isn't defined, it basically means your <div id= "custom_blog_div"></div> has no child elements.
I think your issue is somewhere in the loop that's grabbing your text for highlighting. You're checking the length of the string, but inside the loop you're attempting to target a child node in a case where there may not be one. You might want to change the for loop's condition for continuing to be based on the number of nodes instead:
if (custom_blog.hasChildNodes()) {
var children = custom_blog.childNodes;
for (x = 0; x < children.length; x++) {
if (children[x].innerText.toLowerCase().includes(text_field) === true) {
x = children[x].innerText.toLowerCase();
x.style.backgroundColor = "yellow";
}
}
}
For more info on childNodes: https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
I have this html: <span>some words here</span>. When that span is clicked it turns into a textbox with that string in it. Is there a way to find out which character the click occurred at and put the cursor at that point in the string once the textbox with the string appears?
Here's a quick Fiddle (tested in Chrome) that works for a <span> that includes line breaks, which the above solution choked on:
http://jsfiddle.net/x2dLW/1/
Summarized below:
function swapArea(baseEl) {
// Display <textarea> element, hide the <span>
}
function getCursorLoc (nodes, range) {
var prevChars = 0;
var clickContent = range.startContainer.textContent;
var clickLine;
//iterate backwards through nodes constituting <span> element contents
for (x = nodes.length - 1; x >= 0; x--) {
node = nodes[x];
if (clickContent == node.textContent) {
clickLine = node; //this is the line user clicked in
}
else if (clickLine && (node.nodeName == "#text") ) {
//sum up length of text lines prior, +1 for the NewLine
prevChars += node.textContent.length + 1;
}
}
//return offset in line clicked + sum length of all previous lines' content
return range.startOffset + prevChars;
}
function replaceAndSet(e) {
//Capture the click target as Selection(), convert to Range();
var userRange = window.getSelection().getRangeAt();
newArea = swapArea(source);
//spanLines holds siblings (nodeName #Text or <BR>) constituting <span> contents
var spanLines = userRange.startContainer.parentElement.childNodes;
var clickChar = getCursorLoc(spanLines, userRange);
newArea.focus();
newArea.setSelectionRange(clickChar, clickChar);
}
var source = someSpan; //the span user clicks on
source.onclick = replaceAndSet;