Skip to content

findClosestElement β€” GTM Variable Template for GTM

VARIABLES β€Ί GTM
findClosestElement EXTENDED GTM
Direct (.tpl) Apply (.tpl)

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.



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


πŸ“œ 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'