findClosestElement — GTM Variable Template for 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
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
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'