1SalesforceInteractions.init({
2 cookieDomain: "northerntrailoutfitters.com",
3 consents: [
4 {
5 purpose: SalesforceInteractions.mcis.ConsentPurpose.Personalization,
6 provider: "Example Consent Manager",
7 status: SalesforceInteractions.ConsentStatus.OptIn,
8 },
9 ],
10}).then(() => {
11 const sitemapConfig = {
12 global: {
13 onActionEvent: (actionEvent) => {
14 const email = SalesforceInteractions.mcis.getValueFromNestedObject(
15 "window._etmc.user_info.email",
16 );
17 if (email) {
18 actionEvent.user = actionEvent.user || {};
19 actionEvent.user.identities = actionEvent.user.identities || {};
20 actionEvent.user.identities.emailAddress = email;
21 }
22 return actionEvent;
23 },
24 contentZones: [
25 { name: "global_infobar_top_of_page", selector: "header.site-header" },
26 { name: "global_infobar_bottom_of_page", selector: "footer.site-footer" },
27 ],
28 listeners: [
29 SalesforceInteractions.listener("submit", ".email-signup", () => {
30 const email = SalesforceInteractions.cashDom("#dwfrm_mcsubscribe_email").val();
31 if (email) {
32 SalesforceInteractions.sendEvent({
33 interaction: { name: "Email Sign Up - Footer" },
34 user: { identities: { emailAddress: email } },
35 });
36 }
37 }),
38 ],
39 },
40 pageTypeDefault: {
41 name: "default",
42 interaction: {
43 name: "Default Page",
44 },
45 },
46 pageTypes: [
47 {
48 name: "product_detail",
49 /*
50 The best practice for isMatch is to match as quickly as possible. If matching immediately is not an option, you can use a Promise.
51 The Promise should resolve true or false and not pend indefinitely. This Promise example uses a setTimeout to prevent the isMatch from pending indefinitely if the match condition is not met fast enough. In this scenario, we know that the match condition will be met within 50 milliseconds or not at all. Note that using a timeout like this might not be sufficient in all cases and if you are using a Promise it should be tailored to your specific use-case.
52 */
53 isMatch: () =>
54 new Promise((resolve, reject) => {
55 let isMatchPDP = setTimeout(() => {
56 resolve(false);
57 }, 50);
58 return SalesforceInteractions.DisplayUtils.pageElementLoaded(
59 "div.page[data-action='Product-Show']",
60 "html",
61 ).then(() => {
62 clearTimeout(isMatchPDP);
63 resolve(true);
64 });
65 }),
66 interaction: {
67 name: SalesforceInteractions.CatalogObjectInteractionName.ViewCatalogObject,
68 catalogObject: {
69 type: "Product",
70 id: () => {
71 return SalesforceInteractions.util.resolveWhenTrue.bind(() => {
72 const productId = SalesforceInteractions.cashDom(".product-id").first().text();
73 const products = getProductsFromDataLayer();
74 if (products && products.length > 0) {
75 return products[0].id;
76 } else if (productId) {
77 return productId;
78 } else {
79 return false;
80 }
81 });
82 },
83 attributes: {
84 sku: {
85 id: SalesforceInteractions.cashDom(".product-detail[data-pid]").attr("data-pid"),
86 },
87 name: SalesforceInteractions.resolvers.fromJsonLd("name"),
88 description: SalesforceInteractions.resolvers.fromSelector(
89 ".short-description",
90 (desc) => desc.trim(),
91 ),
92 url: SalesforceInteractions.resolvers.fromHref(),
93 imageUrl: SalesforceInteractions.resolvers.fromSelectorAttribute(
94 ".product-carousel .carousel-item[data-slick-index='0'] img",
95 "src",
96 (url) => window.location.origin + url,
97 ),
98 inventoryCount: 1,
99 price: SalesforceInteractions.resolvers.fromSelector(
100 ".prices .price .value",
101 (price) => parseFloat(price.replace(/[^0-9\.]+/g, "")),
102 ),
103 rating: () => {
104 return SalesforceInteractions.mcis.extractFirstGroup(
105 /([.\w]+) out of/,
106 SalesforceInteractions.cashDom(".ratings .sr-only").text(),
107 );
108 },
109 },
110 relatedCatalogObjects: {
111 Category: SalesforceInteractions.DisplayUtils.pageElementLoaded(
112 ".container .product-breadcrumb .breadcrumb a",
113 "html",
114 ).then((ele) => {
115 return SalesforceInteractions.resolvers.buildCategoryId(
116 ".container .product-breadcrumb .breadcrumb a",
117 null,
118 null,
119 (categoryId) => [categoryId.toUpperCase()],
120 );
121 }),
122 Gender: SalesforceInteractions.DisplayUtils.pageElementLoaded(
123 ".product-breadcrumb .breadcrumb a, h1.product-name",
124 "html",
125 ).then((ele) => {
126 if (
127 SalesforceInteractions.cashDom(".product-breadcrumb .breadcrumb a")
128 .first()
129 .text()
130 .toLowerCase() === "women" ||
131 SalesforceInteractions.cashDom("h1.product-name").text().indexOf("Women") >= 0
132 ) {
133 return ["WOMEN"];
134 } else if (
135 SalesforceInteractions.cashDom(".product-breadcrumb .breadcrumb a")
136 .first()
137 .text()
138 .toLowerCase() === "men" ||
139 SalesforceInteractions.cashDom("h1.product-name").text().indexOf("Men") >= 0
140 ) {
141 return ["MEN"];
142 } else {
143 return;
144 }
145 }),
146 Color: SalesforceInteractions.DisplayUtils.pageElementLoaded(
147 ".attributes",
148 "html",
149 ).then((ele) => {
150 return SalesforceInteractions.resolvers.fromSelectorAttributeMultiple(
151 ".color-value",
152 "data-attr-value",
153 );
154 }),
155 Feature: SalesforceInteractions.DisplayUtils.pageElementLoaded(
156 ".features",
157 "html",
158 ).then((ele) => {
159 return SalesforceInteractions.resolvers.fromSelectorMultiple(
160 ".long-description li",
161 (features) => {
162 return features.map((feature) => {
163 return feature.trim().toUpperCase();
164 });
165 },
166 );
167 }),
168 },
169 },
170 },
171 contentZones: [
172 {
173 name: "product_detail_recs_row_1",
174 selector: ".row.recommendations div[id*='cq']:nth-of-type(1)",
175 },
176 {
177 name: "product_detail_recs_row_2",
178 selector: ".row.recommendations div[id*='cq']:nth-of-type(2)",
179 },
180 { name: "testHeader", selector: ".site-header" },
181 ],
182 listeners: [
183 SalesforceInteractions.listener("click", ".add-to-cart", () => {
184 let lineItem = SalesforceInteractions.mcis.buildLineItemFromPageState(
185 "select.quantity-select option:checked",
186 );
187 SalesforceInteractions.sendEvent({
188 interaction: {
189 name: SalesforceInteractions.CartInteractionName.AddToCart,
190 lineItem: lineItem,
191 },
192 });
193 }),
194 SalesforceInteractions.listener("click", ".attribute", (event) => {
195 let classList = event.target.classList.value.split(" ");
196 if (classList.includes("color-value") || classList.includes("size-value")) {
197 SalesforceInteractions.sendEvent({
198 interaction: {
199 name: SalesforceInteractions.CatalogObjectInteractionName.ViewCatalogObjectDetail,
200 catalogObject: {
201 type: "Product",
202 id: SalesforceInteractions.getSitemapResult().currentPage.interaction
203 .catalogObject.id,
204 attributes: {
205 sku: {
206 id: SalesforceInteractions.cashDom(".product-detail[data-pid]").attr(
207 "data-pid",
208 ),
209 },
210 },
211 relatedCatalogObjects: {
212 Color: [
213 SalesforceInteractions.cashDom(".color-value.selected").attr(
214 "data-attr-value",
215 ),
216 ],
217 },
218 },
219 },
220 });
221 }
222 }),
223 ],
224 },
225 {
226 name: "Category",
227 isMatch: () =>
228 new Promise((resolve, reject) => {
229 let isMatchCat = setTimeout(() => {
230 resolve(false);
231 }, 50);
232 return SalesforceInteractions.DisplayUtils.pageElementLoaded(
233 "#product-search-results",
234 "html",
235 ).then(() => {
236 clearTimeout(isMatchCat);
237 resolve(SalesforceInteractions.cashDom(".breadcrumb").length > 0);
238 });
239 }),
240 interaction: {
241 name: SalesforceInteractions.CatalogObjectInteractionName.ViewCatalogObject,
242 catalogObject: {
243 type: "Category",
244 id: SalesforceInteractions.DisplayUtils.pageElementLoaded(
245 ".breadcrumb .breadcrumb-item a",
246 "html",
247 ).then((ele) => {
248 return SalesforceInteractions.resolvers.buildCategoryId(
249 ".breadcrumb .breadcrumb-item a",
250 1,
251 null,
252 (categoryId) => categoryId.toUpperCase(),
253 );
254 }),
255 },
256 },
257 listeners: [
258 SalesforceInteractions.listener("click", ".quickview", (e) => {
259 const pid = SalesforceInteractions.cashDom(e.target).attr("href").split("pid=")[1];
260 if (!pid) {
261 return;
262 }
263
264 SalesforceInteractions.sendEvent({
265 interaction: {
266 name: SalesforceInteractions.CatalogObjectInteractionName.QuickViewCatalogObject,
267 catalogObject: {
268 type: "Product",
269 id: pid,
270 },
271 },
272 });
273 }),
274 SalesforceInteractions.listener("click", "body", (e) => {
275 if (
276 SalesforceInteractions.cashDom(e.target).closest("button[data-dismiss='modal']")
277 .length > 0
278 ) {
279 SalesforceInteractions.sendEvent({
280 interaction: {
281 name: SalesforceInteractions.mcis.CatalogObjectInteractionName
282 .StopQuickViewCatalogObject,
283 },
284 });
285 } else if (
286 SalesforceInteractions.cashDom(e.target).closest("#quickViewModal").length > 0 &&
287 SalesforceInteractions.cashDom(e.target).find("#quickViewModal .modal-dialog")
288 .length > 0
289 ) {
290 SalesforceInteractions.sendEvent({
291 interaction: {
292 name: SalesforceInteractions.mcis.CatalogObjectInteractionName
293 .StopQuickViewCatalogObject,
294 },
295 });
296 }
297 }),
298 ],
299 },
300 {
301 name: "department",
302 isMatch: () =>
303 new Promise((resolve, reject) => {
304 let isMatchDept = setTimeout(() => {
305 resolve(false);
306 }, 50);
307 return SalesforceInteractions.DisplayUtils.pageElementLoaded(
308 "div.category-tile",
309 "html",
310 ).then(() => {
311 clearTimeout(isMatchDept);
312 resolve(!/\/homepage/.test(window.location.href));
313 });
314 }),
315 },
316 {
317 name: "search_results",
318 isMatch: () => /\/default\/search$/.test(window.location.pathname),
319 },
320 {
321 name: "cart",
322 isMatch: () => /\/cart/.test(window.location.href),
323 interaction: {
324 name: SalesforceInteractions.CartInteractionName.ReplaceCart,
325 lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(
326 ".cart-empty, .checkout-btn",
327 "html",
328 ).then(() => {
329 let cartLineItems = [];
330 SalesforceInteractions.cashDom(".product-info .product-details").each((index, ele) => {
331 let itemQuantity = parseInt(
332 SalesforceInteractions.cashDom(ele).find(".qty-card-quantity-count").text().trim(),
333 );
334 if (itemQuantity && itemQuantity > 0) {
335 let lineItem = {
336 catalogObjectType: "Product",
337 catalogObjectId: SalesforceInteractions.cashDom(ele)
338 .find(".line-item-quanity-info")
339 .attr("data-pid"),
340 price:
341 SalesforceInteractions.cashDom(ele)
342 .find(".pricing")
343 .text()
344 .replace(/[^0-9\.]+/g, "") / itemQuantity,
345 quantity: itemQuantity,
346 };
347 cartLineItems.push(lineItem);
348 }
349 });
350 return cartLineItems;
351 }),
352 },
353 },
354 {
355 name: "order_confirmation",
356 isMatch: () => /\/confirmation/.test(window.location.href),
357 interaction: {
358 name: SalesforceInteractions.OrderInteractionName.Purchase,
359 order: {
360 id: SalesforceInteractions.DisplayUtils.pageElementLoaded(".order-number", "html").then(
361 (ele) => {
362 return SalesforceInteractions.resolvers.fromSelector(".order-number");
363 },
364 ),
365 lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(
366 ".product-line-item",
367 "html",
368 ).then(() => {
369 let purchaseLineItems = [];
370 SalesforceInteractions.cashDom(".product-line-item").each((index, ele) => {
371 let itemQuantity = parseInt(
372 SalesforceInteractions.cashDom(ele)
373 .find(".qty-card-quantity-count")
374 .text()
375 .trim(),
376 );
377 if (itemQuantity && itemQuantity > 0) {
378 let lineItem = {
379 catalogObjectType: "Product",
380 catalogObjectId: SalesforceInteractions.cashDom(ele)
381 .find(".line-item-quanity-info")
382 .attr("data-pid")
383 .trim(),
384 price:
385 SalesforceInteractions.cashDom(ele)
386 .find(".pricing")
387 .text()
388 .trim()
389 .replace(/[^0-9\.]+/g, "") / itemQuantity,
390 quantity: itemQuantity,
391 };
392 purchaseLineItems.push(lineItem);
393 }
394 });
395 return purchaseLineItems;
396 }),
397 },
398 },
399 },
400 {
401 name: "login",
402 isMatch: () =>
403 /\/login/.test(window.location.href) &&
404 window.location.hostname !== "community.northerntrailoutfitters.com",
405 interaction: {
406 name: "Login",
407 },
408 listeners: [
409 SalesforceInteractions.listener("click", "form[name='login-form'] button", () => {
410 const email = SalesforceInteractions.cashDom("#login-form-email").val();
411 if (email) {
412 SalesforceInteractions.sendEvent({
413 interaction: { name: "Logged In" },
414 user: { identities: { emailAddress: email } },
415 });
416 }
417 }),
418 ],
419 },
420 {
421 name: "home",
422 isMatch: () => /\/homepage/.test(window.location.href),
423 interaction: {
424 name: "Homepage",
425 },
426 contentZones: [
427 { name: "home_hero", selector: ".experience-carousel-bannerCarousel" },
428 {
429 name: "home_sub_hero",
430 selector: ".experience-carousel-bannerCarousel + .experience-component",
431 },
432 { name: "home_popup" },
433 ],
434 },
435 {
436 name: "community_login",
437 isMatch: () =>
438 window.location.hostname === "community.northerntrailoutfitters.com" &&
439 /\/s\/login/.test(window.location.href),
440 interaction: {
441 name: "Community Login",
442 },
443 listeners: [
444 SalesforceInteractions.listener("click", ".loginButton", (event) => {
445 const email = SalesforceInteractions.cashDom("input[placeholder='Email']").val();
446 if (email) {
447 SalesforceInteractions.sendEvent({
448 interaction: { name: "Community Log In" },
449 user: { identities: { emailAddress: email } },
450 });
451 }
452 }),
453 ],
454 },
455 {
456 name: "community_home",
457 isMatch: () => {
458 return (
459 window.location.hostname === "community.northerntrailoutfitters.com" &&
460 /^\/s\/?$/.test(window.location.pathname)
461 );
462 },
463 interaction: {
464 name: "Community Homepage",
465 },
466 listeners: [
467 SalesforceInteractions.listener("click", "li.topicItem", (event) => {
468 const topicLabel = SalesforceInteractions.cashDom(event.currentTarget)
469 .find(".topicLabel")
470 .text()
471 .trim();
472 if (topicLabel) {
473 SalesforceInteractions.sendEvent({
474 interaction: { name: "Community Homepage - " + topicLabel },
475 });
476 }
477 }),
478 ],
479 },
480 ],
481 };
482 const getProductsFromDataLayer = () => {
483 if (window.dataLayer) {
484 for (let i = 0; i < window.dataLayer.length; i++) {
485 if (
486 ((window.dataLayer[i].ecommerce && window.dataLayer[i].ecommerce.detail) || {}).products
487 ) {
488 return window.dataLayer[i].ecommerce.detail.products;
489 }
490 }
491 }
492 };
493 SalesforceInteractions.initSitemap(sitemapConfig);
494});