See web-components.fact-finder.de for full documentation.
- Install Node.js
- Type
npm init
and follow the instructions - Add the following line to the
dependencies
section inpackage.json
:Adjust the version as necessary."ff-web-components": "https://github.com/FACT-Finder-Web-Components/ff-web-components.git#5.x.x"
- Run
npm install
- Download a version directly from GitHub or download it via web-components.fact-finder.de
- Unzip the file
- Save the contents to your location of choice
This guide documents the changes to the Web Components library since version 4.x
.
It is not a step-by-step guide to walk you through the migration. Skim through the document in its entirety first to get an overview of all changes before starting to work through each topic in detail.
Web Components has undergone a complete rewrite of its core. The new library more closely reflects the FactFinder REST API with as few abstractions as possible. This shall reduce the number of concepts required to learn while simultaneously increase transparency of the data flow.
The data models the library uses are identical, with very few exceptions, to the models used by the FactFinder API. You can (and should) always consult FactFinder's API documentation for details.
FactFinder versions prior to NG (v5
API) are no longer supported.
Going forward, the latest version of Web Components will only support the latest API version of FactFinder.
There is now more focus on the request/response pipelines which shall provide more clarity of the data flow. See the end of this migration guide for a visual representation of these pipelines.
The fundamental way the library is referenced from your project has not changed.
What is new is that the Custom Elements polyfill is now optional.
<head>
<!-- These two references are now optional.
They are only required if you must support very old browsers that do not support Custom Elements. -->
<!-- <script src="../dist/vendor/custom-elements-es5-adapter.js"></script> -->
<!-- <script src="../dist/vendor/webcomponents-loader.js"></script> -->
<script defer src="../dist/bundle.js"></script>
</head>
The concept of accessing the core has not changed compared to previous versions.
The ffReady
event is now called ffCoreReady
.
This shall emphasize that the core and the custom elements initialize separately.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
});
ffCoreReady
fires exactly once as soon as the core finishes initializing.
The event object in the event listener receives three fields.
factfinder
is the reference to the core.
After initialization, factfinder
is also globally available.
window.factfinder
init
is the function with which you initialize the Web Components application.
It must be called exactly once during the ffCoreReady
event.
Failing to do so results in the Web Components library to throw an exception.
See the Initialization of the Web Components application section for details on usage and available options.
initialSearch
is a function that replaces the search-immediate
concept from previous versions.
It takes a search or navigation params object as its first argument and optionally a search or navigation options object as its second.
Depending on whether the application is configured to be a category page or regular search page, the function will invoke either a search
or navigation
request from the factfinder.request
namespace.
Make sure to always use this function for the initial search request as it provides different handling of the browser history than a regular search request.
initialSearch({ query: `deck chair` }, { userId: `user123` });
You can always pass a SearchParams object to initialSearch
, even when the application is configured to category page.
In this case, initialSearch
will internally convert the SearchParams to NavigationParams.
See your FactFinder API documentation for details.
The core's structure is as follows.
factfinder: {
request: {
campaignPage: (Required CampaignPageParams, Optional RequestOptions) => Promise CampaignsResult,
campaignProduct: (Required CampaignProductParams, Optional RequestOptions) => Promise CampaignsResult,
campaignShoppingCart: (Required CampaignShoppingCartParams, Optional RequestOptions) => Promise CampaignsResult,
navigation: (Required NavigationParams, Optional NavigationOptions) => Promise Result,
navigationCategory: (Required NavigationCategoryParams, Optional RequestOptions) => Promise CategoryNavigation,
predictiveBasket: (Required PredictiveBasketParams, Optional RequestOptions) => Promise PredictiveBasketResult,
recommendation: (Required RecommendationParams, Optional RequestOptions) => Promise RecommendationResultWithFieldRoles,
records: (Required RecordsParams, Optional RequestOptions) => Promise FlatRecordsResult,
search: (Required SearchParams, Optional SearchOptions) => Promise Result,
similar: (Required SimilarParams, Optional RequestOptions) => Promise SimilarProductsWithFieldRoles,
suggest: (Required SuggestParams, Optional RequestOptions) => Promise SuggestionResult,
before: {
campaignPage: (CancellableSubscriber RequestInfoCampaignPage) => SubscriberID,
campaignProduct: (CancellableSubscriber RequestInfoCampaignProduct) => SubscriberID,
campaignShoppingCart: (CancellableSubscriber RequestInfoCampaignShoppingCart) => SubscriberID,
compare: (CancellableSubscriber RequestInfoCompare) => SubscriberID,
navigation: (CancellableSubscriber RequestInfoNavigation) => SubscriberID,
navigationCategory: (CancellableSubscriber RequestInfoNavigationCategory) => SubscriberID,
predictiveBasket: (CancellableSubscriber RequestInfoPredictiveBasket) => SubscriberID,
recommendation: (CancellableSubscriber RequestInfoRecommendation) => SubscriberID,
records: (CancellableSubscriber RequestInfoRecords) => SubscriberID,
search: (CancellableSubscriber RequestInfoSearch) => SubscriberID,
similar: (CancellableSubscriber RequestInfoSimilar) => SubscriberID,
suggest: (CancellableSubscriber RequestInfoSuggest) => SubscriberID,
},
},
response: {
subscribeCampaignPage: (Subscriber CampaignsResult RequestInfoCampaignPage) => SubscriberID,
subscribeCampaignProduct: (Subscriber CampaignsResult RequestInfoCampaignProduct) => SubscriberID,
subscribeCampaignRedirect: (CancellableSubscriber ({ campaign: Campaign,
result: Result,
requestInfo: Optional (RequestInfoNavigation | RequestInfoSearch)
})) => SubscriberID,
subscribeCampaignShoppingCart: (Subscriber CampaignsResult RequestInfoCampaignShoppingCart) => SubscriberID,
subscribeNavigation: (Subscriber Result (Optional RequestInfoNavigation)) => SubscriberID,
subscribeNavigationCategory: (Subscriber CategoryNavigation RequestInfoNavigationCategory) => SubscriberID,
subscribePredictiveBasket: (Subscriber PredictiveBasketResult RequestInfoPredictiveBasket) => SubscriberID,
subscribeRecommendation: (Subscriber RecommendationResultWithFieldRoles RequestInfoRecommendation) => SubscriberID,
subscribeRecords: (Subscriber FlatRecordsResult RequestInfoRecords) => SubscriberID,
subscribeSearch: (Subscriber Result (Optional RequestInfoSearch)) => SubscriberID,
subscribeSimilar: (Subscriber SimilarProductsWithFieldRoles RequestInfoSimilar) => SubscriberID,
subscribeSuggest: (Subscriber SuggestionResult (Optional RequestInfoSuggest)) => SubscriberID,
subscribeSearchAndNavigation: (Subscriber Result (RequestInfoNavigation | RequestInfoSearch)) => SubscriberID,
unsubscribe: (SubscriberID) => Boolean,
transformCampaignPage: (Transformer CampaignsResult RequestInfoCampaignPage) => undefined,
transformCampaignProduct: (Transformer CampaignsResult RequestInfoCampaignProduct) => undefined,
transformCampaignShoppingCart: (Transformer CampaignsResult RequestInfoCampaignShoppingCart) => undefined,
transformCompare: (Transformer CompareResult RequestInfoCompare) => undefined,
transformNavigation: (Transformer Result (Optional RequestInfoNavigation)) => undefined,
transformNavigationCategory: (Transformer CategoryNavigation RequestInfoNavigationCategory) => undefined,
transformPredictiveBasket: (Transformer PredictiveBasketResult RequestInfoPredictiveBasket) => undefined,
transformRecommendation: (Transformer RecommendationResultWithFieldRoles RequestInfoRecommendation) => undefined,
transformRecords: (Transformer FlatRecordsResult RequestInfoRecords) => undefined,
transformSearch: (Transformer Result (Optional RequestInfoSearch)) => undefined,
transformSimilar: (Transformer SimilarProductsWithFieldRoles RequestInfoSimilar) => undefined,
transformSuggest: (Transformer SuggestionResult (Optional RequestInfoSuggest)) => undefined,
dispatch: {
navigation: (Result, Optional RequestInfoNavigation) => Result,
navigationBeforeTransform: (Result, Optional RequestInfoNavigation) => Result,
search: (Result, Optional RequestInfoSearch) => Result,
searchBeforeTransform: (Result, Optional RequestInfoSearch) => Result,
suggest: (SuggestionResult, Optional RequestInfoSuggest) => SuggestionResult,
ssrNavigation: (Result, Optional RequestInfoNavigation) => Result,
ssrSearch: (Result, Optional RequestInfoSearch) => Result,
},
},
tracking: {
cart: (Required (NonEmptyArray CartOrCheckoutEvent), Optional RequestOptions) => Promise undefined,
checkout: (Required (NonEmptyArray CartOrCheckoutEvent), Optional RequestOptions) => Promise undefined,
click: (Required (NonEmptyArray ClickEvent), Optional RequestOptions) => Promise undefined,
landingPageClick: (Required (NonEmptyArray LandingPageClickEvent), Optional RequestOptions) => Promise undefined,
login: (Required (NonEmptyArray LoginEvent), Optional RequestOptions) => Promise undefined,
predbasketClick: (Required (NonEmptyArray PredBasketClickEvent), Optional RequestOptions) => Promise undefined,
recommendationClick: (Required (NonEmptyArray RecommendationClickEvent), Optional RequestOptions) => Promise undefined,
before: {
cart: (CancellableSubscriber RequestInfoTrackCartOrCheckout) => SubscriberID,
checkout: (CancellableSubscriber RequestInfoTrackCartOrCheckout) => SubscriberID,
click: (CancellableSubscriber RequestInfoTrackClick) => SubscriberID,
landingPageClick: (CancellableSubscriber RequestInfoTrackLandingPageClick) => SubscriberID,
login: (CancellableSubscriber RequestInfoTrackLogin) => SubscriberID,
predbasketClick: (CancellableSubscriber RequestInfoTrackPredBasketClick) => SubscriberID,
recommendationClick: (CancellableSubscriber RequestInfoTrackRecommendationClick) => SubscriberID,
},
},
config: {
get: () => Config,
setFFParams: (Required ConfigFFParams) => undefined,
setAppConfig: (Required ConfigAppConfig) => undefined,
},
notifications: {
addSuggestClickListener: (CancellableSubscriber Origin) => undefined,
setSuggestDetailListener: (Subscriber Origin) => undefined,
setSuggestUnmappedListener: (Subscriber Origin) => undefined,
},
routing: {
setNavigateListener: ((url, origin) => Boolean) => undefined,
setUrlParamOptionsListener: (() => UrlParamMappingOptions) => undefined,
sandboxed: {
restoreHistory: (historyPayload) => undefined,
setHistoryWriteListener: (historyPayload => undefined) => undefined,
setSessionReadWriteListeners: ({ read, write }) => undefined,
},
},
utils: {
env: {
searchParamsFromUrl: (options) => SearchParams,
},
filterBuilders: {
categoryFilter: (name, path) => Filter,
filter: (name, values) => Filter,
},
formatters: {
add: (name, fn) => undefined,
},
toNavigationParams: (SearchParams) => NavigationParams,
},
version: String,
}
Methods in this namespace reflect the relevant FactFinder REST API endpoints.
They are used for initiating requests to FactFinder.
This replaces EventAggregator.addFFEvent(ffEvent)
.
These methods take a required parameter which is equivalent to the FactFinder API specification for POST requests and an optional parameter to define various options to the request pipeline's behaviour.
All methods in this namespace return a Promise
that resolves to the relevant result after the request/response pipeline has completed.
Note that this Promise
rejects on server responses that are not 200
.
This is different from the Promise
returned by the platform-native fetch
.
Subscribers to these hooks are called before the related request is issued to FactFinder. This can be useful for adding additional data to the request.
Subscribers in this namespace are of type CancellableSubscriber
.
They receive one argument of the type mentioned above.
It is an object composed of the information received when the request was invoked.
These are the request parameters (e.g. SearchParams
) and the request options, both supplemented with global default values when applicable.
When the CancellableSubscriber
returns false
, the request pipeline halts and no request is emitted.
This is useful when setting up page redirects as the result of the FactFinder request from the redirecting page is discarded anyway.
It is also useful if you need to interrupt the automatic pipeline processing and resume manually later.
Methods in this namespace are used to deal with the response part of the pipeline after the request returns from FactFinder.
subscribe*
methods (e.g. subscribeSearch
or subscribeSuggest
) take a subscriber function and return the subscriber's auto-generated ID.
They replace ResultDispatcher.subscribe(topic, fn)
.
Subscriber functions, when invoked by the response pipeline, receive two parameters.
- The first is the FactFinder result data of the type specified above.
- The second is a RequestInfo object.
factfinder.response.subscribeSuggest((suggestionResult, requestInfo) => {
// suggestionResult: is `SuggestionResult` from the FactFinder REST API
// requestInfo: contains the endpoint-specific params object
// and the `requestOptions` object that were used to invoke the request.
// {
// suggestParams, // This would be `campaignPageParams`, `recommendationParams`, etc. for other endopints.
// requestOptions,
// }
});
There are some special cases.
The RequestInfo object for subscribers to /search
or /navigation
consists of the REST API's SearchRequest
object, or NavigationRequest
respectively, minus its params
field but instead with a requestOptions
field like in subscribers to other endpoints.
factfinder.response.subscribeSearch((result, requestInfoSearch) => {
// result: is `Result` from the FactFinder REST API
// requestInfoSearch:
// {
// searchParams,
// searchOptions: {
// searchControlParams,
// sid,
// userId,
// userInput,
//
// requestOptions,
// },
// }
});
Another special case is the factfinder.response.subscribeSearchAndNavigation
subscription.
Depending on whether the /search
or the /navigation
endpoint was queried, the second argument to the subscriber is either searchOptions
or navigationOptions
.
Lastly, factfinder.response.subscribeCampaignRedirect
also receives different arguments.
Pro tip!
Remember, you can always inspect the arguments to subscribers by logging them to the browser's console and by setting a breakpoint.
factfinder.response.subscribeSearchAndNavigation((...args) => {
console.log(...args);
debugger;
});
Return values of subscribers are discarded.
Subscribers receive data after the Web Components.
You can subscribe as many handlers as you like. Subscribers are called in the order they were registered.
unsubscribe
lets you remove a subscriber by passing its subscriber ID that you received when registering it.
Transformers are used to manipulate a FactFinder result before it is dispatched to the Web Components.
They take one parameter of the type specified above.
They replace ResultDispatcher.addCallback(topic, fn)
.
Transformers must return the manipulated result, or an error will be emitted.
You can register as many transformers as you like. They are called in the order of registration.
You can manually invoke the response pipeline by sending data through the various entry points in the response.dispatch
namespace.
It replaces the ResultDispatcher.dispatchXYZ
family.
Methods in this namespace take the data to be dispatched and an optional RequestInfo
object.
This object would usually be produced on the request side of the pipeline.
Provide it if there is information necessary to the steps in the response pipeline.
Data dispatched this way goes through the same process as data that is coming directly from FactFinder. All eligible subscribers will be invoked. The Web Components application does not distinguish between manually and automatically dispatched responses.
The response.dispatch.search
method, for example, invokes the response pipeline to the /search
API endpoint.
For Server Side Rendering, use response.dispatch.ssrSearch
for dispatching your SSR result on search result pages and response.dispatch.ssrNavigation
on category pages.
These two functions will cause DOM elements to receive their relevant data whenever they connect to the DOM without having to consider rendering delays.
Methods in the tracking
namespace communicate with their respective API endpoint.
They take an array of tracking events and a request options object.
The array must have at least one element or an error will be thrown.
The tracking methods return a Promise
that resolves when the request completes.
There is no response data and no response pipeline will be triggered.
These methods are request-only.
This namespace offers methods to subscribe to the tracking request pipeline.
The mechanics are the same as those in the request.before
namespace.
Registered subscribers receive a request info object with two properties.
events
- an array of the tracking event objects that were passed to the related tracking functionrequestOptions
- an object with options to control the request pipeline (same as inrequest.before
namespace)
You can update parts of the application's configuration at runtime with the functions in this namespace. The objects to pass have the same structure as the relevant counterparts on the config object during the application's initialization.
For details on the parameters' purposes, see the Initialization of the Web Components application section.
get
takes no argument and returns a copy of the application's configuration.
The returned object has the same structure as the one you pass to init
during initialization.
factfinder.config.get()
setFFParams
updates the library's configuration with parameters that are relevant to multiple FactFinder API endpoints.
These parameters will appear in all requests to the FactFinder API that accept them.
The function takes an object with the following fields:
// All fields are optional.
factfinder.config.setFFParams({
purchaserId, // String (pass `undefined` to unset value)
sid, // String (pass `undefined` to unset value)
userId, // String (pass `undefined` to unset value)
});
setAppConfig
updates the library's configuration with parameters that are exclusively relevant to how the client-side application behaves.
It takes an object with the following fields:
// All fields are optional.
factfinder.config.setAppConfig({
autoFetch, // String enum
categoryPage, // Array of _Filter_ objects as defined by the FF API (pass `undefined` to unset value)
dataBindingTags, // Array of String (pass `undefined` to unset value)
debug, // Boolean
fieldRoles, // Object (pass `undefined` to unset value)
formatting: { // Object (pass `undefined` to unset value)
locale, // String
formatOptions, // Same as `options` object in platform-native `Intl.NumberFormat(locales, options)`.
},
sandboxMode, // Boolean
});
Web Components no longer emits DOM events (e.g., suggest-item-clicked
).
Instead, the global factfinder
object offers the notifications
namespace.
There you can register event listeners independent of the current DOM status.
All suggest-related listeners receive a reference to the clicked ff-suggest-item
through their origin
parameter.
Multiple click-listeners can be added.
factfinder.notifications.addSuggestClickListener(origin => {
// Return `false` to prevent the pending request from being sent.
// Returning `false` does NOT prevent other click listeners from being invoked.
// return false;
});
The detail-listener is invoked when a suggest-item with a type specified in ff-suggest
's request-mapping-detail
attribute is clicked.
factfinder.notifications.setSuggestDetailListener(origin => {
});
The unmapped-listener is invoked when a suggest-item is clicked whose type does not appear in any of ff-suggest
's request-mapping
attributes.
factfinder.notifications.setSuggestUnmappedListener(origin => {
});
The routing
namespace has been introduced to the global factfinder
object.
It contains various tools to deal with routing related topics.
It also contains a sub-namespace routing.sandboxed
that, when you run Web Components in sandbox mode, enables you to manually deal with all environment interactions that Web Components would usually do automatically.
setNavigateListener
allows you to register a listener that is invoked whenever Web Components arrives at a point where it would navigate to a new page.
For example after clicking on a product suggestion that leads to a product detail page.
You can interrupt Web Components' attempt to navigate and handle it as your environment requires.
factfinder.routing.setNavigateListener((url, origin) => {
// `url` is the URL to which Web Components is about to navigate to.
window.location.href = `/my/custom/target`;
return false; // To prevent navigation by Web Components.
});
setUrlParamOptionsListener
lets you register a handler that provides options to Web Components for translating SearchParams to a URL string.
The handler is called before Web Components updates the URL and writes to the browser history.
It receives no arguments.
An example with the available options and their default values:
factfinder.routing.setUrlParamOptionsListener(() => ({
allow: [],
block: [`activeAbTests`, `followSearch`, `purchaserId`],
blockFilters: [],
keyMapping: {},
order: [`query`, `filters`, `sortItems`],
postStringifier: undefined,
stringifiers: {},
}));
See Customize URL parameters for more details.
The functions in the routing.sandboxed
namespace are only relevant when the Web Components application is configured to run in sandbox mode.
A typical use case is in single-page-applications.
Caution
Sandbox mode is a very advanced feature for very specific use cases that requires substantial knowledge about web app development, routing and state persistence. If you are not comfortable in these fields, we strongly advise against the use of sandbox mode.
restoreHistory(historyPayload)
restores a search or navigation result.
Outside of sandbox mode Web Components would read the result data from the browser history after the popstate
event.
By calling this function, you are calling Web Components' internal popstate handler.
Therefore, it is best to link this function to your integration's popstate handler or its equivalent.
As its argument restoreHistory
takes an object (historyPayload
) which has two fields:
result
- The Result object as received from FactFinder that shall be restored.
requestInfo
- Either a
RequestInfoSearch
or aRequestInfoNavigation
object depending on whether the result to be restored is a search or a navigation result.
- Either a
Note
It is not necessary to build the
historyPayload
object yourself. You receive it from Web Components whenever it would usually create a browser history entry. Your integration merely has to keep track of these entries and pass them back to Web Components when required.
setHistoryWriteListener(historyPayload => {})
lets you register a handler that is invoked when the response pipeline reaches the point where it would normally write to the URL and browser history.
It is also invoked when a request to FactFinder was issued with the withoutHistory: true
option.
Set this parameter whenever you want to issue a search request without manipulating the browser history but when you still want to have the result dispatched to the custom elements.
The withoutHistory
parameter is only available to search and navigation requests.
The handler receives a historyPayload
object which is the same that you would pass to restoreHistory(historyPayload)
.
factfinder.request.search({ query: `tea and crumpets` }, { withoutHistory: true });
factfinder.routing.sandboxed.setHistoryWriteListener(historyPayload => {
const { result, requestInfo } = historyPayload;
// Handle history and URL manually.
});
setSessionReadWriteListeners({ read, write })
lets you register read and write handlers to deal with session management.
In order to maintain a user session across page navigation Web Components usually uses localStorage
.
In sandbox mode it is your responsibility to manage session data.
To register the listeners you pass an object with two fields (read
and write
) to setSessionReadWriteListeners
.
The read
listener is invoked whenever Web Components requires session data.
It receives no arguments.
Make the listener return the data that you received from the write
listener.
If invalid data is returned, Web Components will generate a new session.
This automatically covers the case of a user visiting your page for the first time and thus being unable to provide valid session data.
The write
listener is invoked whenever Web Components wants to persist updated session data.
It receives the session as its first argument.
Store it wherever appropriate for your integration.
Both read
and write
listeners will be called during each request to FactFinder.
There is no need to keep a history of session data.
Only the latest data is required.
Also make sure to not modify the session data.
The following is a schematic example that outlines the concepts of the Web Components sandbox mode API. Your actual integration is likely going to look tremendously different.
document.addEventListener(`ffCoreReady`, ({ factfinder, init }) => {
init({
ff: {}, // Section omitted for brevity.
appConfig: {
sandboxMode: true,
},
});
factfinder.routing.sandboxed.setSessionReadWriteListeners({
read: () => yourApp.getSessionData(),
write: sessionData => yourApp.storeSessionData(sessionData),
});
factfinder.routing.sandboxed.setHistoryWriteListener(historyPayload => {
yourApp.storeHistoryData(historyPayload);
});
yourApp.router.onPopstateEvent(() => {
factfinder.routing.sandboxed.restoreHistory(yourApp.getHistoryData());
});
});
Throughout the request/response pipeline you will encounter the origin
reference in requestOptions
.
It does not have a specific type as it can be anything.
If you issue a request manually, you have the option to set origin
to whatever makes most sense for you.
If a request is issued by a Web Components element, origin
will be the ff-
DOM element that was interacted with.
You can access origin
in for example the listeners from the factfinder.request.before
namespace.
Be aware that the DOM element referenced in origin
may be reused by the rendering mechanism when the upcoming response is processed.
Avoid storing and using the origin
reference outside the context you encounter it.
Web Components restores previous search results from the browser history when navigating back and forth.
However, it is important to note that DOM elements cannot be stored in the browser history.
Therefore, restored search results that pass through the response pipeline do not contain the origin
field.
factfinder.communication.globalElementValues.currentFFSearchBoxValue
was removed without replacement.
The initialization of the Web Components application happens during the ffCoreReady
event.
The event occurs on the document
object when the library's core finishes its initialization.
Call the init
function and pass it a config object with all parameters your application requires.
The following example shows the minimum setup.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
init({
ff: {
url: `https://your-instance.fact-finder.com/fact-finder`,
channel: `your-channel`,
apiKey: `your.apiKey`,
},
});
});
The config object is divided into three sections: ff
, ffParams
, and appConfig
.
This section must always be specified. It is essential to establish a connection to the FactFinder API.
It has three required fields.
ff: {
url: `https://your-instance.fact-finder.com/fact-finder`,
channel: `your-channel`,
apiKey: `your.apiKey`,
}
Web Components no longer automatically authenticates with the FactFinder API. You have to create an API key first, then pass it to your Web Components application. Refer to your FactFinder documentation for details on how to generate API keys.
The ffParams
section defines parameters that are relevant to various endpoints of the FactFinder REST API.
Once they are specified here, they will be added to all request types that accept them.
The ffParams
section itself as well as each of its fields are optional.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
init({
ff: { ... }, // Section omitted for brevity.
ffParams: {
purchaserId: `optional string`,
sid: `optional string`,
userId: `optional string`
},
});
});
If sid
is empty and Web Components is unable to determine a session ID from the environment, a new random session ID will be generated and preserved across page loads.
Usually, you never have to set it.
You only need to set it if you want to force a particular session ID.
The appConfig
section defines parameters that control how the client-side Web Components application behaves.
None of these parameters are sent to FactFinder.
The appConfig
section itself and all of its fields are optional.
You only need to specify the fields you want to set.
The following example shows all available fields and their default values.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
init({
ff: { ... }, // Section omitted for brevity.
appConfig: {
autoFetch: `SSR_ONLY`, // Enum. Other allowed values: "ALWAYS_ON", "ALWAYS_OFF"
categoryPage: undefined, // Array of Filter
dataBindingTags: undefined, // Two-element array of String
debug: false, // Boolean
fieldRoles: undefined, // Object
formatting: undefined, // Object
sandboxMode: false, // Boolean
},
});
});
The autoFetch
parameter controls how Web Components elements behave when they are added to the DOM.
More generally, it affects all subscribers to the response pipeline.
When auto-fetching is active, subscribers to the response pipeline will be invoked immediately on registration and receive the last search result.
It takes one of three allowed values: SSR_ONLY
(default), ALWAYS_ON
and ALWAYS_OFF
.
With SSR_ONLY
, auto-fetching is not active from the beginning.
It becomes active when you use either factfinder.response.dispatch.ssrSearch
or factfinder.response.dispatch.ssrNavigation
.
Auto-fetching will remain active until a search or navigation request is triggered.
Use these when integrating Server Side Rendering.
ALWAYS_ON
permanently sets auto-fetching to active while ALWAYS_OFF
sets it inactive.
This property takes an array of Filter
objects as defined by the FactFinder API.
When this property is set, Web Components considers the current page to be a category page and behaves accordingly.
Examples of this behavior are:
- usage of the
/navigation
API endpoint instead of/search
- specified category filters are fixed in the ASN and cannot be deselected
- click tracking events send the category path instead of the (non-existing) search query.
See the Category Pages section for details on usage.
The dataBindingTags
property allows you to specify different tags for the HTML data bindings.
This may be necessary to avoid conflicts when you are using Web Components together with a third party rendering framework that uses the same data binding tags.
It is a replacement for the mustache-delimiters
attribute on the discontinued ff-communication
element.
Default tags:
<ff-record>{{regular}}</ff-record>
<ff-record>{{{unescapedHtml}}}</ff-record>
Custom tags:
dataBindingTags
takes an array of two string elements.
They must be different or the template engine will not be able to interpret the templates correctly.
Make sure to choose tags that do not occur anywhere else.
init({
appConfig: {
dataBindingTags: [`[[`, `]]`],
},
});
<ff-record>[[regular]]</ff-record>
<ff-record>[[{unescapedHtml}]]</ff-record>
When using unescaped HTML binding, your template must use the custom tags plus a pair of single braces {
,}
.
A boolean value to define whether Web Components shall run in debug or live mode.
Debug mode does not catch exceptions but instead lets them propagate as they occur. After an error, the Web Components application will be in an invalid state.
There is also more logging.
It is advisable to use this mode during development to find errors quickly.
Live mode is the default setting. It will catch errors and skip to the next operation in order to keep the application running without becoming unusable. Errors will be logged to the browser's console, so it is still possible to detect and locate errors.
Be aware that even in live mode errors may cause data corruption that put the application into a nonsensical state. The most important aim of live mode is to prevent system level exceptions that halt the browser's whole execution process.
By setting this property you can tell the library the field role mapping without waiting for a response from FactFinder. Here you can override the field roles defined in FactFinder.
If unset, the first response with field roles from FactFinder will set it.
While most scenarios don't require this value to be set, it is still advisable to always do so to avoid unexpectedly encountering situations that do require this value.
You do need to set it when you want to issue requests that rely on the field roles, but you don't want to first invoke a search request. This is typically limited to checkout-tracking requests.
Currency formatting in the previous version of Web Components was done in a centralized fashion by configuring attributes such as currency-country-code
or currency-fields
.
When set, Web Components would modify relevant data in-place at the data layer.
The new approach works entirely at the display layer without modifying any data. It uses the web platform's native localization API. See documentation at MDN.
You decide directly in the Web Components HTML templates which fields to format. Web Components offers several default formatters for currency and generic decimal numbers, but you may also specify your own in case the default formatters don't satisfy your requirements.
First, you configure locale and formatting options in the formatting
parameter.
This is an object with two properties locale
and formatOptions
.
These are the same values that you would pass to the platform-native Intl.NumberFormat(locales, options)
constructor.
See MDN for details.
Example:
init({
appConfig: {
formatting: {
locale: `en-GB`,
formatOptions: {
style: `currency`,
currency: `GBP`,
},
},
},
});
You format numbers directly in the HTML templates by adding the desired formatter to the data binding.
If a formatter is for example called $
, this could look like this:
<ff-record>{{$ variantValues.0.Price}}</ff-record>
Formatters are ordinary functions and may take multiple parameters or none at all.
<ff-record>Price rounded to a whole number: {{$ variantValues.0.Price 0}}</ff-record>
Web Components offers the following default formatters:
-
$
(number, fractionDigits): Formats its first parameter according to the currency configuration. The second parameter is optional and allows you to override your global settings for the number of decimal places.<ff-record> <span>{{$ variantValues.0.Old_price 0}}</span> <span>{{$ variantValues.0.Price}}</span> </ff-record>
-
$dec
(number, fractionDigits): Formats numbers according to your locale settings without any units. The second parameter is optional.<ff-record>Tolerance: {{$dec variantValues.0.Tolerance 3}} mm</ff-record>
-
$bctFilter
: This formatter takes no arguments and only works inff-breadcrumb-trail-item
elements withtype="filter"
. This specialized formatter is necessary because of the special data structure provided in theff-breadcrumb-trail-item
template context.Important! It formats only items that are related to facets with a
unit
set. You set thisunit
in your FactFinder UI, it must be identical to the currency unit that your formatting configuration produces. If yourformatOptions
are configured to render a€
symbol, yourunit
settings in the FactFinder UI must also be€
for this particular facet.<ff-breadcrumb-trail-item type="filter">{{$bctFilter}}</ff-breadcrumb-trail-item>
-
$facetElement
: This formatter takes no arguments and only works inff-filter-cloud
andff-asn-group-element
.Important! Like
$bctFilter
it only formats values related to a facet with itsunit
set to the currency symbol that yourformatOptions
produce.<ff-filter-cloud> <span data-template="filter">[x] {{facet.name}}: {{$facetElement}}</span> </ff-filter-cloud> <ff-asn> <ff-asn-group for-group="price"> <ff-asn-group-element> <div slot="unselected">{{$facetElement}} ({{element.totalHits}})</div> <div slot="selected">{{$facetElement}}</div> </ff-asn-group-element> </ff-asn-group> </ff-asn>
If the default formatters don't meet your requirements, you have the option to define your own formatters.
Use the factfinder.utils.formatters.add(name, fn)
method to register them.
(Note that you cannot overwrite existing formatters.)
The function that you pass as the second argument receives all values that are passed to it from the HTML template.
The this
keyword will be a reference of the data object available in the HTML template context.
(Use the function
keyword instead of an arrow function if you want to use this
.)
factfinder.utils.formatters.add(`allCaps`, function(str) {
return str.toUpperCase();
});
<ff-record>{{allCaps variantValues.0.Title}}</ff-record>
In the HTML example above, this
inside the allCaps
function would point to the Record object that you can also access from within ff-record
.
As an alternative to custom formatters, you can also use the factfinder.response.transform___
API to directly manipulate data fields and display them without any further formatting.
Caution
Sandbox mode is a very advanced feature for very specific use cases that requires substantial knowledge about web app development, routing and state persistence. If you are not comfortable in these fields, we strongly advise against the use of sandbox mode.
When sandboxMode
is true
, Web Components will not interact with the browser environment but instead invoke relevant listeners to notify you when Web Components would have manipulated the environment.
From within these listeners you are free to invoke actions as your individual integration requires.
Sandbox mode is typically used in single-page-applications but may also provide the required flexibility whenever Web Components' history management conflicts with your integration.
Actions Web Components will not take when sandboxMode
is true
include
- navigate to another page
- read/write browser history
- read/write URL
- manipulate
localStorage
DOM updates are not affected by sandbox mode.
In previous versions Web Components transformed the data it received from FactFinder into the data structure of FACT-Finder 7.3. Starting from this version, this transformation no longer happens. The response data as reported by the browser's network tab is now reflected in the Web Components' HTML templates.
The ff-communication
element is no longer available.
Instead, you configure your Web Components application with JavaScript.
Listen to the global ffCoreReady
event and pass all relevant parameters to the init
function.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
init({
ff: {
url: `https://your-instance.fact-finder.com/fact-finder`,
channel: `your-channel`,
apiKey: `your.apiKey`,
},
});
// This is also the best place to invoke a search request replacing the `search-immediate` attribute.
initialSearch({ query: `deck chair` });
});
The hide-suggest-onblur
attribute is no longer available.
It was replaced by the hide-onblur
attribute on the ff-suggest
element.
The default value of suggest-delay
was changed from 0
to 350
ms.
ff-searchbutton
relies on the current value in ff-searchbox
.
A page may have multiple search boxes and search buttons where each button needs to know which search box's current value it shall use to generate a search request from.
As a consequence these two elements must now be linked explicitly.
There are two options to establish this link.
By nesting the ff-searchbutton
inside an ff-searchbox
, the elements automatically detect their relation.
<ff-searchbox>
<input type="search">
<ff-searchbutton><button>Search</button></ff-searchbutton>
</ff-searchbox>
Similar to the native HTML elements label
and input
you can define the id
attribute on ff-searchbox
and the for
attribute on ff-searchbutton
and give them the same value.
With this approach the elements' locations in the DOM are irrelevant.
<div>
<ff-searchbox id="ffSearchBox">
<input type="search">
</ff-searchbox>
</div>
<div>
<ff-searchbutton for="ffSearchBox">
<button>Search</button>
</ff-searchbutton>
</div>
ff-searchbox
and ff-searchbutton
no longer emit the before-search
event.
Use the new factfinder.request.before.search()
instead to subscribe to the request pipeline and, if required, interrupt it.
The default value of hide-onblur
is now true
.
The previous mechanism to close ff-suggest
on command of ff-searchbox
was removed.
Toggling through suggest-items with left/right arrow keys on the keyboard is no longer supported. Up/down functionality remains unchanged.
This attributes takes a comma-separated list of ff-searchbox
ids from which ff-suggest
shall receive data.
<ff-searchbox id="box1"></ff-searchbox>
<ff-searchbox id="box2"></ff-searchbox>
<!-- Reacts to the `ff-searchbox` with id=box1 but not to the one with id="box2". -->
<ff-suggest for-searchbox="box1"></ff-suggest>
Make sure there is no additional white-space in the list.
<!-- Would still only listen to the first search box because the second ID is parsed as " box2" (with a leading space). -->
<ff-suggest for-searchbox="box1, box2"></ff-suggest>
The ff-asn
element has a new attribute hide-facets
.
You can use it to prevent the ASN from rendering the specified facets without removing them from the response data.
A common use case is to hide the category facet on category pages or the brand facet on brand pages.
Previously, you had to use CSS, which would still have the ff-asn
element process the data.
Or you had to delete the facet from the search result which could lead to data integrity problems.
The attribute takes a comma-separated list of facet names.
Note that these are not the facet's display name but the field name from your data feed.
In the FactFinder response it is called the associatedFieldName
.
<ff-asn hide-facets="category,brand"></ff-asn>
The elements ff-slider
and ff-slider-one-touch
have been merged into ff-slider
.
The ff-slider-one-touch
element is no longer available as a stand-alone element.
Instead, you can toggle the "one-touch" behaviour on by setting the one-touch
attribute on ff-slider
.
Classic slider:
<ff-slider></ff-slider>
One-Touch slider:
<ff-slider one-touch></ff-slider>
The step-size
attribute on ff-slider
was renamed to step
.
<!-- Previously -->
<ff-slider step-size="5"></ff-slider>
<!-- Now -->
<ff-slider step="5"></ff-slider>
The unit
attribute on ff-slider
is no longer required and was removed.
In ff-slider-control
, the input
elements were required to have an attribute data-control="1"
and data-control="2"
respectively to define which input
controls the lower-value handle and which controls the upper-value handle.
The values to these attributes have been changed from 1
and 2
to min
and max
.
<ff-slider-control>
<div>
<input data-control="min">
</div>
<div>
<input data-control="max">
</div>
</ff-slider-control>
The numbers rendered in the input
elements are no longer formatted with a unit symbol.
They are formatted as a decimal number according to your factfinder.config.get().appConfig.formatting.locale
settings.
The attribute decimal-places
was renamed to fraction-digits
.
It allows you to define the number of decimal places rendered in the input
elements.
The default value changed from undefined
to 0
.
In order to render the facet's unit there is a new attribute data-unit
that can be put on any element.
The innerText
of these elements gets overwritten with the unit
that is defined for the related Facet object.
Leave those elements empty.
<ff-slider-control fraction-digits="2">
<div>
<input data-control="min">
<span data-unit></span>
</div>
<div>
<input data-control="max">
<span data-unit></span>
</div>
</ff-slider-control>
The default template of ff-slider-control
no longer uses inline styles.
If you leave ff-slider-control
empty, the following HTML is used:
<div class="ffw-slider-control-default">
<ff-slider one-touch></ff-slider>
<div class="ffw-slider-control-inputs">
<div class="ffw-input-container">
<input data-control="min">
<span data-unit></span>
</div>
<span class="ffw-slider-control-line"></span>
<div class="ffw-input-container">
<input data-control="max">
<span data-unit></span>
</div>
</div>
</div>
The data format available in the HTML template context of ff-breadcrumb-trail-item
has changed.
Previously, it was the BreadCrumbTrailItem as defined by the FactFinder API. Now it is an object with both the BreadCrumbTrailItem and the related Facet object. For details on the structure of each of these objects, please see your FactFinder API documentation.
Note that the facet
is only available in items with type="filter"
.
In items with other types facet
is undefined
.
Before:
<ff-breadcrumb-trail>
<ff-breadcrumb-trail-item type="filter">{{text}}</ff-breadcrumb-trail-item>
</ff-breadcrumb-trail>
Now:
<!-- Data structure
{
facet: Facet,
item: BreadCrumbTrailItem,
}
-->
<ff-breadcrumb-trail>
<ff-breadcrumb-trail-item type="filter">{{facet.name}}: {{item.text}}</ff-breadcrumb-trail-item>
<ff-breadcrumb-trail-item type="search">Facet is undefined: {{facet}}</ff-breadcrumb-trail-item>
</ff-breadcrumb-trail>
Default values to ff-recommendation
are now aligned with the FactFinder API.
The data format provided in HTML templates changed from TypedFlatRecord
to RecordWithId
.
<ff-recommendation>
<ff-record-list>
<template data-role="record">
<ff-record>
<!-- `Title` and `Price` are example fields that may exist in a data feed. -->
<div>{{variantValues.0.Title}}</div>
<div>{{variantValues.0.Price}}</div>
</ff-record>
</template>
</ff-record-list>
</ff-recommendation>
usePerso
was renamed to usePersonalization
to match the parameter name in the FactFinder API.
The default value is now true
as defined by the FactFinder API.
subscribe
was removed.
ff-recommendation
receives data exclusively from requests issued by itself.
This also means that manually invoking a request via request.recommendation()
through JavaScript will not populate any ff-recommendation
elements.
You can still populate the nested ff-record-list
manually.
record-id
was renamed to product-number
.
It takes the value that is labeled with the productNumber
field role.
Default values to ff-similar-products
are now aligned with the FactFinder API.
The data format provided in HTML templates changed from TypedFlatRecord
to RecordWithId
.
<ff-similar-products>
<ff-record-list>
<template data-role="record">
<ff-record>
<!-- `Title` and `Price` are example fields that may exist in a data feed. -->
<div>{{variantValues.0.Title}}</div>
<div>{{variantValues.0.Price}}</div>
</ff-record>
</template>
</ff-record-list>
</ff-similar-products>
ff-similar-products
receives data exclusively from requests issued by itself.
This also means that manually invoking a request via request.similar()
through JavaScript will not populate any ff-similar-products
elements.
You can still populate the nested ff-record-list
manually.
record-id
was renamed to product-id
.
Depending on id-type
, it either takes the product number or the master ID.
record-id
was renamed to product-number
on ff-checkout-tracking-item
.
It takes the value that is labeled with the productNumber
field role.
ff-checkout-tracking-item
received these new attributes to better reflect FactFinder's API:
campaign
masterId
title
Other parameters accepted by FactFinder API (purchaserId
, sid
, userId
) must be configured in the global Web Components config if required.
The ff-middleware
and related elements have been removed.
Their functionality has been either integrated into the library or it can be replaced easily with custom code.
The ff-product-teaser-campaign-processor
element is no longer available.
Its functionality has been integrated into the response pipeline and is always active.
The ff-multi-attribute-parsing
element is no longer available.
Use the response pipeline instead to replace this feature.
Example:
function multiAttributeParsing(options) {
const {
srcField = undefined,
targetField = undefined,
targetFieldRaw = undefined,
entrySeparator = `|`,
keyValueSeparator = `=`,
unitSeparator = `~~`,
} = options || {};
return result => {
result.hits.forEach(hit => hit.variantValues?.forEach(vals => {
const srcString = vals[srcField];
if (!srcString) return;
if (vals.hasOwnProperty([targetField]) && targetField !== srcField) {
console.warn(`Overwriting target field '${targetField}'`);
}
const rawPairs = srcString.split(entrySeparator).filter(str => !!str);
vals[targetField] = pairsToObj(rawPairs);
if (targetFieldRaw) {
vals[targetFieldRaw] = srcString;
}
}));
return result;
};
function pairsToObj(rawPairs) {
return rawPairs.map(pair => pair.split(keyValueSeparator)).reduce((acc, kv) => {
const [key, unit] = kv[0].split(unitSeparator);
acc[key] = acc[key] || [];
acc[key].push({ unit, value: kv[1] });
return acc;
}, {});
}
}
const multiAttributeHandler = multiAttributeParsing({
srcField: `YourMultiAttributeField`,
targetField: `_parsedAttributes`,
targetFieldRaw: ``,
entrySeparator: `|`,
keyValueSeparator: `|`,
unitSeparator: `~~`,
});
factfinder.response.transformSearch(multiAttributeHandler);
factfinder.response.transformNavigation(multiAttributeHandler);
The available values for ff-template
's scope
attribute have changed to the supported endpoints of FactFinder's REST API.
These are: CampaignPage
, CampaignProduct
, CampaignShoppingCart
, Compare
, Navigation
, NavigationCategory
, PredictiveBasket
, Recommendation
, Records
, Search
, Similar
, Suggest
.
There is a special scope available, SearchAndNavigation
, that makes ff-template
receive data from both endpoints.
This is practical if you want to reuse the HTML for ff-template
on both search result and category pages.
ff-template
's data
property (accessible via JavaScript) is populated with the response data from the endpoint you specified.
This same response data object is also accessible in the element's HTML-template.
Consult your FactFinder API documentation for the structure of these response objects.
<ff-template scope="Search">Your search produced {{totalHits}} hits.</ff-template>
The ff-single-word-search-record
element no longer provides a default template.
Therefore, an empty ff-single-word-search
element will no longer create valid output.
<!-- No longer renders data. Must provide HTML template manually. -->
<ff-single-word-search></ff-single-word-search>
On ff-single-word-search-record
, the word
property was renamed to singleWordResult
.
On ff-single-word-search
, the words
property was renamed to singleWordResults
.
The ff-carousel
element has been removed.
The ff-loading-spinner
element has been removed.
The ff-navigation
element has been removed.
ff-header-navigation
remains available.
The ff-product-detail
element has been removed.
The ff-tag-cloud
element has been removed as this feature is no longer supported by FactFinder.
The ff-search-feedback
element has been removed.
ff-suggest
no longer emits the suggest-item-clicked
event.
ff-suggest-item
no longer emits the item-clicked
event.
ffPreventDefault
inside these events is therefore no longer available.
Instead, Core offers addSuggestClickListener
in the new notifications
API.
Suggest can react in four ways:
- redirect immediately when
deeplink
is available on clicked item - issue search request
- issue navigation request
- call custom handler on product suggestions without deeplink. Suggest no longer issues a follow-up request to obtain the missing deeplink.
Suggest has new attributes request-mapping-search
, request-mapping-navigation
, request-mapping-detail
.
With the mapping attributes you can direct suggestion types to certain actions.
Defaults are:
- search:
searchTerm
- navigation:
category
,brand
- detail:
productName
<ff-suggest
request-mapping-search="searchTerm"
request-mapping-navigation="category,brand"
request-mapping-detail="productName"
></ff-suggest>
Introduced values
attribute on all products-per-page elements (ff-products-per-page-dropdown
, ff-products-per-page-list
, ff-products-per-page-select
).
With this attribute you specify which page sizes shall be available in the element.
<ff-products-per-page-select values="20,40,60"></ff-products-per-page-select>
- Removed
items
property. - Attribute
collapse-onblur
is nowtrue
by default. - Attribute
show-selected
is now type boolean. Default remainsfalse
. - Renamed
show()
toexpand()
. - Renamed
hide()
tocollapse()
. - Changed parameter from
toggle(collapse)
totoggle(expand)
.toggle(true)
will now expand the dropdown list. The parameter remains optional.
Removed items
property.
Removed options
property.
Removed the [data-template]
attribute.
<ff-products-per-page-select>
<option data-template>data-template attribute is obsolete: {{value}}</option>
</ff-products-per-page-select>
ff-paging
attribute show-only
was changed from String to Boolean.
This means the attribute's presence alone results in a true state while its absence results in false.
It takes no value.
<ff-paging show-only></ff-paging>
<ff-paging></ff-paging>
- Paging item type
pageLink
is no longer available. - Default value for
type
is nowcurrentLink
. show-only
changed from String to Boolean.page-item
was replaced bypage
.page
is a Number. See above.
In the HTML template, page
is now the only available value.
<ff-paging-item>{{page}}</ff-paging-item>
- Replaced
items
property withpages
.pages
takes an array of numbers. - Attribute
collapse-onblur
is nowtrue
by default. - Renamed
show()
toexpand()
. - Renamed
hide()
tocollapse()
. - Changed parameter from
toggle(collapse)
totoggle(expand)
.toggle(true)
will now expand the dropdown list. The parameter remains optional.
In the HTML template of ff-paging-item
, page
is now the only available value.
<ff-paging-dropdown>
<ff-paging-item>{{page}}</ff-paging-item>
</ff-paging-dropdown>
Replaced property pagingData
with pages
, an array of Number.
In the HTML templates of the option
elements, page
is now the only available value.
<ff-paging-select>
<option>{{page}}</option>
</ff-paging-select>
- Attribute
show-selected
is now type boolean. Its default value remainsfalse
. - Attribute
show-selected-first
is now type boolean. Its default value remainsfalse
. - Attribute
collapse-onblur
is nowtrue
by default. - Renamed
show()
toexpand()
. - Renamed
hide()
tocollapse()
. - Changed parameter from
toggle(collapse)
totoggle(expand)
.toggle(true)
will now expand the dropdown list. The parameter remains optional.
When defining an ff-sortbox-item
HTML template for Most relevant, the attribute key
now takes the value Relevancy.desc
which reflects the name
and order
of the sort-item in the FactFinder response.
<ff-sortbox>
<ff-sortbox-item key="Relevancy.desc">Most relevant</ff-sortbox-item>
<ff-sortbox-item key="default.template">{{description}}</ff-sortbox-item>
</ff-sortbox>
The ff-campaign-product
attribute record-id
was renamed to product-id
.
Depending on id-type
, it either takes the product number or the master ID.
ff-campaign-advisor
property campaignData
is now called campaign
to mirror the name from the FactFinder API.
campaignData
was undocumented.
You can limit the campaigns to which the element shall react to with the attributes name
and not
.
Both take a comma-separated list of campaign names that you had defined beforehand in FactFinder.
name
defines which campaigns exactly the element shall react to.
not
defines which campaigns the element shall ignore.
When both attributes are set, only the name
attribute will be considered.
not
will be completely ignored.
The ff-campaign-shopping-cart
attribute record-id
was renamed to product-id
.
Attributes blacklist
and whitelist
no longer accept facet names, but only their associatedFieldName
.
The associatedFieldName
is more reliable in multi-language environments.
The ff-record
template is now always defined through a template
element that has the attribute data-role="record"
.
Previously, this method was only used when the record list was in ssr
mode.
<ff-record-list>
<div>Text before records</div>
<template data-role="record">
<ff-record>
<div>Title: {{variantValues.0.Title}}</div>
<div>Brand: {{variantValues.0.Brand}}</div>
</ff-record>
</template>
<div>Text after records</div>
</ff-record-list>
The template
element also acts as the insertion point for new records.
The data-role="records-insertion-point"
template is no longer available.
<ff-record-list ssr>
<!-- No longer available -->
<template data-role="records-insertion-point"></template>
</ff-record-list>
When using the record list in ssr
mode, make sure to position the template after the server-side rendered records as you would have done with the data-role="records-insertion-point"
template in previous versions.
<ff-record-list ssr>
<!-- Do not place the record template here. -->
<ff-record>Product A</ff-record>
<ff-record>Product B</ff-record>
<ff-record>Product C</ff-record>
<template data-role="record">
<ff-record>{{variantValues.0.Title}}</ff-record>
</template>
</ff-record-list>
The data format that is available in the HTML templates for ff-record
varies depending on the data source.
See your Swagger-UI for details.
For search result data ff-record
elements are populated with SearchRecord
.
<ff-record-list>
<template data-role="record">
<ff-record>{{variantValues.0.Title}} at {{position}}</ff-record>
</template>
</ff-record-list>
Records from a dedicated campaign (pushed products) use RecordWithId
.
<ff-campaign-pushed-products is-landing-page-campaign>
<ff-record-list>
<template data-role="record">
<ff-record>{{variantValues.0.Title}}</ff-record> <!-- `position` not available -->
</template>
</ff-record-list>
</ff-campaign-pushed-products>
Records from a Predictive Basket result use TypedFlatRecord
.
<ff-predictive-basket>
<ff-record-list>
<template data-role="record">
<ff-record>{{values.Title}}</ff-record>
</template>
</ff-record-list>
</ff-predictive-basket>
ff-campaign-redirect
dropped the relative-to-origin
attribute.
Make sure to correctly configure the destination URL in your FactFinder UI.
Subscribers to redirect campaigns have received the ability to interrupt the subsequent response pipeline. Requests that were issued in parallel are not affected, however.
The ff-asn-remove-all-filter
was renamed to ff-asn-remove-all-filters
.
Note the added s
at the end.
It no longer supports the keep-category-path
attribute.
See Category Pages for how to implement category and brand pages.
Predictive Basket is now a regular part of the Web Components library. It is no longer necessary to activate it before usage.
// obsolete
factfinder.__experimental.predictiveBasket.enable = true;
record-id
was renamed to product-number
on ff-compare
.
It takes the value that is labeled with the productNumber
field role.
This section explains how to implement various scenarios you might encounter.
The search-immediate
attribute together with the ff-communication
element are no longer available.
Instead, you have to issue the initial search request manually.
This leads to earlier search requests because it makes it unnecessary to wait until ff-communication
is resolved in the DOM before it can issue the request.
The ffCoreReady
event provides a specialized function that replaces the search-immediate
mechanism.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
init({
ff: {
url: `https://your-instance.fact-finder.com/fact-finder`,
channel: `your-channel`,
apiKey: `your.apiKey`,
},
});
// Typically, you would read the search parameters from the URL.
const searchParams = factfinder.utils.env.searchParamsFromUrl();
/* Alternatively, you can build the search parameters manually.
const searchParams = {
query: `query generated by server`,
};
*/
initialSearch(
searchParams,
// Specifying `origin` is optional.
// You can use it to tell your 'before search' listeners and other stations later in the pipeline
// who initiated the request and allow them to react accordingly.
// If you do not specify it, `initialSearch()` will set it to the string "initialSearch".
{ requestOptions: { origin: `initialSearch` } }
);
});
Redirect to another page when a search is triggered from the search box.
document.addEventListener("ffCoreReady", ({ factfinder }) => {
factfinder.request.before.search(({ searchParams, searchOptions }) => {
// If the search request was invoked by `ff-searchbox`, `searchOptions.requestOptions.origin` will be a reference to the `ff-searchbox` element.
if (searchOptions?.requestOptions?.origin?.tagName === `FF-SEARCHBOX`) {
window.location.href = `/search?query=${searchParams.query}`;
// Cancel the pipeline to avoid sending a search request to FactFinder before the redirect is complete.
return false;
}
});
});
The category-page
attribute is now only available via JavaScript during application initialization and through the factfinder.config.setAppConfig(appConfig)
function.
You can read the currently configured value at
factfinder.config.get().appConfig.categoryPage
categoryPage
takes an array of Filter
objects as specified by the FactFinder REST API.
You can specify them manually or use the filter builders.
document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => {
init({
ff: { ... },
appConfig: {
categoryPage: [
factfinder.utils.filterBuilders.categoryFilter(`Category`, [`Clothing`, `Casual`, `T-shirts`]),
factfinder.utils.filterBuilders.filter(`Manufacturer`, [`adidas`]),
],
},
});
});
You can specify any type of filter. This allows you to build category-page style pages based on any filter. Brand pages may be a common use case.
The specified filters are considered the page's fixed filters and cannot be removed by the user. However, they are not automatically applied to requests invoked through JavaScript. This leaves you the option to do searches outside the page's context.
Typically, you then trigger the initial request with the filters you just specified in categoryPage
.
initialSearch({ filters: factfinder.config.get().appConfig.categoryPage });
When categoryPage
is set, Web Components uses the /navigation
endpoint from the FactFinder API for searches.
These navigation requests don't use a query
parameter.
This means that click tracking events must receive a substitute value for the required query
parameter.
If a category filter is specified, Web Components will use the category path.
If a different type of filter (e.g. brand) is specified, Web Components will use the first specified filter value. This would be the brand name in case of a brand page.
If you need a different query
value tracked to identify these requests in your analytics, use the factfinder.tracking.before.click(handler)
pipeline hook to modify the parameters before they are sent to FactFinder.
Whenever a search request happens, Web Components writes the used search parameters to the URL and creates an entry in the browser history. This allows you to share the URL with others and to navigate back and forth through the browser history. Web Components has defaults that convert FactFinder SearchParams to a URL query string and there is no requirement for you to interject unless you choose to.
Conversion between URL parameters and FactFinder SearchParams is required in both directions.
Note that the SearchParams'
customParameters
field is neither parsed nor stringified. If you need custom parameters sent to FactFinder or appear in your URL, you will have to add them manually. The tools described in this section provide options to apply relevant customizations.
Converting URL params to SearchParams is done on page load in order to produce the initial search request based on the current URL params.
It must be done manually in the ffCoreReady
event handler.
const searchParams = factfinder.utils.env.searchParamsFromUrl(parsingOptions);
initialSearch(searchParams);
Conversion of SearchParams to URL params is performed automatically by Web Components during the response pipeline of a search or navigation request.
You can provide custom conversion parameters through the setUrlParamOptionsListener
pipeline hook.
factfinder.routing.setUrlParamOptionsListener(stringificationOptions);
searchParamsFromUrl
converts the query string from the current URL to a valid SearchParams object that you can use to issue a search request.
If you need a NavigationParams object, you can use the
factfinder.utils.toNavigationParams(searchParams)
conversion function.
All options to searchParamsFromUrl
are optional.
const searchParams = factfinder.utils.env.searchParamsFromUrl({
block, // array of strings
categoryFieldName, // string
keyMapping, // object
preparsers, // object
});
block
is an array of strings where you can specify search parameters that shall not appear in the SearchParams object.
Note that these are the parameter names as they appear in the SearchParams object and not as they appear in the URL.
// URL: ?query=atlantis&latitude=30.8&longitude=-42.8
factfinder.utils.env.searchParamsFromUrl();
// { query: "atlantis" , location: { latitude: 30.8, longitude: -42.8 } }
factfinder.utils.env.searchParamsFromUrl({ block: [`location`] });
// { query: "atlantis" }
categoryFieldName
is the field name of your category facet.
While technically optional, you should always specify it because the category filter must be constructed in a different format than regular filters.
Web Components can't discern from URL parameters alone which filter is a category filter and which is not.
You can find the correct field name in your data feed or, in a search response, in the category facet object's associatedFieldName
field.
factfinder.utils.env.searchParamsFromUrl({ categoryFieldName: `Category` });
keyMapping
is an object with which you can specify how URL parameters have been renamed.
The keys in the object are the default names as Web Components would write them to the URL.
The values are the parameter names as they actually appear in the URL.
// URL: ?q=atlantis&lat=30.8&lon=-42.8
factfinder.utils.env.searchParamsFromUrl({ keyMapping: { query: `q`, latitude: `lat`, longitude: `lon` } });
// { query: "atlantis" , location: { latitude: 30.8, longitude: -42.8 } }
If you do not specify keyMapping
, the following default mapping is used.
All other parameters appear as they are in the SearchParams object.
{
activeAbTests: [`activeAbTest`],
filters: [`filter`, `substringFilter`],
location: [`latitude`, `longitude`],
marketIds: [`marketId`],
sortItems: [`sort`],
}
preparsers
takes an object that lets you define functions to process URL parameters before they are converted to SearchParams.
This is necessary when you are using URL parameters in a format that Web Components doesn't understand.
The preparsers must translate them back into the Web Components-compatible format.
The keys of the object passed to preparsers
are the same as those in a SearchParams object.
The values are each a function that receives a list of matched parameters.
Only the preparser for the filter
parameter receives a second argument - the categoryFieldName
.
Here are two examples of possible preparsers.
A simple one (articleNumberSearch
) and a more complex one (location
).
// URL: ?ans=A&loc=30.8:-42.8
const searchParams = factfinder.utils.env.searchParamsFromUrl({
// Translates 'ans' from the URL to 'articleNumberSearch'
// and 'loc' to 'latitude'.
keyMapping: { articleNumberSearch: `ans`, latitude: `loc` },
preparsers: {
// `matchedEntries` has the format: [{ key: "keyAfterMapping", values: ["value1", "value2"] }]
articleNumberSearch: (matchedEntries) => {
const singleEntry = matchedEntries[0];
const val = singleEntry.values[0].toUpperCase();
singleEntry.values[0] = val === `A` ? `ALWAYS` : val === `N` ? `NEVER` : `DETECT`;
// Must return an array in the same format as `matchedEntries`.
return [singleEntry];
},
location: (matchedEntries) => {
const entry = matchedEntries.find(({ key }) => key === `latitude`);
const [lat, lon] = entry.values[0].split(`:`);
return [
{ key: `latitude`, values: [lat] },
{ key: `longitude`, values: [lon] },
];
},
},
});
// searchParams:
// {
// articleNumberSearch: "ALWAYS",
// location: { latitude: 30.8, longitude: -42.8 }
// }
In the URL, the parameter appears as ans=A
.
The presupposition is that the name articleNumberSearch
is too long for a pleasing appearance in the URL and was shortened.
Likewise, the possible values defined by FactFinder (ALWAYS
, NEVER
, DETECT
) are too long and are shortened, too.
As Web Components does not know the parameter ans
, it has to be mapped to one of the known parameters.
This happens in keyMapping: { articleNumberSearch: 'ans' }
.
With the mapping defined, all URL parameters with the key ans
will be passed to the articleNumberSearch
preparser.
The preparser then converts the customized value A
to the FactFinder-defined ALWAYS
.
articleNumberSearch: (matchedEntries) => {
// matchedEntries === [{ key: "articleNumberSearch", values: ["A"] }]
// Translate values to expected format (see above).
// Return modified `matchedEntries` or a new array with the same format (see above).
}
In the URL, the parameter appears as loc=30.8:-42.8
.
location
normally appears as two parameters (latitude
, longitude
) in the URL.
In this example, we only use a single parameter with a custom format.
The internal parser for location
requires both latitude
and longitude
parameters in order to produce a valid location
object.
As we only have a single parameter in the URL, simple key mapping is insufficient, and we must split the custom value into its components.
First, keyMapping: { latitude: 'loc' }
translates the custom loc
to the known latitude
which causes the location
parser to be invoked.
However, this is incomplete data because the longitude
parameter is missing.
The location
preparser must then convert the custom value and add the missing parameter.
preparser: {
location: (matchedEntries) => {
// matchedEntries === [{ key: "latitude", values: ["30.8:-42.8"] } }]
const entry = matchedEntries.find(entry => entry.key === `latitude`);
// entry === { key: "latitude", values: ["30.8:-42.8"] } }
// Splitting the custom value into its components.
const [lat, lon] = entry.values[0].split(`:`);
// Return an array with the entries required by Web Components' parser.
return [
{ key: `latitude`, values: [lat] },
{ key: `longitude`, values: [lon] },
];
},
}
When the response pipeline of a search or navigation request reaches the point where Web Components is about to write the latest SearchParams to the URL, the setUrlParamOptionsListener
pipeline hook is called.
The return value of the listener is an object with various options to affect the resulting URL.
Registering this listener is optional. Typically, it is registered once during app initialization. All options are also optional. Specifying an option overrides the default value (as opposed to append to it).
// Registering the listener with default options.
factfinder.routing.setUrlParamOptionsListener(() => ({
allow: [],
block: [`activeAbTests`, `followSearch`, `purchaserId`],
blockFilters: [],
keyMapping: {},
order: [`query`, `filters`, `sortItems`],
postStringifier: undefined,
stringifiers: {},
}));
allow
specifies a list of search parameter names (as they appear on the SearchParams object used for the current request pipeline) that shall be considered for stringification.
Search parameters that are not listed are discarded and will not appear in the URL.
If the list is empty, all parameters are considered.
block
is a list of parameter names (as they appear in a SearchParams object) that shall not appear in the URL.
blockFilters
works like block but targets individual filters from the filters
search parameter.
It takes a list of filter names.
This option can be useful if your URL already contains a part of the filters. Frequently, this is the category filter. In this case, you don't want the category filter to appear again in the URL's query string.
// URL with category filter: https://yourshop.com/powertools/woodworking?filter=input:230V
factfinder.routing.setUrlParamOptionsListener(() => ({
// Assuming your category filter's field name is 'Category'.
blockFilters: [`Category`],
}));
keyMapping
is the same as in the parsing options.
Always use the same mapping object!
const keyMapping = { query: `q` };
factfinder.routing.setUrlParamOptionsListener(() => ({ keyMapping }));
initialSearch(factfinder.utils.env.searchParamsFromUrl({ keyMapping }));
Mapping happens after stringification.
order
is a list of SearchParams keys that specifies which parameters shall appear first and in which order in the URL.
Parameters that are not specified will appear in alphabetical order after the specified parameters.
// searchParams:
// { page: 2, query: "chisel" }
factfinder.routing.setUrlParamOptionsListener(() => ({ order: [`query`] }));
// URL: ?query=chisel&page=2
postStringifier
takes a function that receives an array of ordered and stringified key/value pairs and returns an array of the same format.
The post-stringifier is called after all other operations are complete.
Each key/value pair the post-stringifier receives represents one URL parameter as it is about to be written to the browser's URL.
// query: jacket
// filters: size=L, features=waterproof
factfinder.routing.setUrlParamOptionsListener(() => ({
postStringifier: pairs => {
// Current contents of `pairs`:
// [
// { key: "query", value: "jacket" },
// { key: "filter", value: "size:L" },
// { key: "filter", value: "features:waterproof" },
// ]
// Manipulate `pairs` as required. E.g. filter, reorder, add additional pairs, etc.
pairs.unshift({ key: `custom`, value: `pole-position` });
// Return the manipulated `pairs` array or a new array with the same format.
return pairs;
},
}));
// URL: ?custom=pole-position&query=jacket&filter=size:L&filter=features:waterproof
stringifiers
allows you to define custom stringifiers.
It is the counterpart to searchParamsFromUrl
's preparsers
.
It takes an object with SearchParams keys and the stringifier functions.
The following example complements the location
example in the parsing options section.
// searchParams:
// { query: "atlantis" , location: { latitude: 30.8, longitude: -42.8 } }
factfinder.routing.setUrlParamOptionsListener(() => ({
// Use the same object as in `searchParamsFromUrl`.
// `articleNumberSearch` is not required in this example, though.
keyMapping: { articleNumberSearch: `ans`, latitude: `loc` },
stringifiers: {
location: (addParam, location) => {
// `addParam` is a function that takes the stringified value and an optional key.
// The second argument is the value from the _SearchParams_ object that is to be stringified.
const value = `${location.latitude}:${location.longitude}`;
// `key` is optional. If you don't specify it, the related _SearchParams_ key is used.
// In this case: "location"
const key = `latitude`;
addParam(value, key);
// You can call `addParam` as many times as you require.
// If you wanted to produce the same result as the default stringifier,
// you would call `addParam` once for each location component.
// addParam(location.latitude, `latitude`);
// addParam(location.longitude, `longitude`);
},
},
}));
// URL: ?query=atlantis&loc=30.8:-42.8
No change in custom elements.
The previous factfinder.communication.ResultDispatcher.dispatchRaw(result)
API was replaced by two functions dedicated to either search or navigation results.
// For regular search result pages.
factfinder.response.dispatch.ssrSearch(responseJson, requestInfo);
// For category pages.
factfinder.response.dispatch.ssrNavigation(responseJson, requestInfo);
The following visualization outlines the various phases a request goes through, as well as the entry and exit points to and from the pipeline.
This diagram might be the most valuable resource for integrating your Web Components application.