@4site/engrid-scripts 0.22.15 → 0.22.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,13 @@
1
- // The DataLayer class is a singleton class that is responsible for managing the data layer events.
2
- // It listens to the EnForm onSubmit event and the RememberMe onLoad event.
3
- // It also listens to the blur, change, and submit events of the form fields.
4
- // It adds the following events to the data layer:
5
- // - EN_PAGE_VIEW
6
- // - EN_SUCCESSFUL_DONATION
7
- // - EN_PAGEJSON_{property}
8
- // - EN_SUBMISSION_SUCCESS_{pageType}
9
- // - EN_URLPARAM_{key}-{value}
10
- // - EN_RECURRING_FREQUENCIES
11
- // - EN_FASTFORMFILL_PERSONALINFO_SUCCESS
12
- // - EN_FASTFORMFILL_PERSONALINFO_PARTIALSUCCESS
13
- // - EN_FASTFORMFILL_PERSONALINFO_FAILURE
14
- // - EN_FASTFORMFILL_ADDRESS_SUCCESS
15
- // - EN_FASTFORMFILL_ADDRESS_PARTIALSUCCESS
16
- // - EN_FASTFORMFILL_ADDRESS_FAILURE
17
- // - EN_FASTFORMFILL_ALL_SUCCESS
18
- // - EN_FASTFORMFILL_ALL_FAILURE
19
- // - EN_SUBMISSION_WITH_EMAIL_OPTIN
20
- // - EN_SUBMISSION_WITHOUT_EMAIL_OPTIN
21
- // - EN_FORM_VALUE_UPDATED
22
- import { EngridLogger, ENGrid, EnForm, FastFormFill, RememberMeEvents, } from ".";
1
+ // DataLayer: singleton helper for pushing structured analytics events/vars to window.dataLayer.
2
+ // On load it emits one aggregated event `pageJsonVariablesReady` with:
3
+ // EN_PAGEJSON_* (normalized pageJson), EN_URLPARAM_*, EN_RECURRING_FREQUENCIES (donation pages),
4
+ // and EN_SUBMISSION_SUCCESS_{PAGETYPE} when on the final page.
5
+ // User actions emit: EN_FORM_VALUE_UPDATED (field changes) and submission opt‑in/out events.
6
+ // Queued end‑of‑gift events/variables (via addEndOfGiftProcessEvent / addEndOfGiftProcessVariable)
7
+ // are replayed after a successful gift process load.
8
+ // Sensitive payment/bank fields are excluded; selected PII fields are Base64 “hashed” (btoa — not cryptographic).
9
+ // Replace with a real hash (e.g., SHA‑256) if required.
10
+ import { EngridLogger, ENGrid, EnForm, RememberMeEvents } from ".";
23
11
  export class DataLayer {
24
12
  constructor() {
25
13
  this.logger = new EngridLogger("DataLayer", "#f1e5bc", "#009cdc", "📊");
@@ -77,108 +65,53 @@ export class DataLayer {
77
65
  }
78
66
  transformJSON(value) {
79
67
  if (typeof value === "string") {
80
- return value.toUpperCase().split(" ").join("-").replace(":-", "-");
68
+ return value
69
+ .toUpperCase()
70
+ .trim()
71
+ .replace(/\s+/g, "-")
72
+ .replace(/:-/g, "-");
81
73
  }
82
- else if (typeof value === "boolean") {
83
- value = value ? "TRUE" : "FALSE";
84
- return value;
74
+ if (typeof value === "boolean") {
75
+ return value ? "TRUE" : "FALSE";
76
+ }
77
+ if (typeof value === "number") {
78
+ return value; // Preserve numeric type for analytics platforms that infer number vs string
85
79
  }
86
80
  return "";
87
81
  }
88
82
  onLoad() {
89
- // Collect all data layer events and variables to push at once
83
+ // Collect all data layer variables to push at once
90
84
  const dataLayerData = {};
91
- const dataLayerEvents = [];
92
85
  if (ENGrid.getGiftProcess()) {
93
86
  this.logger.log("EN_SUCCESSFUL_DONATION");
94
- dataLayerEvents.push("EN_SUCCESSFUL_DONATION");
95
87
  this.addEndOfGiftProcessEventsToDataLayer();
96
88
  }
97
- else {
98
- this.logger.log("EN_PAGE_VIEW");
99
- dataLayerEvents.push("EN_PAGE_VIEW");
100
- }
101
89
  if (window.pageJson) {
102
90
  const pageJson = window.pageJson;
103
91
  for (const property in pageJson) {
104
- if (!Number.isNaN(pageJson[property])) {
105
- dataLayerEvents.push(`EN_PAGEJSON_${property.toUpperCase()}-${pageJson[property]}`);
106
- dataLayerData[`EN_PAGEJSON_${property.toUpperCase()}`] =
107
- pageJson[property];
108
- }
109
- else {
110
- dataLayerEvents.push(`EN_PAGEJSON_${property.toUpperCase()}-${this.transformJSON(pageJson[property])}`);
111
- dataLayerData[`EN_PAGEJSON_${property.toUpperCase()}`] =
112
- this.transformJSON(pageJson[property]);
113
- }
114
- dataLayerEvents.push("EN_PAGEJSON_" + property.toUpperCase());
115
- dataLayerData.eventValue = pageJson[property];
92
+ const key = `EN_PAGEJSON_${property.toUpperCase()}`;
93
+ const value = pageJson[property];
94
+ dataLayerData[key] = this.transformJSON(value);
116
95
  }
117
96
  if (ENGrid.getPageCount() === ENGrid.getPageNumber()) {
118
- dataLayerEvents.push("EN_SUBMISSION_SUCCESS_" + pageJson.pageType.toUpperCase());
119
97
  dataLayerData[`EN_SUBMISSION_SUCCESS_${pageJson.pageType.toUpperCase()}`] = "TRUE";
120
98
  }
121
99
  }
122
100
  const urlParams = new URLSearchParams(window.location.search);
123
101
  urlParams.forEach((value, key) => {
124
- dataLayerEvents.push(`EN_URLPARAM_${key.toUpperCase()}-${this.transformJSON(value)}`);
125
102
  dataLayerData[`EN_URLPARAM_${key.toUpperCase()}`] =
126
103
  this.transformJSON(value);
127
104
  });
128
105
  if (ENGrid.getPageType() === "DONATION") {
129
106
  const recurrFreqEls = document.querySelectorAll('[name="transaction.recurrfreq"]');
130
107
  const recurrValues = [...recurrFreqEls].map((el) => el.value);
131
- dataLayerEvents.push("EN_RECURRING_FREQUENCIES");
132
- dataLayerData[`'EN_RECURRING_FREQEUENCIES'`] = recurrValues;
133
- }
134
- let fastFormFill = false;
135
- // Fast Form Fill - Personal Details
136
- const fastPersonalDetailsFormBlock = document.querySelector(".en__component--formblock.fast-personal-details");
137
- if (fastPersonalDetailsFormBlock) {
138
- const allPersonalMandatoryInputsAreFilled = FastFormFill.allMandatoryInputsAreFilled(fastPersonalDetailsFormBlock);
139
- const somePersonalMandatoryInputsAreFilled = FastFormFill.someMandatoryInputsAreFilled(fastPersonalDetailsFormBlock);
140
- if (allPersonalMandatoryInputsAreFilled) {
141
- dataLayerEvents.push("EN_FASTFORMFILL_PERSONALINFO_SUCCESS");
142
- fastFormFill = true;
143
- }
144
- else if (somePersonalMandatoryInputsAreFilled) {
145
- dataLayerEvents.push("EN_FASTFORMFILL_PERSONALINFO_PARTIALSUCCESS");
146
- }
147
- else {
148
- dataLayerEvents.push("EN_FASTFORMFILL_PERSONALINFO_FAILURE");
149
- }
150
- }
151
- // Fast Form Fill - Address Details
152
- const fastAddressDetailsFormBlock = document.querySelector(".en__component--formblock.fast-address-details");
153
- if (fastAddressDetailsFormBlock) {
154
- const allAddressMandatoryInputsAreFilled = FastFormFill.allMandatoryInputsAreFilled(fastAddressDetailsFormBlock);
155
- const someAddressMandatoryInputsAreFilled = FastFormFill.someMandatoryInputsAreFilled(fastAddressDetailsFormBlock);
156
- if (allAddressMandatoryInputsAreFilled) {
157
- dataLayerEvents.push("EN_FASTFORMFILL_ADDRESS_SUCCESS");
158
- fastFormFill = fastFormFill ? true : false; // Only set to true if it was true before
159
- }
160
- else if (someAddressMandatoryInputsAreFilled) {
161
- dataLayerEvents.push("EN_FASTFORMFILL_ADDRESS_PARTIALSUCCESS");
162
- }
163
- else {
164
- dataLayerEvents.push("EN_FASTFORMFILL_ADDRESS_FAILURE");
165
- }
166
- }
167
- if (fastFormFill) {
168
- dataLayerEvents.push("EN_FASTFORMFILL_ALL_SUCCESS");
169
- }
170
- else {
171
- dataLayerEvents.push("EN_FASTFORMFILL_ALL_FAILURE");
108
+ dataLayerData[`EN_RECURRING_FREQUENCIES`] = recurrValues;
172
109
  }
173
110
  // Push all collected variables at once
174
111
  if (Object.keys(dataLayerData).length > 0) {
175
112
  dataLayerData.event = "pageJsonVariablesReady";
176
113
  this.dataLayer.push(dataLayerData);
177
114
  }
178
- // Push all collected events individually (GTM requirement)
179
- dataLayerEvents.forEach((event) => {
180
- this.dataLayer.push({ event });
181
- });
182
115
  this.attachEventListeners();
183
116
  }
184
117
  onSubmit() {
@@ -266,7 +199,7 @@ export class DataLayer {
266
199
  }
267
200
  addEndOfGiftProcessVariable(variableName, variableValue = "") {
268
201
  this.storeEndOfGiftProcessData({
269
- [`'${variableName.toUpperCase()}'`]: variableValue,
202
+ [variableName.toUpperCase()]: variableValue,
270
203
  });
271
204
  }
272
205
  storeEndOfGiftProcessData(data) {
@@ -15,6 +15,7 @@ export interface Options {
15
15
  MaxAmount?: number;
16
16
  MinAmountMessage?: string;
17
17
  MaxAmountMessage?: string;
18
+ UseAmountValidatorFromEN?: boolean;
18
19
  SkipToMainContentLink?: boolean;
19
20
  SrcDefer?: boolean;
20
21
  NeverBounceAPI?: string | null;
@@ -14,6 +14,7 @@ export const OptionsDefaults = {
14
14
  MaxAmount: 100000,
15
15
  MinAmountMessage: "Amount must be at least $1",
16
16
  MaxAmountMessage: "Amount must be less than $100,000",
17
+ UseAmountValidatorFromEN: false,
17
18
  SkipToMainContentLink: true,
18
19
  SrcDefer: true,
19
20
  NeverBounceAPI: null,
@@ -1,13 +1,16 @@
1
1
  export declare class MinMaxAmount {
2
2
  private _form;
3
3
  private _amount;
4
+ private _frequency;
4
5
  private minAmount;
5
6
  private maxAmount;
6
7
  private minAmountMessage;
7
8
  private maxAmountMessage;
9
+ private enAmountValidator;
8
10
  private logger;
9
11
  constructor();
10
12
  shouldRun(): boolean;
11
13
  enOnValidate(): void;
12
14
  liveValidate(): void;
15
+ private setValidationConfigFromEN;
13
16
  }
@@ -1,19 +1,22 @@
1
1
  // This script adds an erros message to the page if the amount is greater than the max amount or less than the min amount.
2
- import { DonationAmount, EnForm, ENGrid, EngridLogger } from ".";
2
+ import { DonationAmount, DonationFrequency, EnForm, ENGrid, EngridLogger, } from ".";
3
3
  export class MinMaxAmount {
4
4
  constructor() {
5
5
  var _a, _b;
6
6
  this._form = EnForm.getInstance();
7
7
  this._amount = DonationAmount.getInstance();
8
+ this._frequency = DonationFrequency.getInstance();
8
9
  this.minAmount = (_a = ENGrid.getOption("MinAmount")) !== null && _a !== void 0 ? _a : 1;
9
10
  this.maxAmount = (_b = ENGrid.getOption("MaxAmount")) !== null && _b !== void 0 ? _b : 100000;
10
11
  this.minAmountMessage = ENGrid.getOption("MinAmountMessage");
11
12
  this.maxAmountMessage = ENGrid.getOption("MaxAmountMessage");
13
+ this.enAmountValidator = null;
12
14
  this.logger = new EngridLogger("MinMaxAmount", "white", "purple", "🔢");
13
15
  if (!this.shouldRun()) {
14
16
  // If we're not on a Donation Page, get out
15
17
  return;
16
18
  }
19
+ this.setValidationConfigFromEN();
17
20
  this._amount.onAmountChange.subscribe((s) => window.setTimeout(this.liveValidate.bind(this), 1000) // Wait 1 second for the amount to be updated
18
21
  );
19
22
  this._form.onValidate.subscribe(this.enOnValidate.bind(this));
@@ -68,4 +71,56 @@ export class MinMaxAmount {
68
71
  ENGrid.removeError(".en__field--withOther");
69
72
  }
70
73
  }
74
+ setValidationConfigFromEN() {
75
+ if (!ENGrid.getOption("UseAmountValidatorFromEN") ||
76
+ !window.EngagingNetworks.validators) {
77
+ this.logger.log("Not setting validation config from EN.");
78
+ return;
79
+ }
80
+ // Find the amount validator for the donation amount field
81
+ // It should be of type "AMNT" or "FAMNT" and have
82
+ // a componentId that matches the donation amount field.
83
+ this.enAmountValidator = window.EngagingNetworks.validators.find((validator) => {
84
+ var _a;
85
+ return ((validator.type === "FAMNT" || validator.type === "AMNT") &&
86
+ ((_a = document
87
+ .querySelector(".en__field--" + validator.componentId)) === null || _a === void 0 ? void 0 : _a.classList.contains("en__field--donationAmt")));
88
+ });
89
+ if (!this.enAmountValidator || !this.enAmountValidator.format) {
90
+ return;
91
+ }
92
+ this.logger.log(`Detected an amount validator for donation amount on the page:`, this.enAmountValidator);
93
+ // Static amount validator
94
+ if (this.enAmountValidator.type === "AMNT") {
95
+ this.minAmount = Number(this.enAmountValidator.format.split("~")[0]);
96
+ this.maxAmount = Number(this.enAmountValidator.format.split("~")[1]);
97
+ this.minAmountMessage = this.enAmountValidator.errorMessage;
98
+ this.maxAmountMessage = this.enAmountValidator.errorMessage;
99
+ this.logger.log(`Setting new values - Min Amount: ${this.minAmount}, Max Amount: ${this.maxAmount}, Error Message: ${this.minAmountMessage}`);
100
+ }
101
+ // Frequency-based amount validator
102
+ if (this.enAmountValidator.type === "FAMNT") {
103
+ this._frequency.onFrequencyChange.subscribe((freq) => {
104
+ if (!this.enAmountValidator || !this.enAmountValidator.format)
105
+ return;
106
+ // In the validator, "onetime" is written as "SINGLE"
107
+ // Validator format for FAMNT is like SINGLE:10~100000|MONTHLY:5~100000|QUARTERLY:25~100000|ANNUAL:25~100000
108
+ const frequency = freq === "onetime" ? "SINGLE" : freq.toUpperCase();
109
+ const validationRange = this.enAmountValidator.format
110
+ .split("|")
111
+ .find((range) => range.startsWith(frequency));
112
+ if (!validationRange) {
113
+ this.logger.log(`No validation range found for frequency: ${frequency}`);
114
+ return;
115
+ }
116
+ const amounts = validationRange.split(":")[1].split("~");
117
+ this.minAmount = Number(amounts[0]);
118
+ this.maxAmount = Number(amounts[1]);
119
+ this.minAmountMessage = this.enAmountValidator.errorMessage;
120
+ this.maxAmountMessage = this.enAmountValidator.errorMessage;
121
+ this.logger.log(`Frequency changed to ${frequency}, updating min and max amounts`, validationRange);
122
+ this.logger.log(`Setting new values - Min Amount: ${this.minAmount}, Max Amount: ${this.maxAmount}, Error Message: ${this.minAmountMessage}`);
123
+ });
124
+ }
125
+ }
71
126
  }
@@ -10,6 +10,7 @@
10
10
  * "Other": "other",
11
11
  * },
12
12
  * default: 30,
13
+ * stickyDefault: false, // Optional. When true, every swap forces the default amount to be (re)selected
13
14
  * },
14
15
  * "monthly": {
15
16
  * amounts: {
@@ -20,6 +21,7 @@
20
21
  * "Other": "other",
21
22
  * },
22
23
  * default: 15,
24
+ * stickyDefault: true, // Example forcing default on each frequency swap
23
25
  * },
24
26
  * };
25
27
  */
@@ -15,6 +15,7 @@
15
15
  * "Other": "other",
16
16
  * },
17
17
  * default: 30,
18
+ * stickyDefault: false, // Optional. When true, every swap forces the default amount to be (re)selected
18
19
  * },
19
20
  * "monthly": {
20
21
  * amounts: {
@@ -25,6 +26,7 @@
25
26
  * "Other": "other",
26
27
  * },
27
28
  * default: 15,
29
+ * stickyDefault: true, // Example forcing default on each frequency swap
28
30
  * },
29
31
  * };
30
32
  */
@@ -80,6 +82,7 @@ export class SwapAmounts {
80
82
  const config = {
81
83
  amounts: amountsObj,
82
84
  default: defaultAmount,
85
+ // stickyDefault omitted so it defaults to false behavior
83
86
  };
84
87
  window.EngridAmounts = {
85
88
  onetime: config,
@@ -95,7 +98,9 @@ export class SwapAmounts {
95
98
  const config = configs[freq];
96
99
  if (!config)
97
100
  return;
98
- const ignoreCurrentValue = this.ignoreCurrentValue();
101
+ const stickyDefault = !!config.stickyDefault;
102
+ // If stickyDefault, always ignore current value so selected flag in list enforces default
103
+ const ignoreCurrentValue = stickyDefault ? true : this.ignoreCurrentValue();
99
104
  window.EngagingNetworks.require._defined.enjs.swapList("donationAmt", this.toEnAmountList(config), { ignoreCurrentValue });
100
105
  this._amount.load();
101
106
  this.logger.log("Amounts Swapped To", config, { ignoreCurrentValue });
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const AppVersion = "0.22.15";
1
+ export declare const AppVersion = "0.22.17";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const AppVersion = "0.22.15";
1
+ export const AppVersion = "0.22.17";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4site/engrid-scripts",
3
- "version": "0.22.15",
3
+ "version": "0.22.17",
4
4
  "description": "Scripts for the ENGRID Project",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "strongly-typed-events": "^2.0.9"
26
26
  },
27
27
  "homepage": "https://github.com/4site-interactive-studios/engrid#readme",
28
- "gitHead": "d7b50bd7dee71910c245dc7508132b69ddcc6f91",
28
+ "gitHead": "e5c02e6aa5b48df75e149d7fbf4a0a864beb16cb",
29
29
  "devDependencies": {
30
30
  "nodemon": "^2.0.20"
31
31
  }