@searchspring/snap-controller 0.64.0 → 0.65.1
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 +192 -21
- 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 +173 -19
- 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,9 @@ export class SearchController extends AbstractController {
|
|
|
72
121
|
await this.init();
|
|
73
122
|
}
|
|
74
123
|
const params = this.params;
|
|
75
|
-
if (
|
|
124
|
+
if (params.search?.query?.string && params.search?.query?.string.length) {
|
|
76
125
|
// save it to the history store
|
|
77
|
-
this.store.history.save(
|
|
126
|
+
this.store.history.save(params.search.query.string);
|
|
78
127
|
}
|
|
79
128
|
this.store.loading = true;
|
|
80
129
|
try {
|
|
@@ -154,7 +203,9 @@ export class SearchController extends AbstractController {
|
|
|
154
203
|
}
|
|
155
204
|
}
|
|
156
205
|
else {
|
|
157
|
-
// normal request
|
|
206
|
+
// normal request
|
|
207
|
+
// reset events for new search
|
|
208
|
+
this.events = { product: {} };
|
|
158
209
|
// clear previousResults to prevent infinite scroll from using them
|
|
159
210
|
this.previousResults = [];
|
|
160
211
|
[meta, response] = await this.client.search(params);
|
|
@@ -257,6 +308,10 @@ export class SearchController extends AbstractController {
|
|
|
257
308
|
this.store.loading = false;
|
|
258
309
|
}
|
|
259
310
|
};
|
|
311
|
+
this.addToCart = async (product) => {
|
|
312
|
+
this.track.product.addToCart(product);
|
|
313
|
+
this.eventManager.fire('addToCart', { controller: this, products: [product] });
|
|
314
|
+
};
|
|
260
315
|
// deep merge config with defaults
|
|
261
316
|
this.config = deepmerge(defaultConfig, this.config);
|
|
262
317
|
// set restorePosition to be enabled by default when using infinite (if not provided)
|
|
@@ -270,6 +325,41 @@ export class SearchController extends AbstractController {
|
|
|
270
325
|
});
|
|
271
326
|
// set last params to undefined for compare in search
|
|
272
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
|
+
});
|
|
273
363
|
// add 'afterSearch' middleware
|
|
274
364
|
this.eventManager.on('afterSearch', async (search, next) => {
|
|
275
365
|
const config = search.controller.config;
|
|
@@ -278,6 +368,7 @@ export class SearchController extends AbstractController {
|
|
|
278
368
|
if (redirectURL && config?.settings?.redirects?.merchandising && !search?.response?.filters?.length && !searchStore.loaded) {
|
|
279
369
|
//set loaded to true to prevent infinite search/reloading from happening
|
|
280
370
|
searchStore.loaded = true;
|
|
371
|
+
this.track.redirect(redirectURL);
|
|
281
372
|
window.location.replace(redirectURL);
|
|
282
373
|
return false;
|
|
283
374
|
}
|
|
@@ -393,17 +484,15 @@ export class SearchController extends AbstractController {
|
|
|
393
484
|
}
|
|
394
485
|
params.tracking = params.tracking || {};
|
|
395
486
|
params.tracking.domain = window.location.href;
|
|
396
|
-
const userId = this.tracker.
|
|
487
|
+
const { userId, sessionId, pageLoadId, shopperId } = this.tracker.getContext();
|
|
397
488
|
if (userId) {
|
|
398
489
|
params.tracking.userId = userId;
|
|
399
490
|
}
|
|
400
|
-
const sessionId = this.tracker.getContext().sessionId;
|
|
401
491
|
if (sessionId) {
|
|
402
492
|
params.tracking.sessionId = sessionId;
|
|
403
493
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
params.tracking.pageLoadId = pageId;
|
|
494
|
+
if (pageLoadId) {
|
|
495
|
+
params.tracking.pageLoadId = pageLoadId;
|
|
407
496
|
}
|
|
408
497
|
if (!this.config.globals?.personalization?.disabled) {
|
|
409
498
|
const cartItems = this.tracker.cookies.cart.get();
|
|
@@ -416,7 +505,6 @@ export class SearchController extends AbstractController {
|
|
|
416
505
|
params.personalization = params.personalization || {};
|
|
417
506
|
params.personalization.lastViewed = lastViewedItems.join(',');
|
|
418
507
|
}
|
|
419
|
-
const shopperId = this.tracker.getShopperId();
|
|
420
508
|
if (shopperId) {
|
|
421
509
|
params.personalization = params.personalization || {};
|
|
422
510
|
params.personalization.shopper = shopperId;
|
|
@@ -467,3 +555,69 @@ export function generateHrefSelector(element, href, levels = 7) {
|
|
|
467
555
|
}
|
|
468
556
|
return;
|
|
469
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: store.search?.originalQuery?.string ? store.search?.query?.string : undefined,
|
|
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.1",
|
|
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.1",
|
|
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.1",
|
|
29
|
+
"@searchspring/snap-event-manager": "^0.65.1",
|
|
30
|
+
"@searchspring/snap-logger": "^0.65.1",
|
|
31
|
+
"@searchspring/snap-profiler": "^0.65.1",
|
|
32
|
+
"@searchspring/snap-store-mobx": "^0.65.1",
|
|
33
|
+
"@searchspring/snap-tracker": "^0.65.1",
|
|
34
|
+
"@searchspring/snap-url-manager": "^0.65.1"
|
|
35
35
|
},
|
|
36
36
|
"sideEffects": false,
|
|
37
37
|
"files": [
|
|
38
38
|
"dist/**/*"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "cd33cd87ad8d51302000b31bce6aa90b4b25ba89"
|
|
41
41
|
}
|