JavaScript frontend integration This page describes an example Froomle frontend integration using JavaScript. We’ll show how to send different types of events and how to request and render recommendations from the browser. This will be the bulk of the integration work, leaving only the synchronization of your item metadata, which should be handled on the backend for security reasons. You can find a JavaScript file containing all code on this page here. Setup When you sign up with Froomle you will be assigned a subdomain and one or more environments (for example one for production and development). These determine the API url, which we can set once at startup: const froomleApi = "https://yoursubdomain.froomle.com/api/yourenvironment"; Each API call expects some contextual information, namely what type of page the user is currently on, how it is being served (desktop, app, ..) and who the user is. We assign it once at page startup and reuse it for all API calls: const context = { channel: "...", page_type: "...", context_item: "...", context_item_type: "...", device_id: "...", user_id: "..." }; The channel is how the page is being served. Should be set to "www-desktop", "www-mobile", "mobile-app" or "test". The page_type identifies a unique location where you’ve integrated one or more lists with Froomle recommendations that is distinctly different from other locations and in addition, this page-type is useful to differentiate on in our reporting. If the current page is a detail page for one of your items then include the item id as the context_item. If not, omit this field. The device_id is a unique identifier for the user that persists between sessions. It’s typically stored in a cookie or localStorage. If the user does not consent to tracking a value of "anonymous" can be used here. The user_id is an identifier for logged-in users that is stable across devices. If the user is not logged in the field can be omitted here. Sending events Below are some examples of how we can send events to the Froomle events API. We’re using the browser’s modern fetch API for our calls here, but the same could be accomplished with XMLHttpRequest or a library. Similarly you might prefer the fetch().then() syntax over async/await. For an overview of different types of events and fields please refer to the events concept page. The news and e-commerce example pages have an overview of required events and how they are used in those settings. page_visit detail_pageview impression click_on_recommendation Sends a page_visit event for the current page_type as set in the context variable async function sendPageVisit() { const response = await fetch(`${froomleApi}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ events: [Object.assign({ event_type: "page_visit" }, context)] }) }); if (!response.ok) { throw Error(response.statusText); } return await response.json(); } Sends a detail_pageview event for the item currently being viewed, as set in context async function sendDetailPageview() { const response = await fetch(`${froomleApi}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ events: [Object.assign({ event_type: "detail_pageview", action_item: context.context_item, action_item_type: context.context_item_type }, context)] }) }); if (!response.ok) { throw Error(response.statusText); } return await response.json(); } Sends impression events for all recommended items, articles in this case. Here recommendations is the response from the fetchRecommendations call below. For a carousel or similar UI widget where only some recommendations are visible it would be more accurate to send the impressions individually. async function sendImpressions(recommendations, list_name) { const action_items = recommendations.lists .find(l => l.list_name === list_name) .items .map(i => i.item_id); const response = await fetch(`${froomleApi}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ events: action_items.map(id => Object.assign({ event_type: "impression", list_name: list_name, action_item: id, action_item_type: "article", request_id: recommendations.request_id, user_group: recommendations.user_group }, context)) }) }); if (!response.ok) { throw Error(response.statusText); } return await response.json(); } Sends a click_on_recommendation event for a recommended item, an article in this case. Here recommendations is the response from the fetchRecommendations call below. async function sendClickOnRecommendation(recommendations, list_name, action_item) { const response = await fetch(`${froomleApi}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ events: [Object.assign({ event_type: "click_on_recommendation", list_name: list_name, action_item: action_item, action_item_type: "article", request_id: recommendations.request_id, user_group: recommendations.user_group }, context)] }) }); if (!response.ok) { throw Error(response.statusText); } return await response.json(); } Requesting recommendations Below is an example on how to request one or more lists of recommendations from the Froomle recommendations API. If multiple lists of recommendations are placed on the same page please give them unique names. When you sign up with Froomle you can specify what type of recommendations you’d like to appear in each list. async function fetchRecommendations(list_names, list_sizes) { const response = await fetch(`${froomleApi}/recommendations/requests`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(Object.assign({ lists: list_names.map((name, i) => ({ list_name: name, list_size: list_sizes[i] })) }, context)) }); if (!response.ok) { throw Error(response.statusText); } return await response.json(); } Rendering recommendations The response from the recommendations API contains a lists array containing each requested list and its recommendations. You’ll find the ids of the recommended items in the item_id field of the item objects: const list_name = "some_list"; const recommendations = await fetchRecommendations([list_name], [5]); const recommended_items = recommendations.lists .find(l => l.list_name === list_name) .items; Now you have the option to either fetch the metadata (image, url, ..) of these items from your own backend, or to have Froomle include these fields in the item object in the recommendation response. The fields returned in the recommendation response are agreed during the onboarding. As a starting example of how to render the recommendations we simply show the different item ids in a list here. This is where you’d insert your own metadata and add your own styling: // Assuming the HTML contains some <ul id="container"></ul> element const container = document.getElementById("container"); for (const item of recommended_items) { const li = document.createElement("li"); li.innerHTML = ` <div> <a href="#">${item.item_id}</a> </div> `; container.appendChild(li); } Impression events To report on how well its recommendations are performing Froomle needs to know which of the recommendations the user has seen and which have been clicked. We can use the function defined earlier to send impression events for all recommended items: sendImpressions(recommendations, list_name); Ideally impressions are only sent when the recommendations become visible to the user. If they’re far down on the page or you’re using a carousel widget the user may need to scroll to actually see them. This functionality can be added using the browsers’ built-in Intersection Observer API. Click on recommendation events One way to send click_on_recommendation events is to simply add an onclick handler for each list element in the rendering loop above: li.addEventListener("click", () => { sendClickOnRecommendation(recommendations, list_name, item.item_id); }); Supporting old browsers The examples used async/await, fetch and the Intersection Observer API, which are relatively modern browser features. While they are widely supported, you may still have some visitors whose browser does not. If this is not acceptable but you still want the productivity gains from these tools, you can use babel for translating the latest JavaScript standard into backward compatible code, and either use a library or polyfill for fetch and the Intersection Observer API.