Skip to content

Commit 6f346da

Browse files
committed
feat: add first version of the gtm template
1 parent 400b334 commit 6f346da

File tree

1 file changed

+364
-0
lines changed

1 file changed

+364
-0
lines changed

template.tpl

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
___TERMS_OF_SERVICE___
2+
3+
By creating or modifying this file you agree to Google Tag Manager's Community
4+
Template Gallery Developer Terms of Service available at
5+
https://developers.google.com/tag-manager/gallery-tos (or such other URL as
6+
Google may provide), as modified from time to time.
7+
8+
9+
___INFO___
10+
11+
{
12+
"type": "MACRO",
13+
"id": "cvt_temp_public_id",
14+
"version": 1,
15+
"securityGroups": [],
16+
"displayName": "Multiple Inputs RegEx Table",
17+
"description": "This template is similar to a \u003ca href\u003d\"https://j.st/4VwZ\"\u003eRegEx Table\u003c/a\u003e, with the addition of being able to use multiple variables as input.",
18+
"containerContexts": [
19+
"WEB"
20+
],
21+
"categories": [
22+
"UTILITY"
23+
]
24+
}
25+
26+
27+
___TEMPLATE_PARAMETERS___
28+
29+
[
30+
{
31+
"type": "LABEL",
32+
"name": "info",
33+
"displayName": "This template is similar to a \u003ca href\u003d\"https://j.st/4VwZ\"\u003eRegEx Table\u003c/a\u003e, with the addition of being able to use multiple variables as input."
34+
},
35+
{
36+
"type": "SIMPLE_TABLE",
37+
"name": "inputsTable",
38+
"displayName": "Inputs Table",
39+
"simpleTableColumns": [
40+
{
41+
"defaultValue": "",
42+
"displayName": "Input Value",
43+
"name": "inputSource",
44+
"type": "TEXT"
45+
}
46+
],
47+
"newRowButtonText": "Add Input",
48+
"help": "Individual \u003cem\u003eInput Values\u003c/em\u003e in the table below will be concatenated, and a regular expression match will be performed on each pattern in the \u003cem\u003eRegEx Table\u003c/em\u003e below. When a regular expression match is found, the corresponding \u003cem\u003eOutput value\u003c/em\u003e will be returned. \n\u003cbr/\u003e\u003cbr/\u003e\nPlease note the following:\n\u003cbr/\u003e- Values in the \u003cem\u003eInput Values\u003c/em\u003e table will be concatenated with a \"_\" by default; however, you can change this setting in the \u003cem\u003eAdvanced Settings\u003c/em\u003e.\n\u003cbr/\u003e- Regular expression pattern matching is evaluated from top to bottom; reorder these as needed.\n\u003cbr/\u003e- Input values may use \u003ca href\u003d\"https://support.google.com/tagmanager/topic/7683268?visit_id\u003d637556720797425891-3442928510\u0026amp;rd\u003d1#web\"\u003evariables\u003c/a\u003e such as \u003cstrong\u003e{{ Title }}\u003c/strong\u003e."
49+
},
50+
{
51+
"type": "SIMPLE_TABLE",
52+
"name": "regexTable",
53+
"displayName": "RegEx Table",
54+
"simpleTableColumns": [
55+
{
56+
"defaultValue": "",
57+
"displayName": "Pattern",
58+
"name": "pattern",
59+
"type": "TEXT"
60+
},
61+
{
62+
"defaultValue": "",
63+
"displayName": "Output",
64+
"name": "output",
65+
"type": "TEXT"
66+
}
67+
],
68+
"newRowButtonText": "Add Pattern",
69+
"help": "The concatenation of the \u003cem\u003evalues\u003c/em\u003e in the \u003cem\u003eInputs Table\u003c/em\u003e above will be matched against each Pattern in the RegEx Table below, from top to bottom. When a match is found, the Output value from that row will be returned. \n\u003cbr/\u003e\u003cbr/\u003e\nEnter Patterns using \u003ca href\u003d\"https://support.google.com/tagmanager/answer/7679109?visit_id\u003d637556570796808874-755449532\u0026rd\u003d1\"\u003eRegular Expressions\u003c/a\u003e. By default, patterns must match the full input string and are case insensitive. This behavior can be adjusted in \u003cem\u003eAdvanced Settings\u003c/em\u003e."
70+
},
71+
{
72+
"type": "CHECKBOX",
73+
"name": "isDefaultValueChecked",
74+
"checkboxText": "Set Default Value",
75+
"simpleValueType": true,
76+
"help": "Explicitly set the value of this variable when no Patterns match.",
77+
"defaultValue": false
78+
},
79+
{
80+
"type": "TEXT",
81+
"name": "defaultValue",
82+
"displayName": "Default Value",
83+
"simpleValueType": true,
84+
"enablingConditions": [
85+
{
86+
"paramName": "isDefaultValueChecked",
87+
"paramValue": true,
88+
"type": "EQUALS"
89+
}
90+
],
91+
"canBeEmptyString": true,
92+
"help": "To set the value to be an empty string, leave the field blank."
93+
},
94+
{
95+
"type": "GROUP",
96+
"name": "advanced",
97+
"displayName": "Advanced Settings",
98+
"groupStyle": "ZIPPY_CLOSED",
99+
"subParams": [
100+
{
101+
"type": "TEXT",
102+
"name": "concatenatingCharacter",
103+
"displayName": "Concatenating Character",
104+
"simpleValueType": true,
105+
"help": "The character that will be used to concatenate the input values, defaults to \"\u003ccode\u003e_\u003c/code\u003e\". For example, if there are two input values inside the \u003cem\u003eInputs Table\u003c/em\u003e, the first one being \u003cem\u003eFoo\u003c/em\u003e and the second one being \u003cem\u003eBar\u003c/em\u003e, the final input will be \u003cem\u003eFoo_Bar\u003c/em\u003e."
106+
},
107+
{
108+
"type": "CHECKBOX",
109+
"name": "ignoreCase",
110+
"checkboxText": "Ignore Case",
111+
"simpleValueType": true,
112+
"defaultValue": true
113+
},
114+
{
115+
"type": "CHECKBOX",
116+
"name": "fullMatchesOnly",
117+
"checkboxText": "Full Matches Only",
118+
"simpleValueType": true,
119+
"defaultValue": true,
120+
"help": "If enabled, patterns must match entire input. This is equivalent to having start (^) and end ($) anchors implicitly around your pattern. If disabled, patterns will match when they are found anywhere in the input. Disabling this option may cause unexpected behavior with the \u003cem\u003e\"Capture Groups and Replace Functionality\"\u003c/em\u003e."
121+
},
122+
{
123+
"type": "CHECKBOX",
124+
"name": "captureGroups",
125+
"checkboxText": "Enable Capture Groups and Replace Functionality",
126+
"simpleValueType": true,
127+
"defaultValue": true,
128+
"help": "If enabled, you can use \u003ca href\u003d\"https://262.ecma-international.org/5.1/#sec-15.5.4.11\"\u003edollar-sign replacement syntax\u003c/a\u003e to include portions of the input (e.g. from capturing groups in the matched pattern) within the output. Given the \u003ca href\u003d\"https://support.google.com/tagmanager/thread/49104943/google-tag-manager-custom-templates-and-regex-support?hl\u003den\"\u003elimited support for regular expressions in Google Tag Manager templates\u003c/a\u003e, only \u003cstrong\u003e$n\u003c/strong\u003e and \u003cstrong\u003e$\u0026\u003c/strong\u003e are supported. Additionally, only the first 5 capture groups are available. \n\u003cbr/\u003e\u003cbr/\u003e\nPlease note the following:\n\u003cbr/\u003e- Using this option with \u003cem\u003e\"Full Matches Only\"\u003c/em\u003e disabled may result in unexpected behavior (i.e. returning the entire input value with the matched portion replaced). \n\u003cbr/\u003e- Using this option with \u003cem\u003e\"Ignore Case\"\u003c/em\u003e enabled, the returned value from the group capture will be in lowercase."
129+
}
130+
]
131+
}
132+
]
133+
134+
135+
___SANDBOXED_JS_FOR_WEB_TEMPLATE___
136+
137+
// APIs
138+
//const log = require('logToConsole');
139+
const getType = require('getType');
140+
141+
142+
// Inputs
143+
const config = {
144+
// inputs, patterns
145+
inputsTable: data.inputsTable || [],
146+
concatenatingCharacter: data.concatenatingCharacter || '_',
147+
regexTable: data.regexTable || [],
148+
// default values
149+
isDefaultValueChecked: data.isDefaultValueChecked || false,
150+
defaultValue: data.defaultValue || '',
151+
// advanced
152+
ignoreCase: data.ignoreCase || false,
153+
fullMatch: data.fullMatch || false,
154+
captureGroups: data.captureGroups || false
155+
};
156+
157+
158+
// Logic
159+
const input = config.inputsTable.map(item => {
160+
return getType(item.inputSource) == 'undefined' ? null : item.inputSource;
161+
}).filter(el => el)
162+
.map(el => config.ignoreCase ? el.toLowerCase() : el)
163+
.join(config.concatenatingCharacter);
164+
165+
const results = config.regexTable.filter(row => {
166+
const regex = config.ignoreCase ? row.pattern.toLowerCase() : row.pattern;
167+
168+
if (config.fullMatch) {
169+
return input.match(regex) && input.match(regex)[0] === input;
170+
}
171+
172+
return input.match(regex);
173+
});
174+
175+
176+
// Return value
177+
if (results.length === 0) {
178+
return getDefaultValue(config.isDefaultValueChecked, config.defaultValue);
179+
} else if (config.captureGroups) {
180+
return doRegexReplacements(results[0]);
181+
}
182+
183+
return results[0].output;
184+
185+
186+
// Functions
187+
function getDefaultValue(isDefaultValueChecked, defaultValue) {
188+
if (isDefaultValueChecked) {
189+
return defaultValue;
190+
}
191+
192+
return undefined;
193+
}
194+
195+
function doRegexReplacements(result) {
196+
let output = result.output;
197+
const pattern = config.ignoreCase ? result.pattern.toLowerCase() : result.pattern;
198+
const inputMatch = input.match(pattern);
199+
200+
// matchAll/replaceALl are not supported!, so we need to use while :(
201+
// also, limited support: https://j.st/4VwL
202+
[1, 2, 3, 4, 5].map((index) => {
203+
// check if we need to replace any $n and if capture group exists in input
204+
while (output.match("\\$" + index) && typeof inputMatch[index] !== "undefined") {
205+
output = output.replace("$" + index, inputMatch[index]);
206+
}
207+
});
208+
209+
while (output.match("\\$\\&")) {
210+
output = output.replace("$&", inputMatch[0]);
211+
}
212+
213+
return output;
214+
}
215+
216+
217+
___TESTS___
218+
219+
scenarios:
220+
- name: Test Capture Groups
221+
code: |-
222+
const regexTable = [
223+
{
224+
pattern: 'My_(.*?)_My_(.*)',
225+
output: 'Testing! $2 $1 $2 $3 $& $&',
226+
},
227+
{
228+
pattern: 'My_(.*?)_My_(.*)',
229+
output: 'Second Testing, to make sure we return the first one',
230+
}
231+
];
232+
233+
let mockData = {
234+
inputsTable: inputsTable,
235+
regexTable: regexTable,
236+
ignoreCase: false,
237+
captureGroups: true
238+
};
239+
240+
// Call runCode to run the template's code.
241+
let variableResult = runCode(mockData);
242+
243+
assertThat(variableResult).isEqualTo('Testing! Bar Foo Bar $3 My_Foo_My_Bar My_Foo_My_Bar');
244+
- name: Test Capture Groups, ignore Case
245+
code: |-
246+
const regexTable = [
247+
{
248+
pattern: 'my_(.*?)_My_(.*)',
249+
output: 'Testing! $2 $1 $2 $3 $& $&',
250+
},
251+
{
252+
pattern: 'my_(.*?)_My_(.*)',
253+
output: 'Second Testing, to make sure we return the first one',
254+
}
255+
];
256+
257+
let mockData = {
258+
inputsTable: inputsTable,
259+
regexTable: regexTable,
260+
ignoreCase: true,
261+
captureGroups: true
262+
};
263+
264+
// Call runCode to run the template's code.
265+
let variableResult = runCode(mockData);
266+
267+
assertThat(variableResult).isEqualTo('Testing! bar foo bar $3 my_foo_my_bar my_foo_my_bar');
268+
- name: Test without Capture Groups
269+
code: "const regexTable = [\n {\n pattern: 'my_(.*?)_My_(.*)',\n output:\
270+
\ 'Testing! $2 $1 $2 $3 $& $&',\n },\n {\n pattern: 'my_(.*?)_My_(.*)',\n\
271+
\ output: 'Second Testing, to make sure we return the first one',\n }\n];\n\
272+
\nlet mockData = {\n inputsTable: inputsTable,\n regexTable: regexTable,\n \
273+
\ ignoreCase: true, \n captureGroups: false\n};\n\n// Call runCode to run the\
274+
\ template's code.\nlet variableResult = runCode(mockData);\n\nassertThat(variableResult).isEqualTo('Testing!\
275+
\ $2 $1 $2 $3 $& $&');"
276+
- name: Test with Diff Concatenating Character
277+
code: |-
278+
const regexTable = [
279+
{
280+
pattern: 'My_(.*?)-My_(.*)',
281+
output: 'Testing! $2 $1 $&',
282+
}
283+
];
284+
285+
let mockData = {
286+
inputsTable: inputsTable,
287+
regexTable: regexTable,
288+
ignoreCase: false,
289+
captureGroups: true,
290+
concatenatingCharacter: '-'
291+
};
292+
293+
// Call runCode to run the template's code.
294+
let variableResult = runCode(mockData);
295+
296+
assertThat(variableResult).isEqualTo('Testing! Bar Foo My_Foo-My_Bar');
297+
- name: Test Full Match set to False
298+
code: |-
299+
const regexTable = [
300+
{
301+
pattern: 'Foo',
302+
output: 'Testing $&',
303+
}
304+
];
305+
306+
let mockData = {
307+
inputsTable: inputsTable,
308+
regexTable: regexTable,
309+
ignoreCase: false,
310+
captureGroups: true,
311+
fullMatch: false
312+
};
313+
314+
// Call runCode to run the template's code.
315+
let variableResult = runCode(mockData);
316+
317+
assertThat(variableResult).isEqualTo('Testing Foo');
318+
- name: Test Full Match set to True
319+
code: |-
320+
const regexTable = [
321+
{
322+
pattern: 'Foo',
323+
output: 'Testing $&',
324+
}
325+
];
326+
327+
let mockData = {
328+
inputsTable: inputsTable,
329+
regexTable: regexTable,
330+
ignoreCase: false,
331+
captureGroups: true,
332+
fullMatch: true
333+
};
334+
335+
// Call runCode to run the template's code.
336+
let variableResult = runCode(mockData);
337+
338+
assertThat(variableResult).isEqualTo(undefined);
339+
- name: Test Default Value
340+
code: "const regexTable = [\n {\n pattern: 'Foo',\n output: 'Testing $&',\n\
341+
\ }\n];\n\nlet mockData = {\n inputsTable: inputsTable,\n regexTable: regexTable,\n\
342+
\ ignoreCase: false,\n captureGroups: true,\n fullMatch: true,\n isDefaultValueChecked:\
343+
\ true,\n defaultValue: 'Def Value' \n};\n\n// Call runCode to run the template's\
344+
\ code.\nlet variableResult = runCode(mockData);\n\nassertThat(variableResult).isEqualTo('Def\
345+
\ Value');"
346+
setup: |-
347+
const inputsTable = [
348+
{
349+
inputSource: "My_Foo"
350+
},
351+
{
352+
inputSource: "My_Bar"
353+
},
354+
{
355+
inputSource: undefined
356+
}
357+
];
358+
359+
360+
___NOTES___
361+
362+
Created on 5/8/2021, 12:35:44 PM
363+
364+

0 commit comments

Comments
 (0)