@searchspring/snap-controller 0.65.1 → 0.66.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 +2 -1
- package/dist/cjs/Autocomplete/AutocompleteController.d.ts.map +1 -1
- package/dist/cjs/Autocomplete/AutocompleteController.js +79 -32
- package/dist/cjs/Recommendation/RecommendationController.d.ts +2 -3
- package/dist/cjs/Recommendation/RecommendationController.d.ts.map +1 -1
- package/dist/cjs/Recommendation/RecommendationController.js +73 -25
- package/dist/cjs/Search/SearchController.d.ts +1 -1
- package/dist/cjs/Search/SearchController.d.ts.map +1 -1
- package/dist/cjs/Search/SearchController.js +141 -61
- package/dist/cjs/utils/isClickWithinProductLink.d.ts +5 -0
- package/dist/cjs/utils/isClickWithinProductLink.d.ts.map +1 -0
- package/dist/cjs/utils/isClickWithinProductLink.js +22 -0
- package/dist/esm/Autocomplete/AutocompleteController.d.ts +2 -1
- package/dist/esm/Autocomplete/AutocompleteController.d.ts.map +1 -1
- package/dist/esm/Autocomplete/AutocompleteController.js +66 -28
- package/dist/esm/Recommendation/RecommendationController.d.ts +2 -3
- package/dist/esm/Recommendation/RecommendationController.d.ts.map +1 -1
- package/dist/esm/Recommendation/RecommendationController.js +56 -23
- package/dist/esm/Search/SearchController.d.ts +1 -1
- package/dist/esm/Search/SearchController.d.ts.map +1 -1
- package/dist/esm/Search/SearchController.js +123 -53
- package/dist/esm/utils/isClickWithinProductLink.d.ts +5 -0
- package/dist/esm/utils/isClickWithinProductLink.d.ts.map +1 -0
- package/dist/esm/utils/isClickWithinProductLink.js +17 -0
- package/package.json +10 -10
|
@@ -12,9 +12,6 @@ type RecommendationTrackMethods = {
|
|
|
12
12
|
impression: (result: Product) => void;
|
|
13
13
|
addToCart: (result: Product) => void;
|
|
14
14
|
};
|
|
15
|
-
bundle: {
|
|
16
|
-
addToCart: (results: Product[]) => void;
|
|
17
|
-
};
|
|
18
15
|
};
|
|
19
16
|
export declare class RecommendationController extends AbstractController {
|
|
20
17
|
type: ControllerTypes;
|
|
@@ -22,6 +19,7 @@ export declare class RecommendationController extends AbstractController {
|
|
|
22
19
|
config: RecommendationControllerConfig;
|
|
23
20
|
events: {
|
|
24
21
|
product: Record<string, {
|
|
22
|
+
click?: boolean;
|
|
25
23
|
clickThrough?: boolean;
|
|
26
24
|
impression?: boolean;
|
|
27
25
|
render?: boolean;
|
|
@@ -31,6 +29,7 @@ export declare class RecommendationController extends AbstractController {
|
|
|
31
29
|
track: RecommendationTrackMethods;
|
|
32
30
|
get params(): RecommendRequestModel;
|
|
33
31
|
search: () => Promise<void>;
|
|
32
|
+
addToCart: (_products: Product[] | Product) => Promise<void>;
|
|
34
33
|
}
|
|
35
34
|
export {};
|
|
36
35
|
//# sourceMappingURL=RecommendationController.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecommendationController.d.ts","sourceRoot":"","sources":["../../../src/Recommendation/RecommendationController.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"RecommendationController.d.ts","sourceRoot":"","sources":["../../../src/Recommendation/RecommendationController.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,gBAAgB,EAAiB,MAAM,UAAU,CAAC;AAKpH,KAAK,0BAA0B,GAAG;IACjC,OAAO,EAAE;QACR,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QACvD,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QAChD,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QAClC,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QACtC,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;KACrC,CAAC;CACF,CAAC;AAUF,qBAAa,wBAAyB,SAAQ,kBAAkB;IACxD,IAAI,kBAAkC;IACrC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,8BAA8B,CAAC;IAE/C,MAAM,EAAE;QACP,OAAO,EAAE,MAAM,CACd,MAAM,EACN;YACC,KAAK,CAAC,EAAE,OAAO,CAAC;YAChB,YAAY,CAAC,EAAE,OAAO,CAAC;YACvB,UAAU,CAAC,EAAE,OAAO,CAAC;YACrB,MAAM,CAAC,EAAE,OAAO,CAAC;SACjB,CACD,CAAC;KACF,CAEC;gBAGD,MAAM,EAAE,8BAA8B,EACtC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IAwD3B,KAAK,EAAE,0BAA0B,CAuD/B;IAEF,IAAI,MAAM,IAAI,qBAAqB,CA4BlC;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CA6H9B;IAEF,SAAS,cAAqB,OAAO,EAAE,GAAG,OAAO,KAAG,QAAQ,IAAI,CAAC,CAQ/D;CACF"}
|
|
@@ -2,6 +2,7 @@ import deepmerge from 'deepmerge';
|
|
|
2
2
|
import { ErrorType } from '@searchspring/snap-store-mobx';
|
|
3
3
|
import { AbstractController } from '../Abstract/AbstractController';
|
|
4
4
|
import { ControllerTypes } from '../types';
|
|
5
|
+
import { CLICK_DUPLICATION_TIMEOUT, isClickWithinProductLink } from '../utils/isClickWithinProductLink';
|
|
5
6
|
const defaultConfig = {
|
|
6
7
|
id: 'recommend',
|
|
7
8
|
tag: '',
|
|
@@ -25,17 +26,21 @@ export class RecommendationController extends AbstractController {
|
|
|
25
26
|
this.tracker.events.recommendations.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
26
27
|
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
27
28
|
this.events.product[result.id].clickThrough = true;
|
|
28
|
-
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e,
|
|
29
|
+
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, product: result, trackEvent: data });
|
|
29
30
|
},
|
|
30
31
|
click: (e, result) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (href) {
|
|
34
|
-
this.track.product.clickThrough(e, result);
|
|
32
|
+
if (this.events.product[result.id]?.click) {
|
|
33
|
+
return;
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
if (result.type === 'banner') {
|
|
36
|
+
return;
|
|
38
37
|
}
|
|
38
|
+
isClickWithinProductLink(e, result) && this.track.product.clickThrough(e, result);
|
|
39
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
40
|
+
this.events.product[result.id].click = true;
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
this.events.product[result.id].click = false;
|
|
43
|
+
}, CLICK_DUPLICATION_TIMEOUT);
|
|
39
44
|
},
|
|
40
45
|
impression: (result) => {
|
|
41
46
|
if (this.events.product[result.id]?.impression)
|
|
@@ -44,7 +49,7 @@ export class RecommendationController extends AbstractController {
|
|
|
44
49
|
this.tracker.events.recommendations.impression({ data, siteId: this.config.globals?.siteId });
|
|
45
50
|
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
46
51
|
this.events.product[result.id].impression = true;
|
|
47
|
-
this.eventManager.fire('track.product.impression', { controller: this,
|
|
52
|
+
this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
|
|
48
53
|
return data;
|
|
49
54
|
},
|
|
50
55
|
render: (result) => {
|
|
@@ -54,23 +59,13 @@ export class RecommendationController extends AbstractController {
|
|
|
54
59
|
this.tracker.events.recommendations.render({ data, siteId: this.config.globals?.siteId });
|
|
55
60
|
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
56
61
|
this.events.product[result.id].render = true;
|
|
57
|
-
this.eventManager.fire('track.product.render', { controller: this,
|
|
62
|
+
this.eventManager.fire('track.product.render', { controller: this, product: result, trackEvent: data });
|
|
58
63
|
return data;
|
|
59
64
|
},
|
|
60
65
|
addToCart: (result) => {
|
|
61
|
-
const data =
|
|
66
|
+
const data = getRecommendationsAddtocartSchemaData({ store: this.store, results: [result] });
|
|
62
67
|
this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
|
|
63
|
-
this.eventManager.fire('track.product.addToCart', { controller: this,
|
|
64
|
-
return data;
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
bundle: {
|
|
68
|
-
addToCart: (results) => {
|
|
69
|
-
if (this.store.profile.type != 'bundle')
|
|
70
|
-
return;
|
|
71
|
-
const data = getRecommendationsSchemaData({ store: this.store, results });
|
|
72
|
-
this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
|
|
73
|
-
this.eventManager.fire('track.bundle.addToCart', { controller: this, products: results, trackEvent: data });
|
|
68
|
+
this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
|
|
74
69
|
return data;
|
|
75
70
|
},
|
|
76
71
|
},
|
|
@@ -194,6 +189,15 @@ export class RecommendationController extends AbstractController {
|
|
|
194
189
|
this.store.loading = false;
|
|
195
190
|
}
|
|
196
191
|
};
|
|
192
|
+
this.addToCart = async (_products) => {
|
|
193
|
+
const products = typeof _products.slice == 'function' ? _products.slice() : [_products];
|
|
194
|
+
products.forEach((product) => {
|
|
195
|
+
this.track.product.addToCart(product);
|
|
196
|
+
});
|
|
197
|
+
if (products.length > 0) {
|
|
198
|
+
this.eventManager.fire('addToCart', { controller: this, products });
|
|
199
|
+
}
|
|
200
|
+
};
|
|
197
201
|
if (!config.tag) {
|
|
198
202
|
throw new Error(`Invalid config passed to RecommendationController. The "tag" attribute is required.`);
|
|
199
203
|
}
|
|
@@ -209,6 +213,21 @@ export class RecommendationController extends AbstractController {
|
|
|
209
213
|
// deep merge config with defaults
|
|
210
214
|
this.config = deepmerge(defaultConfig, this.config);
|
|
211
215
|
this.store.setConfig(this.config);
|
|
216
|
+
this.eventManager.on('afterStore', async (search, next) => {
|
|
217
|
+
await next();
|
|
218
|
+
const controller = search.controller;
|
|
219
|
+
if (controller.store.loaded && !controller.store.error) {
|
|
220
|
+
const products = controller.store.results.filter((result) => result.type === 'product');
|
|
221
|
+
const results = products.length === 0 ? [] : products;
|
|
222
|
+
const data = getRecommendationsSchemaData({ store: this.store, results });
|
|
223
|
+
this.tracker.events.recommendations.render({ data, siteId: this.config.globals?.siteId });
|
|
224
|
+
products.forEach((result) => {
|
|
225
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
226
|
+
this.events.product[result.id].render = true;
|
|
227
|
+
this.eventManager.fire('track.product.render', { controller: this, product: result, trackEvent: data });
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
212
231
|
// add 'afterStore' middleware
|
|
213
232
|
// this.eventManager.on('afterStore', async (recommend: AfterStoreObj, next: Next): Promise<void | boolean> => {
|
|
214
233
|
// await next();
|
|
@@ -248,16 +267,30 @@ export class RecommendationController extends AbstractController {
|
|
|
248
267
|
return params;
|
|
249
268
|
}
|
|
250
269
|
}
|
|
270
|
+
function getRecommendationsAddtocartSchemaData({ store, results, }) {
|
|
271
|
+
return {
|
|
272
|
+
tag: store.profile.tag,
|
|
273
|
+
results: results?.map((result) => {
|
|
274
|
+
const core = result.mappings.core;
|
|
275
|
+
return {
|
|
276
|
+
uid: core?.uid || '',
|
|
277
|
+
sku: core?.sku,
|
|
278
|
+
price: Number(core?.price),
|
|
279
|
+
qty: result.quantity || 1,
|
|
280
|
+
};
|
|
281
|
+
}) || [],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
251
284
|
function getRecommendationsSchemaData({ store, results }) {
|
|
252
285
|
return {
|
|
253
286
|
tag: store.profile.tag,
|
|
254
287
|
results: results?.map((result) => {
|
|
255
288
|
const core = result.mappings.core;
|
|
289
|
+
const position = result.position;
|
|
256
290
|
return {
|
|
291
|
+
position,
|
|
257
292
|
uid: core.uid || '',
|
|
258
|
-
// childUid: core.uid,
|
|
259
293
|
sku: core.sku,
|
|
260
|
-
// childSku: core.sku,
|
|
261
294
|
};
|
|
262
295
|
}) || [],
|
|
263
296
|
};
|
|
@@ -26,7 +26,7 @@ export declare class SearchController extends AbstractController {
|
|
|
26
26
|
track: SearchTrackMethods;
|
|
27
27
|
get params(): SearchRequestModel;
|
|
28
28
|
search: () => Promise<void>;
|
|
29
|
-
addToCart: (
|
|
29
|
+
addToCart: (_products: Product[] | Product) => Promise<void>;
|
|
30
30
|
}
|
|
31
31
|
export declare function getStorableRequestParams(request: SearchRequestModel): SearchRequestModel;
|
|
32
32
|
export declare function generateHrefSelector(element: HTMLElement, href: string, levels?: number): string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAa,MAAM,+BAA+B,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAa,MAAM,+BAA+B,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAClF,OAAO,KAAK,EACX,sBAAsB,EAGtB,kBAAkB,EAClB,gBAAgB,EAIhB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EACX,kBAAkB,EAQlB,MAAM,2BAA2B,CAAC;AAkCnC,KAAK,kBAAkB,GAAG;IACzB,OAAO,EAAE;QACR,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QACvD,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QACzD,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QAClC,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QACtC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;KACtC,CAAC;IACF,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAGF,qBAAa,gBAAiB,SAAQ,kBAAkB;IAChD,IAAI,kBAA0B;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,sBAAsB,CAAC;IACvC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,MAAM,CAUM;gBAGnB,MAAM,EAAE,sBAAsB,EAC9B,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IAuN3B,KAAK,EAAE,kBAAkB,CA+FvB;IAEF,IAAI,MAAM,IAAI,kBAAkB,CA8C/B;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CAyN9B;IAEF,SAAS,cAAqB,OAAO,EAAE,GAAG,OAAO,KAAG,QAAQ,IAAI,CAAC,CAQ/D;CACF;AAwBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAiBxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,GAAG,SAAS,CAgCvG"}
|
|
@@ -4,6 +4,7 @@ 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
|
+
import { CLICK_DUPLICATION_TIMEOUT, isClickWithinProductLink } from '../utils/isClickWithinProductLink';
|
|
7
8
|
const BACKGROUND_FILTER_FIELD_MATCHES = ['collection', 'category', 'categories', 'hierarchy'];
|
|
8
9
|
const BACKGROUND_FILTERS_VALUE_FLAGS = [1, 0, '1', '0', 'true', 'false', true, false];
|
|
9
10
|
const defaultConfig = {
|
|
@@ -22,6 +23,7 @@ const defaultConfig = {
|
|
|
22
23
|
},
|
|
23
24
|
},
|
|
24
25
|
};
|
|
26
|
+
const schemaMap = {};
|
|
25
27
|
export class SearchController extends AbstractController {
|
|
26
28
|
constructor(config, { client, store, urlManager, eventManager, profiler, logger, tracker }, context) {
|
|
27
29
|
super(config, { client, store, urlManager, eventManager, profiler, logger, tracker }, context);
|
|
@@ -61,52 +63,53 @@ export class SearchController extends AbstractController {
|
|
|
61
63
|
}
|
|
62
64
|
// store position data or empty object
|
|
63
65
|
this.storage.set('scrollMap', scrollMap);
|
|
64
|
-
const data =
|
|
66
|
+
const data = schemaMap[result.id];
|
|
65
67
|
this.tracker.events[this.pageType].clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
66
68
|
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
67
69
|
this.events.product[result.id].clickThrough = true;
|
|
68
|
-
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e,
|
|
70
|
+
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, product: result, trackEvent: data });
|
|
69
71
|
},
|
|
70
72
|
click: (e, result) => {
|
|
71
|
-
if (result.
|
|
73
|
+
if (this.events.product[result.id]?.click) {
|
|
72
74
|
return;
|
|
73
75
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (href) {
|
|
77
|
-
this.track.product.clickThrough(e, result);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
// TODO: in future, send as an interaction event
|
|
76
|
+
if (result.type === 'banner') {
|
|
77
|
+
return;
|
|
81
78
|
}
|
|
79
|
+
isClickWithinProductLink(e, result) && this.track.product.clickThrough(e, result);
|
|
80
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
81
|
+
this.events.product[result.id].click = true;
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
this.events.product[result.id].click = false;
|
|
84
|
+
}, CLICK_DUPLICATION_TIMEOUT);
|
|
82
85
|
},
|
|
83
86
|
render: (result) => {
|
|
84
87
|
if (this.events.product[result.id]?.render) {
|
|
85
88
|
return;
|
|
86
89
|
}
|
|
87
|
-
const data =
|
|
90
|
+
const data = schemaMap[result.id];
|
|
88
91
|
this.tracker.events[this.pageType].render({ data, siteId: this.config.globals?.siteId });
|
|
89
92
|
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
90
93
|
this.events.product[result.id].render = true;
|
|
91
|
-
this.eventManager.fire('track.product.render', { controller: this,
|
|
94
|
+
this.eventManager.fire('track.product.render', { controller: this, product: result, trackEvent: data });
|
|
92
95
|
},
|
|
93
96
|
impression: (result) => {
|
|
94
97
|
if (this.events.product[result.id]?.impression) {
|
|
95
98
|
return;
|
|
96
99
|
}
|
|
97
|
-
const data =
|
|
100
|
+
const data = schemaMap[result.id];
|
|
98
101
|
this.tracker.events[this.pageType].impression({ data, siteId: this.config.globals?.siteId });
|
|
99
102
|
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
100
103
|
this.events.product[result.id].impression = true;
|
|
101
|
-
this.eventManager.fire('track.product.impression', { controller: this,
|
|
104
|
+
this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
|
|
102
105
|
},
|
|
103
106
|
addToCart: (result) => {
|
|
104
|
-
const data =
|
|
107
|
+
const data = getSearchAddtocartSchemaData({ searchSchemaData: schemaMap[result.id], results: [result] });
|
|
105
108
|
this.tracker.events[this.pageType].addToCart({
|
|
106
109
|
data,
|
|
107
110
|
siteId: this.config.globals?.siteId,
|
|
108
111
|
});
|
|
109
|
-
this.eventManager.fire('track.product.addToCart', { controller: this,
|
|
112
|
+
this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
|
|
110
113
|
},
|
|
111
114
|
},
|
|
112
115
|
redirect: (redirectURL) => {
|
|
@@ -142,7 +145,7 @@ export class SearchController extends AbstractController {
|
|
|
142
145
|
throw err;
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
|
-
const stringyParams = JSON.stringify(params);
|
|
148
|
+
const stringyParams = JSON.stringify(getStorableRequestParams(params));
|
|
146
149
|
const prevStringyParams = this.storage.get('lastStringyParams');
|
|
147
150
|
if (stringyParams == prevStringyParams) {
|
|
148
151
|
// no param change - not searching
|
|
@@ -164,6 +167,7 @@ export class SearchController extends AbstractController {
|
|
|
164
167
|
// infinite backfill is enabled AND we have not yet fetched any results
|
|
165
168
|
if (this.config.settings?.infinite.backfill && !this.store.loaded) {
|
|
166
169
|
// create requests for all missing pages (using Arrray(page).fill() to populate an array to map)
|
|
170
|
+
const backfillRequestsParams = [];
|
|
167
171
|
const backfillRequests = Array(params.pagination.page)
|
|
168
172
|
.fill('backfill')
|
|
169
173
|
.map((v, i) => {
|
|
@@ -177,6 +181,7 @@ export class SearchController extends AbstractController {
|
|
|
177
181
|
delete backfillParams?.search?.redirectResponse;
|
|
178
182
|
}
|
|
179
183
|
}
|
|
184
|
+
backfillRequestsParams.push(backfillParams);
|
|
180
185
|
return this.client.search(backfillParams);
|
|
181
186
|
});
|
|
182
187
|
const backfillResponses = await Promise.all(backfillRequests);
|
|
@@ -185,8 +190,8 @@ export class SearchController extends AbstractController {
|
|
|
185
190
|
meta = backfillResponses[0][0];
|
|
186
191
|
response = backfillResponses[0][1];
|
|
187
192
|
// accumulate results from all backfill responses
|
|
188
|
-
const backfillResults = backfillResponses.reduce((results, response) => {
|
|
189
|
-
|
|
193
|
+
const backfillResults = backfillResponses.reduce((results, response, index) => {
|
|
194
|
+
createResultSchemaMapping({ request: backfillRequestsParams[index], response: response });
|
|
190
195
|
return results.concat(...response[1].results);
|
|
191
196
|
}, []);
|
|
192
197
|
// overwrite pagination params to expected state
|
|
@@ -198,6 +203,7 @@ export class SearchController extends AbstractController {
|
|
|
198
203
|
else {
|
|
199
204
|
// infinite with no backfills.
|
|
200
205
|
[meta, response] = await this.client.search(params);
|
|
206
|
+
createResultSchemaMapping({ request: params, response: [meta, response] });
|
|
201
207
|
// append new results to previous results
|
|
202
208
|
response.results = [...this.previousResults, ...(response.results || [])];
|
|
203
209
|
}
|
|
@@ -209,6 +215,7 @@ export class SearchController extends AbstractController {
|
|
|
209
215
|
// clear previousResults to prevent infinite scroll from using them
|
|
210
216
|
this.previousResults = [];
|
|
211
217
|
[meta, response] = await this.client.search(params);
|
|
218
|
+
createResultSchemaMapping({ request: params, response: [meta, response] });
|
|
212
219
|
}
|
|
213
220
|
// MockClient will overwrite the client search() method and use SearchData to return mock data which already contains meta data
|
|
214
221
|
if (!response.meta) {
|
|
@@ -308,9 +315,14 @@ export class SearchController extends AbstractController {
|
|
|
308
315
|
this.store.loading = false;
|
|
309
316
|
}
|
|
310
317
|
};
|
|
311
|
-
this.addToCart = async (
|
|
312
|
-
|
|
313
|
-
|
|
318
|
+
this.addToCart = async (_products) => {
|
|
319
|
+
const products = typeof _products.slice == 'function' ? _products.slice() : [_products];
|
|
320
|
+
products.forEach((product) => {
|
|
321
|
+
this.track.product.addToCart(product);
|
|
322
|
+
});
|
|
323
|
+
if (products.length > 0) {
|
|
324
|
+
this.eventManager.fire('addToCart', { controller: this, products });
|
|
325
|
+
}
|
|
314
326
|
};
|
|
315
327
|
// deep merge config with defaults
|
|
316
328
|
this.config = deepmerge(defaultConfig, this.config);
|
|
@@ -372,25 +384,14 @@ export class SearchController extends AbstractController {
|
|
|
372
384
|
window.location.replace(redirectURL);
|
|
373
385
|
return false;
|
|
374
386
|
}
|
|
375
|
-
const nonBackgroundFilters = search?.request?.filters?.filter((filter) => !filter.background);
|
|
376
|
-
if (config?.settings?.redirects?.singleResult &&
|
|
377
|
-
search?.response?.search?.query &&
|
|
378
|
-
search?.response?.pagination?.totalResults === 1 &&
|
|
379
|
-
!nonBackgroundFilters?.length) {
|
|
380
|
-
//set loaded to true to prevent infinite search/reloading from happening
|
|
381
|
-
searchStore.loaded = true;
|
|
382
|
-
window.location.replace(search?.response.results[0].mappings.core.url);
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
387
|
await next();
|
|
386
388
|
});
|
|
387
389
|
this.eventManager.on('afterStore', async (search, next) => {
|
|
388
390
|
await next();
|
|
389
|
-
// save last params
|
|
390
|
-
this.storage.set('lastStringyParams', JSON.stringify(search.request));
|
|
391
391
|
// get scrollTo positioning and send it to 'restorePosition' event
|
|
392
392
|
const storableRequestParams = getStorableRequestParams(search.request);
|
|
393
393
|
const stringyParams = JSON.stringify(storableRequestParams);
|
|
394
|
+
this.storage.set('lastStringyParams', stringyParams);
|
|
394
395
|
const scrollMap = this.storage.get('scrollMap') || {};
|
|
395
396
|
const elementPosition = scrollMap[stringyParams];
|
|
396
397
|
if (!elementPosition) {
|
|
@@ -400,6 +401,34 @@ export class SearchController extends AbstractController {
|
|
|
400
401
|
// not awaiting this event as it relies on render, and render is blocked by afterStore event
|
|
401
402
|
this.eventManager.fire('restorePosition', { controller: this, element: elementPosition });
|
|
402
403
|
});
|
|
404
|
+
this.eventManager.on('afterStore', async (search, next) => {
|
|
405
|
+
await next();
|
|
406
|
+
const controller = search.controller;
|
|
407
|
+
if (controller.store.loaded && !controller.store.error) {
|
|
408
|
+
const products = controller.store.results.filter((result) => result.type === 'product' && !this.events.product[result.id]?.render);
|
|
409
|
+
if (products.length === 0) {
|
|
410
|
+
// handle no results
|
|
411
|
+
const data = getSearchSchemaData({ params: search.request, results: [] });
|
|
412
|
+
this.tracker.events[this.pageType].render({ data, siteId: this.config.globals?.siteId });
|
|
413
|
+
}
|
|
414
|
+
products.forEach((result) => {
|
|
415
|
+
const data = schemaMap[result.id];
|
|
416
|
+
this.tracker.events[this.pageType].render({ data, siteId: this.config.globals?.siteId });
|
|
417
|
+
this.events.product[result.id] = this.events.product[result.id] || {};
|
|
418
|
+
this.events.product[result.id].render = true;
|
|
419
|
+
this.eventManager.fire('track.product.render', { controller: this, product: result, trackEvent: data });
|
|
420
|
+
});
|
|
421
|
+
const config = search.controller.config;
|
|
422
|
+
const nonBackgroundFilters = search?.request?.filters?.filter((filter) => !filter.background);
|
|
423
|
+
if (config?.settings?.redirects?.singleResult &&
|
|
424
|
+
search?.response?.search?.query &&
|
|
425
|
+
search?.response?.pagination?.totalResults === 1 &&
|
|
426
|
+
!nonBackgroundFilters?.length) {
|
|
427
|
+
window.location.replace(search?.response.results[0].mappings.core.url);
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
403
432
|
// restore position
|
|
404
433
|
if (this.config.settings?.restorePosition?.enabled) {
|
|
405
434
|
this.eventManager.on('restorePosition', async ({ controller, element }, next) => {
|
|
@@ -513,6 +542,26 @@ export class SearchController extends AbstractController {
|
|
|
513
542
|
return params;
|
|
514
543
|
}
|
|
515
544
|
}
|
|
545
|
+
function createResultSchemaMapping({ request, response }) {
|
|
546
|
+
const [_, searchResponse] = response;
|
|
547
|
+
const schema = getSearchSchemaData({
|
|
548
|
+
params: request,
|
|
549
|
+
results: [], // results added below because this would contain all results
|
|
550
|
+
response: searchResponse,
|
|
551
|
+
});
|
|
552
|
+
searchResponse.results?.forEach((result) => {
|
|
553
|
+
schemaMap[result.id] = {
|
|
554
|
+
...schema,
|
|
555
|
+
results: [
|
|
556
|
+
{
|
|
557
|
+
position: result.position,
|
|
558
|
+
uid: result.mappings?.core?.uid || '',
|
|
559
|
+
sku: result.mappings?.core?.sku,
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
}
|
|
516
565
|
export function getStorableRequestParams(request) {
|
|
517
566
|
return {
|
|
518
567
|
siteId: request.siteId,
|
|
@@ -560,7 +609,21 @@ function getSearchRedirectSchemaData({ redirectURL }) {
|
|
|
560
609
|
redirect: redirectURL,
|
|
561
610
|
};
|
|
562
611
|
}
|
|
563
|
-
function
|
|
612
|
+
function getSearchAddtocartSchemaData({ searchSchemaData, results, }) {
|
|
613
|
+
return {
|
|
614
|
+
...searchSchemaData,
|
|
615
|
+
results: results?.map((result) => {
|
|
616
|
+
const core = result.mappings.core;
|
|
617
|
+
return {
|
|
618
|
+
uid: core.uid || '',
|
|
619
|
+
sku: core.sku,
|
|
620
|
+
price: Number(core.price),
|
|
621
|
+
qty: result.quantity || 1,
|
|
622
|
+
};
|
|
623
|
+
}) || [],
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function getSearchSchemaData({ params, results, response, }) {
|
|
564
627
|
const filters = params.filters?.reduce((acc, filter) => {
|
|
565
628
|
const key = filter.background ? 'bgfilter' : 'filter';
|
|
566
629
|
acc[key] = acc[key] || [];
|
|
@@ -581,27 +644,34 @@ function getSearchSchemaData({ params, store, results, }) {
|
|
|
581
644
|
}
|
|
582
645
|
return acc;
|
|
583
646
|
}, {});
|
|
647
|
+
let correctedQuery;
|
|
648
|
+
if (response?.search?.originalQuery && response?.search?.query) {
|
|
649
|
+
correctedQuery = response?.search?.query;
|
|
650
|
+
}
|
|
651
|
+
const campaigns = response?.merchandising?.campaigns || [];
|
|
652
|
+
const experiments = response?.merchandising?.experiments || [];
|
|
584
653
|
return {
|
|
585
654
|
q: params.search?.query?.string || '',
|
|
586
|
-
correctedQuery
|
|
655
|
+
correctedQuery,
|
|
656
|
+
matchType: response?.search?.matchType,
|
|
587
657
|
...filters,
|
|
588
|
-
sort:
|
|
589
|
-
{
|
|
590
|
-
field:
|
|
591
|
-
dir:
|
|
592
|
-
}
|
|
593
|
-
|
|
658
|
+
sort: params.sorts?.map((sort) => {
|
|
659
|
+
return {
|
|
660
|
+
field: sort.field,
|
|
661
|
+
dir: sort.direction,
|
|
662
|
+
};
|
|
663
|
+
}),
|
|
594
664
|
pagination: {
|
|
595
|
-
totalResults:
|
|
596
|
-
page:
|
|
597
|
-
resultsPerPage:
|
|
665
|
+
totalResults: response?.pagination?.totalResults,
|
|
666
|
+
page: response?.pagination?.page,
|
|
667
|
+
resultsPerPage: response?.pagination?.pageSize,
|
|
598
668
|
},
|
|
599
669
|
merchandising: {
|
|
600
|
-
personalized:
|
|
601
|
-
redirect:
|
|
602
|
-
triggeredCampaigns: (
|
|
603
|
-
|
|
604
|
-
const experiement =
|
|
670
|
+
personalized: response?.merchandising?.personalized,
|
|
671
|
+
redirect: response?.merchandising?.redirect,
|
|
672
|
+
triggeredCampaigns: (campaigns.length &&
|
|
673
|
+
campaigns.map((campaign) => {
|
|
674
|
+
const experiement = experiments.find((experiment) => experiment.campaignId === campaign.id);
|
|
605
675
|
return {
|
|
606
676
|
id: campaign.id,
|
|
607
677
|
experimentId: experiement?.experimentId,
|
|
@@ -611,12 +681,12 @@ function getSearchSchemaData({ params, store, results, }) {
|
|
|
611
681
|
undefined,
|
|
612
682
|
},
|
|
613
683
|
results: results?.map((result) => {
|
|
614
|
-
const core = result.mappings
|
|
684
|
+
const core = result.mappings?.core;
|
|
685
|
+
const position = result.position;
|
|
615
686
|
return {
|
|
687
|
+
position,
|
|
616
688
|
uid: core.uid || '',
|
|
617
|
-
// childUid: core.uid,
|
|
618
689
|
sku: core.sku,
|
|
619
|
-
// childSku: core.sku,
|
|
620
690
|
};
|
|
621
691
|
}) || [],
|
|
622
692
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Product } from '@searchspring/snap-store-mobx';
|
|
2
|
+
export declare const CLICK_DUPLICATION_TIMEOUT = 300;
|
|
3
|
+
export declare const CLICK_THROUGH_CLOSEST_MAX_LEVELS = 12;
|
|
4
|
+
export declare const isClickWithinProductLink: (e: MouseEvent, result: Product) => boolean;
|
|
5
|
+
//# sourceMappingURL=isClickWithinProductLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isClickWithinProductLink.d.ts","sourceRoot":"","sources":["../../../src/utils/isClickWithinProductLink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAE7D,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,eAAO,MAAM,wBAAwB,MAAO,UAAU,UAAU,OAAO,KAAG,OAezE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const CLICK_DUPLICATION_TIMEOUT = 300;
|
|
2
|
+
export const CLICK_THROUGH_CLOSEST_MAX_LEVELS = 12;
|
|
3
|
+
export const isClickWithinProductLink = (e, result) => {
|
|
4
|
+
let currentElement = e.target;
|
|
5
|
+
let href = null;
|
|
6
|
+
let level = 0;
|
|
7
|
+
const resultUrl = result?.display?.mappings.core?.url || result?.mappings.core?.url || '';
|
|
8
|
+
while (currentElement && level < CLICK_THROUGH_CLOSEST_MAX_LEVELS) {
|
|
9
|
+
href = currentElement.getAttribute('href');
|
|
10
|
+
if (href && resultUrl && href.includes(resultUrl)) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
currentElement = currentElement.parentElement;
|
|
14
|
+
level++;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@searchspring/snap-controller",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.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.66.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.66.0",
|
|
29
|
+
"@searchspring/snap-event-manager": "^0.66.0",
|
|
30
|
+
"@searchspring/snap-logger": "^0.66.0",
|
|
31
|
+
"@searchspring/snap-profiler": "^0.66.0",
|
|
32
|
+
"@searchspring/snap-store-mobx": "^0.66.0",
|
|
33
|
+
"@searchspring/snap-tracker": "^0.66.0",
|
|
34
|
+
"@searchspring/snap-url-manager": "^0.66.0"
|
|
35
35
|
},
|
|
36
36
|
"sideEffects": false,
|
|
37
37
|
"files": [
|
|
38
38
|
"dist/**/*"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "97d209ef85cfb63946bea8ce3737b7dcb2803b89"
|
|
41
41
|
}
|