0

I'd like to record all the interactions of the users of a web app so I can anlayze how they do use the application. In the first attempt I just want to store the js path of any element clicked. JSPath is to JSON what XPath is to xml.

On Chromium browsers you can get the js path of an element: just open de dev tools (F12), select the "Elements" tab (first one) and right click a node in the DOM and select Copy > Copy JS Path. You'll get the JS Path of the element in your clipboard, something like:

document.querySelector("#how-to-format > div.s-sidebarwidget--content.d-block > div:nth-child(5) > span:nth-child(4)")

If you pase the above code in the console you'll get a reference to the selected node. It's specially useful if the app contains web components so the JSPath contains the shadowRoot selector to traverse the dom to reach the element:

document.querySelector("#one").shadowRoot.querySelector("div > div")

On the first attempt, I think I can just listen for any click in dom and then record the jspath of the element that got the click


 document.addEventListener('click', function (event){
   //Get the jspath of the element

});

Is there any easy way to get the jspath of the event's target?

Thanks

Daní
  • 355
  • 1
  • 17
  • 2
    Please visit [help], take [tour] to see what and [ask]. Do some research, search for related topics on SO; if you get stuck, post a [mcve] of your attempt, noting input and expected output, preferably in a [Stacksnippet](https://blog.stackoverflow.com/2014/09/introducing-runnable-javascript-css-and-html-code-snippets/) – mplungjan Feb 27 '21 at 18:58
  • 2
    What is a jspath? – evolutionxbox Feb 27 '21 at 19:01
  • jspath is a DSL that allows to navigate and find data within JSON documents. jspath is to json what xpath is to xml. On chrome, you can open the developer tools, select a node in the 'elements' tab, right click and copy > Copy JS path to get the jspath of that element. You can then paste the selection in the console and you'll get the same element. https://www.npmjs.com/package/jspath – Daní Feb 27 '21 at 19:09
  • [this question solves your problem](https://stackoverflow.com/questions/5728558/get-the-dom-path-of-the-clicked-a) – Omar Keshk Feb 28 '21 at 11:04

1 Answers1

1

After some research, I've not been able to find any lib at npm or github neither a response in stackoverflow that supplies what I did expect, so I decided to implement it. Below I'm pasting a simple typescript module that it does the trick. The hardest thing was to deal with slots.

//last 3 elements are window, document and html nodes. We don't need them
function shouldDismiss(tg): boolean {
    return tg === window || tg === document || tg?.nodeName == 'HTML';
}

function isSlot(tg): boolean {
    return tg?.nodeName === 'SLOT';
}

function isDocumentFragment(node): boolean {
    return node?.nodeName === '#document-fragment';
}

function getNodeId(node) {
    return node.id ? `#${node.id}` : ''
}

function getNodeClass(node) {
    if (!node?.classList)
        return '';
    let classes = '';
    for (let cssClass of node.classList)
        classes += `.${cssClass}`
    return classes;
}

function getNodeSelector(node) {
    return `${node.localName || node.nodeName}${getNodeId(node)}${getNodeClass(node)}`
}

export function getEventJSPath(event: Event): string {
    const path = event.composedPath();

    let inSlot = false;
    let jsPath = path.reduce((previousValue: string, currentValue: any) => {
        if (shouldDismiss(currentValue))
            return previousValue;
        if (isSlot(currentValue)) {
            inSlot = true;
            return previousValue
        }
        if (inSlot) {
            if (isDocumentFragment(currentValue))
                inSlot = false;
            return previousValue;
        }
        if (isDocumentFragment(currentValue))
            return previousValue;
        const selector = `.querySelector("${getNodeSelector(currentValue)}")${previousValue}`;

        //if parent node is a document fragment we need to query its shadowRoot
        return isDocumentFragment(currentValue?.parentNode) ? '.shadowRoot' + selector : selector
    }, '');
    jsPath = 'document' + jsPath;

    //Check success on non production environments
    if (process?.env != 'prod') {
        try {
            const el = eval(jsPath);
            if (el != path[0]) {
                debugger;
                console.error('js path error');
            }
        }
        catch (e) {
            debugger;
            console.error('js path error: ' + e.toString());
        }

    }
    return jsPath;
}


Daní
  • 355
  • 1
  • 17