@searchspring/snap-controller 0.63.5 → 0.65.0
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.
- package/dist/cjs/Autocomplete/AutocompleteController.d.ts +15 -2
- package/dist/cjs/Autocomplete/AutocompleteController.d.ts.map +1 -1
- package/dist/cjs/Autocomplete/AutocompleteController.js +133 -7
- package/dist/cjs/Finder/FinderController.d.ts.map +1 -1
- package/dist/cjs/Finder/FinderController.js +1 -3
- package/dist/cjs/Recommendation/RecommendationController.d.ts +12 -16
- package/dist/cjs/Recommendation/RecommendationController.d.ts.map +1 -1
- package/dist/cjs/Recommendation/RecommendationController.js +83 -320
- package/dist/cjs/Search/SearchController.d.ts +10 -3
- package/dist/cjs/Search/SearchController.d.ts.map +1 -1
- package/dist/cjs/Search/SearchController.js +221 -48
- package/dist/esm/Autocomplete/AutocompleteController.d.ts +15 -2
- package/dist/esm/Autocomplete/AutocompleteController.d.ts.map +1 -1
- package/dist/esm/Autocomplete/AutocompleteController.js +127 -7
- package/dist/esm/Finder/FinderController.d.ts.map +1 -1
- package/dist/esm/Finder/FinderController.js +1 -3
- package/dist/esm/Recommendation/RecommendationController.d.ts +12 -16
- package/dist/esm/Recommendation/RecommendationController.d.ts.map +1 -1
- package/dist/esm/Recommendation/RecommendationController.js +79 -302
- package/dist/esm/Search/SearchController.d.ts +10 -3
- package/dist/esm/Search/SearchController.d.ts.map +1 -1
- package/dist/esm/Search/SearchController.js +178 -22
- package/package.json +10 -10
|
@@ -4,6 +4,8 @@ import { AbstractController } from '../Abstract/AbstractController';
|
|
|
4
4
|
import { StorageStore, ErrorType } from '@searchspring/snap-store-mobx';
|
|
5
5
|
import { getSearchParams } from '../utils/getParams';
|
|
6
6
|
import { ControllerTypes } from '../types';
|
|
7
|
+
const BACKGROUND_FILTER_FIELD_MATCHES = ['collection', 'category', 'categories', 'hierarchy'];
|
|
8
|
+
const BACKGROUND_FILTERS_VALUE_FLAGS = [1, 0, '1', '0', 'true', 'false', true, false];
|
|
7
9
|
const defaultConfig = {
|
|
8
10
|
id: 'search',
|
|
9
11
|
globals: {},
|
|
@@ -25,11 +27,16 @@ export class SearchController extends AbstractController {
|
|
|
25
27
|
super(config, { client, store, urlManager, eventManager, profiler, logger, tracker }, context);
|
|
26
28
|
this.type = ControllerTypes.search;
|
|
27
29
|
this.previousResults = [];
|
|
30
|
+
this.pageType = 'search';
|
|
31
|
+
this.events = { product: {} };
|
|
28
32
|
this.track = {
|
|
29
33
|
product: {
|
|
30
|
-
|
|
34
|
+
clickThrough: (e, result) => {
|
|
35
|
+
if (this.events.product[result.id]?.clickThrough) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
31
38
|
const target = e.target;
|
|
32
|
-
const resultHref = result.display?.mappings.core?.url || result.mappings.core?.url;
|
|
39
|
+
const resultHref = result.display?.mappings.core?.url || result.mappings.core?.url || '';
|
|
33
40
|
const elemHref = target?.getAttribute('href');
|
|
34
41
|
// the href that should be used for restoration - if the elemHref contains the resultHref - use resultHref
|
|
35
42
|
const storedHref = elemHref?.indexOf(resultHref) != -1 ? resultHref : elemHref || resultHref;
|
|
@@ -54,17 +61,59 @@ export class SearchController extends AbstractController {
|
|
|
54
61
|
}
|
|
55
62
|
// store position data or empty object
|
|
56
63
|
this.storage.set('scrollMap', scrollMap);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
const data = getSearchSchemaData({ params: this.params, store: this.store, results: [result] });
|
|
65
|
+
this.tracker.events[this.pageType].clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
66
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
67
|
+
this.events.product[result.id].clickThrough = true;
|
|
68
|
+
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, products: [result], trackEvent: data });
|
|
69
|
+
},
|
|
70
|
+
click: (e, result) => {
|
|
71
|
+
if (result.type === 'banner') {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// TODO: closest might be going too far - write own function to only go n levels up - additionally check that href includes result.url
|
|
75
|
+
const href = e.target?.getAttribute('href') || e.target?.closest('a')?.getAttribute('href');
|
|
76
|
+
if (href) {
|
|
77
|
+
this.track.product.clickThrough(e, result);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// TODO: in future, send as an interaction event
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
render: (result) => {
|
|
84
|
+
if (this.events.product[result.id]?.render) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const data = getSearchSchemaData({ params: this.params, store: this.store, results: [result] });
|
|
88
|
+
this.tracker.events[this.pageType].render({ data, siteId: this.config.globals?.siteId });
|
|
89
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
90
|
+
this.events.product[result.id].render = true;
|
|
91
|
+
this.eventManager.fire('track.product.render', { controller: this, products: [result], trackEvent: data });
|
|
92
|
+
},
|
|
93
|
+
impression: (result) => {
|
|
94
|
+
if (this.events.product[result.id]?.impression) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const data = getSearchSchemaData({ params: this.params, store: this.store, results: [result] });
|
|
98
|
+
this.tracker.events[this.pageType].impression({ data, siteId: this.config.globals?.siteId });
|
|
99
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
100
|
+
this.events.product[result.id].impression = true;
|
|
101
|
+
this.eventManager.fire('track.product.impression', { controller: this, products: [result], trackEvent: data });
|
|
102
|
+
},
|
|
103
|
+
addToCart: (result) => {
|
|
104
|
+
const data = getSearchSchemaData({ params: this.params, store: this.store, results: [result] });
|
|
105
|
+
this.tracker.events[this.pageType].addToCart({
|
|
106
|
+
data,
|
|
107
|
+
siteId: this.config.globals?.siteId,
|
|
63
108
|
});
|
|
64
|
-
this.eventManager.fire('track.product.
|
|
65
|
-
return event;
|
|
109
|
+
this.eventManager.fire('track.product.addToCart', { controller: this, products: [result], trackEvent: data });
|
|
66
110
|
},
|
|
67
111
|
},
|
|
112
|
+
redirect: (redirectURL) => {
|
|
113
|
+
const data = getSearchRedirectSchemaData({ redirectURL });
|
|
114
|
+
this.tracker.events.search.redirect({ data, siteId: this.config.globals?.siteId });
|
|
115
|
+
this.eventManager.fire('track.product.redirect', { controller: this, redirectURL, trackEvent: data });
|
|
116
|
+
},
|
|
68
117
|
};
|
|
69
118
|
this.search = async () => {
|
|
70
119
|
try {
|
|
@@ -72,9 +121,11 @@ export class SearchController extends AbstractController {
|
|
|
72
121
|
await this.init();
|
|
73
122
|
}
|
|
74
123
|
const params = this.params;
|
|
75
|
-
|
|
124
|
+
// reset events for new search
|
|
125
|
+
this.events = { product: {} };
|
|
126
|
+
if (params.search?.query?.string && params.search?.query?.string.length) {
|
|
76
127
|
// save it to the history store
|
|
77
|
-
this.store.history.save(
|
|
128
|
+
this.store.history.save(params.search.query.string);
|
|
78
129
|
}
|
|
79
130
|
this.store.loading = true;
|
|
80
131
|
try {
|
|
@@ -186,10 +237,8 @@ export class SearchController extends AbstractController {
|
|
|
186
237
|
}
|
|
187
238
|
afterSearchProfile.stop();
|
|
188
239
|
this.log.profile(afterSearchProfile);
|
|
189
|
-
// store previous results for infinite usage
|
|
190
|
-
|
|
191
|
-
this.previousResults = JSON.parse(JSON.stringify(response.results));
|
|
192
|
-
}
|
|
240
|
+
// store previous results for infinite usage (need to alsways store in case switch to infinite after pagination)
|
|
241
|
+
this.previousResults = JSON.parse(JSON.stringify(response.results));
|
|
193
242
|
// update the store
|
|
194
243
|
this.store.update(response);
|
|
195
244
|
const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
|
|
@@ -259,6 +308,10 @@ export class SearchController extends AbstractController {
|
|
|
259
308
|
this.store.loading = false;
|
|
260
309
|
}
|
|
261
310
|
};
|
|
311
|
+
this.addToCart = async (product) => {
|
|
312
|
+
this.track.product.addToCart(product);
|
|
313
|
+
this.eventManager.fire('addToCart', { controller: this, products: [product] });
|
|
314
|
+
};
|
|
262
315
|
// deep merge config with defaults
|
|
263
316
|
this.config = deepmerge(defaultConfig, this.config);
|
|
264
317
|
// set restorePosition to be enabled by default when using infinite (if not provided)
|
|
@@ -272,12 +325,50 @@ export class SearchController extends AbstractController {
|
|
|
272
325
|
});
|
|
273
326
|
// set last params to undefined for compare in search
|
|
274
327
|
this.storage.set('lastStringyParams', undefined);
|
|
328
|
+
this.eventManager.on('beforeSearch', async ({ request }, next) => {
|
|
329
|
+
// wait for other middleware to resolve
|
|
330
|
+
await next();
|
|
331
|
+
if (this.context?.pageType === 'category') {
|
|
332
|
+
this.pageType = 'category';
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const req = request;
|
|
336
|
+
const query = req.search?.query;
|
|
337
|
+
if (!query) {
|
|
338
|
+
const hasCategoryBackgroundFilters = req.filters
|
|
339
|
+
?.filter((filter) => filter.background)
|
|
340
|
+
.filter((filter) => {
|
|
341
|
+
return BACKGROUND_FILTER_FIELD_MATCHES.find((bgFilter) => {
|
|
342
|
+
return filter.field?.toLowerCase().includes(bgFilter);
|
|
343
|
+
});
|
|
344
|
+
})
|
|
345
|
+
.filter((filter) => {
|
|
346
|
+
return BACKGROUND_FILTERS_VALUE_FLAGS.every((flag) => {
|
|
347
|
+
switch (filter.type) {
|
|
348
|
+
case 'range':
|
|
349
|
+
const rangeFilter = filter;
|
|
350
|
+
return rangeFilter.value !== flag;
|
|
351
|
+
case 'value':
|
|
352
|
+
default:
|
|
353
|
+
const valueFilter = filter;
|
|
354
|
+
return valueFilter.value !== flag;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
if (hasCategoryBackgroundFilters?.length) {
|
|
359
|
+
this.pageType = 'category';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
275
363
|
// add 'afterSearch' middleware
|
|
276
364
|
this.eventManager.on('afterSearch', async (search, next) => {
|
|
277
365
|
const config = search.controller.config;
|
|
278
366
|
const redirectURL = search.response?.merchandising?.redirect;
|
|
279
367
|
const searchStore = search.controller.store;
|
|
280
368
|
if (redirectURL && config?.settings?.redirects?.merchandising && !search?.response?.filters?.length && !searchStore.loaded) {
|
|
369
|
+
//set loaded to true to prevent infinite search/reloading from happening
|
|
370
|
+
searchStore.loaded = true;
|
|
371
|
+
this.track.redirect(redirectURL);
|
|
281
372
|
window.location.replace(redirectURL);
|
|
282
373
|
return false;
|
|
283
374
|
}
|
|
@@ -286,6 +377,8 @@ export class SearchController extends AbstractController {
|
|
|
286
377
|
search?.response?.search?.query &&
|
|
287
378
|
search?.response?.pagination?.totalResults === 1 &&
|
|
288
379
|
!nonBackgroundFilters?.length) {
|
|
380
|
+
//set loaded to true to prevent infinite search/reloading from happening
|
|
381
|
+
searchStore.loaded = true;
|
|
289
382
|
window.location.replace(search?.response.results[0].mappings.core.url);
|
|
290
383
|
return false;
|
|
291
384
|
}
|
|
@@ -391,17 +484,15 @@ export class SearchController extends AbstractController {
|
|
|
391
484
|
}
|
|
392
485
|
params.tracking = params.tracking || {};
|
|
393
486
|
params.tracking.domain = window.location.href;
|
|
394
|
-
const userId = this.tracker.
|
|
487
|
+
const { userId, sessionId, pageLoadId, shopperId } = this.tracker.getContext();
|
|
395
488
|
if (userId) {
|
|
396
489
|
params.tracking.userId = userId;
|
|
397
490
|
}
|
|
398
|
-
const sessionId = this.tracker.getContext().sessionId;
|
|
399
491
|
if (sessionId) {
|
|
400
492
|
params.tracking.sessionId = sessionId;
|
|
401
493
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
params.tracking.pageLoadId = pageId;
|
|
494
|
+
if (pageLoadId) {
|
|
495
|
+
params.tracking.pageLoadId = pageLoadId;
|
|
405
496
|
}
|
|
406
497
|
if (!this.config.globals?.personalization?.disabled) {
|
|
407
498
|
const cartItems = this.tracker.cookies.cart.get();
|
|
@@ -414,7 +505,6 @@ export class SearchController extends AbstractController {
|
|
|
414
505
|
params.personalization = params.personalization || {};
|
|
415
506
|
params.personalization.lastViewed = lastViewedItems.join(',');
|
|
416
507
|
}
|
|
417
|
-
const shopperId = this.tracker.getShopperId();
|
|
418
508
|
if (shopperId) {
|
|
419
509
|
params.personalization = params.personalization || {};
|
|
420
510
|
params.personalization.shopper = shopperId;
|
|
@@ -465,3 +555,69 @@ export function generateHrefSelector(element, href, levels = 7) {
|
|
|
465
555
|
}
|
|
466
556
|
return;
|
|
467
557
|
}
|
|
558
|
+
function getSearchRedirectSchemaData({ redirectURL }) {
|
|
559
|
+
return {
|
|
560
|
+
redirect: redirectURL,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function getSearchSchemaData({ params, store, results, }) {
|
|
564
|
+
const filters = params.filters?.reduce((acc, filter) => {
|
|
565
|
+
const key = filter.background ? 'bgfilter' : 'filter';
|
|
566
|
+
acc[key] = acc[key] || [];
|
|
567
|
+
const value = filter.type === 'range' &&
|
|
568
|
+
!isNaN(filter.value?.low) &&
|
|
569
|
+
!isNaN(filter.value?.low)
|
|
570
|
+
? [`low=${filter.value?.low}`, `high=${filter.value?.high}`]
|
|
571
|
+
: [`${filter.value}`];
|
|
572
|
+
const existing = acc[key].find((item) => item.field === filter.field);
|
|
573
|
+
if (existing && !existing.value.includes(value[0])) {
|
|
574
|
+
existing.value.push(...value);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
acc[key].push({
|
|
578
|
+
field: filter.field,
|
|
579
|
+
value,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return acc;
|
|
583
|
+
}, {});
|
|
584
|
+
return {
|
|
585
|
+
q: params.search?.query?.string || '',
|
|
586
|
+
correctedQuery: params.search?.originalQuery,
|
|
587
|
+
...filters,
|
|
588
|
+
sort: [
|
|
589
|
+
{
|
|
590
|
+
field: store.sorting.current?.field,
|
|
591
|
+
dir: store.sorting.current?.direction,
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
pagination: {
|
|
595
|
+
totalResults: store.pagination.totalResults,
|
|
596
|
+
page: store.pagination.page,
|
|
597
|
+
resultsPerPage: store.pagination.pageSize,
|
|
598
|
+
},
|
|
599
|
+
merchandising: {
|
|
600
|
+
personalized: store.merchandising.personalized,
|
|
601
|
+
redirect: store.merchandising.redirect,
|
|
602
|
+
triggeredCampaigns: (store.merchandising.campaigns?.length &&
|
|
603
|
+
store.merchandising.campaigns?.map((campaign) => {
|
|
604
|
+
const experiement = store.merchandising.experiments.find((experiment) => experiment.campaignId === campaign.id);
|
|
605
|
+
return {
|
|
606
|
+
id: campaign.id,
|
|
607
|
+
experimentId: experiement?.experimentId,
|
|
608
|
+
variationId: experiement?.variationId,
|
|
609
|
+
};
|
|
610
|
+
})) ||
|
|
611
|
+
undefined,
|
|
612
|
+
},
|
|
613
|
+
results: results?.map((result) => {
|
|
614
|
+
const core = result.mappings.core;
|
|
615
|
+
return {
|
|
616
|
+
uid: core.uid || '',
|
|
617
|
+
// childUid: core.uid,
|
|
618
|
+
sku: core.sku,
|
|
619
|
+
// childSku: core.sku,
|
|
620
|
+
};
|
|
621
|
+
}) || [],
|
|
622
|
+
};
|
|
623
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@searchspring/snap-controller",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"description": "Snap Controllers",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -20,22 +20,22 @@
|
|
|
20
20
|
"test:watch": "jest --watch"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@searchspring/snap-toolbox": "^0.
|
|
23
|
+
"@searchspring/snap-toolbox": "^0.65.0",
|
|
24
24
|
"css.escape": "1.5.1",
|
|
25
25
|
"deepmerge": "4.3.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@searchspring/snap-client": "^0.
|
|
29
|
-
"@searchspring/snap-event-manager": "^0.
|
|
30
|
-
"@searchspring/snap-logger": "^0.
|
|
31
|
-
"@searchspring/snap-profiler": "^0.
|
|
32
|
-
"@searchspring/snap-store-mobx": "^0.
|
|
33
|
-
"@searchspring/snap-tracker": "^0.
|
|
34
|
-
"@searchspring/snap-url-manager": "^0.
|
|
28
|
+
"@searchspring/snap-client": "^0.65.0",
|
|
29
|
+
"@searchspring/snap-event-manager": "^0.65.0",
|
|
30
|
+
"@searchspring/snap-logger": "^0.65.0",
|
|
31
|
+
"@searchspring/snap-profiler": "^0.65.0",
|
|
32
|
+
"@searchspring/snap-store-mobx": "^0.65.0",
|
|
33
|
+
"@searchspring/snap-tracker": "^0.65.0",
|
|
34
|
+
"@searchspring/snap-url-manager": "^0.65.0"
|
|
35
35
|
},
|
|
36
36
|
"sideEffects": false,
|
|
37
37
|
"files": [
|
|
38
38
|
"dist/**/*"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "e96fc4426a64178a94a8ca603ec834bdca020fdb"
|
|
41
41
|
}
|