Live feedback tracking system

One of the first problems I had to solve was how to track the HTML Element where user left its feedback. Live feedback will do something like this:

  • User wants to leave feedback on a specific element on the page.
  • User clicks on the + button to leave feedback.
  • User clicks on the element where he wants to leave feedback.
  • User writes the feedback and clicks on the Send button.
  • Feedback is sent to a server (not really since there is no server at the moment I'm writing this. We will cover this in another post).

First approach

Why don't we track elements by coordinates?

Spoiler: didn't work so far.

It was something like this:

  1. When the user clicks on the + button, we will add a click event listener to the document object.
  2. When the user clicks on the element where he wants to leave feedback, we will get the target property of the event object.
document.addEventListener('click', (event) => {
  const target = event.target;
  console.log(target);
});
  1. With target.getBoundingClientRect() we can get the coordinates of the element.
document.addEventListener('click', (event) => {
  const target = event.target;
  const { x, y } = target.getBoundingClientRect();
  console.log(x, y);
});
  1. We can store these coordinates in a variable and send them to the server when the user clicks on the Send button.
// Examples are illustrative only, not real code

let coordinates = { x: 0, y: 0 };

document.addEventListener('click', (event) => {
  const target = event.target;
  const { x, y } = target.getBoundingClientRect();
  coordinates = { x, y };
});

document.getElementById('send-feedback').addEventListener('click', () => {
  createFeedback(message, coordinates);
});

Problems happened

As soon as I started to implement this approach, I realized that it wouldn't work.

  1. The coordinates are relative to the viewport, not to the element itself.
  1. In modern web apps, layout changes are very common. Elements can be added, removed, or moved around the page. So, the coordinates are not reliable.
  2. The user can resize the browser window. The coordinates will be wrong again.
  3. The user can zoom in or out. The coordinates will be wrong again.
  4. The user can rotate the device. The coordinates will be wrong again.

Of course these problems can be solved, but it would be a lot of work and I would have to deal with a lot of edge cases. So, if something is too complicated or forced, it's probably because we are doing it wrong. So I decided to change the approach.

Second approach

Understanding the point above, I decided to change the approach. All of the weaknesses of the first approach can be simplified to a single sentences: "If we need to track an element why aren't we tracking the element itself?".

In other words, all the time we are using CSS Selectors to select elements on the page, why don't we use these selectors to track the elements?

How it works

  1. When the user clicks on the + button, we will add a click event listener to the document object.
  2. When the user clicks on the element where he wants to leave feedback, we will get the target property of the event object.
  3. We will get the CSS Selector of the element using the target property.
  4. We can store this selector in a variable and send it to the server when the user clicks on the Send button.
  5. When we want to show the feedback, we can use the CSS Selector to select the element and show the feedback.
document.addEventListener('click', (event) => {
  const target = event.target;
  const selector = getSelector(target);
  createFeedback(message, selector);
});
  1. To get the CSS Selector of an element we can use the document.querySelector() method.
function getElement(selector) {
  return document.querySelector(selector);
}

This approach is much simpler and more reliable than the first one. We don't need to worry about the viewport, layout changes, browser window resizing, zooming, or device rotation. We just need to worry about the CSS Selectors.

Problems

This way is much simpler and more reliable than the first one, but it's not perfect. There are some problems that we need to solve:

  1. The CSS Selector can be too long and complex.
  2. The CSS Selector can be too generic and select more than one element.
  3. If CSS Selector is too specific, it can break easily if the layout changes.
  4. It's very common to have dynamic css classes and attributes.
  5. If we are using CSS-in-JS classes are generated hash strings generated by the library and may change on every build.
  6. If we are using Shadow DOM the CSS Selector will not work.
  7. If we are using iFrames the CSS Selector will not work.
  8. We have to deal with pseudo-classes and pseudo-elements.

Right now you will probably be thinking: "Wow, this approach is not so good either the previous one had fewer problems 🤔".

Yes, you are right. But the difference is that the problems of this approach are easier to solve and there are already libraries that can help us with that. For example, css-selector-generator or @medv/finder.

In the next post, I will show you how this solution is being implemented and how we are solving these problems. Stay tuned! 🚀.


Click here to share this article with your friends on X if you liked it.