Paint certain words in google docs - Google Apps Script - javascript

I have this code below:
var textToHighlight = 'Normal';
var highLightStyle = {};
highLightStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = '#FFC0CB';
var paras = doc.getParagraphs();
var textLocation = {};
for (i=0; i<paras.lenght; i++) {
textLocation = paras[i].findText(textToHighlight);
if (textLocation != null && textLocation.getStartOffset() != -1) {
textLocation.getElement().setAttributes(textLocation.getStartOffset(), textLocation.getEndOffsetInclusive(), highLightStyle);
}
}
With it, I want to color all the words 'normal' that appear in my document, but when I run the code, nothing happens and it doesn't accuse any error, it compiles normally.
I tried this another code:
let pinkColor = "#FFC0CB"
let pinkElements = body.findText("Normal")
let elem = pinkElements.getElement().asText();
let t = elem.getText();
elem.setForegroundColor(t.indexOf('Normal'), t.indexOf('High')+3, pinkColor)
But with the code above it paints only the first word 'Normal' that it finds, the rest remains neutral.
Does anyone know what may be happening to both codes?

Does anyone know what may be happening to both codes?
Code 1:
You made a typo, lenght should be length.
Code 2:
See my answer below.
Explanation:
You need to iterate over all elements with the particular keyword.
To achieve that you need to follow these steps:
get the first found element:
pinkElement = body.findText(searchWord);
check if an element with searchWord exists
do some code for this element
assign a new element which is the next one you found before:
pinkElement = body.findText(searchWord, pinkElement);
repeat steps 1-4 until there is no other element:
while (pinkElement != null)
Solution:
function myFunction() {
let doc = DocumentApp.getActiveDocument();
let body = doc.getBody();
let pinkColor = "#FFC0CB";
let searchWord = "Normal";
let pinkElement = body.findText(searchWord);
while (pinkElement != null) {
let elem = pinkElement.getElement().asText();
let t = elem.getText();
elem.setForegroundColor(t.indexOf(searchWord), t.indexOf('High')+3, pinkColor);
pinkElement = body.findText(searchWord, pinkElement);
}
}

