Skip to content

findClosestElement — GTM Variable Template for DOM

VARIABLES › DOM
findClosestElement EXTENDED DOM

Traverses up the DOM from a starting element to find the closest parent where an attribute matches a value. Returns the dataLayer path to the matching element, or undefined if not found.



Examples

Find parent by tag name
INPUT
Starting Element Path: gtm.element
Attribute to Match: tagName
Value to Match: A
Comparison Mode: eql
OUTPUT
gtm.element.parentElement.parentElement
No match returns undefined
INPUT
Starting Element Path: gtm.element
Value to Match: A
Attribute to Match: tagName
Comparison Mode: eql
OUTPUT
undefined

Related Variables

Same category: DOM


Under the Hood

📜 View Implementation Code
/**
 * Finds the closest parent element where an attribute matches a value.
 * 
 * @param {string} data.src - The starting element path in dataLayer (default: "gtm.element").
 * @param {string} data.attr - The attribute to check (href, class, id, data-*, tagName, innerText, custom).
 * @param {string} [data.customAttr] - Custom attribute name when attr is "custom".
 * @param {*} data.val - Value to match against.
 * @param {string} [data.mod] - Comparison mode: "eql" (exact, default), "cnt" (contains), "rgx" (regex).
 * @param {Function} [data.fn] - Optional custom comparison function (receives attrValue, refValue).
 * @param {Function|string} [data.out] - Optional output handler.
 *
 * Direct-mode specific parameters:
 * @param {Function} [data.pre] - Optional pre-processor function.
 * 
 * @returns {string|undefined} The dataLayer path to the matching element, or undefined if not found.
 *
 * @framework ggLowCodeGTMKit
 */
const copyFromDataLayer = require('copyFromDataLayer');
const getAttributePath = function(attr) {
    if (attr.indexOf('data-') === 0) {
        const camelCase = attr.substring(5).split('-').map(function(text, index) {
            if (index === 0) return text;
            return text.charAt(0).toUpperCase() + text.substring(1);
        }).join('');
        return '.dataset.' + camelCase;
    }
    if (attr === 'tagName') return '.tagName';
    if (attr === 'innerText') return '.innerText';
    return '.attributes.' + attr + '.value';
};
const getCompareFunction = function(mode) {
    if (mode === 'cnt') {
        return function(attrValue, refValue) {
            return typeof attrValue === 'string' && attrValue.indexOf(refValue) > -1;
        };
    }
    if (mode === 'rgx') {
        return function(attrValue, refValue) {
            return typeof attrValue === 'string' && !!attrValue.match(refValue);
        };
    }
    return function(attrValue, refValue) {
        return attrValue === refValue;
    };
};
const findClosestElement = function(startPath, attribute, value, mode, compareFn) {
    if (!attribute) return undefined;
    
    const attrPath = getAttributePath(attribute);
    let currentPath = startPath || 'gtm.element';
    const MAX_DEPTH = 15;
    let depth = 0;

    const compare = typeof compareFn === 'function'
        ? compareFn
        : getCompareFunction(mode);
    
    while (depth < MAX_DEPTH && copyFromDataLayer(currentPath + '.tagName')) {
        const attrValue = copyFromDataLayer(currentPath + attrPath);
        
        if (compare(attrValue, value)) {
            return currentPath;
        }
        
        currentPath = currentPath + '.parentElement';
        depth++;
    }
    
    return undefined;
};
const safeFunction = fn => typeof fn === 'function' ? fn : x => x;
const out = safeFunction(data.out);
// ===============================================================================
// findClosestElement - Direct mode
// ===================================================================
🧪 View Test Scenarios (5 tests)
✅ '[example] Find parent by tag name'
✅ Test find parent by data attribute
✅ Test find parent with custom contains function
✅ Test find parent with exists function (no value needed)
✅ '[example] No match returns undefined'