GithubHelp home page GithubHelp logo

Comments (10)

hjhart avatar hjhart commented on July 20, 2024

I'm looking for exactly this functionality. It looks like this issue in particular doesn't have any activity though.

@willcosgrove can you share you code here?

Maintainers, any chance of getting this feature added?

from ahoy.js.

willcosgrove avatar willcosgrove commented on July 20, 2024

It unfortunately takes a fair bit of code to patch it in, but it would be pretty trivial to add into the library itself.

I was only adding this to the trackClicks() function, not any of the other tracking functions.

trackClicks() looks like this:

trackClicks = function () {
  onEvent("click", "a, button, input[type=submit]", function (e) {
    let properties = eventProperties.call(this, e);
    properties.text = properties.tag == "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
    properties.href = this.href;
    ahoy.track("$click", properties);
  })
}

My solution was to modify the onEvent() function which is responsible for adding the event listener, and firing the provided callback function when a loggable event occurs. So my onEvent function looks like this:

function onEvent(eventName, selector, callback) {
  document.addEventListener(eventName, function (e) {
    let matchedElement = matchesSelector(e.target, selector);
    if (matchedElement && !matchesSelector(matchedElement, "[data-no-track], [data-no-track] *")) {
      callback.call(matchedElement, e);
    }
  });
}

So to patch all of that in, I've got my own ahoy.js file, which imports ahoy and then modifies the trackClicks() function on the ahoy object, and re-exports it. Then in my normal application code, I import ahoy from my ahoy.js file, instead of from the package.

The final ahoy.js file I have looks like this:

let ahoy = require("ahoy.js").default

// Overriding trackClicks() functionality to support not tracking clicks inside
// a [data-no-track] element
function matchesSelector(element, selector) {
  let matches = element.matches ||
    element.matchesSelector ||
    element.mozMatchesSelector ||
    element.msMatchesSelector ||
    element.oMatchesSelector ||
    element.webkitMatchesSelector;

  if (matches) {
    if (matches.apply(element, [selector])) {
      return element;
    } else if (element.parentElement) {
      return matchesSelector(element.parentElement, selector);
    }
    return null;
  } else {
    log("Unable to match");
    return null;
  }
}

function onEvent(eventName, selector, callback) {
  document.addEventListener(eventName, function (e) {
    let matchedElement = matchesSelector(e.target, selector);
    if (matchedElement && !matchesSelector(matchedElement, "[data-no-track], [data-no-track] *")) {
      callback.call(matchedElement, e);
    }
  });
}

function cleanObject(obj) {
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (obj[key] === null) {
        delete obj[key];
      }
    }
  }
  return obj;
}

function page() {
  return window.location.pathname;
}

function presence(str) {
  return (str && str.length > 0) ? str : null;
}

function getClosestSection(element) {
  for ( ; element && element !== document; element = element.parentNode) {
    if (element.hasAttribute('data-section')) {
      return element.getAttribute('data-section');
    }
  }

  return null;
}

function eventProperties() {
  return cleanObject({
    tag: this.tagName.toLowerCase(),
    id: presence(this.id),
    "class": presence(this.className),
    page: page(),
    section: getClosestSection(this)
  });
}

ahoy.trackClicks = function () {
  onEvent("click", "a, button, input[type=submit]", function (e) {
    let properties = eventProperties.call(this, e);
    properties.text = properties.tag == "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
    properties.href = this.href;
    ahoy.track("$click", properties);
  });
};

export default ahoy

If you also needed to extend this modification for form submissions, it would be pretty straightforward. You could just copy the trackSubmissions() function from the package and redefine it in your ahoy.js file using the new onEvent defined in that file.

from ahoy.js.

ankane avatar ankane commented on July 20, 2024

Hey, sorry for the delay. With Ahoy.js 0.3.9+, you can pass a selector to trackClicks to accomplish this.

ahoy.trackClicks("a:not([data-no-track])")

Edit: The selector for the behavior described in the initial comment should be:

ahoy.trackClicks(":not([data-no-track]) > a:not([data-no-track])")

Edit 2: My bad, this only works for the direct parent element, while the initial comment was for all parents.

from ahoy.js.

hjhart avatar hjhart commented on July 20, 2024

Whoa! Thanks so much @ankane. Thanks also to @willcosgrove.

Do you feel like this issue is resolved now @willcosgrove?

from ahoy.js.

ankane avatar ankane commented on July 20, 2024

It looks like there's not an easy way to constructor a CSS selector that works for all parent elements (at least not that I can figure out) and leads to noisy CSS selectors, so happy to include this functionality if you'd like to submit a PR @willcosgrove. Let's go with data-ahoy-skip for the name.

from ahoy.js.

willcosgrove avatar willcosgrove commented on July 20, 2024

@ankane maybe I'm just not seeing it, but couldn't you just drop the direct child operator and get the desired result?

ahoy.trackClicks(":not([data-no-track]) a:not([data-no-track])")

edit: Regardless, you are right that it does lead to some noisy selectors. I would be happy to submit a PR for it. It may take me a couple of days to get to though.

from ahoy.js.

hjhart avatar hjhart commented on July 20, 2024

@willcosgrove I tried this out briefly and it stopped tracking for this:

<a href="blah" data-no-track=true>Click me!</a>

But not for

<div data-no-track="true">
  <a href="blah">Click me!</a>
</div>

Am I misunderstanding the selector?

I may just end up being okay with the below example for my purposes. Thanks for this thread!

ahoy.trackClicks("a:not([data-no-track])")

from ahoy.js.

willcosgrove avatar willcosgrove commented on July 20, 2024

Ah, I understand now why that selector doesn't work. It matches too much. Yeah I think this would need to be supported inside ahoy.js so that it can check for data-ahoy-skip itself. I'll try and work on a PR for this soon.

from ahoy.js.

willcosgrove avatar willcosgrove commented on July 20, 2024

Is there any desire to also support the inverse operation? Enabling tracking nested under a certain data attribute? data-ahoy to turn it back on? Or just combine the two into a boolean data-ahoy="true" or data-ahoy="false" which matches how Turbo enables and disables its functionality.

It wouldn't be a much more complicated implementation. Just check the matched element for its closest("[data-ahoy]") (which can potentially be itself), and if one is found return early if that element's dataset.ahoy === "false".

from ahoy.js.

hjhart avatar hjhart commented on July 20, 2024

Is there any desire to also support the inverse operation? Enabling tracking nested under a certain data attribute? data-ahoy to turn it back on? Or just combine the two into a boolean data-ahoy="true" or data-ahoy="false" which matches how Turbo enables and disables its functionality.

It wouldn't be a much more complicated implementation. Just check the matched element for its closest("[data-ahoy]") (which can potentially be itself), and if one is found return early if that element's dataset.ahoy === "false".

I personally don't have a need for disabling anything but a single link, so I'm fine with the original data-skip-ahoy approach.

I'll let others weigh in, though!

from ahoy.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.