I'm surprised it is returning the first one. "length" is spelled wrong on this line:
for (i=0; i<paras.**lenght**; i++) {
See if changing it to ".length" fixes it. If not, there is a similar example in the Docs at Class Range you could use.

Related

How to make a search box that searches for attributes' value then hide or show elements based on users input?

I'm pretty new to Javascript. I'm trying to make a search box that searches for images based users input. Here's what I got so far.
<input type="text" id="search-box">
<a class="image-link" href="photos/02.jpg" data-rel="lightcase:collection" data-lc-caption="The lake was so calm today. We had a great view of the snow on the mountains from here." data-alt="Lake">
<img class="image-link" src="photos/thumbnails/02.jpg" alt="Lake">
</a>
So, I'm trying to make a search that uses attributes value to display/hide the images. For example, if I typed "lake" in the search box this image and any images that have the word "lake" as their attributes' value would appear and hid the rest.
Here's my Javascript code so far. I'm not quite sure if I'm on the right path here. I'd appreciate any help, please.
let y = document.getElementsByTagName('a');
document.getElementById("search-box").addEventListener("keyup", function() {
x = document.getElementById("search-box");
x = x.value.toLowerCase();
console.log(x);
for (i = 0; i < y.length; i += 1) {
e = descent[i].getAttribute("data-lc-caption").toLowerCase();
console.log(e);
}
if (e.includes(x)) {
console.log(yes);
}
});
I used console.logs to check my results. I know that I'll have to remove them eventually.
You are on right path. Here are few general suggestions based on your code
1 Use better variable names, for example instead y name it something
like videoLinks
2 Use const unless you know you will need to change value of variable at some point
3 Do not declare variable by just saying x = 'something'. That way you are creating global variables and using memory for no reason
4 Use onchange input because onkeyup will fire only on typing, what is user pastes in content?
Now the code:
document.getElementById('search-box').addEventListener('change', function() {
const linksWithImages = document.getElementsByTagName('a')
const searchValue = document.getElementById("search-box").value
for (let i = 0; i < linksWithImages.length) {
if (linksWithImages[i].getAttribute('data-lc-caption') === searchValue) {
linksWithImages[i].style.display === 'block'
} else {
linksWithImages[i].style.display === 'none'
}
}
})
Firstly, let's improve the event listener by detecting change and call the main function only after the user finish typing. Then use RegExp instead of toLowerCase for Case-insensitive search. And finally, update the style.
Also, it's better to get the images by using class name instead of tag name.
const searchBox = document.getElementById("search-box");
const images = document.getElementsByTagName("a");
let globalTimeout = null;
// Detect for input change when user finish typing
searchBox.addEventListener("keyup", function () {
if (globalTimeout !== null)
clearTimeout(globalTimeout);
globalTimeout = setTimeout(onFinishTyping, 200);
});
function onFinishTyping() {
const input = searchBox.value;
globalTimeout = null;
console.log("Searching for", input);
for (let i = 0, length = images.length; i < length; i++) {
const image = images[i];
if (!image.getAttribute("data-lc-caption").match(new RegExp(input, "ig")))
image.style.display = "none";
else
image.style.display = "block";
}
}
document.getElementById('search-box').addEventListener('input', function() {
const linksWithImages = document.getElementsByTagName('a')
const searchValue = document.getElementById("search-box").value
let i = 0, len = linksWithImages.length;
for (; i < len; i++) {
let lnk = linksWithImages[i];
let val = lnk.getAttribute("data-lc-caption");
lnk.style.visibility = val.indexOf(searchValue) > -1 ? "visible" : "hidden";
}
})

Javascript regex.exec() looping for no logical reason

This is quite likely the weirdest problem I've encountered in javascript and I don't even know if the title correctly describes it. I have a script that searches DOM (using getElementByTagName) for all <p> elements inside a specified container (once), and then iterates through them giving each to this function:
var re = /(>.+(<br>)?[\n\r]{0,2}\s*)+/gi;
var temp, nextp, nextptext, greenp, greenptext, curtext;
function greenChange(givenp) {
curtext = givenp.innerHTML;
var txt = re.exec(curtext);
while(txt) {
console.log(txt[0]);
temp = curtext.indexOf(txt[0]);
curtext = curtext.replace(txt[0],"");
greenp = document.createElement("p");
givenp.parentNode.insertBefore(greenp, givenp.nextSibling);
greenp.innerHTML = txt[0];
txt = re.exec(curtext);
}
}
It is supposed to extract every match into a different <p> element, and the text after it into another <p> element. I know it has a few derps (will place the paragraphs in the wrong order), and a few things I commented out and ommited here (like the part that creates <p> elements for unmatched text) I'm not asking about those, the main problem is:
If the greenp.innerHTML = txt[0]; line is like this, it loops forever, infinitely outputting the last match into the console, but what's REALLY weird is the fact that if I change it into for example greenp.innerHTML = "example";, it doesn't. Properly creates and places the <p> elements one after the other, all properly having "example" as text and finishes. The only semi-logical explanation I came up with is that the re.exec(curtext) somehow catches the newly created <p> sibling, even though it seems impossible. Does anyone have any tips what's wrong and how to fix it?
Note: If you want to see the full code, it is below. Just please note that this is my first time using clean JS to manipulate website's code. It has errors I am aware of and will fix when I get the greenChange function working. Tips are much appreciated though.
var re = /(>.+(<br>)?[\n\r]{0,2}\s*)+/gi;
var temp, nextp, nextptext, greenp, greenptext, tempsub, curtext;
function greenChange(givenp) {
curtext = givenp.innerHTML;
var txt = re.exec(curtext);
while(txt) {
console.log(txt[0]);
temp = curtext.indexOf(txt[0]);
curtext = curtext.replace(txt[0],"");
greenp = document.createElement("p");
//greenptext = document.createTextNode(txt[0]);
//greenp.appendChild(greenptext);
//nextptext = document.createTextNode(curtext.substring(temp));
//nextp.appendChild(nextptext);
givenp.parentNode.insertBefore(greenp, givenp.nextSibling);
greenp.innerHTML = txt[0];
/*if(tempsub = curtext.substring(temp)) {
nextp = document.createElement("p");
givenp.parentNode.insertBefore(nextp, givenp.nextSibling);
//nextp.innerHTML = curtext.substring(temp);
}*/
txt = re.exec(curtext);
}
}
window.onload = function() {
alert("Document loaded!");
var plist = document.getElementById("contentArea").getElementsByTagName('p');
console.log("After ploading.");
console.log(plist[0].innerHTML);
console.log("That was the test, now the real thing:");
for(var i = 0;i < plist.length;i++) {
greenChange(plist[i]);
}
}

Keep Javascript constant the same after function is recalled

I am trying to create a dynamic list so when the user performs a search it will repopulate the list. The problem is that I can't seem to make an immutable constant to store the original div content. Every time the function get's called this variable gets reinitialized.
Is there a way to achieve this without using cookies ? Any help is sincerely appreciated. The code is not complete because I couldn't get passed this step but if you think I am totally heading toward the wrong direction please let me know.
const originalList = document.getElementById('patientList').getElementsByTagName('li');
frozen = Object.freeze(originalList);
<script>
const originalList = document.getElementById('patientList').getElementsByTagName('li');
frozen = Object.freeze(originalList);
var newList = '';
var found = false;
function filterPatients(){
var searchQuery = document.getElementById('search');
var query = searchQuery.value;
var listContainer = document.getElementById('patientList');
var patientList = listContainer.getElementsByTagName('li');
for (var i = 0; i < originalList.length; i++){
var link = patientList[i].getElementsByTagName('a');
var link = link[0].text;
/** remove whitespaces for easy comparison **/
link = link.toLowerCase();
query = query.toLowerCase();
link = link.replace(/\s/g, "");
query = query.replace(/\s/g, "");
/** check every character in query **/
if (link.length > query.length && link.substring(0,query.length) == query){
found = true;
newList += '<li>' + patientList[i].innerHTML + '</li>';
}
}
if (found == true){
listContainer.innerHTML = newList;
newList = '';
}
else{
listContainer.innerHTML = "<li>No patient by that name</li>";
}
console.log(frozen);
}
</script>
const originalList = document.getElementById('patientList').getElementsByTagName('li').cloneNode(true);
Make originalList a copy of the element. Currently, you are setting originalList and patientList to be the same list of elements, so changing one will also change the other. Use element.cloneNode(true) to make a deep copy of a DOM element

textContent Vs. innerText Cross Browser solution

I've been having a hard time with cross browser compatibility and scrapping the dom.
I've added data analytics tracking to ecommerce transactions in order to grab the product and transaction amount for each purchase.
Initially I was using document.querySelectorAll('#someId')[0].textContent to get the product name and that was working fine for every browser except internet explorer.
It took some time to figure out that it was the .textContent part that was causing ie problems.
Yesterday I changed .textContent to .innerText. From looking inside analytics it seems that the issue has been resolved for ie but now Firefox is failing.
I was hoping to find a solution without writing an if statement to check for the functionality of .textContent or .innerText.
Is there a cross browser solution .getTheText?
If not what would be the best way around this? Is there a simple solution? (I ask given my knowledge and experience with scripting, which is limited)
** added following comments **
If this is my code block:
// build products object
var prods = [];
var brand = document.querySelectorAll('.txtStayRoomLocation');
var name = document.querySelectorAll('.txtStayRoomDescription');
var price = document.querySelectorAll('.txtStayRoomSplashPriceAmount');
for(var i = 0; i < brand.length; i++) {
//set granular vars
var prd = {};
//add to prd object
prd.brand = brand[i].innerText;
prd.name = name[i].innerText;
prd.price = price[i].innerText;
prd.quantity = window.session_context_vars.BookingContext.Booking.ReservationLineItems[i].ReservationCharges.length/2;;
//add to prods array
prods.push(prd);
}
Then if I understand the syntax from the comments and the question linked to in the comment, is this what I should do:
// build products object
var prods = [];
var brand = document.querySelectorAll('.txtStayRoomLocation');
var name = document.querySelectorAll('.txtStayRoomDescription');
var price = document.querySelectorAll('.txtStayRoomSplashPriceAmount');
for(var i = 0; i < brand.length; i++) {
//set granular vars
var prd = {};
//add to prd object
prd.brand = brand[i].textContent || brand[i].innerText;
prd.name = name[i].textContent || name[i].innerText;
prd.price = price[i].textContent || price[i].innerText;
prd.quantity = window.session_context_vars.BookingContext.Booking.ReservationLineItems[i].ReservationCharges.length/2;;
//add to prods array
prods.push(prd);
}
So using or with a double bar || assigns the first non null value?
Re: your edit, not quite. The way to access methods or properties on an object (eg a DOM element) is to use dot notation if you have the name itself, or square brackets in case of variables/expressions (also works with strings, as in obj["propName"], which is equivalent to obj.propName). You can also just test the property against one element and use that from there on:
// build products object
var prods = [];
var brand = document.querySelectorAll('.txtStayRoomLocation');
var name = document.querySelectorAll('.txtStayRoomDescription');
var price = document.querySelectorAll('.txtStayRoomSplashPriceAmount');
for(var i = 0; i < brand.length; i++) {
//set granular vars
var prd = {};
//add to prd object
var txtProp = ("innerText" in brand[i]) ? "innerText" : "textContent"; //added string quotes as per comments
prd.brand = brand[i][txtProp];
prd.name = name[i][txtProp];
prd.price = price[i][txtProp];
prd.quantity = window.session_context_vars.BookingContext.Booking.ReservationLineItems[i].ReservationCharges.length/2;;
//add to prods array
prods.push(prd);
}
Regarding the line:
var txtProp = (innerText in brand[i]) ? innerText : textContent;
The in keyword checks an object to access the property (syntax: var property in object). As for the question notation (I made an error earlier, using ||, the correct thing to use was a :),
var myVar = (prop in object) ? object[prop] : false;
As an expression, it basically evaluates the stuff before the ?, and if it's true, returns the expression before the :, else the one after. So the above is the same as / a shorthand for:
if(prop in object){
var myVar = object[prop];
}
else{
var myVar = false;
}
Since you are checking between two properties only and wanting to assign one or the other, the shortest way would indeed be:
var txtProp = brand[i].innerText || brand[i].textContent;
It would basically test the first property, and if it were false or undefined, it would use the second one. The only reason I (pedantically) avoid using this is because the first test of a || b would fail even if a existed but just had a value of 0, or an empty string (""), or was set to null.

How to get the next element of an array with Jquery onclick

Hi all i am trying to change the html of an object from an array of htmls. But i am having problem iterating properly. I managed to make it work once
EDIT
After a few complains about the clarity of my question I will rephrase it. I have a div panel called .trpanel and a button called #trigger2 (it is a next button). Then I have a series of divs with texts that contain translations. I want when I press the button (called next) to cycle through the translations one by one on the trpanel.
var ltranslation = [];
ltranslation[0] = $("#translation-en-1").html();
ltranslation[1] = $("#translation-ur-en").html();
ltranslation[2] = $("#translation-fr-en").html();
ltranslation[3] = $("#translation-it-en").html();
ltranslation[4] = $("#translation-sp-en").html();
ltranslation[5] = $("#translation-po-en").html();
ltranslation[6] = $("#translation-fr-en").html();
ltranslation[7] = $("#translation-de-en").html();
var l= ltranslation;
$("#trigger2").off('click').on('click',function(){
for (var i = 0; i <= ltranslation.length; i++){
if (i==7){i=0;}
$(".trpanel").html.ltranslation[i]; or ???//replace().ltranslation[]+i??? the code throws errors
}
});
I am quite new to Javascript and i am getting a bit confused with the types of objects and arrays and loops. I managed once to add the htmls but without replacing them ... so they all came one after the other. The i tried to change the code and it hasn't worked since. Any help will be greatly appreciated.
A lot of guessing, but seems like you are trying to do this :
var trans = $('[id^="translation-"]'),
idx = 0;
$("#trigger2").on('click',function(){
$(".trpanel").html( trans.eq(idx).html() );
idx = idx > 6 ? 0 : idx+1;
});
FIDDLE
I think you are trying to do this:
if (i == 7) {
i = 0; // I don't really know why you are doing this, but it will reset the loop
}
$(".trpanel").html(ltranslation[i]); //I'm passing ltranslation[i] to the html method. Instead of .html.ltranslation[i].
}
Also, without seeing any html, I'm not sure but I think you may want to iterate over .trpanel ?
Something like:
$(".trpanel").eq(i).html(ltranslation[i]);
Another thing (so you can make your code clearer I think). You can abstract the array population in a function, like this:
var ltranslation = [];
var languages = ["en-1", "ur-en", "fr-en", "it-en", "sp-en", "po-en", "fr-en", "de-en"];
$.each(languages, function(index) {
ltranslation[index] = $("#translation-" + this).html();
});
// Then you can use ltranslation
If you want to flip through several translations I would implement it that way:
var translations=["hej","hello", "hallo","hoy"];
var showTranslation=function(){
var current=0;
var len=translations.length;
return function(){
var direction=1;
if (current>=len) current=0;
$("#text").text(translations[current]);
current+=direction;
}
}();
$("#butt").on("click", showTranslation);
Fiddle: http://jsfiddle.net/Xr9fz/
Further: You should give your translations a class, so you could easily grab all of them with a single line:
$(".translation).each(function(index,value){ ltranslation.push(value); })
From the question : I managed once to add the htmls but without replacing them -
I think you want to add all of these items into $(".trpanel"). First, dont take the HTML of each element, clone the element itself :
//method ripped from Nico's answer.
var ltranslation = [];
var languages = ["en-1", "ur-en", "fr-en", "it-en", "sp-en", "po-en", "fr-en", "de-en"];
$.each(languages, function(index) {
ltranslation[index] = $("#translation-" + this).clone();
});
Then you could append everything into the container, so add the htmls but without replacing them. append takes in an array without replacing the previous html.
$("#trigger2").off('click').on('click',function() {
$(".trpanel").append(ltranslation);
});
I don't know what exactly you're tring to do, but I've put comments in your code to help you better understand what your code is doing. The net effect of your code is this (which I doubt you want) :
$("#trigger2").off('click').on('click',function(){
$(".trpanel").html(ltranslation[7]);
});
This is your code with some comments and minor changes
var ltranslation = [];
ltranslation[0] = $("#translation-en-1").html();
ltranslation[1] = $("#translation-ur-en").html();
ltranslation[2] = $("#translation-fr-en").html();
ltranslation[3] = $("#translation-it-en").html();
ltranslation[4] = $("#translation-sp-en").html();
ltranslation[5] = $("#translation-po-en").html();
ltranslation[6] = $("#translation-fr-en").html();
ltranslation[7] = $("#translation-de-en").html();
var l= ltranslation;
$("#trigger2").off('click').on('click',function(){
for (var i = 0; i < ltranslation.length; i++){
//if (i==7){i=0;} <-- This will cause an infinite loop won't it? are you trying to reset i? i will reset next time loop is called,
$(".trpanel").html(ltranslation[i]); //<-- this will overwrite elements with class .trpanel ltranslation.length times...
///you'll see only the value of translation[7] in the end
}
});
EDIT
To do what you want to do based on your comments, try this:
var ltranslation = [];
ltranslation[0] = $("#translation-en-1").html();
ltranslation[1] = $("#translation-ur-en").html();
ltranslation[2] = $("#translation-fr-en").html();
ltranslation[3] = $("#translation-it-en").html();
ltranslation[4] = $("#translation-sp-en").html();
ltranslation[5] = $("#translation-po-en").html();
ltranslation[6] = $("#translation-fr-en").html();
ltranslation[7] = $("#translation-de-en").html();
var counter = 0;//a global counter variable
$("#trigger2").click(function(){ //eeverytime button is clicked do this
$(".trpanel").html(ltranslation[counter]); //set the html to an element of array
counter++; //increment counter
if(counter==ltranslation.length) //reset the counter if its bigger than array len
counter=0;
});

Categories