1
+ import {
2
+ AlgoliaSearchHelper ,
3
+ SearchParameters ,
4
+ SearchResults ,
5
+ } from 'algoliasearch-helper' ;
1
6
import {
2
7
checkRendering ,
3
8
escapeRefinement ,
@@ -7,6 +12,12 @@ import {
7
12
noop ,
8
13
toArray ,
9
14
} from '../../lib/utils' ;
15
+ import {
16
+ Connector ,
17
+ CreateURL ,
18
+ InstantSearch ,
19
+ WidgetRenderState ,
20
+ } from '../../types' ;
10
21
11
22
const withUsage = createDocumentationMessageGenerator ( {
12
23
name : 'toggle-refinement' ,
@@ -15,56 +26,118 @@ const withUsage = createDocumentationMessageGenerator({
15
26
16
27
const $$type = 'ais.toggleRefinement' ;
17
28
18
- const createSendEvent = ( { instantSearchInstance, attribute, on, helper } ) => (
19
- ...args
20
- ) => {
21
- if ( args . length === 1 ) {
22
- instantSearchInstance . sendEventToInsights ( args [ 0 ] ) ;
23
- return ;
24
- }
25
- const [ eventType , isRefined , eventName = 'Filter Applied' ] = args ;
26
- if ( eventType !== 'click' || on === undefined ) {
27
- return ;
28
- }
29
- // Checking
30
- if ( ! isRefined ) {
31
- instantSearchInstance . sendEventToInsights ( {
32
- insightsMethod : 'clickedFilters' ,
33
- widgetType : $$type ,
34
- eventType,
35
- payload : {
36
- eventName,
37
- index : helper . getIndex ( ) ,
38
- filters : on . map ( value => `${ attribute } :${ value } ` ) ,
39
- } ,
40
- attribute,
41
- } ) ;
42
- }
29
+ type BuiltInSendEventForToggle = (
30
+ eventType : string ,
31
+ isRefined : boolean ,
32
+ eventName ?: string
33
+ ) => void ;
34
+ type CustomSendEventForToggle = ( customPayload : any ) => void ;
35
+
36
+ export type SendEventForToggle = BuiltInSendEventForToggle &
37
+ CustomSendEventForToggle ;
38
+
39
+ const createSendEvent = ( {
40
+ instantSearchInstance,
41
+ helper,
42
+ attribute,
43
+ on,
44
+ } : {
45
+ instantSearchInstance : InstantSearch ;
46
+ helper : AlgoliaSearchHelper ;
47
+ attribute : string ;
48
+ on : string [ ] | undefined ;
49
+ } ) => {
50
+ const sendEventForToggle : SendEventForToggle = ( ...args ) => {
51
+ if ( args . length === 1 ) {
52
+ instantSearchInstance . sendEventToInsights ( args [ 0 ] ) ;
53
+ return ;
54
+ }
55
+ const [ eventType , isRefined , eventName = 'Filter Applied' ] = args ;
56
+ if ( eventType !== 'click' || on === undefined ) {
57
+ return ;
58
+ }
59
+
60
+ // only send an event when the refinement gets applied,
61
+ // not when it gets removed
62
+ if ( ! isRefined ) {
63
+ instantSearchInstance . sendEventToInsights ( {
64
+ insightsMethod : 'clickedFilters' ,
65
+ widgetType : $$type ,
66
+ eventType,
67
+ payload : {
68
+ eventName,
69
+ index : helper . getIndex ( ) ,
70
+ filters : on . map ( value => `${ attribute } :${ value } ` ) ,
71
+ } ,
72
+ attribute,
73
+ } ) ;
74
+ }
75
+ } ;
76
+ return sendEventForToggle ;
43
77
} ;
44
78
45
- /**
46
- * @typedef {Object } ToggleValue
47
- * @property {boolean } isRefined `true` if the toggle is on.
48
- * @property {number } count Number of results matched after applying the toggle refinement.
49
- * @property {Object } onFacetValue Value of the toggle when it's on.
50
- * @property {Object } offFacetValue Value of the toggle when it's off.
51
- */
79
+ export type ToggleRefinementValue = {
80
+ /** whether this option is enabled */
81
+ isRefined : boolean ;
82
+ /** number of result if this option is enabled */
83
+ count : number | null ;
84
+ } ;
52
85
53
- /**
54
- * @typedef {Object } CustomToggleWidgetParams
55
- * @property {string } attribute Name of the attribute for faceting (eg. "free_shipping").
56
- * @property {Object } [on = true] Value to filter on when toggled.
57
- * @property {Object } [off] Value to filter on when not toggled.
58
- */
86
+ export type ToggleRefinementConnectorParams = {
87
+ /** Name of the attribute for faceting (eg. "free_shipping"). */
88
+ attribute : string ;
89
+ /**
90
+ * Value to filter on when toggled.
91
+ * @default "true"
92
+ */
93
+ on ?: string | string [ ] | boolean | boolean [ ] ;
94
+ /**
95
+ * Value to filter on when not toggled.
96
+ */
97
+ off ?: string | string [ ] | boolean | boolean [ ] ;
98
+ } ;
59
99
60
- /**
61
- * @typedef {Object } ToggleRenderingOptions
62
- * @property {ToggleValue } value The current toggle value.
63
- * @property {function():string } createURL Creates an URL for the next state.
64
- * @property {boolean } canRefine Indicates if search state can be refined.
65
- * @property {function(value) } refine Updates to the next state by applying the toggle refinement.
66
- * @property {Object } widgetParams All original `CustomToggleWidgetParams` forwarded to the `renderFn`.
67
- */
100
+ export type ToggleRefinementRenderState = {
101
+ /** The current toggle value */
102
+ value : {
103
+ name : string ;
104
+ isRefined : boolean ;
105
+ count : number | null ;
106
+ onFacetValue : ToggleRefinementValue ;
107
+ offFacetValue : ToggleRefinementValue ;
108
+ } ;
109
+ /** Creates an URL for the next state. */
110
+ createURL : CreateURL < string > ;
111
+ /** send a "facet clicked" insights event */
112
+ sendEvent : SendEventForToggle ;
113
+ /** Indicates if search state can be refined. */
114
+ canRefine : boolean ;
115
+ /** Updates to the next state by applying the toggle refinement. */
116
+ refine : ( value : { isRefined : boolean } ) => void ;
117
+ } ;
118
+
119
+ export type ToggleRefinementWidgetDescription = {
120
+ $$type : 'ais.toggleRefinement' ;
121
+ renderState : ToggleRefinementRenderState ;
122
+ indexRenderState : {
123
+ toggleRefinement : {
124
+ [ attribute : string ] : WidgetRenderState <
125
+ ToggleRefinementRenderState ,
126
+ ToggleRefinementConnectorParams
127
+ > ;
128
+ } ;
129
+ } ;
130
+ indexUiState : {
131
+ toggle : {
132
+ [ attribute : string ] : boolean ;
133
+ } ;
134
+ } ;
135
+ } ;
136
+
137
+ export type ToggleRefinementConnector = Connector <
138
+ ToggleRefinementWidgetDescription ,
139
+ ToggleRefinementConnectorParams
140
+ > ;
68
141
69
142
/**
70
143
* **Toggle** connector provides the logic to build a custom widget that will provide
@@ -73,95 +146,66 @@ const createSendEvent = ({ instantSearchInstance, attribute, on, helper }) => (
73
146
* Two modes are implemented in the custom widget:
74
147
* - with or without the value filtered
75
148
* - switch between two values.
76
- *
77
- * @type {Connector }
78
- * @param {function(ToggleRenderingOptions, boolean) } renderFn Rendering function for the custom **Toggle** widget.
79
- * @param {function } unmountFn Unmount function called when the widget is disposed.
80
- * @return {function(CustomToggleWidgetParams) } Re-usable widget factory for a custom **Toggle** widget.
81
- * @example
82
- * // custom `renderFn` to render the custom ClearAll widget
83
- * function renderFn(ToggleRenderingOptions, isFirstRendering) {
84
- * ToggleRenderingOptions.widgetParams.containerNode
85
- * .find('a')
86
- * .off('click');
87
- *
88
- * var buttonHTML = `
89
- * <a href="${ToggleRenderingOptions.createURL()}">
90
- * <input
91
- * type="checkbox"
92
- * value="${ToggleRenderingOptions.value.name}"
93
- * ${ToggleRenderingOptions.value.isRefined ? 'checked' : ''}
94
- * />
95
- * ${ToggleRenderingOptions.value.name} (${ToggleRenderingOptions.value.count})
96
- * </a>
97
- * `;
98
- *
99
- * ToggleRenderingOptions.widgetParams.containerNode.html(buttonHTML);
100
- * ToggleRenderingOptions.widgetParams.containerNode
101
- * .find('a')
102
- * .on('click', function(event) {
103
- * event.preventDefault();
104
- * event.stopPropagation();
105
- *
106
- * ToggleRenderingOptions.refine(ToggleRenderingOptions.value);
107
- * });
108
- * }
109
- *
110
- * // connect `renderFn` to Toggle logic
111
- * var customToggle = instantsearch.connectors.connectToggleRefinement(renderFn);
112
- *
113
- * // mount widget on the page
114
- * search.addWidgets([
115
- * customToggle({
116
- * containerNode: $('#custom-toggle-container'),
117
- * attribute: 'free_shipping',
118
- * })
119
- * ]);
120
149
*/
121
- export default function connectToggleRefinement ( renderFn , unmountFn = noop ) {
150
+ const connectToggleRefinement : ToggleRefinementConnector = function connectToggleRefinement (
151
+ renderFn ,
152
+ unmountFn = noop
153
+ ) {
122
154
checkRendering ( renderFn , withUsage ( ) ) ;
123
155
124
- return ( widgetParams = { } ) => {
125
- const { attribute, on : userOn = true , off : userOff } = widgetParams ;
156
+ return widgetParams => {
157
+ const { attribute, on : userOn = true , off : userOff } = widgetParams || { } ;
126
158
127
159
if ( ! attribute ) {
128
160
throw new Error ( withUsage ( 'The `attribute` option is required.' ) ) ;
129
161
}
130
162
131
163
const hasAnOffValue = userOff !== undefined ;
132
- const hasAnOnValue = userOn !== undefined ;
133
- const on = hasAnOnValue ? toArray ( userOn ) . map ( escapeRefinement ) : undefined ;
164
+ const on = toArray ( userOn ) . map ( escapeRefinement ) ;
134
165
const off = hasAnOffValue
135
166
? toArray ( userOff ) . map ( escapeRefinement )
136
167
: undefined ;
137
168
138
- let sendEvent ;
169
+ let sendEvent : SendEventForToggle ;
139
170
140
- const toggleRefinementFactory = helper => ( { isRefined } = { } ) => {
141
- // Checking
171
+ const toggleRefinementFactory = ( helper : AlgoliaSearchHelper ) => (
172
+ {
173
+ isRefined,
174
+ } : {
175
+ isRefined : boolean ;
176
+ } = { isRefined : false }
177
+ ) => {
142
178
if ( ! isRefined ) {
143
179
sendEvent ( 'click' , isRefined ) ;
144
180
if ( hasAnOffValue ) {
145
- off . forEach ( v =>
181
+ off ! . forEach ( v =>
146
182
helper . removeDisjunctiveFacetRefinement ( attribute , v )
147
183
) ;
148
184
}
149
185
150
186
on . forEach ( v => helper . addDisjunctiveFacetRefinement ( attribute , v ) ) ;
151
187
} else {
152
- // Unchecking
153
188
on . forEach ( v => helper . removeDisjunctiveFacetRefinement ( attribute , v ) ) ;
154
189
155
190
if ( hasAnOffValue ) {
156
- off . forEach ( v => helper . addDisjunctiveFacetRefinement ( attribute , v ) ) ;
191
+ off ! . forEach ( v => helper . addDisjunctiveFacetRefinement ( attribute , v ) ) ;
157
192
}
158
193
}
159
194
160
195
helper . search ( ) ;
161
196
} ;
162
197
163
198
const connectorState = {
164
- createURLFactory : ( isRefined , { state, createURL } ) => ( ) => {
199
+ createURLFactory : (
200
+ isRefined : boolean ,
201
+ {
202
+ state,
203
+ createURL,
204
+ } : {
205
+ state : SearchParameters ;
206
+ createURL ( parameters : SearchParameters ) : string ;
207
+ }
208
+ ) => ( ) => {
165
209
state = state . resetPage ( ) ;
166
210
167
211
const valuesToRemove = isRefined ? on : off ;
@@ -233,28 +277,29 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
233
277
instantSearchInstance,
234
278
} ) {
235
279
const isRefined = results
236
- ? on ? .every ( v => helper . state . isDisjunctiveFacetRefined ( attribute , v ) )
237
- : on ? .every ( v => state . isDisjunctiveFacetRefined ( attribute , v ) ) ;
280
+ ? on . every ( v => helper . state . isDisjunctiveFacetRefined ( attribute , v ) )
281
+ : on . every ( v => state . isDisjunctiveFacetRefined ( attribute , v ) ) ;
238
282
239
- let onFacetValue = {
283
+ let onFacetValue : ToggleRefinementValue = {
240
284
isRefined,
241
285
count : 0 ,
242
286
} ;
243
287
244
- let offFacetValue = {
288
+ let offFacetValue : ToggleRefinementValue = {
245
289
isRefined : hasAnOffValue && ! isRefined ,
246
290
count : 0 ,
247
291
} ;
248
292
249
293
if ( results ) {
250
294
const offValue = toArray ( off || false ) ;
251
- const allFacetValues = results . getFacetValues ( attribute ) || [ ] ;
295
+ const allFacetValues = ( results . getFacetValues ( attribute , { } ) ||
296
+ [ ] ) as SearchResults . FacetValue [ ] ;
252
297
253
298
const onData = on
254
- ? .map ( v =>
299
+ . map ( v =>
255
300
find ( allFacetValues , ( { name } ) => name === unescapeRefinement ( v ) )
256
301
)
257
- . filter ( v => v !== undefined ) ;
302
+ . filter ( ( v ) : v is SearchResults . FacetValue => v !== undefined ) ;
258
303
259
304
const offData = hasAnOffValue
260
305
? offValue
@@ -264,7 +309,7 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
264
309
( { name } ) => name === unescapeRefinement ( v )
265
310
)
266
311
)
267
- . filter ( v => v !== undefined )
312
+ . filter ( ( v ) : v is SearchResults . FacetValue => v !== undefined )
268
313
: [ ] ;
269
314
270
315
onFacetValue = {
@@ -285,7 +330,7 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
285
330
) ;
286
331
}
287
332
288
- helper . setPage ( helper . state . page ) ;
333
+ helper . setPage ( helper . state . page ! ) ;
289
334
}
290
335
291
336
if ( ! sendEvent ) {
@@ -380,4 +425,6 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
380
425
} ,
381
426
} ;
382
427
} ;
383
- }
428
+ } ;
429
+
430
+ export default connectToggleRefinement ;
0 commit comments