2

I am trying to create a Chrome Extension that will replace the price of products on Amazon with the number of hours that would be needed to work at your given hourly rate to afford to buy the item.

My code currently looks as follows:

const hourlyWage = 15;

const priceElements = document.querySelectorAll("*:not(script):not(style)"); // select all elements on the page except for scripts and styles
//console.log(priceElements); // log the value of priceElements to the console

priceElements.forEach(function(element) {
  const priceMatch = element.innerText ? element.innerText.match(/\$\d+\.\d{2}/) : null; // use a regex to find a string that starts with a $ and has two decimal places
  console.log(priceMatch)
  if (priceMatch !== null) {
    const priceString = priceMatch[0].replace(/[^0-9.]/g, "");
    const price = parseFloat(priceString);

    const hours = price / hourlyWage;
    const roundedHours = Math.round(hours * 100) / 100;

    element.innerText = `${roundedHours} hours of work`;
  }
});

The goal was for this code to execute and replace the price on the Amazon with "2 hours of work" rather than $30.

Now, upon going to an amazon product page, the entire page is replaced with "2 hours of work" rather than just the prices on the page.

LMaddalena
  • 31
  • 5

2 Answers2

1

Here is a simple snippet that you will do what you want and you can adjust accordingly:

var hourlyWage = 15
document.querySelectorAll('.a-price').forEach(function(element){
    var price = parseFloat(element.innerText.replace('$',''));
    element.innerText = Math.round(price / hourlyWage * 100) /100 + ' hours of work';
});

The issue with your code if you really want to go that way is the selector here:

const priceElements = document.querySelectorAll("*:not(script):not(style)"); 

it should be:

const priceElements = document.querySelectorAll(".a-price");

enter image description here

Marios
  • 129
  • 1
  • 6
0

The problem is that:

const priceElements = document.querySelectorAll("*:not(script):not(style)"); // select all elements on the page except for scripts and styles

will really select all elements on the page, except for scripts and styles. This includes the html element, the body element, any article element inside that, any section elements inside that, the div elements inside each of those etc. etc. which will all have their innerText to include the string you are looking for.

So as soon as you find the string already on the html or body element you are replacing the whole innerHTML of that top-level element with just one snippet of new innerText.

Unfortunately there's not a super convenient way to query for just the "elements" you actually should be looping over. Because, they are not elements!

See Select "Text" node using querySelector — i.e. what you want to be looping over is just the text nodes, so only the "leaves" of the HTML document model "tree".

But fortunately once you switch to code somehow like:

const priceNodes = getTextNodes(document);

I think you will be farther on your way, where you would just need a helper function something like:

function getTextNodes(node) {
  if (
    node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
    node.nodeType === Node.DOCUMENT_NODE ||
    node.nodeType === Node.ELEMENT_NODE
  ) {
    // this is a container node, recurse into children instead
    return Array.from(node.childNodes).flatMap(
      (childNode) => getTextNodes(childNode)
    );
  } else if (node.nodeType === Node.TEXT_NODE) {
    return [node];
  } else {
    // see https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType re. when this applies
    return [];
  }
}

I might also recommend using node.textContent over node.innerText but either should work — just so long as you are only looking at one single leaf node rather than the concatenation of all the root/branch node's children.

Once that's done I think your actual string replacement needs a bit of tweaking too. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_function_as_the_replacement for how to use the RegEx to directly manipulate. Something more like:

const hourlyWage = 15.00;
getTextNodes(document.body).forEach(node => {
  node.textContent = node.textContent.replace(/\$\d+\.\d{2}/g, (match) => {
    let price = parseFloat(match.slice(1)),
        hours = price / hourlyWage,
        roundedHours = Math.round(hours * 100) / 100;
    return `${roundedHours} hours of work`;
  });
});
natevw
  • 16,807
  • 8
  • 66
  • 90